Skip to content

feat(codex): Added last subcommand: npx @ccusage/codex@latest last --day <n>#912

Open
Cheese-Yu wants to merge 4 commits intoryoppippi:mainfrom
Cheese-Yu:codex/add-support-for-codex-usage-statistics
Open

feat(codex): Added last subcommand: npx @ccusage/codex@latest last --day <n>#912
Cheese-Yu wants to merge 4 commits intoryoppippi:mainfrom
Cheese-Yu:codex/add-support-for-codex-usage-statistics

Conversation

@Cheese-Yu
Copy link
Copy Markdown

@Cheese-Yu Cheese-Yu commented Mar 29, 2026

Motivation

  • Provide a simple CLI command to view Codex token usage for the most recent N days as a fixed rolling window that explicitly excludes today.

Description

  • Add a new last subcommand implemented in apps/codex/src/commands/last.ts that validates --day, computes the date range, builds the daily report, and emits table or JSON output.
  • Introduce getLastNDaysRange and helper shiftDateKey in apps/codex/src/date-utils.ts to compute the closed since/until range (until = yesterday, since = n-1 days before yesterday).
  • Register the new command in apps/codex/src/run.ts and add user-facing documentation and navigation changes including docs/guide/codex/last-n-days.md, updates to README.md, and sidebar changes in docs/.vitepress/config.ts and related guide pages.
  • Include an inline Vitest unit test for getLastNDaysRange to ensure the date math excludes today and yields the expected range.

Files Changed

  • apps/codex/src/commands/last.ts
  • apps/codex/src/date-utils.ts
  • apps/codex/src/run.ts
  • apps/codex/README.md
  • docs/guide/codex/last-n-days.md
  • docs/.vitepress/config.ts
  • docs/guide/codex/daily.md
  • docs/guide/codex/index.md

Example Usage

# Last 10 days, excluding today
npx @ccusage/codex@latest last --day 10

# JSON output
npx @ccusage/codex@latest last --day 10 --json

Testing

  • pnpm --filter @ccusage/codex run start -- last --day 10 --json
  • pnpm --filter @ccusage/codex run start -- last --day 0
  • pnpm --filter @ccusage/codex run start -- last --day abc
  • pnpm --filter @ccusage/codex run start -- last --day=-3

Summary by CodeRabbit

  • New Features

    • Added "Last N Days Report" command to retrieve token usage for the most recent N days, excluding today, via last --day <n>.
    • Supports JSON output format with the --json flag.
  • Documentation

    • Added new guide page for the Last N Days Report command.
    • Updated existing documentation with the new reporting option.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new last command for the Codex CLI that retrieves token usage for the most recent N days, excluding today. It includes the command implementation with argument validation and dual output formats, date range calculation utilities, command registration, and comprehensive documentation of the feature.

Changes

Cohort / File(s) Summary
Command Implementation
apps/codex/src/commands/last.ts
New last command with --day argument validation, date range computation, event loading, daily report building, JSON and terminal table output formatting, and pricing resource disposal via Symbol.dispose.
Date Range Utilities
apps/codex/src/date-utils.ts
Added getLastNDaysRange() helper function to compute date ranges for N days excluding today, with timezone support and unit tests using fake timers.
Command Registration
apps/codex/src/run.ts
Imported and registered the new lastCommand in the subcommands dispatcher map.
Documentation
docs/guide/codex/last-n-days.md, docs/guide/codex/index.md, docs/guide/codex/daily.md, docs/.vitepress/config.ts, apps/codex/README.md
Added new documentation page describing the "last N days" feature, updated sidebar navigation, cross-linked related command references, and appended CLI usage example to README.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Parser
    participant Validator as Arg Validator
    participant DateUtil as Date Utils
    participant Session as Session Loader
    participant Pricer as Pricing Source
    participant Report as Report Builder
    participant Output as Output Formatter

    CLI->>Validator: Validate --day argument
    Validator-->>CLI: Valid day count or exit
    CLI->>DateUtil: getLastNDaysRange(dayCount, timezone)
    DateUtil-->>CLI: {since, until} date range
    CLI->>Session: Load token events from session dirs
    Session-->>CLI: Events or warnings for missing dirs
    alt Events exist
        CLI->>Pricer: Create CodexPricingSource
        CLI->>Report: Build daily report within date bounds
        Report-->>CLI: Rows with per-day metrics
        CLI->>Output: Format as JSON or terminal table
        Output-->>CLI: Render result
    else No events
        CLI->>Output: Emit empty JSON or message
        Output-->>CLI: Output
    end
    CLI->>Pricer: Dispose via Symbol.dispose
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • ryoppippi

Poem

🐰 Hop, skip, and a day away!
N days of usage come to play,
Last command hops through time so fine,
Excluding today—the perfect line!
Reports and tables, JSON arrays,
Token counts from yesterdays! 📊✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: adding a new 'last' subcommand to the Codex CLI tool with the --day flag for viewing token usage over N days.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Cheese-Yu
Copy link
Copy Markdown
Author

#906

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/codex/src/date-utils.ts (1)

115-136: UTC calendar math is the right approach here, but the exported helper still needs an input guard.

getLastNDaysRange(0) currently returns an inverted window (since after until). The CLI validates --day, but this helper is exported, so it should reject non-positive values itself instead of relying only on the command layer.

Proposed fix
 export function getLastNDaysRange(
 	dayCount: number,
 	timezone?: string,
 ): { since: string; until: string } {
+	if (!Number.isSafeInteger(dayCount) || dayCount <= 0) {
+		throw new Error('dayCount must be a positive integer.');
+	}
+
 	const todayDateKey = toDateKey(new Date().toISOString(), timezone);
 	const until = shiftDateKey(todayDateKey, -1);
 	const since = shiftDateKey(until, -(dayCount - 1));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/codex/src/date-utils.ts` around lines 115 - 136, getLastNDaysRange lacks
validation for non-positive dayCount, allowing getLastNDaysRange(0) to return an
inverted range; add an input guard at the start of getLastNDaysRange to reject
values < 1 (e.g., throw a RangeError with a clear message), so callers cannot
request zero or negative days. Keep the rest of the logic (using toDateKey and
shiftDateKey) intact and only validate the dayCount parameter before computing
todayDateKey, until, and since.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/codex/src/commands/last.ts`:
- Around line 58-65: The args object currently spreads sharedArgs (from
_shared-args.ts) which exposes --since/--until even though last.ts always uses
getLastNDaysRange(); remove the range flags by not spreading sharedArgs or by
creating an args object that only includes the needed flags (e.g., copy
sharedArgs fields you need except since/until) and keep the day field as
defined; update the args declaration in last.ts (the args variable in this file)
so --since/--until are no longer present in the exported CLI metadata.
- Around line 67-73: The code currently sets jsonOutput (logger.level = 0)
before validating the --day value, which hides parseDayCount() errors; reorder
and/or validate so parseDayCount(ctx.values.day) runs and any parsing errors are
handled before setting logger.level to 0 (or only set logger.level = 0 after
parseDayCount succeeds); specifically update the logic around jsonOutput,
logger.level and parseDayCount in the command handler so parseDayCount is called
first (or its error is surfaced) and only then enable silent JSON mode.

---

Nitpick comments:
In `@apps/codex/src/date-utils.ts`:
- Around line 115-136: getLastNDaysRange lacks validation for non-positive
dayCount, allowing getLastNDaysRange(0) to return an inverted range; add an
input guard at the start of getLastNDaysRange to reject values < 1 (e.g., throw
a RangeError with a clear message), so callers cannot request zero or negative
days. Keep the rest of the logic (using toDateKey and shiftDateKey) intact and
only validate the dayCount parameter before computing todayDateKey, until, and
since.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e77ea5a-c50e-4255-abdd-48e92338ec1d

📥 Commits

Reviewing files that changed from the base of the PR and between 61ee04d and a9a0e1e.

📒 Files selected for processing (8)
  • apps/codex/README.md
  • apps/codex/src/commands/last.ts
  • apps/codex/src/date-utils.ts
  • apps/codex/src/run.ts
  • docs/.vitepress/config.ts
  • docs/guide/codex/daily.md
  • docs/guide/codex/index.md
  • docs/guide/codex/last-n-days.md

Comment on lines +58 to +65
args: {
...sharedArgs,
day: {
type: 'string',
description: 'Number of days to include (must be a positive integer)',
required: true,
},
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Drop --since/--until from this subcommand's args.

...sharedArgs also exposes the range filters from apps/codex/src/_shared-args.ts, but this command always overwrites them with getLastNDaysRange(). That makes the help text advertise flags that do nothing.

Proposed fix
 export const lastCommand = define({
 	name: 'last',
 	description: 'Show Codex token usage for the last N days (excluding today)',
 	args: {
-		...sharedArgs,
+		json: sharedArgs.json,
+		timezone: sharedArgs.timezone,
+		locale: sharedArgs.locale,
+		offline: sharedArgs.offline,
+		compact: sharedArgs.compact,
+		color: sharedArgs.color,
+		noColor: sharedArgs.noColor,
 		day: {
 			type: 'string',
 			description: 'Number of days to include (must be a positive integer)',
 			required: true,
 		},
 	},
Based on learnings: "Treat Codex as a sibling to apps/ccusage; reuse shared packages, command names, and flag semantics; diverge only when Codex-specific data requires it and document inline".
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
args: {
...sharedArgs,
day: {
type: 'string',
description: 'Number of days to include (must be a positive integer)',
required: true,
},
},
args: {
json: sharedArgs.json,
timezone: sharedArgs.timezone,
locale: sharedArgs.locale,
offline: sharedArgs.offline,
compact: sharedArgs.compact,
color: sharedArgs.color,
noColor: sharedArgs.noColor,
day: {
type: 'string',
description: 'Number of days to include (must be a positive integer)',
required: true,
},
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/codex/src/commands/last.ts` around lines 58 - 65, The args object
currently spreads sharedArgs (from _shared-args.ts) which exposes
--since/--until even though last.ts always uses getLastNDaysRange(); remove the
range flags by not spreading sharedArgs or by creating an args object that only
includes the needed flags (e.g., copy sharedArgs fields you need except
since/until) and keep the day field as defined; update the args declaration in
last.ts (the args variable in this file) so --since/--until are no longer
present in the exported CLI metadata.

Comment on lines +67 to +73
const jsonOutput = Boolean(ctx.values.json);
if (jsonOutput) {
logger.level = 0;
}

const dayCount = parseDayCount(ctx.values.day);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate --day before enabling silent JSON mode.

logger.level = 0 is documented as silent, so last --json --day abc will exit with code 1 but no diagnostic from parseDayCount().

Proposed fix
 	async run(ctx) {
-		const jsonOutput = Boolean(ctx.values.json);
-		if (jsonOutput) {
-			logger.level = 0;
-		}
-
 		const dayCount = parseDayCount(ctx.values.day);
+		const jsonOutput = Boolean(ctx.values.json);
+		if (jsonOutput) {
+			logger.level = 0;
+		}
 
 		const range = getLastNDaysRange(dayCount, ctx.values.timezone);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/codex/src/commands/last.ts` around lines 67 - 73, The code currently
sets jsonOutput (logger.level = 0) before validating the --day value, which
hides parseDayCount() errors; reorder and/or validate so
parseDayCount(ctx.values.day) runs and any parsing errors are handled before
setting logger.level to 0 (or only set logger.level = 0 after parseDayCount
succeeds); specifically update the logic around jsonOutput, logger.level and
parseDayCount in the command handler so parseDayCount is called first (or its
error is surfaced) and only then enable silent JSON mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant