idletime 0.1.0 → 0.1.2
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 +26 -2
- package/assets/idle-time-readme.png +0 -0
- package/dist/idletime.js +195 -67
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -178,6 +178,19 @@ bun run typecheck
|
|
|
178
178
|
bun test
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
Release QA:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
bun run qa
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
That QA pass reads:
|
|
188
|
+
|
|
189
|
+
- `qa/data/user-journeys.csv` for installed-binary shell journeys
|
|
190
|
+
- `qa/data/coverage-matrix.csv` for required release coverage rows
|
|
191
|
+
|
|
192
|
+
It builds the package, packs the current checkout, installs the tarball into an isolated temp `BUN_INSTALL`, seeds synthetic Codex session logs, and runs the shell journeys against the installed `idletime` binary.
|
|
193
|
+
|
|
181
194
|
## Release Prep
|
|
182
195
|
|
|
183
196
|
Build the publishable CLI bundle:
|
|
@@ -192,6 +205,13 @@ Dry-run the release checks:
|
|
|
192
205
|
bun run check:release
|
|
193
206
|
```
|
|
194
207
|
|
|
208
|
+
`check:release` now runs:
|
|
209
|
+
|
|
210
|
+
- `bun run typecheck`
|
|
211
|
+
- `bun test`
|
|
212
|
+
- `bun run qa`
|
|
213
|
+
- `npm pack --dry-run`
|
|
214
|
+
|
|
195
215
|
Dry-run the Bun publish flow:
|
|
196
216
|
|
|
197
217
|
```bash
|
|
@@ -206,11 +226,15 @@ bun run pack:dry-run
|
|
|
206
226
|
|
|
207
227
|
## GitHub Release Flow
|
|
208
228
|
|
|
209
|
-
This repo now includes
|
|
229
|
+
This repo now includes:
|
|
230
|
+
|
|
231
|
+
- `.github/workflows/ci.yml` for push and pull-request release checks
|
|
232
|
+
- `.github/workflows/publish.yml` for the actual npm publish flow
|
|
210
233
|
|
|
211
234
|
What it does:
|
|
212
235
|
|
|
213
|
-
- runs on
|
|
236
|
+
- `ci.yml` runs on pushes to `dev` and `main`, plus pull requests
|
|
237
|
+
- `publish.yml` runs on manual dispatch or GitHub release publish
|
|
214
238
|
- installs Bun and Node on a GitHub-hosted runner
|
|
215
239
|
- runs `bun run check:release`
|
|
216
240
|
- publishes to npm with `npm publish --access public --provenance`
|
|
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.2",
|
|
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,12 +46,15 @@ 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
|
|
49
|
+
"check:release": "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
52
|
"pack:dry-run": "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
|
+
qa: "bun run qa:gaps && bun run qa:journeys",
|
|
56
|
+
"qa:gaps": "bun run qa/find-gaps.ts",
|
|
57
|
+
"qa:journeys": "bun run qa/run-shell-journeys.ts",
|
|
55
58
|
test: "bun test",
|
|
56
59
|
typecheck: "tsc --noEmit"
|
|
57
60
|
},
|
|
@@ -518,6 +521,16 @@ function parseCodexLogLine(lineText, sourceFilePath, lineNumber) {
|
|
|
518
521
|
}
|
|
519
522
|
|
|
520
523
|
// src/codex-session-log/token-usage.ts
|
|
524
|
+
function zeroTokenUsage() {
|
|
525
|
+
return {
|
|
526
|
+
inputTokens: 0,
|
|
527
|
+
cachedInputTokens: 0,
|
|
528
|
+
outputTokens: 0,
|
|
529
|
+
reasoningOutputTokens: 0,
|
|
530
|
+
totalTokens: 0,
|
|
531
|
+
practicalBurn: 0
|
|
532
|
+
};
|
|
533
|
+
}
|
|
521
534
|
function readTokenUsage(value, label) {
|
|
522
535
|
const record = expectObject(value, label);
|
|
523
536
|
const inputTokens = readNumber(record, "input_tokens", label);
|
|
@@ -545,7 +558,7 @@ function subtractTokenUsages(currentUsage, previousUsage) {
|
|
|
545
558
|
};
|
|
546
559
|
for (const [key, value] of Object.entries(nextUsage)) {
|
|
547
560
|
if (value < 0) {
|
|
548
|
-
|
|
561
|
+
return null;
|
|
549
562
|
}
|
|
550
563
|
}
|
|
551
564
|
return nextUsage;
|
|
@@ -567,9 +580,11 @@ function extractTokenPoints(records) {
|
|
|
567
580
|
continue;
|
|
568
581
|
}
|
|
569
582
|
const infoRecord = expectObject(info, "event_msg.payload.info");
|
|
583
|
+
const lastUsageValue = infoRecord.last_token_usage;
|
|
570
584
|
tokenPoints.push({
|
|
571
585
|
timestamp: record.timestamp,
|
|
572
|
-
usage: readTokenUsage(infoRecord.total_token_usage, "event_msg.payload.info.total_token_usage")
|
|
586
|
+
usage: readTokenUsage(infoRecord.total_token_usage, "event_msg.payload.info.total_token_usage"),
|
|
587
|
+
lastUsage: lastUsageValue === null || lastUsageValue === undefined ? null : readTokenUsage(lastUsageValue, "event_msg.payload.info.last_token_usage")
|
|
573
588
|
});
|
|
574
589
|
}
|
|
575
590
|
return tokenPoints.sort((leftPoint, rightPoint) => leftPoint.timestamp.getTime() - rightPoint.timestamp.getTime());
|
|
@@ -581,12 +596,21 @@ function buildTokenDeltaPoints(tokenPoints) {
|
|
|
581
596
|
deltaPoints.push({
|
|
582
597
|
timestamp: tokenPoint.timestamp,
|
|
583
598
|
cumulativeUsage: tokenPoint.usage,
|
|
584
|
-
deltaUsage:
|
|
599
|
+
deltaUsage: resolveTokenDeltaUsage(tokenPoint, previousPoint)
|
|
585
600
|
});
|
|
586
601
|
previousPoint = tokenPoint;
|
|
587
602
|
}
|
|
588
603
|
return deltaPoints;
|
|
589
604
|
}
|
|
605
|
+
function resolveTokenDeltaUsage(tokenPoint, previousPoint) {
|
|
606
|
+
if (tokenPoint.lastUsage) {
|
|
607
|
+
return tokenPoint.lastUsage;
|
|
608
|
+
}
|
|
609
|
+
if (!previousPoint) {
|
|
610
|
+
return tokenPoint.usage;
|
|
611
|
+
}
|
|
612
|
+
return subtractTokenUsages(tokenPoint.usage, previousPoint.usage) ?? zeroTokenUsage();
|
|
613
|
+
}
|
|
590
614
|
|
|
591
615
|
// src/codex-session-log/extract-turn-attribution.ts
|
|
592
616
|
function extractTurnAttribution(records) {
|
|
@@ -1039,33 +1063,130 @@ function shortenPath(pathText, maxLength) {
|
|
|
1039
1063
|
|
|
1040
1064
|
// src/reporting/render-theme.ts
|
|
1041
1065
|
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
|
-
|
|
1066
|
+
focus: "1;38;2;236;239;148",
|
|
1067
|
+
active: "1;38;2;208;219;96",
|
|
1068
|
+
agent: "1;38;2;166;182;77",
|
|
1069
|
+
idle: "1;38;2;138;150;66",
|
|
1070
|
+
burn: "1;38;2;228;209;92",
|
|
1071
|
+
raw: "1;38;2;188;172;80",
|
|
1072
|
+
frame: "1;38;2;149;158;56",
|
|
1073
|
+
heading: "1;38;2;249;246;212",
|
|
1074
|
+
muted: "38;2;142;145;96",
|
|
1075
|
+
value: "1;38;2;242;236;179"
|
|
1051
1076
|
};
|
|
1052
1077
|
function createRenderOptions(shareMode) {
|
|
1053
1078
|
return {
|
|
1054
1079
|
colorEnabled: Boolean(process.stdout.isTTY) && process.env.NO_COLOR === undefined,
|
|
1055
|
-
shareMode
|
|
1080
|
+
shareMode,
|
|
1081
|
+
terminalWidth: process.stdout.columns ?? null
|
|
1056
1082
|
};
|
|
1057
1083
|
}
|
|
1058
1084
|
function paint(text, role, options) {
|
|
1085
|
+
return paintAnsi(text, roleStyles[role], options);
|
|
1086
|
+
}
|
|
1087
|
+
function paintAnsi(text, style, options) {
|
|
1059
1088
|
if (!options.colorEnabled || text.length === 0) {
|
|
1060
1089
|
return text;
|
|
1061
1090
|
}
|
|
1062
|
-
return `\x1B[${
|
|
1091
|
+
return `\x1B[${style}m${text}\x1B[0m`;
|
|
1063
1092
|
}
|
|
1064
1093
|
function dim(text, options) {
|
|
1065
|
-
|
|
1066
|
-
|
|
1094
|
+
return paintAnsi(text, "2", options);
|
|
1095
|
+
}
|
|
1096
|
+
function measureVisibleTextWidth(text) {
|
|
1097
|
+
let visibleWidth = 0;
|
|
1098
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
1099
|
+
if (text[index] === "\x1B" && text[index + 1] === "[") {
|
|
1100
|
+
index += 2;
|
|
1101
|
+
while (index < text.length && text[index] !== "m") {
|
|
1102
|
+
index += 1;
|
|
1103
|
+
}
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
visibleWidth += 1;
|
|
1067
1107
|
}
|
|
1068
|
-
return
|
|
1108
|
+
return visibleWidth;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/reporting/render-logo-section.ts
|
|
1112
|
+
var baseBackgroundStyle = "48;2;12;15;8";
|
|
1113
|
+
var wordmarkStyle = `${baseBackgroundStyle};1;38;2;247;245;204`;
|
|
1114
|
+
var wordmarkLines = [
|
|
1115
|
+
" ▄▄ ▄▄",
|
|
1116
|
+
"▀▀ ██ ██ ██ ▀▀",
|
|
1117
|
+
"██ ▄████ ██ ▄█▀█▄ ▀██▀▀ ██ ███▄███▄ ▄█▀█▄",
|
|
1118
|
+
"██ ██ ██ ██ ██▄█▀ ██ ██ ██ ██ ██ ██▄█▀",
|
|
1119
|
+
"██▄ ▀████ ██ ▀█▄▄▄ ██ ██▄ ██ ██ ██ ▀█▄▄▄"
|
|
1120
|
+
];
|
|
1121
|
+
var patternColors = [
|
|
1122
|
+
{ red: 20, green: 24, blue: 10 },
|
|
1123
|
+
{ red: 48, green: 58, blue: 18 },
|
|
1124
|
+
{ red: 86, green: 96, blue: 24 },
|
|
1125
|
+
{ red: 128, green: 138, blue: 30 },
|
|
1126
|
+
{ red: 176, green: 188, blue: 40 },
|
|
1127
|
+
{ red: 220, green: 228, blue: 78 }
|
|
1128
|
+
];
|
|
1129
|
+
var monochromePatternCharacters = ["░", "░", "▒", "▓", "█"];
|
|
1130
|
+
function buildLogoSection(requestedWidth, options) {
|
|
1131
|
+
const wordmarkWidth = Math.max(...wordmarkLines.map((line) => line.length));
|
|
1132
|
+
const sectionWidth = Math.max(requestedWidth, wordmarkWidth);
|
|
1133
|
+
const patternWidth = Math.max(0, sectionWidth - wordmarkWidth);
|
|
1134
|
+
return wordmarkLines.map((line, rowIndex) => {
|
|
1135
|
+
const paddedWordmark = padRight(line, wordmarkWidth);
|
|
1136
|
+
const patternTail = buildPatternTail(patternWidth, rowIndex, options);
|
|
1137
|
+
return `${paintAnsi(paddedWordmark, wordmarkStyle, options)}${patternTail}`;
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
function resolveLogoSectionWidth(minimumWidth, options) {
|
|
1141
|
+
return Math.max(minimumWidth, options.terminalWidth ?? 0);
|
|
1142
|
+
}
|
|
1143
|
+
function buildPatternTail(width, rowIndex, options) {
|
|
1144
|
+
if (!options.colorEnabled) {
|
|
1145
|
+
return buildMonochromePatternTail(width, rowIndex);
|
|
1146
|
+
}
|
|
1147
|
+
let patternTail = "";
|
|
1148
|
+
let currentStyle = "";
|
|
1149
|
+
let currentSegmentWidth = 0;
|
|
1150
|
+
for (let columnIndex = 0;columnIndex < width; columnIndex += 1) {
|
|
1151
|
+
const style = getPatternCellStyle(getPatternIntensity(width, rowIndex, columnIndex));
|
|
1152
|
+
if (style === currentStyle) {
|
|
1153
|
+
currentSegmentWidth += 1;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (currentSegmentWidth > 0) {
|
|
1157
|
+
patternTail += paintAnsi(" ".repeat(currentSegmentWidth), currentStyle, options);
|
|
1158
|
+
}
|
|
1159
|
+
currentStyle = style;
|
|
1160
|
+
currentSegmentWidth = 1;
|
|
1161
|
+
}
|
|
1162
|
+
if (currentSegmentWidth > 0) {
|
|
1163
|
+
patternTail += paintAnsi(" ".repeat(currentSegmentWidth), currentStyle, options);
|
|
1164
|
+
}
|
|
1165
|
+
return patternTail;
|
|
1166
|
+
}
|
|
1167
|
+
function buildMonochromePatternTail(width, rowIndex) {
|
|
1168
|
+
let patternTail = "";
|
|
1169
|
+
for (let columnIndex = 0;columnIndex < width; columnIndex += 1) {
|
|
1170
|
+
const intensity = getPatternIntensity(width, rowIndex, columnIndex);
|
|
1171
|
+
const characterIndex = Math.min(monochromePatternCharacters.length - 1, Math.floor(intensity * monochromePatternCharacters.length));
|
|
1172
|
+
patternTail += monochromePatternCharacters[characterIndex];
|
|
1173
|
+
}
|
|
1174
|
+
return patternTail;
|
|
1175
|
+
}
|
|
1176
|
+
function getPatternIntensity(width, rowIndex, columnIndex) {
|
|
1177
|
+
const normalizedColumn = width <= 1 ? 0 : columnIndex / Math.max(1, width - 1);
|
|
1178
|
+
const envelope = 0.18 + 0.82 * Math.pow(normalizedColumn, 0.82);
|
|
1179
|
+
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;
|
|
1180
|
+
const blockOffset = ((Math.floor(columnIndex / 2) + rowIndex) % 2 === 0 ? 0.06 : -0.04) + (rowIndex === 0 ? -0.06 : 0);
|
|
1181
|
+
return clamp(envelope + wave + blockOffset, 0, 1);
|
|
1182
|
+
}
|
|
1183
|
+
function getPatternCellStyle(intensity) {
|
|
1184
|
+
const colorIndex = Math.min(patternColors.length - 1, Math.floor(intensity * patternColors.length));
|
|
1185
|
+
const color = patternColors[colorIndex];
|
|
1186
|
+
return `48;2;${color.red};${color.green};${color.blue}`;
|
|
1187
|
+
}
|
|
1188
|
+
function clamp(value, minValue, maxValue) {
|
|
1189
|
+
return Math.min(maxValue, Math.max(minValue, value));
|
|
1069
1190
|
}
|
|
1070
1191
|
|
|
1071
1192
|
// src/reporting/render-layout.ts
|
|
@@ -1095,32 +1216,39 @@ function renderSectionTitle(title, options) {
|
|
|
1095
1216
|
}
|
|
1096
1217
|
|
|
1097
1218
|
// src/reporting/render-rhythm-section.ts
|
|
1219
|
+
var groupSize = 4;
|
|
1098
1220
|
function buildRhythmSection(report, options) {
|
|
1099
1221
|
const quietValues = report.buckets.map((bucket) => Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs));
|
|
1100
1222
|
const idleValues = report.hasWakeWindow ? report.buckets.map((bucket) => bucket.awakeIdleMs) : quietValues;
|
|
1101
1223
|
const idleLabel = report.hasWakeWindow ? "idle" : "quiet";
|
|
1102
|
-
|
|
1224
|
+
const idleTotal = formatDurationCompact(idleValues.reduce((totalDurationMs, idleDurationMs) => totalDurationMs + idleDurationMs, 0));
|
|
1225
|
+
const lines = [
|
|
1103
1226
|
...renderSectionTitle("24h Rhythm", options),
|
|
1104
1227
|
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)
|
|
1228
|
+
renderRhythmRow("focus", buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.engagedMs))), formatDurationCompact(report.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + bucket.engagedMs, 0)), "focus", options),
|
|
1229
|
+
renderRhythmRow("active", buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.directActivityMs))), formatDurationCompact(report.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + bucket.directActivityMs, 0)), "active", options)
|
|
1109
1230
|
];
|
|
1231
|
+
lines.push(renderRhythmRow(padRight(idleLabel, 6).trimEnd(), buildGroupedTrack(buildSparkline(idleValues)), idleTotal, "idle", options));
|
|
1232
|
+
lines.push(renderRhythmRow("burn", buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.practicalBurn))), formatCompactInteger(report.buckets.reduce((totalBurn, bucket) => totalBurn + bucket.practicalBurn, 0)), "burn", options));
|
|
1233
|
+
return lines;
|
|
1234
|
+
}
|
|
1235
|
+
function buildGroupedTrack(text) {
|
|
1236
|
+
const groups = [];
|
|
1237
|
+
for (let i = 0;i < text.length; i += groupSize) {
|
|
1238
|
+
groups.push(text.slice(i, i + groupSize));
|
|
1239
|
+
}
|
|
1240
|
+
return groups.join("│");
|
|
1110
1241
|
}
|
|
1111
1242
|
function buildHourMarkerLine(report) {
|
|
1112
|
-
const
|
|
1113
|
-
for (
|
|
1114
|
-
|
|
1243
|
+
const markerGroups = [];
|
|
1244
|
+
for (let index = 0;index < report.buckets.length; index += groupSize) {
|
|
1245
|
+
const bucket = report.buckets[index];
|
|
1246
|
+
if (!bucket) {
|
|
1115
1247
|
continue;
|
|
1116
1248
|
}
|
|
1117
|
-
|
|
1118
|
-
markerCharacters[index] = hourLabel[0] ?? " ";
|
|
1119
|
-
if (index + 1 < markerCharacters.length) {
|
|
1120
|
-
markerCharacters[index + 1] = hourLabel[1] ?? " ";
|
|
1121
|
-
}
|
|
1249
|
+
markerGroups.push(padRight(formatHourOfDay(bucket.start, report.window), Math.min(groupSize, report.buckets.length - index)));
|
|
1122
1250
|
}
|
|
1123
|
-
return
|
|
1251
|
+
return markerGroups.join("│");
|
|
1124
1252
|
}
|
|
1125
1253
|
function renderRhythmRow(label, sparkline, totalText, role, options) {
|
|
1126
1254
|
return `${paint(` ${padRight(label, 6)}`, role, options)} ${paint(sparkline, role, options)} ${paint(totalText, "value", options)}`;
|
|
@@ -1147,11 +1275,16 @@ function renderFullHourlyReport(report, options) {
|
|
|
1147
1275
|
const lines = [];
|
|
1148
1276
|
const peakBurnBucket = report.buckets.reduce((currentPeak, bucket) => bucket.practicalBurn > currentPeak.practicalBurn ? bucket : currentPeak, report.buckets[0]);
|
|
1149
1277
|
const peakFocusBucket = report.buckets.reduce((currentPeak, bucket) => bucket.engagedMs > currentPeak.engagedMs ? bucket : currentPeak, report.buckets[0]);
|
|
1150
|
-
|
|
1278
|
+
const panelLines = renderPanel(`idletime hourly • ${report.window.label}`, [
|
|
1151
1279
|
`${formatTimestamp(report.window.start, report.window)} -> ${formatTimestamp(report.window.end, report.window)}`,
|
|
1152
1280
|
buildFilterLine(report),
|
|
1153
1281
|
`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)
|
|
1282
|
+
], options);
|
|
1283
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1284
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1285
|
+
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
1286
|
+
lines.push("");
|
|
1287
|
+
lines.push(...panelLines);
|
|
1155
1288
|
lines.push("");
|
|
1156
1289
|
lines.push(...buildRhythmSection(report, options));
|
|
1157
1290
|
lines.push("");
|
|
@@ -1171,11 +1304,16 @@ function renderFullHourlyReport(report, options) {
|
|
|
1171
1304
|
function renderShareHourlyReport(report, options) {
|
|
1172
1305
|
const lines = [];
|
|
1173
1306
|
const peakBurnBucket = report.buckets.reduce((currentPeak, bucket) => bucket.practicalBurn > currentPeak.practicalBurn ? bucket : currentPeak, report.buckets[0]);
|
|
1174
|
-
|
|
1307
|
+
const panelLines = renderPanel(`idletime hourly • ${report.window.label}`, [
|
|
1175
1308
|
`${formatTimestamp(report.window.start, report.window)} -> ${formatTimestamp(report.window.end, report.window)}`,
|
|
1176
1309
|
buildFilterLine(report),
|
|
1177
1310
|
`peak burn ${formatCompactInteger(peakBurnBucket.practicalBurn)} @ ${formatHourBucketLabel(peakBurnBucket.start, report.window)}`
|
|
1178
|
-
], options)
|
|
1311
|
+
], options);
|
|
1312
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1313
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1314
|
+
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
1315
|
+
lines.push("");
|
|
1316
|
+
lines.push(...panelLines);
|
|
1179
1317
|
lines.push("");
|
|
1180
1318
|
lines.push(...buildRhythmSection(report, options));
|
|
1181
1319
|
lines.push("");
|
|
@@ -1324,19 +1462,18 @@ function renderFullSummaryReport(report, options, hourlyReport) {
|
|
|
1324
1462
|
const windowDurationMs = report.window.end.getTime() - report.window.start.getTime();
|
|
1325
1463
|
const headerLines = [
|
|
1326
1464
|
formatTimeRange(report.window.start, report.window.end, report.window),
|
|
1327
|
-
report.
|
|
1328
|
-
buildHeaderMeta(report),
|
|
1465
|
+
`${report.sessionCounts.total} sessions · ${formatDurationHours(requestedMetrics.strictEngagementMs)} focused · ${formatCompactInteger(report.tokenTotals.practicalBurn)} tokens`,
|
|
1329
1466
|
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
1330
1467
|
];
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
lines.push(...
|
|
1468
|
+
const panelLines = renderPanel(`idletime • ${report.window.label}`, headerLines, options);
|
|
1469
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1470
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1471
|
+
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
1472
|
+
lines.push("");
|
|
1473
|
+
lines.push(...panelLines);
|
|
1335
1474
|
if (hourlyReport) {
|
|
1336
1475
|
lines.push("");
|
|
1337
1476
|
lines.push(...buildRhythmSection(hourlyReport, options));
|
|
1338
|
-
lines.push("");
|
|
1339
|
-
lines.push(...buildSpikeSection(hourlyReport, options));
|
|
1340
1477
|
}
|
|
1341
1478
|
lines.push("");
|
|
1342
1479
|
lines.push(...renderSectionTitle("Activity", options));
|
|
@@ -1358,10 +1495,10 @@ function renderFullSummaryReport(report, options, hourlyReport) {
|
|
|
1358
1495
|
lines.push(...renderSectionTitle("Tokens", options));
|
|
1359
1496
|
const maxBurnValue = Math.max(report.tokenTotals.practicalBurn, report.directTokenTotals.practicalBurn);
|
|
1360
1497
|
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`, "
|
|
1498
|
+
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"));
|
|
1499
|
+
lines.push(renderMetricRow("all raw", report.tokenTotals.rawTotalTokens, maxRawValue, formatCompactInteger(report.tokenTotals.rawTotalTokens), `${formatInteger(report.tokenTotals.rawTotalTokens)} total`, "█", "raw", options, "raw"));
|
|
1500
|
+
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"));
|
|
1501
|
+
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
1502
|
if (report.wakeSummary) {
|
|
1366
1503
|
lines.push("");
|
|
1367
1504
|
lines.push(...renderSectionTitle("Wake Window", options));
|
|
@@ -1386,19 +1523,18 @@ function renderShareSummaryReport(report, options, hourlyReport) {
|
|
|
1386
1523
|
const lines = [];
|
|
1387
1524
|
const headerLines = [
|
|
1388
1525
|
formatTimeRange(report.window.start, report.window.end, report.window),
|
|
1389
|
-
report.
|
|
1390
|
-
buildHeaderMeta(report),
|
|
1526
|
+
`${report.sessionCounts.total} sessions · ${formatDurationHours(report.metrics.strictEngagementMs)} focused · ${formatCompactInteger(report.tokenTotals.practicalBurn)} tokens`,
|
|
1391
1527
|
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
1392
1528
|
];
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
lines.push(...
|
|
1529
|
+
const panelLines = renderPanel(`idletime • ${report.window.label}`, headerLines, options);
|
|
1530
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1531
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1532
|
+
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
1533
|
+
lines.push("");
|
|
1534
|
+
lines.push(...panelLines);
|
|
1397
1535
|
if (hourlyReport) {
|
|
1398
1536
|
lines.push("");
|
|
1399
1537
|
lines.push(...buildRhythmSection(hourlyReport, options));
|
|
1400
|
-
lines.push("");
|
|
1401
|
-
lines.push(...buildSpikeSection(hourlyReport, options));
|
|
1402
1538
|
}
|
|
1403
1539
|
lines.push("");
|
|
1404
1540
|
lines.push(...renderSectionTitle("Snapshot", options));
|
|
@@ -1430,16 +1566,8 @@ function formatAppliedFilters(report) {
|
|
|
1430
1566
|
function formatDurationLabel(durationMs) {
|
|
1431
1567
|
return `${Math.round(durationMs / 60000)}m`;
|
|
1432
1568
|
}
|
|
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)}`;
|
|
1569
|
+
function renderMetricRow(label, value, maxValue, primaryText, detailText, filledCharacter, role, options, valueRole = "value") {
|
|
1570
|
+
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
1571
|
}
|
|
1444
1572
|
function renderSnapshotRow(label, primaryText, detailText, role, options) {
|
|
1445
1573
|
return `${paint(padRight(` ${label}`, 12), role, options)} ${paint(padRight(primaryText, 10), "value", options)} ${dim(detailText, options)}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idletime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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,12 +44,15 @@
|
|
|
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
|
|
47
|
+
"check:release": "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
50
|
"pack:dry-run": "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
|
+
"qa": "bun run qa:gaps && bun run qa:journeys",
|
|
54
|
+
"qa:gaps": "bun run qa/find-gaps.ts",
|
|
55
|
+
"qa:journeys": "bun run qa/run-shell-journeys.ts",
|
|
53
56
|
"test": "bun test",
|
|
54
57
|
"typecheck": "tsc --noEmit"
|
|
55
58
|
},
|