idletime
v0.3.0
Published
Visual CLI for Codex focus, token burn, spikes, and idle time from local session logs.
Maintainers
Readme
idletime
Local Bun CLI for Codex activity, token burn, visual 24-hour rhythm charts, and wake-window idle time.
idletime reads local Codex session logs from ~/.codex/sessions and turns them into a trailing-window dashboard that answers:
- When was I actually focused?
- When was the direct thread active?
- Where were the dead spots or awake idle gaps?
- Which hours spiked in token burn?
- How much of the day was direct work versus subagent runtime?
- How much OpenAI 5-hour and weekly quota is left right now?

How It Works
The dashboard and snapshot commands are read-only. They scan your local Codex session logs under ~/.codex/sessions/YYYY/MM/DD/*.jsonl and build reports from those raw events. The explicit refresh-bests command is the maintenance exception: it updates ~/.idletime/ and can fire best-related notifications.
At a high level:
- it classifies sessions as direct or subagent from
session_meta.payload.source - it treats real
user_messagearrivals as the strongest focus signal - it builds activity blocks by extending events forward by the idle cutoff
- it computes hourly and summary burn from token-count deltas, not just final session totals
- it reads live Codex rate limits from
codex app-serverand ties quota usage rows to the active OpenAI windows - it clips everything to the requested window so
last24hmeans the actual last 24 hours
That is why the dashboard can show both a fast visual story and defensible totals.
Install
Local development:
bun installAfter publish, expected install paths are:
npm install -g idletime@latestbun add -g idletime@latestTo update an existing Bun global install to the latest published version:
bun add -g idletime@latest --forceUse that instead of bun update idletime. In local verification on March 31, 2026, bun update idletime left the global idletime binary on 0.1.2, while bun add -g idletime@latest --force updated the Bun global install to 0.2.0.
Or run it without a global install:
npx idletime@latest --helpbunx idletime@latest --helpTo see the exact supported next step for the current install mode, run:
idletime updateIt prints the Bun or npm global update command when the binary was installed that way, and it explains when there is no persistent package to update.
Start Here
The default command is the main product:
bun run idletimeThat shows:
- A gold
BESTplaque in the header for your top concurrent agents, top 24-hour raw burn, and top agent-sum record - A framed trailing-24h dashboard
- A
Limitssection with live5h remaining,week remaining,5h used, andweek usedrows tied to OpenAI's current quota windows - A dedicated
Agentssection that charts concurrent child-task windows over the day - A
24h Rhythmstrip forfocus,active,quietoridle, andburn Spike Calloutsfor the biggest burn hours- A lower detail section with activity, tokens, and wake-window stats
Core Concepts
focus: strict engagement inferred from realuser_messagearrivalsactive: broader direct-session movement in the main threadidle: awake idle time, only shown when you pass--wakequiet: non-active time when no wake window is providedburn: practical burn, calculated asinput - cached_input + outputAgents: concurrent child-task windows derived from transcript lifecycle records when they exist, with a compatibility fallback for older subagent logs
Additional behavior:
last24h: the default trailing window, clipped to the actual last 24 hourstoday: local midnight to nowlive: global task scoreboard by default, withwaiting on you,running, recent concurrency, and per-project live staterefresh-bests: explicit full-history personal-record refresh for theBESTplaque and best-related notificationsdirect: user-started work in the main CLI or compatible direct session typessubagent: spawned agent sessionsidle cutoff: how long activity stays alive after the last event before it counts as quiet or idle
Commands
Default trailing-24h dashboard:
bun run idletimeTurn quiet time into real awake idle:
bun run idletime --wake 07:45-23:30Trim the output into a screenshot card:
bun run idletime --wake 07:45-23:30 --shareShow the current local day only:
bun run idletime todayLimit to one workspace:
bun run idletime today --workspace-only /path/to/demo-workspaceOpen the full hourly table:
bun run idletime hourly --window 24h --workspace-only /path/to/demo-workspaceOpen the live global scoreboard:
bun run idletime livelive defaults to all local sessions. Use --workspace-only when you want to pin the board to one repo or project path.
Use --global when you want to clear a previously added workspace scope explicitly.
When stdout is a TTY it repaints in place like a scoreboard. When stdout is not a TTY, it renders one snapshot and exits, which makes it usable in scripts and validation.
Pin the live board to one workspace:
bun run idletime live --workspace-only /path/to/demo-workspaceRefresh your BEST records explicitly:
bun run idletime refresh-bestsGroup the summary by model and effort:
bun run idletime last24h --group-by model --group-by effortGet machine-readable snapshots:
bun run idletime --jsonbun run idletime today --jsonbun run idletime hourly --jsonbun run idletime live --json--json is read-only. It emits one versioned JSON snapshot and exits. On last24h and today, it does not refresh best metrics or trigger best-related notifications. --share is human-only and cannot be combined with --json.
Share Mode
--share keeps the visual story and trims the secondary detail:
- header
- 24-hour rhythm strip
- top-3 burn spike callouts
- compact snapshot block
That is the best mode for terminal screenshots.
What The Visuals Mean
The top of the dashboard is intentionally visual-first.
Agentsplots concurrent child-task windows with real clock labels like8am,12pm, and4pm24h Rhythmgives one character per hour bucket across the trailing dayfocusmakes it obvious where you were actually engagedactiveshows the broader direct-session footprintidleappears when you pass--wake, otherwise that lane becomesquietburnhighlights token spikes without making you read the table firstSpike Calloutssurfaces the top burn hours immediately
The live board is intentionally narrower:
waiting on you: recent direct sessions whose latest task completed after your lastuser_message, so you likely owe the next replyrunning: active task windows across the selected scope after effort-aware stalenessrunning at: per-project counts for currently running workwaiting at: per-project counts for sessions currently waiting on youtop waiting: the specific waiting threads, shown asproject • age • thread idrecent: short live concurrency strip across the last 15 minutesthis turn: completed child tasks anchored to the latest still-warm directuser_messagetoday peak: highest observed concurrent child-task count since local midnight
Help
bun run idletime --helpThe help screen explains the modes, the chart lanes, and includes copy-paste examples.
Version output:
bun run idletime --versionOnce published, that also works as:
idletime --versionRecord Tracking
idletime now keeps a local personal-best ledger under ~/.idletime/.
bests-v1.json: durable best values for the header plaquebest-events.ndjson: append-only new-best historynear-best-notifications-v1.json: opt-in state for “close to best” nudges
By default:
- dashboards read cached
BESTvalues whenbests-v1.jsonalready exists - a cold cache renders without the
BESTplaque instead of bootstrapping best state implicitly - genuine new-best events can trigger a local macOS notification during the explicit refresh path
- near-best nudges are stored but disabled until you opt in by setting
nearBestEnabledtotrue
When you want to recompute those records, update the ledger, and fire any eligible notifications, run:
bun run idletime refresh-bestsValidation
bun run typecheck
bun testRelease QA:
bun run qaThat QA pass reads:
qa/data/user-journeys.csvfor installed-binary shell journeysqa/data/coverage-matrix.csvfor required release coverage rows
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.
Operational note: the default last24h and today commands now stay on the fast read path. They render from the requested report window plus any cached BEST ledger state and do not refresh records implicitly. refresh-bests is the only CLI command that performs the full-history best-metrics scan.
Release Prep
Build the publishable CLI bundle:
bun run buildDry-run the release checks:
bun run check:releasecheck:release now runs:
bun run typecheckbun testbun run qanpm pack --dry-run
Dry-run the Bun publish flow:
bun run publish:dry-runPreview exactly what npm would ship:
bun run pack:dry-runGitHub Release Flow
This repo now includes:
.github/workflows/ci.ymlfor push and pull-request release checks.github/workflows/publish.ymlfor the actual npm publish flow
What it does:
ci.ymlruns on pushes todevandmain, plus pull requestspublish.ymlruns only on manual dispatch frommain- requires the requested version to match
package.json - fails before publish when the npm version, Git tag, or GitHub release already exists
- installs Bun and Node on a GitHub-hosted runner
- runs
bun run check:release - publishes to npm with
npm publish --access public --provenance - creates the GitHub release only after npm publish succeeds
What you need in GitHub:
- the
NPM_TOKENsecret already added to the repo - the repo pushed to GitHub so Actions can run
- the repo URL in
package.jsonnow points athttps://github.com/ParkerRex/idletime
Release Notes
- The published binary is
idletime. - The package is prepared for public publish on npm and Bun.
- Use
bunx idletime@latestfor one-off runs. In a clean temp directory on March 30, 2026,bunx idletimeresolved0.1.2whilebunx idletime@latestresolved0.2.0. - Use
idletime updateto print the supported update command for the current install mode. package.jsoncurrently useslicense: "UNLICENSED"as a deliberate placeholder. Choose the real license you want before the first public release.
npm Site Checklist
If you want the cleanest setup, use npm trusted publishing with GitHub Actions instead of a long-lived token.
Preferred path:
- Publish the package from GitHub Actions, not manually from your laptop.
- On npm, open the package settings and configure a
Trusted Publisherfor your GitHub Actions workflow. - Keep
id-token: writein the publish workflow so npm can use OIDC.
Official docs:
- npm trusted publishers: https://docs.npmjs.com/trusted-publishers/
- GitHub npm publishing workflow: https://docs.github.com/en/actions/tutorials/publish-packages/publish-nodejs-packages
If you want to use a token instead:
- On npm, go to
Access Tokens. - Generate a new granular token on the website.
- Give it
Read and writepackage access. - Make sure it can publish when 2FA is enabled. npm rejected the first workflow run because the token did not satisfy that requirement.
- Store it in GitHub as
NPM_TOKEN.
Official token docs:
- https://docs.npmjs.com/creating-and-viewing-access-tokens/
Important:
- trusted publishing currently requires GitHub-hosted runners
- trusted publishing is preferred over long-lived tokens
- provenance is enabled in
package.json, but for provenance to be fully useful you should also add the real public GitHubrepositorymetadata before first publish - since you already added
NPM_TOKEN, the included GitHub Actions workflow can publish with the token path right now - if the workflow fails with
403 Forbiddenand mentions two-factor authentication, replaceNPM_TOKENwith an automation token or a granular token that can bypass 2FA for publish
Before First Publish
- choose the real license and replace
UNLICENSEDinpackage.json - add the actual GitHub repo metadata to
package.json - run
bun run check:release - recheck
npm view idletime version - run the
Publish idletimeworkflow manually frommainwith the exactpackage.jsonversion - let the workflow create the GitHub release after npm publish succeeds
