idletime 0.1.0 → 0.1.1
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/assets/idle-time-readme.png +0 -0
- package/dist/idletime.js +167 -63
- package/package.json +1 -1
|
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.1",
|
|
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",
|
|
@@ -1039,33 +1039,130 @@ function shortenPath(pathText, maxLength) {
|
|
|
1039
1039
|
|
|
1040
1040
|
// src/reporting/render-theme.ts
|
|
1041
1041
|
var roleStyles = {
|
|
1042
|
-
focus: "1;38;2;
|
|
1043
|
-
active: "1;38;2;
|
|
1044
|
-
agent: "1;38;2;
|
|
1045
|
-
idle: "1;38;2;
|
|
1046
|
-
burn: "1;38;2;
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1042
|
+
focus: "1;38;2;236;239;148",
|
|
1043
|
+
active: "1;38;2;208;219;96",
|
|
1044
|
+
agent: "1;38;2;166;182;77",
|
|
1045
|
+
idle: "1;38;2;138;150;66",
|
|
1046
|
+
burn: "1;38;2;228;209;92",
|
|
1047
|
+
raw: "1;38;2;188;172;80",
|
|
1048
|
+
frame: "1;38;2;149;158;56",
|
|
1049
|
+
heading: "1;38;2;249;246;212",
|
|
1050
|
+
muted: "38;2;142;145;96",
|
|
1051
|
+
value: "1;38;2;242;236;179"
|
|
1051
1052
|
};
|
|
1052
1053
|
function createRenderOptions(shareMode) {
|
|
1053
1054
|
return {
|
|
1054
1055
|
colorEnabled: Boolean(process.stdout.isTTY) && process.env.NO_COLOR === undefined,
|
|
1055
|
-
shareMode
|
|
1056
|
+
shareMode,
|
|
1057
|
+
terminalWidth: process.stdout.columns ?? null
|
|
1056
1058
|
};
|
|
1057
1059
|
}
|
|
1058
1060
|
function paint(text, role, options) {
|
|
1061
|
+
return paintAnsi(text, roleStyles[role], options);
|
|
1062
|
+
}
|
|
1063
|
+
function paintAnsi(text, style, options) {
|
|
1059
1064
|
if (!options.colorEnabled || text.length === 0) {
|
|
1060
1065
|
return text;
|
|
1061
1066
|
}
|
|
1062
|
-
return `\x1B[${
|
|
1067
|
+
return `\x1B[${style}m${text}\x1B[0m`;
|
|
1063
1068
|
}
|
|
1064
1069
|
function dim(text, options) {
|
|
1065
|
-
|
|
1066
|
-
|
|
1070
|
+
return paintAnsi(text, "2", options);
|
|
1071
|
+
}
|
|
1072
|
+
function measureVisibleTextWidth(text) {
|
|
1073
|
+
let visibleWidth = 0;
|
|
1074
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
1075
|
+
if (text[index] === "\x1B" && text[index + 1] === "[") {
|
|
1076
|
+
index += 2;
|
|
1077
|
+
while (index < text.length && text[index] !== "m") {
|
|
1078
|
+
index += 1;
|
|
1079
|
+
}
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
visibleWidth += 1;
|
|
1067
1083
|
}
|
|
1068
|
-
return
|
|
1084
|
+
return visibleWidth;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// src/reporting/render-logo-section.ts
|
|
1088
|
+
var baseBackgroundStyle = "48;2;12;15;8";
|
|
1089
|
+
var wordmarkStyle = `${baseBackgroundStyle};1;38;2;247;245;204`;
|
|
1090
|
+
var wordmarkLines = [
|
|
1091
|
+
" ▄▄ ▄▄",
|
|
1092
|
+
"▀▀ ██ ██ ██ ▀▀",
|
|
1093
|
+
"██ ▄████ ██ ▄█▀█▄ ▀██▀▀ ██ ███▄███▄ ▄█▀█▄",
|
|
1094
|
+
"██ ██ ██ ██ ██▄█▀ ██ ██ ██ ██ ██ ██▄█▀",
|
|
1095
|
+
"██▄ ▀████ ██ ▀█▄▄▄ ██ ██▄ ██ ██ ██ ▀█▄▄▄"
|
|
1096
|
+
];
|
|
1097
|
+
var patternColors = [
|
|
1098
|
+
{ red: 20, green: 24, blue: 10 },
|
|
1099
|
+
{ red: 48, green: 58, blue: 18 },
|
|
1100
|
+
{ red: 86, green: 96, blue: 24 },
|
|
1101
|
+
{ red: 128, green: 138, blue: 30 },
|
|
1102
|
+
{ red: 176, green: 188, blue: 40 },
|
|
1103
|
+
{ red: 220, green: 228, blue: 78 }
|
|
1104
|
+
];
|
|
1105
|
+
var monochromePatternCharacters = ["░", "░", "▒", "▓", "█"];
|
|
1106
|
+
function buildLogoSection(requestedWidth, options) {
|
|
1107
|
+
const wordmarkWidth = Math.max(...wordmarkLines.map((line) => line.length));
|
|
1108
|
+
const sectionWidth = Math.max(requestedWidth, wordmarkWidth);
|
|
1109
|
+
const patternWidth = Math.max(0, sectionWidth - wordmarkWidth);
|
|
1110
|
+
return wordmarkLines.map((line, rowIndex) => {
|
|
1111
|
+
const paddedWordmark = padRight(line, wordmarkWidth);
|
|
1112
|
+
const patternTail = buildPatternTail(patternWidth, rowIndex, options);
|
|
1113
|
+
return `${paintAnsi(paddedWordmark, wordmarkStyle, options)}${patternTail}`;
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
function resolveLogoSectionWidth(minimumWidth, options) {
|
|
1117
|
+
return Math.max(minimumWidth, options.terminalWidth ?? 0);
|
|
1118
|
+
}
|
|
1119
|
+
function buildPatternTail(width, rowIndex, options) {
|
|
1120
|
+
if (!options.colorEnabled) {
|
|
1121
|
+
return buildMonochromePatternTail(width, rowIndex);
|
|
1122
|
+
}
|
|
1123
|
+
let patternTail = "";
|
|
1124
|
+
let currentStyle = "";
|
|
1125
|
+
let currentSegmentWidth = 0;
|
|
1126
|
+
for (let columnIndex = 0;columnIndex < width; columnIndex += 1) {
|
|
1127
|
+
const style = getPatternCellStyle(getPatternIntensity(width, rowIndex, columnIndex));
|
|
1128
|
+
if (style === currentStyle) {
|
|
1129
|
+
currentSegmentWidth += 1;
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
if (currentSegmentWidth > 0) {
|
|
1133
|
+
patternTail += paintAnsi(" ".repeat(currentSegmentWidth), currentStyle, options);
|
|
1134
|
+
}
|
|
1135
|
+
currentStyle = style;
|
|
1136
|
+
currentSegmentWidth = 1;
|
|
1137
|
+
}
|
|
1138
|
+
if (currentSegmentWidth > 0) {
|
|
1139
|
+
patternTail += paintAnsi(" ".repeat(currentSegmentWidth), currentStyle, options);
|
|
1140
|
+
}
|
|
1141
|
+
return patternTail;
|
|
1142
|
+
}
|
|
1143
|
+
function buildMonochromePatternTail(width, rowIndex) {
|
|
1144
|
+
let patternTail = "";
|
|
1145
|
+
for (let columnIndex = 0;columnIndex < width; columnIndex += 1) {
|
|
1146
|
+
const intensity = getPatternIntensity(width, rowIndex, columnIndex);
|
|
1147
|
+
const characterIndex = Math.min(monochromePatternCharacters.length - 1, Math.floor(intensity * monochromePatternCharacters.length));
|
|
1148
|
+
patternTail += monochromePatternCharacters[characterIndex];
|
|
1149
|
+
}
|
|
1150
|
+
return patternTail;
|
|
1151
|
+
}
|
|
1152
|
+
function getPatternIntensity(width, rowIndex, columnIndex) {
|
|
1153
|
+
const normalizedColumn = width <= 1 ? 0 : columnIndex / Math.max(1, width - 1);
|
|
1154
|
+
const envelope = 0.18 + 0.82 * Math.pow(normalizedColumn, 0.82);
|
|
1155
|
+
const wave = Math.sin((columnIndex + rowIndex * 1.9) / 2.9) * 0.22 + Math.cos((columnIndex - rowIndex * 2.7) / 6.3) * 0.18 + Math.sin((columnIndex + rowIndex * 3.4) / 10.5) * 0.12;
|
|
1156
|
+
const blockOffset = ((Math.floor(columnIndex / 2) + rowIndex) % 2 === 0 ? 0.06 : -0.04) + (rowIndex === 0 ? -0.06 : 0);
|
|
1157
|
+
return clamp(envelope + wave + blockOffset, 0, 1);
|
|
1158
|
+
}
|
|
1159
|
+
function getPatternCellStyle(intensity) {
|
|
1160
|
+
const colorIndex = Math.min(patternColors.length - 1, Math.floor(intensity * patternColors.length));
|
|
1161
|
+
const color = patternColors[colorIndex];
|
|
1162
|
+
return `48;2;${color.red};${color.green};${color.blue}`;
|
|
1163
|
+
}
|
|
1164
|
+
function clamp(value, minValue, maxValue) {
|
|
1165
|
+
return Math.min(maxValue, Math.max(minValue, value));
|
|
1069
1166
|
}
|
|
1070
1167
|
|
|
1071
1168
|
// src/reporting/render-layout.ts
|
|
@@ -1095,32 +1192,39 @@ function renderSectionTitle(title, options) {
|
|
|
1095
1192
|
}
|
|
1096
1193
|
|
|
1097
1194
|
// src/reporting/render-rhythm-section.ts
|
|
1195
|
+
var groupSize = 4;
|
|
1098
1196
|
function buildRhythmSection(report, options) {
|
|
1099
1197
|
const quietValues = report.buckets.map((bucket) => Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs));
|
|
1100
1198
|
const idleValues = report.hasWakeWindow ? report.buckets.map((bucket) => bucket.awakeIdleMs) : quietValues;
|
|
1101
1199
|
const idleLabel = report.hasWakeWindow ? "idle" : "quiet";
|
|
1102
|
-
|
|
1200
|
+
const idleTotal = formatDurationCompact(idleValues.reduce((totalDurationMs, idleDurationMs) => totalDurationMs + idleDurationMs, 0));
|
|
1201
|
+
const lines = [
|
|
1103
1202
|
...renderSectionTitle("24h Rhythm", options),
|
|
1104
1203
|
paint(` hours ${buildHourMarkerLine(report)}`, "muted", options),
|
|
1105
|
-
renderRhythmRow("focus", buildSparkline(report.buckets.map((bucket) => bucket.engagedMs)), formatDurationCompact(report.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + bucket.engagedMs, 0)), "focus", options),
|
|
1106
|
-
renderRhythmRow("active", buildSparkline(report.buckets.map((bucket) => bucket.directActivityMs)), formatDurationCompact(report.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + bucket.directActivityMs, 0)), "active", options)
|
|
1107
|
-
renderRhythmRow(padRight(idleLabel, 6).trimEnd(), buildSparkline(idleValues), formatDurationCompact(idleValues.reduce((totalDurationMs, idleDurationMs) => totalDurationMs + idleDurationMs, 0)), "idle", options),
|
|
1108
|
-
renderRhythmRow("burn", buildSparkline(report.buckets.map((bucket) => bucket.practicalBurn)), formatCompactInteger(report.buckets.reduce((totalBurn, bucket) => totalBurn + bucket.practicalBurn, 0)), "burn", options)
|
|
1204
|
+
renderRhythmRow("focus", buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.engagedMs))), formatDurationCompact(report.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + bucket.engagedMs, 0)), "focus", options),
|
|
1205
|
+
renderRhythmRow("active", buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.directActivityMs))), formatDurationCompact(report.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + bucket.directActivityMs, 0)), "active", options)
|
|
1109
1206
|
];
|
|
1207
|
+
lines.push(renderRhythmRow(padRight(idleLabel, 6).trimEnd(), buildGroupedTrack(buildSparkline(idleValues)), idleTotal, "idle", options));
|
|
1208
|
+
lines.push(renderRhythmRow("burn", buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.practicalBurn))), formatCompactInteger(report.buckets.reduce((totalBurn, bucket) => totalBurn + bucket.practicalBurn, 0)), "burn", options));
|
|
1209
|
+
return lines;
|
|
1210
|
+
}
|
|
1211
|
+
function buildGroupedTrack(text) {
|
|
1212
|
+
const groups = [];
|
|
1213
|
+
for (let i = 0;i < text.length; i += groupSize) {
|
|
1214
|
+
groups.push(text.slice(i, i + groupSize));
|
|
1215
|
+
}
|
|
1216
|
+
return groups.join("│");
|
|
1110
1217
|
}
|
|
1111
1218
|
function buildHourMarkerLine(report) {
|
|
1112
|
-
const
|
|
1113
|
-
for (
|
|
1114
|
-
|
|
1219
|
+
const markerGroups = [];
|
|
1220
|
+
for (let index = 0;index < report.buckets.length; index += groupSize) {
|
|
1221
|
+
const bucket = report.buckets[index];
|
|
1222
|
+
if (!bucket) {
|
|
1115
1223
|
continue;
|
|
1116
1224
|
}
|
|
1117
|
-
|
|
1118
|
-
markerCharacters[index] = hourLabel[0] ?? " ";
|
|
1119
|
-
if (index + 1 < markerCharacters.length) {
|
|
1120
|
-
markerCharacters[index + 1] = hourLabel[1] ?? " ";
|
|
1121
|
-
}
|
|
1225
|
+
markerGroups.push(padRight(formatHourOfDay(bucket.start, report.window), Math.min(groupSize, report.buckets.length - index)));
|
|
1122
1226
|
}
|
|
1123
|
-
return
|
|
1227
|
+
return markerGroups.join("│");
|
|
1124
1228
|
}
|
|
1125
1229
|
function renderRhythmRow(label, sparkline, totalText, role, options) {
|
|
1126
1230
|
return `${paint(` ${padRight(label, 6)}`, role, options)} ${paint(sparkline, role, options)} ${paint(totalText, "value", options)}`;
|
|
@@ -1147,11 +1251,16 @@ function renderFullHourlyReport(report, options) {
|
|
|
1147
1251
|
const lines = [];
|
|
1148
1252
|
const peakBurnBucket = report.buckets.reduce((currentPeak, bucket) => bucket.practicalBurn > currentPeak.practicalBurn ? bucket : currentPeak, report.buckets[0]);
|
|
1149
1253
|
const peakFocusBucket = report.buckets.reduce((currentPeak, bucket) => bucket.engagedMs > currentPeak.engagedMs ? bucket : currentPeak, report.buckets[0]);
|
|
1150
|
-
|
|
1254
|
+
const panelLines = renderPanel(`idletime hourly • ${report.window.label}`, [
|
|
1151
1255
|
`${formatTimestamp(report.window.start, report.window)} -> ${formatTimestamp(report.window.end, report.window)}`,
|
|
1152
1256
|
buildFilterLine(report),
|
|
1153
1257
|
`peaks burn ${formatCompactInteger(peakBurnBucket.practicalBurn)} @ ${formatHourBucketLabel(peakBurnBucket.start, report.window)} • focus ${formatDurationCompact(peakFocusBucket.engagedMs)} @ ${formatHourBucketLabel(peakFocusBucket.start, report.window)} • concurrency ${Math.max(...report.buckets.map((bucket) => bucket.peakConcurrentAgents), 0)}`
|
|
1154
|
-
], options)
|
|
1258
|
+
], options);
|
|
1259
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1260
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1261
|
+
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
1262
|
+
lines.push("");
|
|
1263
|
+
lines.push(...panelLines);
|
|
1155
1264
|
lines.push("");
|
|
1156
1265
|
lines.push(...buildRhythmSection(report, options));
|
|
1157
1266
|
lines.push("");
|
|
@@ -1171,11 +1280,16 @@ function renderFullHourlyReport(report, options) {
|
|
|
1171
1280
|
function renderShareHourlyReport(report, options) {
|
|
1172
1281
|
const lines = [];
|
|
1173
1282
|
const peakBurnBucket = report.buckets.reduce((currentPeak, bucket) => bucket.practicalBurn > currentPeak.practicalBurn ? bucket : currentPeak, report.buckets[0]);
|
|
1174
|
-
|
|
1283
|
+
const panelLines = renderPanel(`idletime hourly • ${report.window.label}`, [
|
|
1175
1284
|
`${formatTimestamp(report.window.start, report.window)} -> ${formatTimestamp(report.window.end, report.window)}`,
|
|
1176
1285
|
buildFilterLine(report),
|
|
1177
1286
|
`peak burn ${formatCompactInteger(peakBurnBucket.practicalBurn)} @ ${formatHourBucketLabel(peakBurnBucket.start, report.window)}`
|
|
1178
|
-
], options)
|
|
1287
|
+
], options);
|
|
1288
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1289
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1290
|
+
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
1291
|
+
lines.push("");
|
|
1292
|
+
lines.push(...panelLines);
|
|
1179
1293
|
lines.push("");
|
|
1180
1294
|
lines.push(...buildRhythmSection(report, options));
|
|
1181
1295
|
lines.push("");
|
|
@@ -1324,19 +1438,18 @@ function renderFullSummaryReport(report, options, hourlyReport) {
|
|
|
1324
1438
|
const windowDurationMs = report.window.end.getTime() - report.window.start.getTime();
|
|
1325
1439
|
const headerLines = [
|
|
1326
1440
|
formatTimeRange(report.window.start, report.window.end, report.window),
|
|
1327
|
-
report.
|
|
1328
|
-
buildHeaderMeta(report),
|
|
1441
|
+
`${report.sessionCounts.total} sessions · ${formatDurationHours(requestedMetrics.strictEngagementMs)} focused · ${formatCompactInteger(report.tokenTotals.practicalBurn)} tokens`,
|
|
1329
1442
|
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
1330
1443
|
];
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
lines.push(...
|
|
1444
|
+
const panelLines = renderPanel(`idletime • ${report.window.label}`, headerLines, options);
|
|
1445
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1446
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1447
|
+
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
1448
|
+
lines.push("");
|
|
1449
|
+
lines.push(...panelLines);
|
|
1335
1450
|
if (hourlyReport) {
|
|
1336
1451
|
lines.push("");
|
|
1337
1452
|
lines.push(...buildRhythmSection(hourlyReport, options));
|
|
1338
|
-
lines.push("");
|
|
1339
|
-
lines.push(...buildSpikeSection(hourlyReport, options));
|
|
1340
1453
|
}
|
|
1341
1454
|
lines.push("");
|
|
1342
1455
|
lines.push(...renderSectionTitle("Activity", options));
|
|
@@ -1358,10 +1471,10 @@ function renderFullSummaryReport(report, options, hourlyReport) {
|
|
|
1358
1471
|
lines.push(...renderSectionTitle("Tokens", options));
|
|
1359
1472
|
const maxBurnValue = Math.max(report.tokenTotals.practicalBurn, report.directTokenTotals.practicalBurn);
|
|
1360
1473
|
const maxRawValue = Math.max(report.tokenTotals.rawTotalTokens, report.directTokenTotals.rawTotalTokens);
|
|
1361
|
-
lines.push(renderMetricRow("practical burn", report.tokenTotals.practicalBurn, maxBurnValue, formatCompactInteger(report.tokenTotals.practicalBurn), `${formatPercentage(report.tokenTotals.practicalBurn / report.tokenTotals.rawTotalTokens)} of raw`, "█", "burn", options));
|
|
1362
|
-
lines.push(renderMetricRow("all raw", report.tokenTotals.rawTotalTokens, maxRawValue, formatCompactInteger(report.tokenTotals.rawTotalTokens), `${formatInteger(report.tokenTotals.rawTotalTokens)} total`, "
|
|
1363
|
-
lines.push(renderMetricRow("direct burn", report.directTokenTotals.practicalBurn, maxBurnValue, formatCompactInteger(report.directTokenTotals.practicalBurn), `${formatPercentage(report.directTokenTotals.practicalBurn / report.tokenTotals.practicalBurn)} of burn`, "▒", "burn", options));
|
|
1364
|
-
lines.push(renderMetricRow("direct raw", report.directTokenTotals.rawTotalTokens, maxRawValue, formatCompactInteger(report.directTokenTotals.rawTotalTokens), `${formatPercentage(report.directTokenTotals.rawTotalTokens / report.tokenTotals.rawTotalTokens)} of raw`, "
|
|
1474
|
+
lines.push(renderMetricRow("practical burn", report.tokenTotals.practicalBurn, maxBurnValue, formatCompactInteger(report.tokenTotals.practicalBurn), `${formatPercentage(report.tokenTotals.practicalBurn / report.tokenTotals.rawTotalTokens)} of raw`, "█", "burn", options, "burn"));
|
|
1475
|
+
lines.push(renderMetricRow("all raw", report.tokenTotals.rawTotalTokens, maxRawValue, formatCompactInteger(report.tokenTotals.rawTotalTokens), `${formatInteger(report.tokenTotals.rawTotalTokens)} total`, "█", "raw", options, "raw"));
|
|
1476
|
+
lines.push(renderMetricRow("direct burn", report.directTokenTotals.practicalBurn, maxBurnValue, formatCompactInteger(report.directTokenTotals.practicalBurn), `${formatPercentage(report.directTokenTotals.practicalBurn / report.tokenTotals.practicalBurn)} of burn`, "▒", "burn", options, "burn"));
|
|
1477
|
+
lines.push(renderMetricRow("direct raw", report.directTokenTotals.rawTotalTokens, maxRawValue, formatCompactInteger(report.directTokenTotals.rawTotalTokens), `${formatPercentage(report.directTokenTotals.rawTotalTokens / report.tokenTotals.rawTotalTokens)} of raw`, "▒", "raw", options, "raw"));
|
|
1365
1478
|
if (report.wakeSummary) {
|
|
1366
1479
|
lines.push("");
|
|
1367
1480
|
lines.push(...renderSectionTitle("Wake Window", options));
|
|
@@ -1386,19 +1499,18 @@ function renderShareSummaryReport(report, options, hourlyReport) {
|
|
|
1386
1499
|
const lines = [];
|
|
1387
1500
|
const headerLines = [
|
|
1388
1501
|
formatTimeRange(report.window.start, report.window.end, report.window),
|
|
1389
|
-
report.
|
|
1390
|
-
buildHeaderMeta(report),
|
|
1502
|
+
`${report.sessionCounts.total} sessions · ${formatDurationHours(report.metrics.strictEngagementMs)} focused · ${formatCompactInteger(report.tokenTotals.practicalBurn)} tokens`,
|
|
1391
1503
|
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
1392
1504
|
];
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
lines.push(...
|
|
1505
|
+
const panelLines = renderPanel(`idletime • ${report.window.label}`, headerLines, options);
|
|
1506
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1507
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1508
|
+
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
1509
|
+
lines.push("");
|
|
1510
|
+
lines.push(...panelLines);
|
|
1397
1511
|
if (hourlyReport) {
|
|
1398
1512
|
lines.push("");
|
|
1399
1513
|
lines.push(...buildRhythmSection(hourlyReport, options));
|
|
1400
|
-
lines.push("");
|
|
1401
|
-
lines.push(...buildSpikeSection(hourlyReport, options));
|
|
1402
1514
|
}
|
|
1403
1515
|
lines.push("");
|
|
1404
1516
|
lines.push(...renderSectionTitle("Snapshot", options));
|
|
@@ -1430,16 +1542,8 @@ function formatAppliedFilters(report) {
|
|
|
1430
1542
|
function formatDurationLabel(durationMs) {
|
|
1431
1543
|
return `${Math.round(durationMs / 60000)}m`;
|
|
1432
1544
|
}
|
|
1433
|
-
function
|
|
1434
|
-
return `${
|
|
1435
|
-
}
|
|
1436
|
-
function buildPeakLine(report) {
|
|
1437
|
-
const peakBurnBucket = report.buckets.reduce((currentPeak, bucket) => bucket.practicalBurn > currentPeak.practicalBurn ? bucket : currentPeak, report.buckets[0]);
|
|
1438
|
-
const peakFocusBucket = report.buckets.reduce((currentPeak, bucket) => bucket.engagedMs > currentPeak.engagedMs ? bucket : currentPeak, report.buckets[0]);
|
|
1439
|
-
return `peaks burn ${formatCompactInteger(peakBurnBucket.practicalBurn)} @ ${formatHourOfDay(peakBurnBucket.start, report.window)} • focus ${formatDurationCompact(peakFocusBucket.engagedMs)} @ ${formatHourOfDay(peakFocusBucket.start, report.window)}`;
|
|
1440
|
-
}
|
|
1441
|
-
function renderMetricRow(label, value, maxValue, primaryText, detailText, filledCharacter, role, options) {
|
|
1442
|
-
return `${paint(padRight(` ${label}`, 14), "muted", options)} ${paint(buildBar(value, maxValue, summaryBarWidth, filledCharacter), role, options)} ${paint(padRight(primaryText, 7), "value", options)} ${dim(detailText, options)}`;
|
|
1545
|
+
function renderMetricRow(label, value, maxValue, primaryText, detailText, filledCharacter, role, options, valueRole = "value") {
|
|
1546
|
+
return `${paint(padRight(` ${label}`, 14), "muted", options)} ${paint(buildBar(value, maxValue, summaryBarWidth, filledCharacter), role, options)} ${paint(padRight(primaryText, 7), valueRole, options)} ${dim(detailText, options)}`;
|
|
1443
1547
|
}
|
|
1444
1548
|
function renderSnapshotRow(label, primaryText, detailText, role, options) {
|
|
1445
1549
|
return `${paint(padRight(` ${label}`, 12), role, options)} ${paint(padRight(primaryText, 10), "value", options)} ${dim(detailText, options)}`;
|