DEV Community

Cesar Castillo
Cesar Castillo

Posted on • Edited on

Track Your GitHub Copilot Pro from the Terminal πŸ–₯️ - New UI πŸ§‘β€πŸŽ¨

GitHub Copilot CLI Challenge Submission

This is a submission for the GitHub Copilot CLI Challenge

Author: Cesar Castillo

Repository: https://github.com/CGCM070/copilot-usage_tui


From Invisible to Visible

Stop guessing. Start knowing.

GitHub Copilot Pro gives you 300 requests per month… but having to go to the site, navigate through menus, and dig into a specific section just to check your usage? That’s tedious

I built a terminal-based TUI that transforms your Copilot usage from an invisible mystery into a beautiful, real-time dashboard. Because developers deserve to know exactly how they're using their AI pair programmer.


What I Built : Copilot-usage

A Rust-based CLI tool with an interactive terminal UI that tracks GitHub Copilot Pro usage in real-time, featuring progress bars, per-model statistics, and seamless Waybar integration for Hyprland users.

The Problem

If you're a GitHub Copilot Pro user, you know the drill:
You get 300 premium requests per month
There is a page where you can check usage…
But you have to go to the site, open the right section, and dig through settings to find it
So you’re never quite sure where you stand until you manually go look
300 requests. Visibility β€” technically. Convenience? Not so much.

The Solution: Copilot Usage

I built a terminal dashboard that gives you complete visibility into your Copilot consumption with zero friction.


Key Features

πŸ“Š Beautiful Terminal Dashboard

  • Real-time progress bars with color-coded zones (green β†’ orange β†’ red)
  • Per-model usage breakdown (GPT-4, Claude 3.5, Gemini, etc.)
  • Compact, density-focused layout inspired by btop
  • 9 gorgeous color themes: Dark, Nord, Monokai, Gruvbox, Catppuccin, OneDark, TokyoNight, Solarized, Kanagawa

⚑ Async Architecture

  • Non-blocking UI with tokio async runtime
  • Smooth spinner animations during API calls (20 FPS)
  • Cancelable operations with Escape key
  • Background refresh while you keep working

🎨 Theme System

  • Instant theme switching without exiting TUI
  • Persistent theme configuration
  • Consistent warning (orange) and error (red) colors across all themes
  • Segmented progress bars for visual feedback

πŸ’Ύ Smart Caching

  • Local cache with 5-minute TTL (configurable)
  • Cache status indicator (Fresh/Expired/Missing)
  • Manual refresh with 'r' key
  • Automatic cache invalidation on --refresh flag

πŸ”§ Interactive Modals

  • Command menu (press /) for quick actions
  • Theme selector with live preview
  • Help dialog with all keybindings (press ?)
  • Error dialogs with debug mode toggle (press d)
  • Cache info modal showing last update time

πŸ“ˆ Waybar Integration

  • JSON output for Waybar status bar
  • Customizable format strings
  • Shows percentage in your status bar
  • Perfect for Hyprland/wayland users

πŸš€ Zero-Config Setup

  • Interactive first-run wizard
  • Automatic GitHub username detection
  • Fine-grained PAT or classic token support
  • XDG-compliant config directory

Architecture Deep Dive

This isn't just a simple API wrapper. It's a full async TUI application with clean separation of concerns:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Terminal UI (ratatui)                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  UI Components                                   β”‚   β”‚
β”‚  β”‚  β€’ Header (user info)                            β”‚   β”‚
β”‚  β”‚  β€’ Usage Progress Bar                            β”‚   β”‚
β”‚  β”‚  β€’ Model Usage Table                             β”‚   β”‚
β”‚  β”‚  β€’ Modal Dialogs                                 β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                       ↓                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Event Loop (50ms poll)                          β”‚   β”‚
β”‚  β”‚  β€’ Keyboard input handling                       β”‚   β”‚
β”‚  β”‚  β€’ Async result processing                       β”‚   β”‚
β”‚  β”‚  β€’ Spinner animation updates                     β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                       ↓                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Async Handler (tokio)                           β”‚   β”‚
β”‚  β”‚  β€’ Background API calls                          β”‚   β”‚
β”‚  β”‚  β€’ Cache operations                              β”‚   β”‚
β”‚  β”‚  β€’ mpsc channels for results                     β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  GitHub API                                             β”‚
β”‚  β€’ /users/{username}/settings/billing/...              β”‚
β”‚  β€’ 5000 req/hour rate limit                            β”‚
β”‚  β€’ Fine-grained PAT with Plan (Read)                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Async Event Loop Pattern

The heart of the application is a non-blocking event loop:

loop {
    // 1. Handle user input (non-blocking)
    if event::poll(Duration::from_millis(50))? {
        // Process keyboard events
    }

    // 2. Animate spinner during loading
    if matches!(app.state, LoadingRefresh | LoadingCache) {
        app.advance_spinner();
    }

    // 3. Check for async results
    if let Some(result) = async_handler.try_recv() {
        match result {
            RefreshComplete(Ok(stats)) => update_dashboard(stats),
            RefreshComplete(Err(e)) => show_error_modal(e),
            CacheInfoReady(info) => show_cache_modal(info),
        }
    }

    // 4. Render UI (20 FPS)
    terminal.draw(|f| render_ui(f, &app, &stats, theme))?;
}
Enter fullscreen mode Exit fullscreen mode

Why async matters:

  • API calls don't freeze the UI
  • Spinner animations stay smooth
  • Users can cancel operations anytime
  • Background tasks complete even after cancel

Technical Decisions

1. Modular Component Architecture

Each UI component is a pure function with zero side effects:

// Pure rendering function - no state mutations
pub fn render_usage_overall(
    f: &mut Frame, 
    area: Rect, 
    stats: &UsageStats, 
    colors: &ThemeColors
) {
    // Calculate progress bar segments
    // Render with theme colors
    // No state changes, no events handled
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Easy to test (input β†’ output)
  • No hidden state mutations
  • Components compose naturally
  • Theme changes apply instantly

2. State Machine Pattern

AppState enum models all UI states explicitly:

pub enum AppState {
    Dashboard,                    // Main view
    LoadingRefresh,              // Spinner during API call
    ShowCacheInfo(CacheInfo),    // Cache status modal
    ShowError {                  // Error with debug toggle
        message: String,
        debug_message: String,
        show_debug: bool,
    },
    // ... more states
}
Enter fullscreen mode Exit fullscreen mode

No boolean flags. No hidden state. Every state is explicit and handled.

3. Constant Warning/Error Colors

Across all 9 themes, warning and error colors remain constant:

  • Warning: rgb(255, 184, 108) (orange)
  • Error: rgb(255, 85, 85) (red)

Users instantly recognize status regardless of theme.

4. Compact Layout Philosophy

Inspired by system monitors like btop:

  • Dynamic height: Model table resizes based on data (N + 3 rows)
  • Compact width: 50% of terminal for readability
  • Minimal padding: Information density over whitespace
  • Rounded borders: Modern, polished feel

5. Never Exit TUI Philosophy

All operations happen via modals:

  • Reconfigure token? Modal dialog
  • Change theme? Theme selector modal
  • Refresh data? Loading spinner modal
  • Error occurred? Error modal with debug info

The TUI stays alive. No jarring exits. No lost context.


πŸ“Έ Screenshots

Dashboard - Before

TUI_Gruvbox

Dashboard - After

New

Theme Selector

Theme selector

Waybar Integration

Waybar

Refresh

Refresh


My Experience with GitHub Copilot CLI

This project was built with extensive help from GitHub Copilot CLI, turning natural language into idiomatic Rust code.

How I Used Copilot CLI

1. Async Architecture Design

# Asked: "Best way to structure async TUI in Rust?"
# Copilot suggested: tokio + mpsc channels + non-blocking event loop
# Result: Smooth 20 FPS UI with background API calls
Enter fullscreen mode Exit fullscreen mode

2. State Machine Pattern

# Asked: "How to model TUI states without boolean flags?"
# Copilot proposed: AppState enum with exhaustive match
# Result: Clean state transitions, no hidden bugs
Enter fullscreen mode Exit fullscreen mode

3. Theme System

# Asked: "Implement 9 color themes with constant warning/error colors"
# Copilot generated: ThemeColors struct + theme implementations
# Result: 9 gorgeous themes with consistent UX
Enter fullscreen mode Exit fullscreen mode

4. Progress Bar Segmentation

# Asked: "Create gradient progress bars (green→orange→red)"
# Copilot designed: Segmented bar with zone calculations
# Result: Visual feedback at a glance
Enter fullscreen mode Exit fullscreen mode

5. Cache System

# Asked: "Implement file-based cache with TTL in Rust"
# Copilot built: Cache with CacheStatus enum (Fresh/Expired/Missing)
# Result: Fast cached responses, automatic invalidation
Enter fullscreen mode Exit fullscreen mode

Copilot suggested the modular component pattern that made the codebase maintainable and testable.

Edge Cases: Error handling, terminal cleanup on panic, XDG directory resolution - Copilot handled these gracefully.

Installation ( Only tested on LINUX )

Using install script (Recommended)

git clone https://github.com/CGCM070/copilot-usage.git
cd copilot-usage/
./install.sh
Enter fullscreen mode Exit fullscreen mode

Manual installation

cargo install --path . --force
Enter fullscreen mode Exit fullscreen mode

Usage

# First run - interactive setup
copilot-usage

# Dashboard (uses cache if available)
copilot-usage

# Force refresh from API
copilot-usage --refresh

# Waybar JSON output
copilot-usage --waybar

# Change theme temporarily
copilot-usage --theme nord

# Reconfigure token/theme
copilot-usage reconfigure

# Check cache status
copilot-usage --cache-status
Enter fullscreen mode Exit fullscreen mode

Keybindings (in TUI)

Key Action
r Refresh data from API
t Open theme selector
? Show help dialog
/ Open command menu
c Show cache info
q Esc Quit / Close modal

Configuration

File: ~/.config/copilot-usage/config.toml

token = "ghp_..."
theme = "dark"
cache_ttl_minutes = 5
waybar_format = "{}"
username = "octocat"
Enter fullscreen mode Exit fullscreen mode

Required GitHub Permissions

  • Fine-grained PAT with Plan (Read) permission
  • Classic tokens need read:user scope
  • NOT "Copilot Requests" - different permission!

What's Next?

Notification Support - Alert when usage > 80%
Historical Graphs - Usage trends over time

🀝 Open Source
This project is open source. Want to contribute?

  • PRs welcome
  • Good first issues: new themes, documentation, tests

Credits & Thanks

  • GitHub for Copilot Pro - the AI that powers our coding
  • GitHub Copilot CLI for helping build this tool faster than ever <3
  • DEV.to for hosting this challenge

Top comments (9)

Collapse
 
rohan_sharma profile image
Rohan Sharma

I loved the TUI. It's so clean and smooth!

Collapse
 
cgcm070 profile image
Cesar Castillo

Appreciate it, Rohan! I’m going to keep refining it to match my style

Collapse
 
rohan_sharma profile image
Rohan Sharma

that's cool!

Collapse
 
francistrdev profile image
πŸ‘Ύ FrancisTRᴅᴇᴠ πŸ‘Ύ

Very clean and reliable. Great work!

Collapse
 
cgcm070 profile image
Cesar Castillo

Thanks a lot, Francis! I appreciate you taking the time to read it.

Collapse
 
nube_colectiva_nc profile image
Nube Colectiva

Nice outfit, looks great πŸ”₯

Collapse
 
cgcm070 profile image
Cesar Castillo

Thanks Nube, appreciate the comment! πŸ™Œ

Collapse
 
faithomobude profile image
Faith Omobude

This is just so well detailed.

Collapse
 
cgcm070 profile image
Cesar Castillo

Thanks, Faith! Was there any specific part that you found most useful? Best regards!