Skip to content

Conversation

@ryoppippi
Copy link
Owner

@ryoppippi ryoppippi commented Aug 23, 2025

Summary

This PR refactors table-related functionality to improve code organization and reduce duplication across the codebase.

Changes Made

1. Code Organization

  • Move all table utilities from src/_utils.ts to new src/_table.ts file
  • Create dedicated module for table-related functionality following single responsibility principle
  • Update imports across 8 files to use the new _table.ts module

2. Type Safety Improvements

  • Add TableCellAlign type for better type safety (replaces generic string literals)
  • Define UsageReportConfig and UsageData interfaces for consistent table configuration
  • Improve type definitions throughout table utilities

3. Code Consolidation

  • Add shared table factory function createUsageReportTable() with consistent headers and styling
  • Add formatUsageDataRow() for standardized data row formatting with token calculations
  • Add formatTotalsRow() with yellow highlighting and optional Last Activity column support
  • Add addEmptySeparatorRow() utility for consistent visual separation
  • Refactor daily.ts, monthly.ts, weekly.ts, and session.ts to use shared functions
  • Support Last Activity column for session reports via includeLastActivity config option

4. Code Reduction

  • Remove ~50-70 lines of duplicate code per command file
  • Centralize table creation logic while preserving all existing functionality
  • Maintain responsive design and compact mode functionality across all commands

5. Quality Improvements

  • Fix ESLint strict boolean expression warnings with nullish coalescing
  • All tests passing (301 tests)
  • TypeScript compilation successful
  • Consistent table formatting across all report types

Files Changed

  • src/_table.ts (new) - Centralized table utilities with shared functions
  • src/_utils.ts - Removed table code, kept utility functions
  • src/commands/daily.ts - Refactored to use shared table functions
  • src/commands/monthly.ts - Refactored to use shared table functions
  • src/commands/weekly.ts - Refactored to use shared table functions
  • src/commands/session.ts - Refactored to use shared table functions
  • src/commands/blocks.ts - Updated imports
  • src/commands/statusline.ts - Updated imports
  • src/commands/_session_id.ts - Updated imports
  • src/_live-rendering.ts - Updated imports

Benefits

  • Better maintainability: Table logic centralized in one place
  • Reduced duplication: Common patterns extracted into reusable functions
  • Type safety: Proper interfaces and types for table configuration
  • Consistency: All commands use the same table creation patterns
  • Easier testing: Shared functions can be tested independently

All existing functionality is preserved with no breaking changes to the user interface.

Summary by CodeRabbit

  • New Features

    • Responsive terminal tables with compact mode and on-screen compact-mode guidance.
    • Consistent number/currency/model formatting and timezone-aware date display across reports.
    • Project grouping headers and clearer model breakdown rows (tokens, costs) in reports.
  • Refactor

    • Table and formatting utilities consolidated into a dedicated table API; daily/weekly/monthly/session/blocks commands use the new API for consistent rendering.

- Create new src/_table.ts with ResponsiveTable class and formatting functions
- Move table-related utilities from _utils.ts to _table.ts:
  - ResponsiveTable class with responsive design and compact mode support
  - formatNumber(), formatCurrency() for data formatting
  - formatModelsDisplay(), formatModelsDisplayMultiline() for model display
  - pushBreakdownRows() for model breakdown table rows
  - TableRow, TableOptions, TableCellAlign types
- Update imports in all command files to use _table.ts instead of _utils.ts
- Keep only non-table utilities in _utils.ts (unreachable, getFileModifiedTime)
- Move all table-related tests to _table.ts with comprehensive coverage
- Improve type safety with dedicated TableCellAlign type for cell alignment

This refactoring improves code organization by separating table-related
utilities from general utilities, following the single responsibility principle.
All tests pass and functionality remains unchanged.
@coderabbitai
Copy link

coderabbitai bot commented Aug 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Caution

Review failed

The pull request is closed.

Walkthrough

Moves terminal table and formatting utilities from src/_utils.ts into a new src/_table.ts, adds a responsive, width-aware table API and helpers, and updates multiple command and rendering files to import and use the new module; no public signatures changed.

Changes

Cohort / File(s) Summary
New table module
src/_table.ts
Adds ResponsiveTable class, Table types, responsive column sizing, compact-mode logic, formatting helpers (formatNumber, formatCurrency, formatModelsDisplay*), pushBreakdownRows, and usage-report helpers (UsageReportConfig, createUsageReportTable, formatUsageDataRow, formatTotalsRow, addEmptySeparatorRow).
Utils cleanup
src/_utils.ts
Removes ResponsiveTable and related table/formatting helpers (moved to _table.ts); retains remaining unrelated utilities (e.g., getFileModifiedTime).
Live rendering import update
src/_live-rendering.ts
Changes imports for formatCurrency, formatModelsDisplay, and formatNumber to ./_table.ts.
Commands — import + API migration
src/commands/daily.ts, src/commands/monthly.ts, src/commands/weekly.ts, src/commands/session.ts, src/commands/blocks.ts, src/commands/statusline.ts, src/commands/_session_id.ts
Replace imports from ../_utils.ts with ../_table.ts. For daily/monthly/weekly/session commands, replace direct ResponsiveTable usage and manual row construction with UsageReportConfig + createUsageReportTable, formatUsageDataRow, formatTotalsRow, addEmptySeparatorRow, and pushBreakdownRows. Other command files only change import sources.
Tests and dependencies
(tests & deps referenced in summary)
New table module relies on cli-table3, picocolors, string-width, es-toolkit/uniq; tests updated to validate compact mode, formatting, and width variations (vitest).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Cmd as Command (daily/weekly/monthly/session/blocks)
  participant Table as _table.ts (createUsageReportTable / ResponsiveTable)
  participant Env as Terminal (COLUMNS / stdout.columns)
  participant CLI as cli-table3

  User->>Cmd: invoke command
  Cmd->>Table: createUsageReportTable(config) / formatUsageDataRow(...)
  Table->>Env: read terminal width
  alt compact criteria met
    Table-->>Cmd: isCompactMode() = true
    Note right of Table #f0f4ff: map compact headers<br/>filter columns
  else full layout
    Table-->>Cmd: isCompactMode() = false
  end
  loop add rows
    Cmd->>Table: push(row) / pushBreakdownRows(...)
  end
  Cmd->>Table: toString()
  Table->>CLI: render with computed widths & alignments
  Table-->>Cmd: formatted table string
  Cmd-->>User: print table
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A hop, a nibble, I tidy the stacks,
Tables moved home with column-wise hacks.
Compact when the terminal grows thin,
Numbers align, models tucked in.
I thump—imports fixed—now print and grin. 🐇📊

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ef631c8 and 1e99896.

📒 Files selected for processing (5)
  • src/_table.ts (1 hunks)
  • src/commands/daily.ts (6 hunks)
  • src/commands/monthly.ts (3 hunks)
  • src/commands/session.ts (4 hunks)
  • src/commands/weekly.ts (3 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/refactor-table-utilities

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @ryoppippi, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the codebase by extracting all table-related utility functions and the ResponsiveTable class from the general-purpose src/_utils.ts file into a new, dedicated module: src/_table.ts. This change aims to improve code organization, strictly adhere to the Single Responsibility Principle, and enhance the maintainability of the project. The refactoring ensures that _utils.ts now contains only truly general utilities, while all table-specific logic resides in its own well-defined module. All consuming files have been updated to reflect these new import paths, and the change introduces no breaking changes to existing functionality.

Highlights

  • New Dedicated Table Utilities File: A new file, src/_table.ts, has been introduced to centralize all table-related logic and components, including the ResponsiveTable class and various formatting utilities.
  • Refactored Table-Related Utilities: The ResponsiveTable class, along with functions like formatNumber, formatCurrency, formatModelsDisplay, formatModelsDisplayMultiline, and pushBreakdownRows, have been relocated from src/_utils.ts to src/_table.ts.
  • Streamlined General Utilities File: src/_utils.ts has been streamlined to contain only general-purpose utilities such as unreachable() and getFileModifiedTime(), enhancing its focus and clarity.
  • Updated Import Paths Across the Project: All files that previously imported table utilities from src/_utils.ts, including several command files and _live-rendering.ts, have been updated to import from the new src/_table.ts.
  • Improved Type Safety: The introduction of a dedicated TableCellAlign type in _table.ts improves type checking and overall code robustness for table cell alignment.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Aug 23, 2025

Open in StackBlitz

npm i https://pkg.pr.new/ryoppippi/ccusage@603

commit: 1e99896

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Aug 23, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ccusage-guide 1e99896 Commit Preview URL

Branch Preview URL
Aug 23 2025, 08:48 PM

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively refactors table-related utilities into a new _table.ts file, which greatly improves code organization and adheres to the single responsibility principle. The changes are well-executed across the codebase. My review includes a couple of suggestions for the new _table.ts file to improve maintainability and fix a minor issue in the tests.

Comment on lines +127 to +269
toString(): string {
// Check environment variable first, then process.stdout.columns, then default
const terminalWidth = Number.parseInt(process.env.COLUMNS ?? '', 10) || process.stdout.columns || 120;

// Determine if we should use compact mode
this.compactMode = this.forceCompact || (terminalWidth < this.compactThreshold && this.compactHead != null);

// Get current table configuration
const { head, colAligns } = this.getCurrentTableConfig();
const compactIndices = this.getCompactIndices();

// Calculate actual content widths first (excluding separator rows)
const dataRows = this.rows.filter(row => !this.isSeparatorRow(row));

// Filter rows to compact mode if needed
const processedDataRows = this.compactMode
? dataRows.map(row => this.filterRowToCompact(row, compactIndices))
: dataRows;

const allRows = [head.map(String), ...processedDataRows.map(row => row.map((cell) => {
if (typeof cell === 'object' && cell != null && 'content' in cell) {
return String(cell.content);
}
return String(cell ?? '');
}))];

const contentWidths = head.map((_, colIndex) => {
const maxLength = Math.max(
...allRows.map(row => stringWidth(String(row[colIndex] ?? ''))),
);
return maxLength;
});

// Calculate table overhead
const numColumns = head.length;
const tableOverhead = 3 * numColumns + 1; // borders and separators
const availableWidth = terminalWidth - tableOverhead;

// Always use content-based widths with generous padding for numeric columns
const columnWidths = contentWidths.map((width, index) => {
const align = colAligns[index];
// For numeric columns, ensure generous width to prevent truncation
if (align === 'right') {
return Math.max(width + 3, 11); // At least 11 chars for numbers, +3 padding
}
else if (index === 1) {
// Models column - can be longer
return Math.max(width + 2, 15);
}
return Math.max(width + 2, 10); // Other columns
});

// Check if this fits in the terminal
const totalRequiredWidth = columnWidths.reduce((sum, width) => sum + width, 0) + tableOverhead;

if (totalRequiredWidth > terminalWidth) {
// Apply responsive resizing and use compact date format if available
const scaleFactor = availableWidth / columnWidths.reduce((sum, width) => sum + width, 0);
const adjustedWidths = columnWidths.map((width, index) => {
const align = colAligns[index];
let adjustedWidth = Math.floor(width * scaleFactor);

// Apply minimum widths based on column type
if (align === 'right') {
adjustedWidth = Math.max(adjustedWidth, 10);
}
else if (index === 0) {
adjustedWidth = Math.max(adjustedWidth, 10);
}
else if (index === 1) {
adjustedWidth = Math.max(adjustedWidth, 12);
}
else {
adjustedWidth = Math.max(adjustedWidth, 8);
}

return adjustedWidth;
});

const table = new Table({
head,
style: this.style,
colAligns,
colWidths: adjustedWidths,
wordWrap: true,
wrapOnWordBoundary: true,
});

// Add rows with special handling for separators and date formatting
for (const row of this.rows) {
if (this.isSeparatorRow(row)) {
// Skip separator rows - cli-table3 will handle borders automatically
continue;
}
else {
// Use compact date format for first column if dateFormatter available
let processedRow = row.map((cell, index) => {
if (index === 0 && this.dateFormatter != null && typeof cell === 'string' && this.isDateString(cell)) {
return this.dateFormatter(cell);
}
return cell;
});

// Filter to compact columns if in compact mode
if (this.compactMode) {
processedRow = this.filterRowToCompact(processedRow, compactIndices);
}

table.push(processedRow);
}
}

return table.toString();
}
else {
// Use generous column widths with normal date format
const table = new Table({
head,
style: this.style,
colAligns,
colWidths: columnWidths,
wordWrap: true,
wrapOnWordBoundary: true,
});

// Add rows with special handling for separators
for (const row of this.rows) {
if (this.isSeparatorRow(row)) {
// Skip separator rows - cli-table3 will handle borders automatically
continue;
}
else {
// Filter to compact columns if in compact mode
const processedRow = this.compactMode
? this.filterRowToCompact(row, compactIndices)
: row;
table.push(processedRow);
}
}

return table.toString();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The toString() method has a large if/else block where the logic for processing and adding rows to the table is duplicated. The only significant difference is that the dateFormatter is applied when the table requires resizing (totalRequiredWidth > terminalWidth), but not when it fits.

This duplication can be removed by refactoring the row processing logic. You can determine the final column widths and whether to use the date formatter first, then have a single loop to process and add all rows. This would make the method shorter, easier to read, and less prone to bugs (like the potentially inconsistent date formatting).

Additionally, there are several magic numbers used for calculating column widths (e.g., 11, 15, 10, 3, 2). It would be great to extract these into named constants at the top of the method or class for better readability and maintainability.

Here's a rough idea of how the refactoring could look:

toString(): string {
    // ... (existing setup code)

    const totalRequiredWidth = columnWidths.reduce((sum, width) => sum + width, 0) + tableOverhead;

    let finalColWidths = columnWidths;
    const useCompactDate = totalRequiredWidth > terminalWidth;

    if (useCompactDate) {
        // Apply responsive resizing
        const scaleFactor = availableWidth / columnWidths.reduce((sum, width) => sum + width, 0);
        finalColWidths = columnWidths.map((width, index) => {
            // ... (adjustedWidths logic)
            return adjustedWidth;
        });
    }

    const table = new Table({
        head,
        style: this.style,
        colAligns,
        colWidths: finalColWidths,
        wordWrap: true,
        wrapOnWordBoundary: true,
    });

    // Single loop for adding rows
    for (const row of this.rows) {
        if (this.isSeparatorRow(row)) {
            continue;
        }

        let processedRow = row;

        // Apply date formatter if needed
        if (useCompactDate && this.dateFormatter) {
            processedRow = processedRow.map((cell, index) => {
                if (index === 0 && typeof cell === 'string' && this.isDateString(cell)) {
                    return this.dateFormatter(cell);
                }
                return cell;
            });
        }

        // Filter to compact columns if in compact mode
        if (this.compactMode) {
            processedRow = this.filterRowToCompact(processedRow, compactIndices);
        }

        table.push(processedRow);
    }

    return table.toString();
}

it('rounds to two decimal places', () => {
expect(formatCurrency(10.999)).toBe('$11.00');
expect(formatCurrency(10.994)).toBe('$10.99');
expect(formatCurrency(10.995)).toBe('$10.99'); // JavaScript's toFixed uses banker's rounding
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The assertion in this test case seems to be incorrect. (10.995).toFixed(2) evaluates to '11.00' in JavaScript, not '10.99'. The comment about banker's rounding is also a bit misleading, as toFixed()'s rounding behavior for values ending in .5 is not standardized across all JavaScript environments and often rounds away from zero.

Please update the expected value to match the actual behavior of toFixed().

Suggested change
expect(formatCurrency(10.995)).toBe('$10.99'); // JavaScript's toFixed uses banker's rounding
expect(formatCurrency(10.995)).toBe('$11.00'); // JavaScript's toFixed rounding can be inconsistent for .5 values.

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

🧹 Nitpick comments (4)
src/commands/blocks.ts (1)

17-17: Use shared TableCellAlign type instead of inline union.

For consistency with the new _table.ts API and stronger type reuse, import the exported TableCellAlign and use it for align arrays.

-import { formatCurrency, formatModelsDisplayMultiline, formatNumber, ResponsiveTable } from '../_table.ts';
+import { formatCurrency, formatModelsDisplayMultiline, formatNumber, ResponsiveTable, type TableCellAlign } from '../_table.ts';
@@
-const tableAligns: ('left' | 'right' | 'center')[] = ['left', 'left', 'left', 'right'];
+const tableAligns: TableCellAlign[] = ['left', 'left', 'left', 'right'];

Also applies to: 386-387

src/_table.ts (3)

102-111: Avoid direct console.warn; use repository logger for consistency

The coding guideline prefers logger over console. Replace the console.warn with logger.warn. This also helps unify output levels with other commands.

Apply this diff:

@@
-      // Log warning for debugging configuration issues
-      console.warn(`Warning: Compact header "${compactHeader}" not found in table headers [${this.head.join(', ')}]. Using first column as fallback.`);
+      // Log warning for debugging configuration issues
+      logger.warn(`Warning: Compact header "${compactHeader}" not found in table headers [${this.head.join(', ')}]. Using first column as fallback.`);
       return 0; // fallback to first column if not found

And add the import at the top of the file:

@@
-import stringWidth from 'string-width';
+import stringWidth from 'string-width';
+import { logger } from './logger.ts';

Note: this will require updating the related unit test that currently spies on console.warn. See the test suggestion below.


321-343: Model formatter is narrowly scoped to claude-<name>-<major>-<yyyymmdd>; consider a more tolerant pattern (optional)

If you expect any hyphenated or non-numeric “generation” segments, the current regex might fail to shorten (it will fall back to the original). Not a blocker for sonnet-4/opus-4, but we can support a broader set without breaking current outputs.

For example:

-const match = modelName.match(/claude-(\w+)-(\d+)-\d+/);
+const match = modelName.match(/^claude-([a-z]+)-(\d+)-\d+$/i);

or keep current and add a comment that we only target Claude 4 naming.


408-815: Add a focused unit test for pushBreakdownRows alignment and width

We currently test ResponsiveTable and formatters thoroughly, but not pushBreakdownRows. A minimal stubbed table lets us assert cell placement precisely and avoids brittle string parsing.

Append this test inside the existing if (import.meta.vitest != null) block:

@@
  describe('formatModelsDisplayMultiline', () => {
@@
   });
+
+  describe('pushBreakdownRows', () => {
+    it('places model bullet under the Models column (second column) and aligns metrics', () => {
+      const rows: (string | number)[][] = [];
+      const stubTable = { push: (r: (string | number)[]) => { rows.push(r); } };
+      pushBreakdownRows(stubTable, [{
+        modelName: 'claude-sonnet-4-20250514',
+        inputTokens: 100,
+        outputTokens: 50,
+        cacheCreationTokens: 10,
+        cacheReadTokens: 5,
+        cost: 1.5,
+      }]);
+      expect(rows).toHaveLength(1);
+      const r = rows[0]!;
+      // Date/Month column should be empty
+      expect(r[0]).toBe('');
+      // Models column should contain the bullet with the shortened model name
+      expect(String(r[1])).toContain('└─ sonnet-4');
+      // Then 6 metric columns
+      expect(r.slice(2)).toHaveLength(6);
+    });
+  });

If you switch to logger.warn per the previous comment, also update the existing warning test to spy on logger.warn instead of console.warn.

I can push these test updates as a follow-up commit if you’d like.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 348b253 and ef631c8.

📒 Files selected for processing (10)
  • src/_live-rendering.ts (1 hunks)
  • src/_table.ts (1 hunks)
  • src/_utils.ts (0 hunks)
  • src/commands/_session_id.ts (1 hunks)
  • src/commands/blocks.ts (1 hunks)
  • src/commands/daily.ts (1 hunks)
  • src/commands/monthly.ts (1 hunks)
  • src/commands/session.ts (1 hunks)
  • src/commands/statusline.ts (1 hunks)
  • src/commands/weekly.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/_utils.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use tab indentation and double quotes; format with ESLint
Do not use console.log; use logger.ts instead (only allow console.log when explicitly eslint-disabled)
Use .ts extensions for local file imports (e.g., import { foo } from "./utils.ts")
Prefer @praha/byethrow Result type over traditional try-catch; use Result.try(), Result.isFailure()/isSuccess() and early returns; reserve try-catch for complex file I/O or legacy code
For async operations, wrap with a function using Result.try() and call it
Variables use camelCase; Types use PascalCase; Constants may use UPPER_SNAKE_CASE
Only export constants/functions/types that are used by other modules; keep internal/private items non-exported
Always use Node.js path utilities for file paths for cross-platform compatibility
Use in-source testing blocks guarded by if (import.meta.vitest != null); do not create separate test files
Use Vitest globals (describe, it, expect) without imports inside test blocks
Only use dynamic imports (await import()) inside test blocks to avoid tree-shaking issues
Mock data in tests should use fs-fixture createFixture() for Claude data directory simulation
Tests must use current Claude 4 models (Sonnet and Opus) and cover both; do not use Claude 3 models
Model names in tests must exactly match LiteLLM pricing database entries

Files:

  • src/commands/session.ts
  • src/commands/monthly.ts
  • src/commands/_session_id.ts
  • src/commands/blocks.ts
  • src/_live-rendering.ts
  • src/commands/daily.ts
  • src/commands/weekly.ts
  • src/commands/statusline.ts
  • src/_table.ts
**/_*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Internal files should use an underscore prefix (e.g., _types.ts, _utils.ts, _consts.ts)

Files:

  • src/commands/_session_id.ts
  • src/_live-rendering.ts
  • src/_table.ts
🧬 Code graph analysis (1)
src/_table.ts (2)
src/_terminal-utils.ts (1)
  • width (155-157)
src/_utils.ts (1)
  • ResponsiveTable (34-292)
🪛 ESLint
src/_table.ts

[error] 107-107: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 107-107: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 129-129: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 129-129: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 129-129: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 129-129: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 129-129: Unsafe member access .stdout on an error typed value.

(ts/no-unsafe-member-access)


[error] 155-155: Unsafe spread of an error array type.

(ts/no-unsafe-argument)


[error] 155-155: Unsafe return of a value of type error.

(ts/no-unsafe-return)


[error] 155-155: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 206-213: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 206-213: Unsafe construction of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 235-235: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 235-235: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 239-239: Unsafe return of a value of type error.

(ts/no-unsafe-return)


[error] 239-239: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 239-239: Unsafe member access .toString on an error typed value.

(ts/no-unsafe-member-access)


[error] 243-250: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 243-250: Unsafe construction of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 263-263: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 263-263: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 267-267: Unsafe return of a value of type error.

(ts/no-unsafe-return)


[error] 267-267: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 267-267: Unsafe member access .toString on an error typed value.

(ts/no-unsafe-member-access)


[error] 341-341: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 341-341: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 342-342: Unsafe return of a value of type error.

(ts/no-unsafe-return)


[error] 342-342: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 342-342: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 342-342: Unsafe member access .sort on an error typed value.

(ts/no-unsafe-member-access)


[error] 342-342: Unsafe member access .join on an error typed value.

(ts/no-unsafe-member-access)


[error] 353-353: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 353-353: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 354-354: Unsafe return of a value of type error.

(ts/no-unsafe-return)


[error] 354-354: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 354-354: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 354-354: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 354-354: Unsafe member access .sort on an error typed value.

(ts/no-unsafe-member-access)


[error] 354-354: Unsafe member access .map on an error typed value.

(ts/no-unsafe-member-access)


[error] 354-354: Unsafe member access .join on an error typed value.

(ts/no-unsafe-member-access)


[error] 391-391: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 391-391: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 391-391: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 392-392: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 392-392: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 392-392: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 393-393: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 393-393: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 393-393: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 394-394: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 394-394: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 394-394: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 395-395: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 395-395: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 395-395: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 396-396: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 396-396: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 396-396: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 419-419: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 419-419: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 420-420: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 428-428: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 428-428: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 439-439: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 439-439: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 440-440: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 448-448: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 448-448: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 458-458: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 458-458: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 459-459: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 467-467: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 467-467: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 482-482: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 482-482: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 483-483: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 497-497: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 497-497: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 510-510: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 510-510: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 511-511: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 525-525: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 525-525: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 538-538: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 538-538: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 539-539: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 550-550: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 550-550: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 561-561: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 561-561: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 563-563: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 566-566: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 566-566: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 567-567: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 583-583: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 583-583: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 584-584: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 584-584: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 595-595: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 595-595: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 596-596: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 607-607: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 607-607: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 632-632: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 632-632: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 633-633: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 645-645: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 645-645: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 656-656: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 656-656: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 657-657: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 670-670: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 670-670: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 681-681: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 681-681: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 682-682: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 682-682: Unsafe member access .stdout on an error typed value.

(ts/no-unsafe-member-access)


[error] 684-684: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 694-694: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 694-694: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 695-695: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 695-695: Unsafe member access .stdout on an error typed value.

(ts/no-unsafe-member-access)


[error] 706-706: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 706-706: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 707-707: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 707-707: Unsafe member access .stdout on an error typed value.

(ts/no-unsafe-member-access)


[error] 709-709: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 720-720: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 720-720: Unsafe member access .env on an error typed value.

(ts/no-unsafe-member-access)


[error] 721-721: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 721-721: Unsafe member access .stdout on an error typed value.

(ts/no-unsafe-member-access)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Workers Builds: ccusage-guide
🔇 Additional comments (8)
src/commands/blocks.ts (1)

17-17: Import path migration to _table.ts looks correct.

The new source aligns with the refactor objective and keeps the local .ts extension as per guidelines. No behavioral changes here.

src/_live-rendering.ts (1)

19-19: Import source updated to _table.ts — looks good.

Matches the centralization of formatting utilities; no logic change in this file.

src/commands/_session_id.ts (1)

6-6: Import migration LGTM.

Switching to ../_table.ts maintains the same API surface and conforms to local .ts import style.

src/commands/session.ts (1)

9-9: Good move to _table.ts; consistent with the refactor.

All imported symbols exist in the new module per PR description; callers unchanged.

src/commands/weekly.ts (1)

9-9: Weekly command imports correctly point to _table.ts.

Consistent with other commands; no behavior change expected.

src/commands/daily.ts (1)

10-10: Migration of table utilities verified

Ran the provided ripgrep command to search for any remaining imports of these symbols from ../_utils.ts and confirmed there are no matches. The import in src/commands/daily.ts is correct and no further changes are needed.

src/commands/monthly.ts (1)

8-8: Import migration to _table.ts is consistent with the refactor

The move is cohesive with the new separation of concerns; usages below read the same.

src/commands/statusline.ts (1)

16-19: formatCurrency import now from _table.ts; utils imports remain scoped appropriately

This change is minimal and keeps getFileModifiedTime/unreachable in _utils.ts. No runtime impact expected in this module.

Comment on lines +365 to +406
export function pushBreakdownRows(
table: { push: (row: (string | number)[]) => void },
breakdowns: Array<{
modelName: string;
inputTokens: number;
outputTokens: number;
cacheCreationTokens: number;
cacheReadTokens: number;
cost: number;
}>,
extraColumns = 1,
trailingColumns = 0,
): void {
for (const breakdown of breakdowns) {
const row: (string | number)[] = [` └─ ${formatModelName(breakdown.modelName)}`];

// Add extra empty columns before data
for (let i = 0; i < extraColumns; i++) {
row.push('');
}

// Add data columns with gray styling
const totalTokens = breakdown.inputTokens + breakdown.outputTokens
+ breakdown.cacheCreationTokens + breakdown.cacheReadTokens;

row.push(
pc.gray(formatNumber(breakdown.inputTokens)),
pc.gray(formatNumber(breakdown.outputTokens)),
pc.gray(formatNumber(breakdown.cacheCreationTokens)),
pc.gray(formatNumber(breakdown.cacheReadTokens)),
pc.gray(formatNumber(totalTokens)),
pc.gray(formatCurrency(breakdown.cost)),
);

// Add trailing empty columns
for (let i = 0; i < trailingColumns; i++) {
row.push('');
}

table.push(row);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Model breakdown rows are misaligned (model name currently appears under the Date/first column)

pushBreakdownRows starts the row with the model bullet, then adds an empty column. In tables like Daily/Monthly where the first column is Date/Month and the second is Models, this places the model bullet in the Date column and leaves the Models column blank.

Fix by:

  • Prepending an empty first cell for the Date/Month column.
  • Placing the model bullet in the Models column.
  • Making extraColumns represent additional spacer columns after the Models column and before metrics (default 0).

Apply this diff:

@@
-export function pushBreakdownRows(
-  table: { push: (row: (string | number)[]) => void },
-  breakdowns: Array<{
+export function pushBreakdownRows(
+  table: { push: (row: (string | number)[]) => void },
+  breakdowns: Array<{
     modelName: string;
     inputTokens: number;
     outputTokens: number;
     cacheCreationTokens: number;
     cacheReadTokens: number;
     cost: number;
-  }>,
-  extraColumns = 1,
-  trailingColumns = 0,
+  }>,
+  extraColumns = 0,
+  trailingColumns = 0,
 ): void {
   for (const breakdown of breakdowns) {
-    const row: (string | number)[] = [`  └─ ${formatModelName(breakdown.modelName)}`];
-
-    // Add extra empty columns before data
-    for (let i = 0; i < extraColumns; i++) {
-      row.push('');
-    }
+    // Lead with an empty Date/Month column, then put the model bullet in the Models column
+    const row: (string | number)[] = [
+      '',
+      `  └─ ${formatModelName(breakdown.modelName)}`,
+    ];
+
+    // Optional spacer columns after Models before metrics (if a layout needs them)
+    for (let i = 0; i < extraColumns; i++) {
+      row.push('');
+    }
@@
     row.push(
       pc.gray(formatNumber(breakdown.inputTokens)),
       pc.gray(formatNumber(breakdown.outputTokens)),
       pc.gray(formatNumber(breakdown.cacheCreationTokens)),
       pc.gray(formatNumber(breakdown.cacheReadTokens)),
       pc.gray(formatNumber(totalTokens)),
       pc.gray(formatCurrency(breakdown.cost)),
     );
@@
     table.push(row);
   }
 }

Additionally, please update the JSDoc above to clarify the new default and semantics of extraColumns (spacer columns after the Models column, before metrics). I can add a focused test for this (see next comment).

📝 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
export function pushBreakdownRows(
table: { push: (row: (string | number)[]) => void },
breakdowns: Array<{
modelName: string;
inputTokens: number;
outputTokens: number;
cacheCreationTokens: number;
cacheReadTokens: number;
cost: number;
}>,
extraColumns = 1,
trailingColumns = 0,
): void {
for (const breakdown of breakdowns) {
const row: (string | number)[] = [` └─ ${formatModelName(breakdown.modelName)}`];
// Add extra empty columns before data
for (let i = 0; i < extraColumns; i++) {
row.push('');
}
// Add data columns with gray styling
const totalTokens = breakdown.inputTokens + breakdown.outputTokens
+ breakdown.cacheCreationTokens + breakdown.cacheReadTokens;
row.push(
pc.gray(formatNumber(breakdown.inputTokens)),
pc.gray(formatNumber(breakdown.outputTokens)),
pc.gray(formatNumber(breakdown.cacheCreationTokens)),
pc.gray(formatNumber(breakdown.cacheReadTokens)),
pc.gray(formatNumber(totalTokens)),
pc.gray(formatCurrency(breakdown.cost)),
);
// Add trailing empty columns
for (let i = 0; i < trailingColumns; i++) {
row.push('');
}
table.push(row);
}
}
export function pushBreakdownRows(
table: { push: (row: (string | number)[]) => void },
breakdowns: Array<{
modelName: string;
inputTokens: number;
outputTokens: number;
cacheCreationTokens: number;
cacheReadTokens: number;
cost: number;
}>,
extraColumns = 0,
trailingColumns = 0,
): void {
for (const breakdown of breakdowns) {
// Lead with an empty Date/Month column, then put the model bullet in the Models column
const row: (string | number)[] = [
"",
` └─ ${formatModelName(breakdown.modelName)}`,
];
// Optional spacer columns after Models before metrics (if a layout needs them)
for (let i = 0; i < extraColumns; i++) {
row.push("");
}
// Add data columns with gray styling
const totalTokens =
breakdown.inputTokens +
breakdown.outputTokens +
breakdown.cacheCreationTokens +
breakdown.cacheReadTokens;
row.push(
pc.gray(formatNumber(breakdown.inputTokens)),
pc.gray(formatNumber(breakdown.outputTokens)),
pc.gray(formatNumber(breakdown.cacheCreationTokens)),
pc.gray(formatNumber(breakdown.cacheReadTokens)),
pc.gray(formatNumber(totalTokens)),
pc.gray(formatCurrency(breakdown.cost)),
);
// Add trailing empty columns
for (let i = 0; i < trailingColumns; i++) {
row.push("");
}
table.push(row);
}
}
🧰 Tools
🪛 ESLint

[error] 391-391: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 391-391: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 391-391: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 392-392: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 392-392: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 392-392: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 393-393: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 393-393: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 393-393: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 394-394: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 394-394: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 394-394: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 395-395: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 395-395: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 395-395: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 396-396: Unsafe argument of type error typed assigned to a parameter of type string | number.

(ts/no-unsafe-argument)


[error] 396-396: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 396-396: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)

🤖 Prompt for AI Agents
In src/_table.ts around lines 365 to 406, the model bullet is currently placed
in the first column (Date/Month) because the row begins with the model cell then
adds extraColumns; change the row layout so the first cell is an empty spacer
(for Date/Month), the second cell contains the model bullet, and extraColumns
now represent additional spacer columns inserted after the Models column and
before the metrics (default 0). Update the function to start each row with an
empty string, then push the model cell (`  └─ ${formatModelName(...)}`), then
push extraColumns empty strings, then the metric columns as before, and finally
trailingColumns empties; also update the JSDoc above the function to state that
extraColumns are spacer columns after the Models column (default 0) and adjust
the description accordingly.


} from '../_session-blocks.ts';
import { sharedCommandConfig } from '../_shared-args.ts';
import { formatCurrency, formatModelsDisplayMultiline, formatNumber, ResponsiveTable } from '../_table.ts';
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Quick repo-wide verification for the import move.

Run this script to confirm no callers still import table utilities from _utils.ts and that all local imports include the .ts extension.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Ensure no migrated symbols are still imported from _utils.ts"
rg -nP --type=ts '(?m)^import\s+{[^}]*\b(formatNumber|formatCurrency|formatModelsDisplayMultiline|formatModelsDisplay|ResponsiveTable|pushBreakdownRows)\b[^}]*}\s+from\s+[\'"][./..]+/_utils\.ts[\'"]' || echo "OK: none found"

echo -e "\n2) List any remaining imports from _utils.ts (should be only allowed survivors like getFileModifiedTime/unreachable)"
rg -nP --type=ts '(?m)^import\s+.*from\s+[\'"][./..]+/_utils\.ts[\'"]' || echo "OK: no imports, or only expected survivors"

echo -e "\n3) Verify _table.ts exports expected surface"
rg -nP -n 'export\s+(?:function|const|class|type)\s+(formatNumber|formatCurrency|formatModelsDisplayMultiline|formatModelsDisplay|ResponsiveTable|pushBreakdownRows|TableCellAlign)\b' src/_table.ts || echo "WARNING: Could not verify exports in src/_table.ts"

echo -e "\n4) Ensure local imports consistently use .ts extensions"
rg -nP --type=ts '(?m)^import\s+.*from\s+[\'"](./|\.\./)[^\'"]+(?<!\.ts)[\'"]' || echo "OK: all local imports include .ts"

Length of output: 266


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "1) Checking for migrated symbols still imported from _utils.ts"
rg -n --type ts -P "import\s+.*\b(formatNumber|formatCurrency|formatModelsDisplayMultiline|formatModelsDisplay|ResponsiveTable|pushBreakdownRows)\b.*from\s+['\"][.][./]+_utils\.ts['\"]" || echo "OK: none found"

echo
echo "2) Listing any remaining imports from _utils.ts"
rg -n --type ts -P "import\s+.*from\s+['\"][.][./]+_utils\.ts['\"]" || echo "OK: no imports or only expected survivors"

echo
echo "3) Verifying exports in src/_table.ts"
rg -n --type ts -P -n "export\s+(function|const|class|type)\s+(formatNumber|formatCurrency|formatModelsDisplayMultiline|formatModelsDisplay|ResponsiveTable|pushBreakdownRows|TableCellAlign)\b" src/_table.ts || echo "WARNING: Could not verify exports in src/_table.ts"

echo
echo "4) Ensuring local imports consistently use .ts extension"
rg -n --type ts -P "import\s+.*from\s+['\"][.][^'\"]+['\"]" | grep -nEv "\.ts['\"]$" && echo "ERROR: some local imports missing .ts extension" || echo "OK: all local imports include .ts extension"

Length of output: 13622


Local imports missing .ts extension

The verification script ran cleanly for migrated symbols (none still import from _utils.ts) and confirmed the expected survivors in src/data-loader.ts:62 and src/commands/statusline.ts:18. It also verified that src/_table.ts exports the full set of moved utilities.

However, several local imports lack the required .ts extension. Please update all import statements below to include the .ts suffix and then re-run the verification script:

  • scripts/

    • scripts/generate-json-schema.ts: lines 16, 18, 20
  • project root

    • src/index.ts: line 14
  • src/debug.ts: lines 15–18

  • src/mcp.ts: lines 11, 21

  • src/_shared-args.ts: lines 2–3

  • src/_live-monitor.ts: lines 11–12, 16, 26

  • src/pricing-fetcher.ts: lines 11–16

  • src/_live-rendering.ts: lines 9–11, 17–19

  • src/commands/session.ts: lines 6–9, 50–54

  • src/commands/weekly.ts: lines 5–9, 59

  • src/commands/monthly.ts: lines 5–9, 66

  • src/commands/statusline.ts: lines 12–16, 18

  • src/commands/daily.ts: lines 5–10, 85

  • src/commands/_blocks.live.ts: lines 9, 13, 24–25

  • src/commands/_session_id.ts: lines 1–7, 112–114

  • src/commands/blocks.ts: lines 1–7, 17

  • src/commands/index.ts: lines 3–9

  • src/data-loader.ts: lines 62–64

  • src/_session-blocks.ts: lines 2–3

After adding the .ts extension to each of these import paths, re-run the script to confirm an “OK: all local imports include .ts extension” result.

🤖 Prompt for AI Agents
In src/commands/blocks.ts (lines 1–7 and 17) some local import paths are missing
the required .ts extension; update each relative/local import to explicitly
include the .ts suffix (e.g., change "../foo" to "../foo.ts"), save, and re-run
the verification script to confirm the "OK: all local imports include .ts
extension" result.

- Add shared table factory function createUsageReportTable() with consistent headers and styling
- Add formatUsageDataRow() for standardized data row formatting with token calculations
- Add formatTotalsRow() with yellow highlighting and optional Last Activity column support
- Add addEmptySeparatorRow() utility for consistent visual separation
- Define UsageReportConfig and UsageData interfaces for better type safety
- Refactor daily.ts, monthly.ts, weekly.ts, and session.ts to use shared table functions
- Support Last Activity column for session reports via includeLastActivity config option
- Reduce code duplication by ~50-70 lines per command file
- Maintain all existing functionality and table formatting consistency
- Fix ESLint strict boolean expression warnings with nullish coalescing

This consolidation improves maintainability by centralizing table creation logic
while preserving responsive design and compact mode functionality across all commands.
@ryoppippi ryoppippi merged commit d11cd9d into main Aug 23, 2025
16 of 17 checks passed
@ryoppippi ryoppippi deleted the feat/refactor-table-utilities branch August 23, 2025 20:50
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.

2 participants