Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Model-Specific Usage Tracking Implementation Plan

## Overview

Add support for model-specific weekly usage limits (Opus, Sonnet) with three display modes:
- **Simple**: Current behavior (overall only)
- **Detailed**: Show Overall, Opus, Sonnet side by side
- **Smart/Bottleneck**: Show the most restrictive limit dynamically

## API Response Structure

```json
{
"five_hour": { "utilization": 29.0, "resets_at": "..." },
"seven_day": { "utilization": 47.0, "resets_at": "..." },
"seven_day_opus": { "utilization": 15.0, "resets_at": "..." } | null,
"seven_day_sonnet": { "utilization": 7.0, "resets_at": "..." } | null
}
```

## Implementation Steps

### 1. Update `src/utils/oauth.ts`

**Changes:**
- Update `ApiResponse` interface to include `seven_day_opus` and `seven_day_sonnet`
- Update `OAuthUsageResponse` interface to include model-specific data:
```typescript
export interface OAuthUsageResponse {
fiveHour: UsageData | null;
sevenDay: UsageData | null;
sevenDayOpus: UsageData | null;
sevenDaySonnet: UsageData | null;
raw?: unknown;
}
```
- Update `fetchUsageFromAPI` to parse the new fields
- Update `TrendInfo` to include model-specific trends (optional, can be done later)

### 2. Update `src/config/types.ts`

**Changes:**
- Add new `WeeklyViewMode` type:
```typescript
export type WeeklyViewMode = "simple" | "detailed" | "smart";
```
- Update `WeeklySegmentConfig` to include view mode:
```typescript
export interface WeeklySegmentConfig extends SegmentConfig {
showWeekProgress?: boolean;
viewMode?: WeeklyViewMode; // New: default "simple"
}
```
- Update `DEFAULT_CONFIG` with `viewMode: "simple"`

### 3. Update `src/segments/weekly.ts`

**Changes:**
- Update `WeeklyInfo` interface to include model-specific data:
```typescript
export interface WeeklyInfo {
percentUsed: number | null;
resetAt: Date | null;
isRealtime: boolean;
weekProgressPercent: number;
// New model-specific fields
opusPercentUsed: number | null;
sonnetPercentUsed: number | null;
opusResetAt: Date | null;
sonnetResetAt: Date | null;
}
```
- Update `getRealtimeWeeklyInfo` to extract and return model-specific data

### 4. Update `src/renderer.ts`

**Changes:**
- Update `renderWeekly` method to handle three view modes:

**Simple mode** (current behavior):
```
○ 47% (wk 85%)
```

**Detailed mode** (show all three):
```
○ 47% | ◈15% | ◇7%
```
Where ○ = Overall, ◈ = Opus, ◇ = Sonnet

Or use labels:
```
○ All:47% Op:15% So:7%
```

**Smart/Bottleneck mode**:
- Show whichever is highest percentage (closest to limit)
- Add indicator of which limit is shown
- Example: `○ 47%▲` (▲ indicates this is the bottleneck)
- Or show model name: `○ 47% (all)` vs `◈ 85% (opus)`

### 5. Update `src/themes/index.ts`

**Changes:**
- Add colors for Opus and Sonnet segments (optional, can reuse existing):
```typescript
export interface ColorTheme {
// ... existing
opus: SegmentColor; // New
sonnet: SegmentColor; // New
}
```

### 6. Update `src/utils/constants.ts`

**Changes:**
- Add symbols for model-specific indicators:
```typescript
export const SYMBOLS = {
// ... existing
opus: "\u25c8", // ◈ Diamond with dot
sonnet: "\u25c7", // ◇ White diamond
};
```

### 7. Update Tests

**Files to update:**
- `src/utils/oauth.test.ts` - Add tests for new API fields
- `src/segments/weekly.test.ts` - Add tests for model-specific data
- `src/renderer.test.ts` - Add tests for three view modes

### 8. Update Documentation

**Files to update:**
- `README.md` - Document new config option and view modes
- `.claude-limitline.example.json` - Add viewMode example

## Configuration Example

```json
{
"weekly": {
"enabled": true,
"displayStyle": "text",
"showWeekProgress": true,
"viewMode": "smart"
}
}
```

## Display Examples

### Simple Mode (default)
```
claude-limitline main Opus 4.5 ◫ 29% ○ 47%
```

### Detailed Mode
```
claude-limitline main Opus 4.5 ◫ 29% ○ 47% ◈ 15% ◇ 7%
```
Or as separate segments (if user wants):
```
claude-limitline main Opus 4.5 ◫ 29% ○ 47% ◈ 15% ◇ 7%
```

### Smart Mode
Shows the most restrictive limit with an indicator:
```
claude-limitline main Opus 4.5 ◫ 29% ○ 47%*
```
Where `*` or similar indicates this is the bottleneck.

If Opus was at 90%:
```
claude-limitline main Opus 4.5 ◫ 29% ◈ 90%*
```

## Edge Cases

1. **Opus/Sonnet is null** (user on Pro without Opus access):
- Simple: Show overall only
- Detailed: Show only available limits
- Smart: Show overall (it's the only limit)

2. **All limits are equal**: Smart mode shows overall by default

3. **Compact mode**:
- Simple: `47%`
- Detailed: `47/15/7%` (compact format)
- Smart: `47%*`

## Implementation Order

1. `oauth.ts` - Parse new API fields (foundation)
2. `config/types.ts` - Add config types
3. `segments/weekly.ts` - Update WeeklyInfo
4. `constants.ts` & `themes/index.ts` - Add symbols/colors
5. `renderer.ts` - Implement three view modes
6. Tests - Add coverage
7. Documentation - Update README
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Create a `claude-limitline.json` file in your Claude config directory (`~/.claud
| `weekly.displayStyle` | `"bar"` or `"text"` | `"text"` |
| `weekly.barWidth` | Width of progress bar in characters | `10` |
| `weekly.showWeekProgress` | Show week progress percentage | `true` |
| `weekly.viewMode` | `"simple"`, `"detailed"`, or `"smart"` | `"simple"` |
| `weekly.viewMode` | `"simple"` or `"smart"` | `"simple"` |
| `budget.pollInterval` | Minutes between API calls | `15` |
| `budget.warningThreshold` | Percentage to trigger warning color | `80` |
| `theme` | Color theme name | `"dark"` |
Expand All @@ -132,15 +132,16 @@ Create a `claude-limitline.json` file in your Claude config directory (`~/.claud

### Weekly View Modes

The weekly segment supports three view modes for displaying usage limits:
The weekly segment supports two view modes for displaying usage limits:

![Mode Preview](imgs/mode-preview.png)

| Mode | Description | Example |
|------|-------------|---------|
| `simple` | Shows overall weekly usage only (default) | `○ 47% (wk 85%)` |
| `detailed` | Shows overall, Opus, and Sonnet usage side by side | `○47% ◈15% ◇7%` |
| `smart` | Shows the most restrictive (bottleneck) limit with indicator | `○47%▲ (wk 85%)` |
| `smart` | Model-aware: shows Sonnet + Overall when using Sonnet | `◇7% \| ○47% (wk 85%)` |

**Note:** Model-specific limits (Opus/Sonnet) are only available on certain subscription tiers. When a model-specific limit is not available, it will be hidden from the display.
**Note:** Model-specific limits (Opus/Sonnet) are only available on certain subscription tiers. Smart mode shows only overall usage when using Opus/Haiku, and shows both Sonnet and overall when using Sonnet.

### Available Themes

Expand Down Expand Up @@ -190,7 +191,7 @@ npm run dev # Watch mode

## Testing

The project uses [Vitest](https://vitest.dev/) for testing with 166 tests covering config loading, themes, segments, utilities, and rendering.
The project uses [Vitest](https://vitest.dev/) for testing with 164 tests covering config loading, themes, segments, utilities, and rendering.

```bash
npm test # Run tests once
Expand All @@ -205,13 +206,13 @@ npm run test:coverage # Coverage report
| `src/config/loader.test.ts` | 7 | Config loading, merging, fallbacks |
| `src/themes/index.test.ts` | 37 | Theme retrieval, color validation |
| `src/segments/block.test.ts` | 8 | Block segment, time calculations |
| `src/segments/weekly.test.ts` | 10 | Weekly segment, week progress |
| `src/utils/oauth.test.ts` | 10 | API responses, caching |
| `src/segments/weekly.test.ts` | 13 | Weekly segment, week progress |
| `src/utils/oauth.test.ts` | 13 | API responses, caching, trends |
| `src/utils/claude-hook.test.ts` | 21 | Model name formatting |
| `src/utils/environment.test.ts` | 20 | Git branch, directory detection |
| `src/utils/terminal.test.ts` | 13 | Terminal width, ANSI handling |
| `src/utils/logger.test.ts` | 8 | Debug/error logging |
| `src/renderer.test.ts` | 21 | Segment rendering, ordering |
| `src/renderer.test.ts` | 24 | Segment rendering, ordering, view modes |

## Debug Mode

Expand Down
88 changes: 88 additions & 0 deletions demo-modes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env node
/**
* Demo script to showcase the three weekly view modes
* Outputs raw ANSI-styled text to demonstrate the modes
*/

// ANSI color helpers
const ansi = {
fg: (n) => `\x1b[38;5;${n}m`,
bg: (n) => `\x1b[48;5;${n}m`,
reset: '\x1b[0m',
};

// Powerline arrow
const arrow = '\ue0b0';

// Symbols
const symbols = {
block: '◫',
weekly: '○',
sonnet: '◇',
branch: '\ue0a0',
model: '✱',
};

// Dark theme colors (approximate ANSI 256 values)
const colors = {
directory: { bg: 94, fg: 231 }, // Brown bg, white fg
git: { bg: 238, fg: 231 }, // Dark gray bg, white fg
model: { bg: 236, fg: 231 }, // Darker gray bg, white fg
block: { bg: 235, fg: 117 }, // Dark bg, cyan fg
weekly: { bg: 234, fg: 120 }, // Darker bg, green fg
};

function segment(text, color, nextColor = null) {
let out = ansi.bg(color.bg) + ansi.fg(color.fg) + text + ansi.reset;
if (nextColor) {
out += ansi.fg(color.bg) + ansi.bg(nextColor.bg) + arrow;
} else {
out += ansi.fg(color.bg) + arrow;
}
return out + ansi.reset;
}

function renderStatusline(weeklyText, weeklyColor = colors.weekly) {
return (
segment(` claude-limitline `, colors.directory, colors.git) +
segment(` ${symbols.branch} main `, colors.git, colors.model) +
segment(` ${symbols.model} Opus 4.5 `, colors.model, colors.block) +
segment(` ${symbols.block} 29% (3h) `, colors.block, weeklyColor) +
segment(weeklyText, weeklyColor)
);
}

console.log("\n┌─────────────────────────────────────────────────────────────┐");
console.log("│ Weekly View Modes Demo │");
console.log("└─────────────────────────────────────────────────────────────┘\n");

// Simple mode
console.log('\x1b[1mSimple Mode\x1b[0m (default):');
console.log('Shows overall weekly usage only');
console.log('Config: { "weekly": { "viewMode": "simple" } }\n');
console.log(renderStatusline(` ${symbols.weekly} 47% (wk 85%) `));
console.log("\n");

// Smart mode - Opus selected
console.log('\x1b[1mSmart Mode\x1b[0m (Opus selected):');
console.log('When using Opus, shows only Overall usage');
console.log('Config: { "weekly": { "viewMode": "smart" } }\n');
console.log(renderStatusline(` ${symbols.weekly}47% (wk 85%) `));
console.log("\n");

// Smart mode - Sonnet selected
console.log('\x1b[1mSmart Mode\x1b[0m (Sonnet selected):');
console.log('When using Sonnet, shows Sonnet | Overall format');
console.log('Config: { "weekly": { "viewMode": "smart" } }\n');
console.log(
segment(` claude-limitline `, colors.directory, colors.git) +
segment(` ${symbols.branch} main `, colors.git, colors.model) +
segment(` ${symbols.model} Sonnet 4 `, colors.model, colors.block) +
segment(` ${symbols.block} 29% (3h) `, colors.block, colors.weekly) +
segment(` ${symbols.sonnet}7% | ${symbols.weekly}47% (wk 85%) `, colors.weekly)
);
console.log("\n");

console.log("─".repeat(62));
console.log("Note: Model-specific limits (Opus/Sonnet) are only available");
console.log("on certain subscription tiers.\n");
2 changes: 1 addition & 1 deletion src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface BlockSegmentConfig extends SegmentConfig {
showTimeRemaining?: boolean;
}

export type WeeklyViewMode = "simple" | "detailed" | "smart";
export type WeeklyViewMode = "simple" | "smart";

export interface WeeklySegmentConfig extends SegmentConfig {
showWeekProgress?: boolean;
Expand Down
Loading