Skip to content

Conversation

@iipanda
Copy link

@iipanda iipanda commented Jan 10, 2026

Summary

Adds a new ccusage-style CLI app for Factory’s droid to report token usage and estimated cost from local Factory data.

This introduces a new package:

  • @ccusage/droid (binary: ccusage-droid)

with subcommands:

  • daily (default)
  • monthly
  • session

Motivation

Factory’s droid CLI records cumulative token counters in ~/.factory/logs/droid-log-*.log and writes per-session settings in ~/.factory/sessions/*.settings.json. This PR makes those artifacts usable for the same daily/monthly/session reporting workflow that ccusage provides for other tools.

What’s included

  • Log parsing + delta aggregation

    • Parses "[Session] Saving session settings" log lines and extracts cumulative tokenUsage.
    • Converts cumulative counters to per-interval deltas per sessionId (handles counter resets).
  • Model attribution improvements (reduces unknown)

    • Prefers model from log tags when present.
    • When log tags omit the model, reads the session settings file referenced by the log (value.path) and uses its model field.
    • Falls back to “last known model for the session”, and finally unknown.
    • Reports label: models resolved from settings are displayed with [inferred].
  • Custom model support

    • Resolves Factory custom model IDs (custom:*) via ~/.factory/settings.json (customModels[]).
    • Displays custom models as ${pricingModel} [custom] in tables.
  • Cost calculation via LiteLLM pricing

    • Uses the existing internal LiteLLM pricing fetcher to calculate costs from token counts.
    • Supports --offline to avoid fetching updated pricing.
    • Missing pricing is treated as $0 with a warning (consistent with other apps).

How to test

pnpm --filter @ccusage/droid test
pnpm --filter @ccusage/droid start -- daily
pnpm --filter @ccusage/droid start -- monthly --since 2026-01-01 --until 2026-01-10
pnpm --filter @ccusage/droid start -- session --json

Notes

For full transparency - this PR was almost fully vibe coded, but It includes unit tests and I also extensively tested it manually and didn't find any bugs.

Summary by CodeRabbit

  • New Features

    • Droid CLI: daily, monthly, and session reports for Factory token usage.
    • JSON or formatted-table output, date-range filters, timezone/locale support, compact view.
    • Automatic cost calculation with offline pricing option, custom model resolution, and warnings for missing pricing or logs.
    • CLI flags for JSON, since/until, custom factory directory, color control, and logging level.
  • Documentation

    • New README with quick start, command usage, pricing details, custom model examples, env vars, and license.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 10, 2026

Warning

Rate limit exceeded

@iipanda has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9b12dd0 and 6435083.

📒 Files selected for processing (1)
  • .github/workflows/ci.yaml
📝 Walkthrough

Walkthrough

Adds a new Droid CLI app that parses Factory Droid logs, builds daily/monthly/session token-usage reports, integrates pricing (online/offline) with prefetch support, and renders JSON or human-readable tables; includes loaders, report builders, utilities, configs, tests, and documentation.

Changes

Cohort / File(s) Summary
Packaging & Tooling
apps/droid/package.json, apps/droid/tsconfig.json, apps/droid/tsdown.config.ts, apps/droid/eslint.config.js, apps/droid/vitest.config.ts
New package metadata, build/test/lint configs, bin entry, engine constraints, dev deps, tsdown/vitest plugin wiring and ESLint preset + custom rule.
Documentation
apps/droid/README.md
New README describing CLI usage, commands, args, custom-model settings, pricing semantics, env vars, and license.
Constants & Logger
apps/droid/src/_consts.ts, apps/droid/src/logger.ts
Introduces factory/location/timezone/locale/log constants and a package-scoped logger export.
Shared CLI Args
apps/droid/src/_shared-args.ts
Centralized Gunshi CLI argument definitions (json, since, until, timezone, locale, offline, compact, factoryDir, color/noColor).
Types
apps/droid/src/_types.ts
Domain typings for TokenUsageEvent, ModelUsage, PricingResult/PricingSource, and report row shapes (daily/monthly/session).
Data Loading & Settings
apps/droid/src/data-loader.ts, apps/droid/src/factory-settings.ts
Log parsing, session-delta computation, model-id resolution (tag/settings/session), settings.json loader for custom models, caching with mtime checks, and public loadFactoryTokenUsageEvents API. (High surface area — review I/O, parsing, cache invalidation.)
Token & Date Utilities
apps/droid/src/token-utils.ts, apps/droid/src/date-utils.ts
Token arithmetic (deltas, resets, totals) and date utilities (normalize, keys, formatting, range checks, timezone handling).
Pricing
apps/droid/src/pricing.ts, apps/droid/src/_macro.ts
FactoryPricingSource adapter with candidate normalization, offline/prefetch handling, calculateCost flow, and a prefetchFactoryPricing helper using pricing-fetch-utils.
Report Builders
apps/droid/src/daily-report.ts, apps/droid/src/monthly-report.ts, apps/droid/src/session-report.ts
Aggregation logic that groups events, accumulates per-model/pricing usage, invokes pricingSource.calculateCost, and returns rows plus missingPricingModels.
CLI Commands & Runner
apps/droid/src/commands/*.ts, apps/droid/src/commands/index.ts, apps/droid/src/run.ts, apps/droid/src/index.ts
Gunshi command implementations for daily, monthly, session, CLI wiring, arg normalization, JSON/table rendering, and entrypoint.
Table Rendering
packages/terminal/src/table.ts
Adds multiline-aware cell width helper (getCellDisplayWidth) and updates width calculations/tests for improved table layout.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant DataLoader
    participant ReportBuilder
    participant PricingSource
    participant Formatter

    User->>CLI: invoke command (daily/monthly/session) + args
    CLI->>DataLoader: loadFactoryTokenUsageEvents(factoryDir)
    DataLoader-->>CLI: TokenUsageEvent[]
    CLI->>ReportBuilder: buildReport(events, {timezone, locale, since, until, pricingSource})
    ReportBuilder->>PricingSource: calculateCost(pricingModel, usage) (per pricing model)
    PricingSource-->>ReportBuilder: PricingResult or error
    ReportBuilder-->>CLI: ReportResult (rows + missingPricingModels)
    CLI->>Formatter: format output (json | table)
    Formatter-->>User: display output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • ryoppippi

Poem

🐰 Hopping through logs at break of day,
I count each token as they dart and play,
Models found and costs put in a line,
Sessions, months, and days all lookin' fine —
A rabbit's tally, quick and bright!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat(droid): add Factory Droid usage tracker' clearly and concisely summarizes the main change: introducing a new CLI package for tracking Factory Droid usage.
Docstring Coverage ✅ Passed Docstring coverage is 93.48% which is sufficient. The required threshold is 80.00%.

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


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.

Copy link

@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

🤖 Fix all issues with AI agents
In @apps/droid/package.json:
- Around line 31-33: The engines/devEngines mismatch should be resolved by
picking a single supported Node baseline and making both fields consistent:
either set "engines.node" to match the development runtime
("devEngines.runtime": "^24.11.0") if Node 24+ is required, or relax
"devEngines.runtime" to match the published requirement ("engines.node":
">=20.19.4") if Node 20+ is supported; update the package.json entries "engines"
and "devEngines.runtime" accordingly so both reflect the chosen Node version
range (and update any related docs/scripts that assert a Node version if
present).

In @apps/droid/src/_macro.ts:
- Line 29: Replace the console.warn call that logs "Failed to prefetch Factory
pricing data, proceeding with empty cache." with the module logger by calling
logger.warn and passing the same message and error; if a logger binding is not
present in _macro.ts, import or obtain the existing logger instance used by the
project and use it instead of console to comply with coding guidelines,
preserving the message and the error argument.
🧹 Nitpick comments (6)
apps/droid/src/factory-settings.ts (2)

5-5: Move test-only import inside the vitest block.

createFixture from fs-fixture is only used in tests but is imported at the module level. This causes the test dependency to be bundled into production code.

Proposed fix
 import { readFile } from 'node:fs/promises';
 import path from 'node:path';
 import process from 'node:process';
 import { Result } from '@praha/byethrow';
-import { createFixture } from 'fs-fixture';
 import * as v from 'valibot';
 import { DEFAULT_FACTORY_DIR, FACTORY_DIR_ENV } from './_consts.ts';
 import { logger } from './logger.ts';

 // ... rest of production code ...

 if (import.meta.vitest != null) {
+	const { createFixture } = await import('fs-fixture');
+
 	describe('loadFactoryCustomModels', () => {

Note: If dynamic imports are disallowed per coding guidelines, consider moving the test to a separate test file or using a different pattern for test-only dependencies.


77-90: Add fixture cleanup with using statement.

Per the codebase patterns, fs-fixture should be used with the using statement to ensure automatic cleanup after the test.

Proposed fix
 	describe('loadFactoryCustomModels', () => {
 		it('loads custom model ids from settings.json', async () => {
-			const fixture = await createFixture({
+			using fixture = await createFixture({
 				'settings.json': JSON.stringify(
 					{
 						customModels: [{ id: 'custom:Test-0', model: 'gpt-5.2', provider: 'openai' }],
 					},
 					null,
 					2,
 				),
 			});

 			const models = await loadFactoryCustomModels(fixture.path);
 			expect(models.get('custom:Test-0')?.model).toBe('gpt-5.2');
 		});
 	});
apps/droid/src/_consts.ts (1)

1-11: Consider defensive initialization for DEFAULT_TIMEZONE.

The initialization of DEFAULT_TIMEZONE using Intl.DateTimeFormat().resolvedOptions().timeZone could throw in some environments or if the Intl API is not fully supported. While the nullish coalescing operator handles a null/undefined return, it won't catch exceptions.

🛡️ Proposed defensive initialization
-export const DEFAULT_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC';
+export const DEFAULT_TIMEZONE = (() => {
+	try {
+		return Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC';
+	} catch {
+		return 'UTC';
+	}
+})();
apps/droid/src/commands/daily.ts (1)

22-31: Extract duplicated helper to shared utility.

The summarizeMissingPricing function is identical in both daily.ts and session.ts (lines 26-35). This code duplication should be extracted to a shared utility module to follow DRY principles and improve maintainability.

♻️ Proposed refactor

Create a new utility file apps/droid/src/_report-utils.ts:

import { logger } from './logger.ts';

export function summarizeMissingPricing(models: string[]): void {
	if (models.length === 0) {
		return;
	}
	const preview = models.slice(0, 5).join(', ');
	const suffix = models.length > 5 ? ', …' : '';
	logger.warn(
		`Missing pricing for ${models.length} models (cost treated as $0): ${preview}${suffix}`,
	);
}

Then import and use in both command files:

+import { summarizeMissingPricing } from '../_report-utils.ts';

-function summarizeMissingPricing(models: string[]): void {
-	if (models.length === 0) {
-		return;
-	}
-	const preview = models.slice(0, 5).join(', ');
-	const suffix = models.length > 5 ? ', …' : '';
-	logger.warn(
-		`Missing pricing for ${models.length} models (cost treated as $0): ${preview}${suffix}`,
-	);
-}

Note: Apply the same change to apps/droid/src/commands/session.ts and apps/droid/src/commands/monthly.ts if it exists there too.

apps/droid/src/monthly-report.ts (1)

26-54: Extract duplicated helper functions to a shared module.

formatModelDisplay, addEventUsage, and getOrCreateModelUsage are identical across daily-report.ts, monthly-report.ts, and session-report.ts. Consider extracting these to a shared utility module (e.g., report-utils.ts) to reduce duplication and improve maintainability.

apps/droid/src/date-utils.ts (1)

67-93: Consider removing unused _timezone parameters.

The _timezone parameters in formatDisplayDate and formatDisplayMonth are unused. If these are intentionally reserved for future use, adding a brief comment would clarify the intent. Otherwise, consider removing them.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82a75b9 and 567fe89.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • apps/droid/README.md
  • apps/droid/eslint.config.js
  • apps/droid/package.json
  • apps/droid/src/_consts.ts
  • apps/droid/src/_macro.ts
  • apps/droid/src/_shared-args.ts
  • apps/droid/src/_types.ts
  • apps/droid/src/commands/daily.ts
  • apps/droid/src/commands/index.ts
  • apps/droid/src/commands/monthly.ts
  • apps/droid/src/commands/session.ts
  • apps/droid/src/daily-report.ts
  • apps/droid/src/data-loader.ts
  • apps/droid/src/date-utils.ts
  • apps/droid/src/factory-settings.ts
  • apps/droid/src/index.ts
  • apps/droid/src/logger.ts
  • apps/droid/src/monthly-report.ts
  • apps/droid/src/pricing.ts
  • apps/droid/src/run.ts
  • apps/droid/src/session-report.ts
  • apps/droid/src/token-utils.ts
  • apps/droid/tsconfig.json
  • apps/droid/tsdown.config.ts
  • apps/droid/vitest.config.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Import conventions: Use .ts extensions for local file imports (e.g., import { foo } from './utils.ts')
Use ESLint for linting and formatting with tab indentation and double quotes
No console.log allowed except where explicitly disabled with eslint-disable
Use camelCase for variable names (e.g., usageDataSchema, modelBreakdownSchema)
Use PascalCase for type names (e.g., UsageData, ModelBreakdown)
Use UPPER_SNAKE_CASE for constants (e.g., DEFAULT_CLAUDE_CODE_PATH)
Only export constants, functions, and types that are actually used by other modules - do not export internal/private constants used only within the same file
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
Use Result.try() for wrapping operations that may throw (JSON parsing, etc.)
Use Result.isFailure() for checking errors (more readable than !Result.isSuccess())
Use early return pattern (if (Result.isFailure(result)) continue;) instead of ternary operators with Result types
NEVER use await import() dynamic imports anywhere in the codebase - this causes tree-shaking issues
Do not use console.log - use logger.ts instead
Vitest globals (describe, it, expect) are available automatically without imports in test blocks
NEVER use await import() dynamic imports in vitest test blocks - this is particularly problematic for test execution
All test files must use current Claude 4 models (claude-sonnet-4-20250514, claude-opus-4-20250514), not outdated Claude 3 models
Model names in tests must exactly match LiteLLM's pricing database entries

Files:

  • apps/droid/src/commands/monthly.ts
  • apps/droid/src/_consts.ts
  • apps/droid/src/commands/session.ts
  • apps/droid/src/_shared-args.ts
  • apps/droid/src/commands/daily.ts
  • apps/droid/src/commands/index.ts
  • apps/droid/src/session-report.ts
  • apps/droid/src/run.ts
  • apps/droid/src/monthly-report.ts
  • apps/droid/vitest.config.ts
  • apps/droid/src/_macro.ts
  • apps/droid/src/date-utils.ts
  • apps/droid/src/data-loader.ts
  • apps/droid/tsdown.config.ts
  • apps/droid/src/daily-report.ts
  • apps/droid/src/pricing.ts
  • apps/droid/src/factory-settings.ts
  • apps/droid/src/logger.ts
  • apps/droid/src/token-utils.ts
  • apps/droid/src/index.ts
  • apps/droid/src/_types.ts
**/_*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use underscore prefix for internal files (e.g., _types.ts, _utils.ts, _consts.ts)

Files:

  • apps/droid/src/_consts.ts
  • apps/droid/src/_shared-args.ts
  • apps/droid/src/_macro.ts
  • apps/droid/src/_types.ts
apps/**/package.json

📄 CodeRabbit inference engine (CLAUDE.md)

All projects under apps/ ship as bundled CLIs/binaries - list runtime dependencies in devDependencies (never dependencies) so the bundler owns the runtime payload

Files:

  • apps/droid/package.json
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.md: In documentation pages with screenshots, always place screenshots immediately after the main heading (H1)
Use relative image paths like /screenshot.png for images stored in /docs/public/
Always include descriptive alt text for images in documentation for accessibility

Files:

  • apps/droid/README.md
🧠 Learnings (52)
📚 Learning: 2026-01-09T12:07:00.515Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/pi/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:07:00.515Z
Learning: Applies to apps/pi/src/**/*.{ts,tsx} : Use `.ts` extensions for local imports

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/src/_consts.ts
  • apps/droid/tsdown.config.ts
  • apps/droid/src/index.ts
📚 Learning: 2026-01-09T12:07:00.515Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/pi/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:07:00.515Z
Learning: Applies to apps/pi/src/**/*.{ts,tsx} : Only export what's actually used from modules

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/src/commands/index.ts
  • apps/droid/tsdown.config.ts
  • apps/droid/src/logger.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Only export constants, functions, and types that are actually used by other modules - do not export internal/private constants used only within the same file

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/src/_consts.ts
  • apps/droid/src/commands/index.ts
  • apps/droid/tsdown.config.ts
📚 Learning: 2026-01-09T11:07:24.081Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/AGENTS.md:0-0
Timestamp: 2026-01-09T11:07:24.081Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Add JSDoc comments to exported functions and classes to document their purpose, parameters, and return values

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/tsdown.config.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/*.ts : Use `.ts` extensions for local imports (e.g., `import { foo } from './utils.ts'`)

Applied to files:

  • apps/droid/tsconfig.json
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/*.ts : Use `.ts` extensions for local (relative) imports

Applied to files:

  • apps/droid/tsconfig.json
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Place all vitest blocks alongside implementation files via `if (import.meta.vitest != null)` guards

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/eslint.config.js
  • apps/droid/vitest.config.ts
  • apps/droid/tsdown.config.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Import conventions: Use `.ts` extensions for local file imports (e.g., `import { foo } from './utils.ts'`)

Applied to files:

  • apps/droid/tsconfig.json
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/*.ts : Only export what is actually used

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/src/commands/index.ts
  • apps/droid/tsdown.config.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Only export symbols that are actually used by other modules

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/src/commands/index.ts
  • apps/droid/package.json
  • apps/droid/src/token-utils.ts
📚 Learning: 2026-01-09T12:35:27.240Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/amp/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:35:27.240Z
Learning: Applies to apps/amp/**/*.{test,spec}.{js,ts,jsx,tsx} : Enable vitest globals (describe, it, expect) without imports in test blocks

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/eslint.config.js
  • apps/droid/vitest.config.ts
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.test.{ts,tsx,js,jsx} : Vitest globals are enabled - use `describe`, `it`, `expect` directly without imports

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/eslint.config.js
  • apps/droid/vitest.config.ts
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/*.{test,spec}.ts : Vitest globals enabled: use `describe`, `it`, `expect` directly without importing them

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/vitest.config.ts
📚 Learning: 2026-01-09T12:07:00.515Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/pi/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:07:00.515Z
Learning: Applies to apps/pi/**/*.test.{ts,tsx} : Use vitest globals (`describe`, `it`, `expect`) directly without imports in test files

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/eslint.config.js
  • apps/droid/vitest.config.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Vitest globals (`describe`, `it`, `expect`) are available automatically without imports in test blocks

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/vitest.config.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/**.test.ts : Use in-source testing with `if (import.meta.vitest != null)` blocks instead of separate test files

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/vitest.config.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Write tests in-source using `if (import.meta.vitest != null)` blocks instead of separate test files

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/eslint.config.js
  • apps/droid/vitest.config.ts
  • apps/droid/src/token-utils.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Use Vitest globals (`describe`, `it`, `expect`) without imports in test blocks

Applied to files:

  • apps/droid/tsconfig.json
  • apps/droid/vitest.config.ts
  • apps/droid/src/token-utils.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Use ESLint for linting and formatting with tab indentation and double quotes

Applied to files:

  • apps/droid/tsconfig.json
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: The data loading architecture follows a clear separation of concerns: Data Loading (data-loader.ts), Token Aggregation (calculate-cost.ts), Command Execution (commands/), and CLI Entry (index.ts)

Applied to files:

  • apps/droid/src/commands/monthly.ts
  • apps/droid/src/commands/daily.ts
  • apps/droid/src/commands/index.ts
  • apps/droid/src/data-loader.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Use UPPER_SNAKE_CASE for constants (e.g., `DEFAULT_CLAUDE_CODE_PATH`)

Applied to files:

  • apps/droid/src/_consts.ts
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Data discovery relies on `OPENCODE_DATA_DIR` environment variable with default path `~/.local/share/opencode`

Applied to files:

  • apps/droid/src/_consts.ts
📚 Learning: 2026-01-09T12:35:27.240Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/amp/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:35:27.240Z
Learning: Use Gunshi framework with subcommands (daily, monthly, session) as entry point for Amp CLI

Applied to files:

  • apps/droid/src/commands/index.ts
  • apps/droid/src/run.ts
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Reuse shared packages (`ccusage/terminal`, `ccusage/internal`) wherever possible

Applied to files:

  • apps/droid/src/commands/index.ts
  • apps/droid/package.json
  • apps/droid/src/logger.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Entry point remains Gunshi-based; only the daily subcommand is wired for now

Applied to files:

  • apps/droid/src/commands/index.ts
  • apps/droid/src/run.ts
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/package.json : Add new dependencies as `devDependencies` unless explicitly requested otherwise

Applied to files:

  • apps/droid/package.json
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to apps/**/package.json : All projects under `apps/` ship as bundled CLIs/binaries - list runtime dependencies in `devDependencies` (never `dependencies`) so the bundler owns the runtime payload

Applied to files:

  • apps/droid/package.json
📚 Learning: 2026-01-09T12:07:00.515Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/pi/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:07:00.515Z
Learning: Applies to apps/pi/**/package.json : Add dependencies as `devDependencies` unless explicitly requested to add as runtime dependencies

Applied to files:

  • apps/droid/package.json
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Applies to apps/codex/**/package.json : Package Codex as a bundled CLI and keep every runtime dependency in devDependencies so the bundle includes shipped code

Applied to files:

  • apps/droid/package.json
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/package.json : Add dependencies as devDependencies unless explicitly required otherwise

Applied to files:

  • apps/droid/package.json
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/package.json : Keep every runtime dependency in `devDependencies` because OpenCode is packaged as a bundled CLI

Applied to files:

  • apps/droid/package.json
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/package.json : Because the CLI is bundled, keep all runtime libraries in devDependencies so the bundler captures them

Applied to files:

  • apps/droid/package.json
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Calculate costs using model pricing data when pre-calculated `cost` field is not present in OpenCode messages

Applied to files:

  • apps/droid/src/monthly-report.ts
  • apps/droid/src/_macro.ts
  • apps/droid/src/pricing.ts
  • apps/droid/src/_types.ts
📚 Learning: 2026-01-09T12:35:27.240Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/amp/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:35:27.240Z
Learning: Write all vitest test blocks alongside implementation files using if (import.meta.vitest != null) pattern

Applied to files:

  • apps/droid/vitest.config.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Fetch per-model pricing from LiteLLM model_prices_and_context_window.json via LiteLLMPricingFetcher using an offline cache scoped to Codex-prefixed models; handle aliases (e.g., gpt-5-codex → gpt-5) in CodexPricingSource

Applied to files:

  • apps/droid/src/_macro.ts
  • apps/droid/src/pricing.ts
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Map token fields: `inputTokens` ← `tokens.input`, `outputTokens` ← `tokens.output`, `cacheReadInputTokens` ← `tokens.cache.read`, `cacheCreationInputTokens` ← `tokens.cache.write`

Applied to files:

  • apps/droid/src/data-loader.ts
  • apps/droid/src/token-utils.ts
  • apps/droid/src/_types.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Use PascalCase for type names (e.g., `UsageData`, `ModelBreakdown`)

Applied to files:

  • apps/droid/src/data-loader.ts
  • apps/droid/src/_types.ts
📚 Learning: 2026-01-09T12:35:27.240Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/amp/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:35:27.240Z
Learning: Extract token usage from usageLedger.events[] array and cache token information from messages[].usage in thread JSON files

Applied to files:

  • apps/droid/src/data-loader.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Do not use console.log; use the logger utilities from `src/logger.ts` instead

Applied to files:

  • apps/droid/src/data-loader.ts
  • apps/droid/src/logger.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Use `fs-fixture` with `createFixture()` to simulate Claude data in tests

Applied to files:

  • apps/droid/src/data-loader.ts
  • apps/droid/src/factory-settings.ts
📚 Learning: 2026-01-09T12:07:00.515Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/pi/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:07:00.515Z
Learning: Applies to apps/pi/**/*.test.{ts,tsx} : Use `fs-fixture` for creating mock data in tests for testing data loading functionality

Applied to files:

  • apps/droid/src/data-loader.ts
  • apps/droid/src/factory-settings.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: For sessions missing model metadata, fall back to model gpt-5 to keep tokens visible; tag events isFallbackModel === true and surface isFallback in aggregates

Applied to files:

  • apps/droid/src/data-loader.ts
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.test.{ts,tsx,js,jsx} : Use `fs-fixture` with `using` statement to ensure cleanup in tests

Applied to files:

  • apps/droid/src/data-loader.ts
  • apps/droid/src/factory-settings.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Pricing tests must inject stub offline loaders to avoid network access

Applied to files:

  • apps/droid/src/pricing.ts
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/*.{test,spec}.ts : Use `fs-fixture` for mock data in tests of MCP server functionality

Applied to files:

  • apps/droid/src/factory-settings.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Tests should use fs-fixture with using to ensure cleanup

Applied to files:

  • apps/droid/src/factory-settings.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Do not use console.log - use logger.ts instead

Applied to files:

  • apps/droid/src/logger.ts
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Message structure must include `tokens.input`, `tokens.output`, `tokens.cache.read`, and `tokens.cache.write` fields

Applied to files:

  • apps/droid/src/token-utils.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : In tests, use current Claude 4 models (sonnet-4, opus-4)

Applied to files:

  • apps/droid/src/token-utils.ts
📚 Learning: 2026-01-09T12:07:00.515Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/pi/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:07:00.515Z
Learning: Applies to apps/pi/src/**/*.{ts,tsx} : NEVER use `await import()` dynamic imports anywhere in the codebase

Applied to files:

  • apps/droid/src/index.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : NEVER use `await import()` dynamic imports in vitest test blocks - this is particularly problematic for test execution

Applied to files:

  • apps/droid/src/index.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: After any code change, run format, typecheck, and tests in parallel (`pnpm run format`, `pnpm typecheck`, `pnpm run test`)

Applied to files:

  • apps/droid/src/index.ts
🧬 Code graph analysis (9)
apps/droid/src/commands/monthly.ts (7)
apps/droid/src/_shared-args.ts (1)
  • sharedArgs (4-57)
apps/droid/src/date-utils.ts (1)
  • normalizeFilterDate (38-49)
apps/droid/src/data-loader.ts (1)
  • loadFactoryTokenUsageEvents (212-340)
apps/droid/src/pricing.ts (1)
  • FactoryPricingSource (62-108)
apps/droid/src/monthly-report.ts (1)
  • buildMonthlyReport (56-120)
apps/droid/src/_consts.ts (1)
  • DEFAULT_TIMEZONE (6-6)
packages/terminal/src/table.ts (4)
  • ResponsiveTable (87-352)
  • formatModelsDisplayMultiline (428-435)
  • formatNumber (359-361)
  • addEmptySeparatorRow (638-641)
apps/droid/src/_shared-args.ts (1)
apps/droid/src/_consts.ts (2)
  • DEFAULT_TIMEZONE (6-6)
  • DEFAULT_LOCALE (7-7)
apps/droid/src/session-report.ts (3)
apps/droid/src/_types.ts (4)
  • ModelUsage (18-25)
  • PricingSource (32-34)
  • SessionReportRow (60-72)
  • TokenUsageEvent (3-16)
apps/droid/src/token-utils.ts (2)
  • addUsage (55-62)
  • createEmptyUsage (23-32)
apps/droid/src/date-utils.ts (2)
  • toDateKey (14-24)
  • isWithinRange (51-65)
apps/droid/src/monthly-report.ts (3)
apps/droid/src/_types.ts (4)
  • ModelUsage (18-25)
  • PricingSource (32-34)
  • MonthlyReportRow (48-58)
  • TokenUsageEvent (3-16)
apps/droid/src/token-utils.ts (2)
  • addUsage (55-62)
  • createEmptyUsage (23-32)
apps/droid/src/date-utils.ts (4)
  • toDateKey (14-24)
  • isWithinRange (51-65)
  • toMonthKey (26-36)
  • formatDisplayMonth (82-93)
apps/droid/src/_macro.ts (2)
packages/internal/src/pricing.ts (1)
  • LiteLLMModelPricing (49-49)
packages/internal/src/pricing-fetch-utils.ts (3)
  • fetchLiteLLMPricingDataset (11-34)
  • filterPricingDataset (36-47)
  • createPricingDataset (7-9)
apps/droid/src/data-loader.ts (5)
apps/droid/src/_consts.ts (3)
  • FACTORY_SESSIONS_SUBDIR (11-11)
  • FACTORY_LOGS_SUBDIR (10-10)
  • DROID_LOG_GLOB (9-9)
apps/droid/src/_types.ts (2)
  • TokenUsageEvent (3-16)
  • ModelIdSource (1-1)
apps/droid/src/factory-settings.ts (2)
  • resolveFactoryDir (31-33)
  • loadFactoryCustomModels (35-73)
apps/droid/src/token-utils.ts (3)
  • toTotalTokens (7-21)
  • createEmptyUsage (23-32)
  • subtractUsage (64-104)
apps/droid/src/logger.ts (1)
  • logger (5-5)
apps/droid/src/logger.ts (1)
packages/internal/src/logger.ts (1)
  • createLogger (5-17)
apps/droid/src/token-utils.ts (1)
apps/droid/src/_types.ts (1)
  • ModelUsage (18-25)
apps/droid/src/index.ts (1)
apps/droid/src/run.ts (1)
  • run (14-29)
🔇 Additional comments (50)
apps/droid/eslint.config.js (1)

1-16: No issues found—this ESLint config file is correct.

The ESM module type is properly configured ("type": "module" set at both repo root and app level), and test/no-importing-vitest-globals is a standard rule enforced consistently across the entire repository via the @ryoppippi/eslint-config preset. The configuration aligns with the repo's vitest testing approach.

apps/droid/tsconfig.json (1)

1-25: LGTM! Modern, strict TypeScript configuration aligned with Bun 1.2.

The configuration correctly uses "module": "Preserve" and "moduleResolution": "bundler" per Bun documentation, enables strict type safety, and includes vitest types for in-source testing. The verbatimModuleSyntax + erasableSyntaxOnly combination ensures proper type-only import handling.

The minor differences from React apps (ccusage, pi)—omitting @types/bun, JSX, and allowJs: true—are intentional and appropriate for a CLI tool. The vitest.config.ts is properly configured with includeSource and globals: true for the in-source testing pattern.

apps/droid/src/logger.ts (1)

1-7: LGTM!

Clean logger setup following the established pattern. Properly imports from @ccusage/internal/logger and creates a package-scoped logger instance.

apps/droid/src/pricing.ts (2)

91-93: Verify: Thinking tokens billed as output tokens.

The implementation adds thinkingTokens to outputTokens when calculating cost. This is likely intentional (thinking tokens are typically billed at output rates), but please confirm this matches the expected pricing behavior for Factory models.


110-137: LGTM!

Well-structured in-source test using import.meta.vitest guard. The test verifies parentheses normalization and offline pricing calculation with a stub loader, correctly using the using keyword for automatic disposal.

apps/droid/src/token-utils.ts (1)

1-104: LGTM!

Well-designed token utility functions with proper handling of normalization, aggregation, and delta computation. The reset detection in subtractUsage is a thoughtful approach for handling counter resets in log data.

apps/droid/src/_types.ts (1)

1-72: LGTM!

Well-organized type definitions following naming conventions (PascalCase for types, camelCase for fields). The types provide a clear contract for the token usage tracking and reporting features.

apps/droid/vitest.config.ts (1)

10-14: The Macros plugin filter does not need to include _macro.ts.

While _macro.ts is imported by pricing.ts, it contains no macro directives that require processing by the Macros plugin. It is a regular TypeScript module that exports a standard async function. The filename is misleading but does not indicate actual macro usage.

apps/droid/src/index.ts (1)

1-6: LGTM! Clean CLI entry point.

The entry point correctly uses a Node shebang, imports with .ts extension per guidelines, and leverages top-level await for async CLI execution. The structure is simple and appropriate for a CLI binary.

apps/droid/src/run.ts (1)

14-29: LGTM! CLI wiring is well-structured.

The run() function correctly normalizes arguments for npx invocation, wires the gunshi CLI framework with all three subcommands, and sets appropriate metadata. The JSON import from package.json is supported in modern Node/TypeScript environments.

apps/droid/src/commands/index.ts (1)

1-3: LGTM! Clean command barrel export.

The module correctly re-exports the three command implementations using .ts extensions and only exports what's consumed by run.ts. This follows the established pattern and guidelines.

apps/droid/package.json (1)

44-63: LGTM! Dependencies correctly configured for bundled CLI.

All runtime dependencies are properly listed in devDependencies per the guidelines for bundled CLIs. This ensures the bundler controls the runtime payload and nothing is unnecessarily included in the published package dependencies.

apps/droid/tsdown.config.ts (1)

1-25: LGTM!

The tsdown build configuration is well-structured with appropriate settings for a CLI application. The Macros plugin integration and vitest stripping are correctly configured.

apps/droid/README.md (1)

1-90: LGTM!

The documentation is comprehensive, well-organized, and provides clear examples for users. The custom model resolution feature and environment variable documentation are particularly helpful.

apps/droid/src/commands/session.ts (4)

26-35: LGTM!

The helper function provides clear user feedback for missing pricing data with appropriate truncation for readability.


37-56: LGTM!

Proper handling of JSON mode, date normalization, and error scenarios. The logger level adjustment for JSON output is essential for clean JSON responses.


58-94: LGTM!

Proper handling of data loading, empty results, and missing directory scenarios. The separation between "no data" and "no data matching filters" improves user experience.


96-222: LGTM!

The totals calculation, table rendering, and resource cleanup are all correctly implemented. Proper use of Symbol.dispose() in the finally block ensures pricing resources are always cleaned up.

apps/droid/src/commands/daily.ts (3)

33-76: LGTM!

The command setup, date filtering, data loading, and report building follow the same solid patterns as the session command. Consistent error handling and user feedback improve the overall CLI experience.


78-125: LGTM!

The totals calculation and JSON output formatting are correctly implemented with appropriate structure for programmatic consumption.


127-189: LGTM!

The table rendering with responsive layout and resource cleanup is correctly implemented. The compact mode messaging helps users understand why they're not seeing all columns.

apps/droid/src/_shared-args.ts (1)

1-57: LGTM!

The shared arguments are well-structured with clear descriptions, appropriate types, and sensible defaults. The use of satisfies Args ensures type safety, and the export is correct since these arguments are consumed by multiple command modules.

apps/droid/src/commands/monthly.ts (8)

1-18: LGTM!

Imports are well-organized with proper .ts extensions for local files, and the module structure follows the established patterns in the codebase.


21-30: LGTM!

The helper function provides clear, user-friendly warnings about missing pricing models with appropriate truncation for long lists.


42-51: LGTM!

Date filter normalization with proper error handling and early exit on invalid input.


53-65: LGTM!

Data loading with appropriate handling for missing logs directory and empty results.


67-87: LGTM!

Proper resource lifecycle management with try/finally ensuring FactoryPricingSource disposal.


89-109: LGTM!

Totals aggregation logic is correct.


111-124: LGTM!

JSON output includes all relevant data in a well-structured format.


126-184: LGTM!

Table output implementation with responsive layout, compact mode support, and clear user feedback.

apps/droid/src/monthly-report.ts (3)

1-5: LGTM!

Imports are correct with proper .ts extensions for local modules.


6-24: LGTM!

Type definitions follow PascalCase convention and are appropriately scoped (internal vs exported).


56-119: LGTM!

The report building logic is well-structured with proper date filtering, lazy summary initialization, and graceful handling of missing pricing data.

apps/droid/src/daily-report.ts (3)

1-5: LGTM!

Imports are correct with proper .ts extensions for local modules.


56-118: LGTM!

The daily report logic correctly aggregates by date key and follows the same well-structured pattern as the monthly report.


120-183: LGTM!

Inline tests properly use vitest globals without imports and cover key scenarios including aggregation, missing pricing tolerance, and cost calculation.

apps/droid/src/session-report.ts (2)

1-5: LGTM!

Imports are correct with proper .ts extensions for local modules.


58-128: LGTM!

Session report logic correctly groups by project and session, tracks last activity timestamps, and sorts by most recent activity first.

apps/droid/src/date-utils.ts (5)

1-12: LGTM!

safeTimeZone provides robust timezone validation with sensible fallbacks.


14-36: LGTM!

Date key functions use en-CA locale for consistent ISO-like date formatting with proper timezone support.


38-49: LGTM!

Date normalization handles both compact and hyphenated formats with clear error messaging.


51-65: LGTM!

Range check logic correctly handles optional bounds with inclusive comparison.


95-108: LGTM!

DateTime formatting with proper timezone support and locale-aware style.

apps/droid/src/data-loader.ts (7)

16-32: LGTM!

Error wrapper and type definition are correct.


34-58: LGTM!

Valibot schemas are well-structured with appropriate optional field handling.


60-140: LGTM!

Helper functions are well-implemented with proper Result type usage and efficient mtime-based caching.


142-201: LGTM!

Log line parsing is defensive with proper null returns on any parsing failure and schema validation.


203-340: LGTM!

The main data loading function is well-structured with proper resource cleanup, Result type usage for error handling, and delta computation for token usage.


342-506: LGTM!

Comprehensive test coverage for log parsing, delta computation, counter resets, and model ID resolution fallbacks.


1-14: The current code structure is correct—do not move the import.

The top-level fs-fixture import is the established pattern across the entire codebase (apps/ccusage, apps/amp, apps/codex, etc.). Vitest's includeSource configuration combined with define: { 'import.meta.vitest': 'undefined' } automatically strips test blocks and their imports from production builds, so tree-shaking concerns do not apply. Using await import() would violate coding guidelines that explicitly forbid dynamic imports in this context.

Likely an incorrect or invalid review comment.

Copy link

@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: 0

🧹 Nitpick comments (7)
apps/droid/src/_macro.ts (1)

29-37: Consider using Result type pattern for error handling.

The coding guidelines recommend preferring @praha/byethrow Result type over traditional try-catch for functional error handling. Consider refactoring this function to use Result.try().

♻️ Refactor to use Result pattern
-export async function prefetchFactoryPricing(): Promise<Record<string, LiteLLMModelPricing>> {
-	try {
-		const dataset = await fetchLiteLLMPricingDataset();
-		return filterPricingDataset(dataset, isFactoryModel);
-	} catch (error) {
-		logger.warn('Failed to prefetch Factory pricing data, proceeding with empty cache.', error);
-		return createPricingDataset();
-	}
-}
+export async function prefetchFactoryPricing(): Promise<Record<string, LiteLLMModelPricing>> {
+	const result = await Result.try(async () => {
+		const dataset = await fetchLiteLLMPricingDataset();
+		return filterPricingDataset(dataset, isFactoryModel);
+	});
+
+	if (Result.isFailure(result)) {
+		logger.warn('Failed to prefetch Factory pricing data, proceeding with empty cache.', result.error);
+		return createPricingDataset();
+	}
+
+	return result.value;
+}

Note: You'll need to import Result from @praha/byethrow.

Based on coding guidelines.

apps/droid/src/date-utils.ts (1)

91-104: Consider clarifying unused timezone parameters.

The _timezone parameter in formatDisplayDate and formatDisplayMonth is marked as unused (underscore prefix) and both functions hardcode timeZone: 'UTC'. If these parameters are present for API consistency with formatDisplayDateTime, consider adding a comment to clarify. Alternatively, if timezone-aware formatting isn't needed for these functions, the parameter could be removed.

Also applies to: 109-120

apps/droid/src/daily-report.ts (1)

30-67: Consider extracting shared helpers to a common module.

The functions formatModelDisplay, addEventUsage, and getOrCreateModelUsage are duplicated verbatim in session-report.ts (and likely in monthly-report.ts). Extracting them to a shared module like report-utils.ts would improve maintainability and reduce duplication.

♻️ Suggested extraction

Create a new file apps/droid/src/report-utils.ts:

// apps/droid/src/report-utils.ts
import type { ModelUsage, TokenUsageEvent } from './_types.ts';
import { addUsage, createEmptyUsage } from './token-utils.ts';

export function formatModelDisplay(event: TokenUsageEvent): string {
	const suffix = event.modelIdSource === 'settings' ? ' [inferred]' : '';
	if (event.modelId.startsWith('custom:')) {
		const base = event.pricingModel.trim() !== '' ? event.pricingModel : event.modelId;
		return `${base} [custom]${suffix}`;
	}
	return `${event.modelId}${suffix}`;
}

export function addEventUsage(target: ModelUsage, event: TokenUsageEvent): void {
	addUsage(target, {
		inputTokens: event.inputTokens,
		outputTokens: event.outputTokens,
		thinkingTokens: event.thinkingTokens,
		cacheReadTokens: event.cacheReadTokens,
		cacheCreationTokens: event.cacheCreationTokens,
	});
}

export function getOrCreateModelUsage(map: Map<string, ModelUsage>, key: string): ModelUsage {
	const existing = map.get(key);
	if (existing != null) {
		return existing;
	}
	const created = createEmptyUsage();
	map.set(key, created);
	return created;
}
apps/droid/src/factory-settings.ts (3)

1-16: Move fs-fixture import inside the test block.

The fs-fixture import at line 12 is only used within the test block. Importing it at the module level means it will be loaded in production, which may fail if it's a dev dependency or cause unnecessary overhead.

♻️ Proposed fix

Move the import inside the test block:

-import { createFixture } from 'fs-fixture';

Then at line 98:

 if (import.meta.vitest != null) {
+	const { createFixture } = await import('fs-fixture');
 	describe('loadFactoryCustomModels', () => {

Note: Per coding guidelines, await import() is discouraged. However, for test-only dependencies within import.meta.vitest blocks, this is the standard pattern in the codebase to avoid bundling test dependencies in production.


17-29: Consider extracting toError to a shared module.

The toError helper is duplicated in data-loader.ts (lines 26-28). Consider extracting it to a shared utility module.


98-115: Test fixture cleanup missing.

The test creates a fixture but doesn't clean it up. Consider using fixture.rm() in an afterEach or wrapping in a try/finally.

♻️ Proposed fix
 	it('loads custom model ids from settings.json', async () => {
 		const fixture = await createFixture({
 			'settings.json': JSON.stringify(
 				{
 					customModels: [{ id: 'custom:Test-0', model: 'gpt-5.2', provider: 'openai' }],
 				},
 				null,
 				2,
 			),
 		});

-		const models = await loadFactoryCustomModels(fixture.path);
-		expect(models.get('custom:Test-0')?.model).toBe('gpt-5.2');
+		try {
+			const models = await loadFactoryCustomModels(fixture.path);
+			expect(models.get('custom:Test-0')?.model).toBe('gpt-5.2');
+		} finally {
+			await fixture.rm();
+		}
 	});
apps/droid/src/data-loader.ts (1)

1-22: Move fs-fixture import inside the test block.

Same issue as factory-settings.ts: the fs-fixture import is only used within tests but imported at the module level.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec151bb and 9b12dd0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • apps/droid/src/_consts.ts
  • apps/droid/src/_macro.ts
  • apps/droid/src/_shared-args.ts
  • apps/droid/src/_types.ts
  • apps/droid/src/commands/daily.ts
  • apps/droid/src/commands/monthly.ts
  • apps/droid/src/commands/session.ts
  • apps/droid/src/daily-report.ts
  • apps/droid/src/data-loader.ts
  • apps/droid/src/date-utils.ts
  • apps/droid/src/factory-settings.ts
  • apps/droid/src/index.ts
  • apps/droid/src/logger.ts
  • apps/droid/src/monthly-report.ts
  • apps/droid/src/pricing.ts
  • apps/droid/src/run.ts
  • apps/droid/src/session-report.ts
  • apps/droid/src/token-utils.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/droid/src/commands/session.ts
  • apps/droid/src/logger.ts
  • apps/droid/src/index.ts
  • apps/droid/src/_consts.ts
  • apps/droid/src/run.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Import conventions: Use .ts extensions for local file imports (e.g., import { foo } from './utils.ts')
Use ESLint for linting and formatting with tab indentation and double quotes
No console.log allowed except where explicitly disabled with eslint-disable
Use camelCase for variable names (e.g., usageDataSchema, modelBreakdownSchema)
Use PascalCase for type names (e.g., UsageData, ModelBreakdown)
Use UPPER_SNAKE_CASE for constants (e.g., DEFAULT_CLAUDE_CODE_PATH)
Only export constants, functions, and types that are actually used by other modules - do not export internal/private constants used only within the same file
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
Use Result.try() for wrapping operations that may throw (JSON parsing, etc.)
Use Result.isFailure() for checking errors (more readable than !Result.isSuccess())
Use early return pattern (if (Result.isFailure(result)) continue;) instead of ternary operators with Result types
NEVER use await import() dynamic imports anywhere in the codebase - this causes tree-shaking issues
Do not use console.log - use logger.ts instead
Vitest globals (describe, it, expect) are available automatically without imports in test blocks
NEVER use await import() dynamic imports in vitest test blocks - this is particularly problematic for test execution
All test files must use current Claude 4 models (claude-sonnet-4-20250514, claude-opus-4-20250514), not outdated Claude 3 models
Model names in tests must exactly match LiteLLM's pricing database entries

Files:

  • apps/droid/src/_macro.ts
  • apps/droid/src/commands/daily.ts
  • apps/droid/src/factory-settings.ts
  • apps/droid/src/_shared-args.ts
  • apps/droid/src/monthly-report.ts
  • apps/droid/src/pricing.ts
  • apps/droid/src/commands/monthly.ts
  • apps/droid/src/session-report.ts
  • apps/droid/src/date-utils.ts
  • apps/droid/src/daily-report.ts
  • apps/droid/src/_types.ts
  • apps/droid/src/token-utils.ts
  • apps/droid/src/data-loader.ts
**/_*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use underscore prefix for internal files (e.g., _types.ts, _utils.ts, _consts.ts)

Files:

  • apps/droid/src/_macro.ts
  • apps/droid/src/_shared-args.ts
  • apps/droid/src/_types.ts
🧠 Learnings (18)
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Calculate costs using model pricing data when pre-calculated `cost` field is not present in OpenCode messages

Applied to files:

  • apps/droid/src/_macro.ts
  • apps/droid/src/monthly-report.ts
  • apps/droid/src/pricing.ts
  • apps/droid/src/session-report.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Model names in tests must exactly match LiteLLM's pricing database entries

Applied to files:

  • apps/droid/src/_macro.ts
  • apps/droid/src/pricing.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Do not use console.log; use the logger utilities from `src/logger.ts` instead

Applied to files:

  • apps/droid/src/_macro.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Do not use console.log - use logger.ts instead

Applied to files:

  • apps/droid/src/_macro.ts
📚 Learning: 2025-09-18T17:42:16.550Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-09-18T17:42:16.550Z
Learning: Applies to docs/**/*.{js,cjs,mjs,ts,cts,mts} : Do not use console.log in documentation scripts; use appropriate logging instead

Applied to files:

  • apps/droid/src/_macro.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Fetch per-model pricing from LiteLLM model_prices_and_context_window.json via LiteLLMPricingFetcher using an offline cache scoped to Codex-prefixed models; handle aliases (e.g., gpt-5-codex → gpt-5) in CodexPricingSource

Applied to files:

  • apps/droid/src/_macro.ts
  • apps/droid/src/pricing.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: The data loading architecture follows a clear separation of concerns: Data Loading (data-loader.ts), Token Aggregation (calculate-cost.ts), Command Execution (commands/), and CLI Entry (index.ts)

Applied to files:

  • apps/droid/src/commands/daily.ts
  • apps/droid/src/data-loader.ts
📚 Learning: 2026-01-09T12:07:00.515Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/pi/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:07:00.515Z
Learning: Applies to apps/pi/**/*.test.{ts,tsx} : Use `fs-fixture` for creating mock data in tests for testing data loading functionality

Applied to files:

  • apps/droid/src/factory-settings.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Use `fs-fixture` with `createFixture()` to simulate Claude data in tests

Applied to files:

  • apps/droid/src/factory-settings.ts
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Reuse shared packages (`ccusage/terminal`, `ccusage/internal`) wherever possible

Applied to files:

  • apps/droid/src/_shared-args.ts
📚 Learning: 2026-01-09T12:35:27.240Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/amp/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:35:27.240Z
Learning: Reuse shared packages ccusage/terminal and ccusage/internal wherever possible in Amp CLI

Applied to files:

  • apps/droid/src/_shared-args.ts
📚 Learning: 2026-01-09T12:22:39.520Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-09T12:22:39.520Z
Learning: Applies to **/*.ts : Use PascalCase for type names (e.g., `UsageData`, `ModelBreakdown`)

Applied to files:

  • apps/droid/src/_types.ts
  • apps/droid/src/data-loader.ts
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Map token fields: `inputTokens` ← `tokens.input`, `outputTokens` ← `tokens.output`, `cacheReadInputTokens` ← `tokens.cache.read`, `cacheCreationInputTokens` ← `tokens.cache.write`

Applied to files:

  • apps/droid/src/_types.ts
  • apps/droid/src/token-utils.ts
  • apps/droid/src/data-loader.ts
📚 Learning: 2026-01-09T12:07:00.515Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/pi/CLAUDE.md:0-0
Timestamp: 2026-01-09T12:07:00.515Z
Learning: Applies to apps/pi/src/**/*.{ts,tsx} : Use `.ts` extensions for local imports

Applied to files:

  • apps/droid/src/_types.ts
📚 Learning: 2026-01-09T11:07:17.610Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/opencode/CLAUDE.md:0-0
Timestamp: 2026-01-09T11:07:17.610Z
Learning: Applies to apps/opencode/**/*.{ts,tsx,js,jsx} : Message structure must include `tokens.input`, `tokens.output`, `tokens.cache.read`, and `tokens.cache.write` fields

Applied to files:

  • apps/droid/src/_types.ts
  • apps/droid/src/token-utils.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Only export symbols that are actually used by other modules

Applied to files:

  • apps/droid/src/token-utils.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Write tests in-source using `if (import.meta.vitest != null)` blocks instead of separate test files

Applied to files:

  • apps/droid/src/token-utils.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Use payload.info.total_token_usage as cumulative totals and payload.info.last_token_usage as per-turn delta; when only cumulative totals exist, compute delta by subtracting previous totals

Applied to files:

  • apps/droid/src/token-utils.ts
🧬 Code graph analysis (7)
apps/droid/src/_macro.ts (3)
packages/internal/src/pricing.ts (1)
  • LiteLLMModelPricing (49-49)
packages/internal/src/pricing-fetch-utils.ts (3)
  • fetchLiteLLMPricingDataset (11-34)
  • filterPricingDataset (36-47)
  • createPricingDataset (7-9)
apps/droid/src/logger.ts (1)
  • logger (9-9)
apps/droid/src/factory-settings.ts (2)
apps/droid/src/_consts.ts (2)
  • FACTORY_DIR_ENV (8-8)
  • DEFAULT_FACTORY_DIR (9-9)
apps/droid/src/logger.ts (1)
  • logger (9-9)
apps/droid/src/_shared-args.ts (1)
apps/droid/src/_consts.ts (2)
  • DEFAULT_TIMEZONE (10-10)
  • DEFAULT_LOCALE (11-11)
apps/droid/src/monthly-report.ts (3)
apps/droid/src/_types.ts (4)
  • ModelUsage (31-38)
  • PricingSource (51-53)
  • MonthlyReportRow (73-83)
  • TokenUsageEvent (13-26)
apps/droid/src/token-utils.ts (2)
  • addUsage (79-86)
  • createEmptyUsage (39-48)
apps/droid/src/date-utils.ts (4)
  • toDateKey (24-34)
  • isWithinRange (72-86)
  • toMonthKey (39-49)
  • formatDisplayMonth (109-120)
apps/droid/src/session-report.ts (3)
apps/droid/src/_types.ts (4)
  • ModelUsage (31-38)
  • PricingSource (51-53)
  • SessionReportRow (88-100)
  • TokenUsageEvent (13-26)
apps/droid/src/token-utils.ts (2)
  • addUsage (79-86)
  • createEmptyUsage (39-48)
apps/droid/src/date-utils.ts (2)
  • toDateKey (24-34)
  • isWithinRange (72-86)
apps/droid/src/token-utils.ts (1)
apps/droid/src/_types.ts (1)
  • ModelUsage (31-38)
apps/droid/src/data-loader.ts (5)
apps/droid/src/_consts.ts (3)
  • FACTORY_SESSIONS_SUBDIR (15-15)
  • FACTORY_LOGS_SUBDIR (14-14)
  • DROID_LOG_GLOB (13-13)
apps/droid/src/_types.ts (2)
  • TokenUsageEvent (13-26)
  • ModelIdSource (8-8)
apps/droid/src/factory-settings.ts (2)
  • resolveFactoryDir (49-51)
  • loadFactoryCustomModels (58-96)
apps/droid/src/token-utils.ts (3)
  • toTotalTokens (20-34)
  • createEmptyUsage (39-48)
  • subtractUsage (93-133)
apps/droid/src/logger.ts (1)
  • logger (9-9)
🔇 Additional comments (36)
apps/droid/src/monthly-report.ts (1)

1-136: LGTM! Well-structured monthly aggregation logic.

The implementation follows established patterns from daily-report.ts, properly separates internal helpers from the public API, and handles missing pricing gracefully by collecting affected models for user feedback.

apps/droid/src/date-utils.ts (1)

8-86: LGTM! Solid date utility implementations.

The functions provide robust date handling with:

  • Safe timezone fallbacks
  • Consistent ISO-like date formatting using en-CA locale
  • Input validation with clear error messages
  • Appropriate use of UTC for display functions to avoid timezone-related display issues
apps/droid/src/_shared-args.ts (1)

1-64: LGTM! Well-defined shared CLI arguments.

The argument definitions are comprehensive and properly structured with:

  • Type-safe constraints via satisfies Args
  • Consistent short flags for common options
  • Appropriate defaults using constants
  • Support for negatable boolean flags (offline)

This will provide a consistent UX across daily, monthly, and session commands.

apps/droid/src/commands/daily.ts (4)

1-24: LGTM! Proper imports and constants.

All imports use .ts extensions as required, and the TABLE_COLUMN_COUNT constant is appropriately defined and matches the table structure.


29-38: LGTM! User-friendly warning helper.

The function provides clear feedback about missing pricing models with a preview, helping users understand potential cost estimation gaps without overwhelming them with long lists.


44-73: LGTM! Robust validation and error handling.

The command properly:

  • Configures logger level for JSON mode
  • Validates date inputs with appropriate error messages and exit codes
  • Handles missing log directories gracefully
  • Provides early returns with appropriate output for edge cases

75-196: LGTM! Well-structured command implementation with proper resource management.

The implementation demonstrates good practices:

  • Resource cleanup via Symbol.dispose in finally block
  • Comprehensive totals calculation
  • Dual output modes (JSON and table) with appropriate formatting
  • Responsive table with compact mode support
  • User-friendly feedback for missing pricing and compact mode
apps/droid/src/commands/monthly.ts (5)

1-22: LGTM!

Clean imports and file structure. The command follows established patterns from other ccusage apps with proper module organization.


23-37: LGTM!

The TABLE_COLUMN_COUNT constant correctly matches the 9 columns defined in the table header, and the summarizeMissingPricing helper provides useful feedback without overwhelming the user.


39-72: LGTM!

The command setup follows best practices: proper logger silencing for JSON mode, date normalization with error handling, and graceful handling of missing logs directory.


74-131: LGTM!

Report building and JSON output logic are well-structured with proper try/finally for resource cleanup. The totals accumulation is correct.


133-195: LGTM!

Table rendering with responsive mode support and compact mode messaging follows established patterns. The Symbol.dispose usage in the finally block ensures proper cleanup of the pricing source.

apps/droid/src/daily-report.ts (3)

1-29: LGTM!

Clean type definitions and imports. The DailyReportOptions and DailyReportResult types provide a clear public API.


69-137: LGTM!

The buildDailyReport function correctly aggregates events by day, handles missing pricing gracefully, and produces well-sorted output. The pricing error handling via try/catch with accumulation into missingPricingModels is appropriate.


139-201: LGTM!

The test correctly validates aggregation behavior and missing pricing handling. The stub pricing source appropriately simulates partial pricing availability.

apps/droid/src/pricing.ts (5)

1-33: LGTM!

Clean imports and type definitions. The provider prefixes cover common Factory-supported providers.


35-45: LGTM!

The singleton promise pattern for prefetched pricing is appropriate for caching expensive network calls across multiple FactoryPricingSource instances within the same process.


47-73: LGTM!

The normalizeModelCandidates function correctly generates fallback candidates by stripping parenthetical suffixes and handling -thinking variants. This improves LiteLLM pricing match rates for Factory's model naming conventions.


75-132: LGTM!

The FactoryPricingSource class properly implements the PricingSource interface with:

  • Correct disposal forwarding to the underlying fetcher
  • Iterative candidate matching for pricing lookups
  • Proper error propagation when all candidates fail

134-161: LGTM!

The test correctly validates parentheses-suffix normalization by providing a custom offlineLoader with mocked pricing data. Since the test uses its own pricing dataset, the model name doesn't need to match LiteLLM's actual database entries.

apps/droid/src/token-utils.ts (4)

1-48: LGTM!

Clean utility functions with proper documentation. The ensureNonNegativeNumber helper correctly handles edge cases (non-numbers, negative values, non-finite values), and createEmptyUsage provides a consistent factory function.


50-86: LGTM!

The normalizeUsage and addUsage functions correctly handle unknown input validation and mutable aggregation with automatic totalTokens recalculation.


88-133: LGTM!

The subtractUsage function correctly handles delta computation with reset detection. When any counter decreases (indicating a session reset), it returns the current values as the delta rather than a negative value.


135-184: LGTM!

Tests correctly validate delta computation and reset behavior. The test cases cover normal deltas and counter reset scenarios.

apps/droid/src/session-report.ts (2)

1-31: LGTM!

Clean type definitions and imports. The SessionReportOptions and SessionReportResult types provide a clear public API consistent with other report modules.


71-146: LGTM, but consider adding unit tests.

The buildSessionReport function correctly aggregates events by session, handles missing pricing gracefully, and sorts by most recent activity. However, unlike daily-report.ts, this file lacks inline tests. Consider adding test coverage for key behaviors like session grouping and pricing error handling.

apps/droid/src/factory-settings.ts (2)

31-51: LGTM!

The schemas are well-defined with appropriate optionality, and resolveFactoryDir correctly implements the precedence chain: CLI → environment → default.


53-96: LGTM!

The loadFactoryCustomModels function properly handles all error cases (missing file, invalid JSON, schema validation failures) with appropriate warnings. The ENOENT case correctly returns an empty map silently.

apps/droid/src/data-loader.ts (7)

23-68: LGTM!

Well-structured schemas for parsing log line payloads. The tokenUsageSchema with optional fields handles partial data gracefully.


70-110: LGTM!

The helper functions asNonEmptyString, extractTimestampFromLogLine, and extractProjectKeyFromSettingsPath are clean and handle edge cases appropriately.


112-164: LGTM!

The loadModelIdFromSessionSettings function correctly implements mtime-based caching to avoid redundant file reads. The cache invalidation logic is sound.


166-230: LGTM!

The parseSessionSettingsLogLine function is well-structured with proper validation at each step. The early returns for invalid data keep the happy path clear.


232-261: LGTM!

Clean type definitions for the public API. The LoadFactoryResult correctly signals missing logs directory separately from empty events.


254-382: LGTM!

The loadFactoryTokenUsageEvents function is well-implemented:

  • Log files are sorted by mtime for chronological processing
  • Model ID resolution follows correct precedence (tag → settings → session)
  • Delta computation handles counter resets via subtractUsage
  • Stream resources are properly cleaned up in the finally block

384-547: LGTM!

Comprehensive test coverage for:

  • Delta computation from cumulative counters
  • Counter reset detection
  • Model ID fallback from session history
  • Model ID resolution from settings files

Consider adding fixture cleanup (as noted for factory-settings.ts), but the test logic is solid.

apps/droid/src/_types.ts (1)

1-100: LGTM! Well-structured type definitions.

The type definitions are clean, consistent, and follow all coding guidelines:

  • PascalCase for type names, camelCase for fields ✓
  • Appropriate underscore prefix for internal file ✓
  • Consistent token field structure across ModelUsage and all report row types ✓
  • Clear JSDoc documentation for each type ✓
  • All exports are used by other modules per the AI summary ✓

The file provides a solid API surface for the Droid usage pipeline with no runtime logic, which is exactly what a _types.ts file should contain.

@socket-security
Copy link

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedxdg-basedir@​5.1.01001008980100

View full report

@iipanda
Copy link
Author

iipanda commented Jan 11, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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