feat(tui): rename token activity command to usage

This commit is contained in:
Felipe Coury
2026-06-03 13:14:04 -03:00
parent 1c00784ce5
commit 78b2ede38f
9 changed files with 54 additions and 54 deletions
+1 -1
View File
@@ -739,7 +739,7 @@ impl App {
.finish_token_activity_refresh(request_id, result)
{
// Commit synchronously so an already queued /clear cannot overtake this card.
// Do not route through ChatWidget::add_to_history: /tokens may complete during
// Do not route through ChatWidget::add_to_history: /usage may complete during
// active work, and flushing an in-progress tool cell would corrupt its lifecycle.
// If an answer stream is active, keep the settled card transient until its
// provisional transcript cells have been consolidated.
+1 -1
View File
@@ -28,7 +28,7 @@ impl App {
.history_wrap_width(tui.terminal.last_known_screen_size.width),
);
}
// A committed cell can unblock a settled /tokens card that was waiting
// A committed cell can unblock a settled /usage card that was waiting
// behind a transient active cell or a provisional stream tail.
self.chat_widget
.request_completed_token_activity_output_insertion();
+1 -1
View File
@@ -293,7 +293,7 @@ pub(crate) enum AppEvent {
result: Result<Vec<RateLimitSnapshot>, String>,
},
/// Fetch account-wide token activity for a `/tokens` history card.
/// Fetch account-wide token activity for a `/usage` history card.
RefreshTokenActivity {
request_id: u64,
},
@@ -76,7 +76,7 @@ pub(crate) fn builtins_for_input(flags: BuiltinCommandFlags) -> Vec<(&'static st
.filter(|(_, cmd)| flags.collaboration_modes_enabled || *cmd != SlashCommand::Plan)
.filter(|(_, cmd)| flags.connectors_enabled || *cmd != SlashCommand::Apps)
.filter(|(_, cmd)| flags.plugins_command_enabled || *cmd != SlashCommand::Plugins)
.filter(|(_, cmd)| flags.token_activity_command_enabled || *cmd != SlashCommand::Tokens)
.filter(|(_, cmd)| flags.token_activity_command_enabled || *cmd != SlashCommand::Usage)
.filter(|(_, cmd)| flags.goal_command_enabled || *cmd != SlashCommand::Goal)
.filter(|(_, cmd)| flags.personality_command_enabled || *cmd != SlashCommand::Personality)
.filter(|(_, cmd)| flags.realtime_conversation_enabled || *cmd != SlashCommand::Realtime)
@@ -262,24 +262,24 @@ mod tests {
}
#[test]
fn tokens_command_is_hidden_from_input_when_account_token_activity_is_disabled() {
fn usage_command_is_hidden_from_input_when_account_token_activity_is_disabled() {
let mut flags = all_enabled_flags();
flags.token_activity_command_enabled = false;
assert_eq!(
builtins_for_input(flags)
.into_iter()
.find(|(_, command)| *command == SlashCommand::Tokens),
.find(|(_, command)| *command == SlashCommand::Usage),
None
);
}
#[test]
fn tokens_command_exact_lookup_still_resolves_when_account_token_activity_is_disabled() {
fn usage_command_exact_lookup_still_resolves_when_account_token_activity_is_disabled() {
let mut flags = all_enabled_flags();
flags.token_activity_command_enabled = false;
assert_eq!(
find_builtin_command("tokens", flags),
Some(SlashCommand::Tokens)
find_builtin_command("usage", flags),
Some(SlashCommand::Usage)
);
}
@@ -324,7 +324,7 @@ mod tests {
SlashCommand::Diff,
SlashCommand::Mention,
SlashCommand::Status,
SlashCommand::Tokens,
SlashCommand::Usage,
]
);
}
@@ -35,7 +35,7 @@ const SIDE_SLASH_COMMAND_UNAVAILABLE_HINT: &str =
const GOAL_USAGE: &str = "Usage: /goal <objective>";
const GOAL_USAGE_HINT: &str = "Example: /goal improve benchmark coverage";
const RAW_USAGE: &str = "Usage: /raw [on|off]";
const TOKENS_CHATGPT_LOGIN_REQUIRED: &str = "Sign in with ChatGPT to use /tokens.";
const USAGE_CHATGPT_LOGIN_REQUIRED: &str = "Sign in with ChatGPT to use /usage.";
impl ChatWidget {
/// Dispatch a bare slash command and record its staged local-history entry.
@@ -415,7 +415,7 @@ impl ChatWidget {
);
}
}
SlashCommand::Tokens => {
SlashCommand::Usage => {
if self.ensure_token_activity_command_available() {
self.add_token_activity_output(tokens::TokenActivityView::Daily);
}
@@ -620,12 +620,12 @@ impl ChatWidget {
} = prepared;
let trimmed = args.trim();
match cmd {
SlashCommand::Tokens => {
SlashCommand::Usage => {
if self.ensure_token_activity_command_available() {
match tokens::TokenActivityView::parse(trimmed) {
Some(view) => self.add_token_activity_output(view),
None => self.add_error_message(
"Usage: /tokens [daily|weekly|cumulative]".to_string(),
"Usage: /usage [daily|weekly|cumulative]".to_string(),
),
}
}
@@ -975,7 +975,7 @@ impl ChatWidget {
if self.has_chatgpt_account {
return true;
}
self.add_error_message(TOKENS_CHATGPT_LOGIN_REQUIRED.to_string());
self.add_error_message(USAGE_CHATGPT_LOGIN_REQUIRED.to_string());
false
}
@@ -986,7 +986,7 @@ impl ChatWidget {
match cmd {
SlashCommand::Ide
| SlashCommand::Status
| SlashCommand::Tokens
| SlashCommand::Usage
| SlashCommand::DebugConfig
| SlashCommand::Ps
| SlashCommand::Stop
@@ -3,7 +3,7 @@ source: tui/src/chatwidget/tests/slash_commands.rs
expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
---
/tokens daily
/usage daily
Token activity
Loading...
@@ -1137,10 +1137,10 @@ async fn usage_error_slash_command_is_available_from_local_recall() {
}
#[tokio::test]
async fn signed_out_tokens_command_reports_chatgpt_login_requirement() {
async fn signed_out_usage_command_reports_chatgpt_login_requirement() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
submit_composer_text(&mut chat, "/tokens");
submit_composer_text(&mut chat, "/usage");
let cells = drain_insert_history(&mut rx);
let rendered = cells
@@ -1149,17 +1149,17 @@ async fn signed_out_tokens_command_reports_chatgpt_login_requirement() {
.collect::<Vec<_>>()
.join("\n");
assert!(
rendered.contains("Sign in with ChatGPT to use /tokens."),
rendered.contains("Sign in with ChatGPT to use /usage."),
"expected ChatGPT login requirement, got: {rendered:?}"
);
assert_eq!(recall_latest_after_clearing(&mut chat), "/tokens");
assert_eq!(recall_latest_after_clearing(&mut chat), "/usage");
}
#[tokio::test]
async fn signed_out_tokens_command_with_args_reports_chatgpt_login_requirement() {
async fn signed_out_usage_command_with_args_reports_chatgpt_login_requirement() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
submit_composer_text(&mut chat, "/tokens weekly");
submit_composer_text(&mut chat, "/usage weekly");
let cells = drain_insert_history(&mut rx);
let rendered = cells
@@ -1168,10 +1168,10 @@ async fn signed_out_tokens_command_with_args_reports_chatgpt_login_requirement()
.collect::<Vec<_>>()
.join("\n");
assert!(
rendered.contains("Sign in with ChatGPT to use /tokens."),
rendered.contains("Sign in with ChatGPT to use /usage."),
"expected ChatGPT login requirement, got: {rendered:?}"
);
assert_eq!(recall_latest_after_clearing(&mut chat), "/tokens weekly");
assert_eq!(recall_latest_after_clearing(&mut chat), "/usage weekly");
}
#[tokio::test]
@@ -1179,7 +1179,7 @@ async fn clearing_pending_token_activity_refreshes_discards_late_result() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.dispatch_command(SlashCommand::Tokens);
chat.dispatch_command(SlashCommand::Usage);
let request_id = match rx.try_recv() {
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
@@ -1188,12 +1188,12 @@ async fn clearing_pending_token_activity_refreshes_discards_late_result() {
assert_eq!(
chat.pending_token_activity_output()
.map(|cell| lines_to_single_string(&cell.display_lines(u16::MAX))),
Some("/tokens daily\n\n Token activity\n Loading...\n".to_string()),
Some("/usage daily\n\n Token activity\n Loading...\n".to_string()),
);
assert_eq!(
chat.active_cell_transcript_lines(u16::MAX)
.map(|lines| lines_to_single_string(&lines)),
Some("/tokens daily\n\n Token activity\n Loading...\n".to_string()),
Some("/usage daily\n\n Token activity\n Loading...\n".to_string()),
);
chat.clear_pending_token_activity_refreshes();
@@ -1211,7 +1211,7 @@ async fn pending_token_activity_refresh_renders_above_composer_snapshot() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.dispatch_command(SlashCommand::Tokens);
chat.dispatch_command(SlashCommand::Usage);
assert_matches!(
rx.try_recv(),
Ok(AppEvent::RefreshTokenActivity { .. }),
@@ -1238,7 +1238,7 @@ async fn completed_token_activity_refresh_returns_one_history_cell() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.dispatch_command(SlashCommand::Tokens);
chat.dispatch_command(SlashCommand::Usage);
let request_id = match rx.try_recv() {
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
@@ -1260,7 +1260,7 @@ async fn completed_token_activity_refresh_returns_one_history_cell() {
assert!(chat.pending_token_activity_output().is_none());
assert_eq!(
lines_to_single_string(&cell.display_lines(u16::MAX)),
"/tokens daily\n\n Token activity\n Token activity unavailable\n",
"/usage daily\n\n Token activity\n Token activity unavailable\n",
);
}
@@ -1269,7 +1269,7 @@ async fn completed_token_activity_refresh_waits_for_active_stream() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.dispatch_command(SlashCommand::Tokens);
chat.dispatch_command(SlashCommand::Usage);
let request_id = match rx.try_recv() {
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
other => panic!("expected token activity refresh request, got {other:?}"),
@@ -1286,7 +1286,7 @@ async fn completed_token_activity_refresh_waits_for_active_stream() {
assert_eq!(
chat.pending_token_activity_output()
.map(|cell| lines_to_single_string(&cell.display_lines(u16::MAX))),
Some("/tokens daily\n\n Token activity\n Token activity unavailable\n".to_string()),
Some("/usage daily\n\n Token activity\n Token activity unavailable\n".to_string()),
);
chat.finalize_turn();
@@ -1303,7 +1303,7 @@ async fn completed_token_activity_refresh_waits_for_queued_stream_consolidation(
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.dispatch_command(SlashCommand::Tokens);
chat.dispatch_command(SlashCommand::Usage);
let request_id = match rx.try_recv() {
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
other => panic!("expected token activity refresh request, got {other:?}"),
@@ -1330,7 +1330,7 @@ async fn completed_token_activity_refresh_waits_for_active_history_cell() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.dispatch_command(SlashCommand::Tokens);
chat.dispatch_command(SlashCommand::Usage);
let request_id = match rx.try_recv() {
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
other => panic!("expected token activity refresh request, got {other:?}"),
@@ -1360,7 +1360,7 @@ async fn completed_token_activity_refresh_retries_after_plan_item_completion() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.dispatch_command(SlashCommand::Tokens);
chat.dispatch_command(SlashCommand::Usage);
let request_id = match rx.try_recv() {
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
other => panic!("expected token activity refresh request, got {other:?}"),
@@ -1395,7 +1395,7 @@ async fn pending_token_activity_refresh_keeps_composer_visible_in_short_viewport
std::iter::repeat_n(Line::from("active output"), /*n*/ 20).collect(),
)));
chat.dispatch_command(SlashCommand::Tokens);
chat.dispatch_command(SlashCommand::Usage);
assert_matches!(
rx.try_recv(),
Ok(AppEvent::RefreshTokenActivity { .. }),
@@ -1426,13 +1426,13 @@ async fn repeated_token_activity_refreshes_keep_only_latest_card() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.dispatch_command(SlashCommand::Tokens);
chat.dispatch_command(SlashCommand::Usage);
let first_request_id = match rx.try_recv() {
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
other => panic!("expected first token activity refresh request, got {other:?}"),
};
chat.dispatch_command_with_args(SlashCommand::Tokens, "weekly".to_string(), Vec::new());
chat.dispatch_command_with_args(SlashCommand::Usage, "weekly".to_string(), Vec::new());
let second_request_id = match rx.try_recv() {
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
other => panic!("expected second token activity refresh request, got {other:?}"),
@@ -1441,7 +1441,7 @@ async fn repeated_token_activity_refreshes_keep_only_latest_card() {
assert_eq!(
chat.pending_token_activity_output()
.map(|cell| lines_to_single_string(&cell.display_lines(u16::MAX))),
Some("/tokens weekly\n\n Token activity\n Loading...\n".to_string()),
Some("/usage weekly\n\n Token activity\n Loading...\n".to_string()),
);
assert!(!chat.finish_token_activity_refresh(
first_request_id,
+9 -9
View File
@@ -1,4 +1,4 @@
//! Renders account token activity and coordinates asynchronous `/tokens` cards.
//! Renders account token activity and coordinates asynchronous `/usage` cards.
//!
//! The slash command builds a composite history cell immediately, but the widget
//! keeps that cell transient while the account request runs. The transient card is
@@ -72,9 +72,9 @@ pub(super) enum TokenActivityView {
}
impl TokenActivityView {
/// Parses the optional `/tokens` argument into a supported chart view.
/// Parses the optional `/usage` argument into a supported chart view.
///
/// An empty argument selects the daily view so `/tokens` and `/tokens daily`
/// An empty argument selects the daily view so `/usage` and `/usage daily`
/// behave identically. Returning `None` lets the slash-command dispatcher
/// report unsupported arguments instead of silently choosing a view.
pub(super) fn parse(value: &str) -> Option<Self> {
@@ -115,7 +115,7 @@ pub(super) struct TokenActivityHandle {
/// Holds the one transient token activity card waiting on its background response.
///
/// The request ID prevents late results from mutating a newer `/tokens` card. The
/// The request ID prevents late results from mutating a newer `/usage` card. The
/// cell stays out of transcript history until the matching response completes and
/// the widget confirms that active output no longer blocks insertion.
pub(super) struct PendingTokenActivityOutput {
@@ -141,14 +141,14 @@ impl TokenActivityHandle {
}
}
/// Renders one `/tokens` card from shared asynchronous state.
/// Renders one `/usage` card from shared asynchronous state.
#[derive(Debug)]
struct TokenActivityHistoryCell {
view: TokenActivityView,
state: Arc<RwLock<TokenActivityState>>,
}
/// Creates the card contents and completion handle for one `/tokens` invocation.
/// Creates the card contents and completion handle for one `/usage` invocation.
///
/// The composite cell includes the echoed slash command and a loading card from
/// the start. Callers must retain the returned handle and complete it when the
@@ -157,7 +157,7 @@ pub(super) fn new_token_activity_output(
view: TokenActivityView,
) -> (CompositeHistoryCell, TokenActivityHandle) {
let command = PlainHistoryCell::new(vec![
format!("/tokens {}", view.label().to_lowercase())
format!("/usage {}", view.label().to_lowercase())
.magenta()
.into(),
]);
@@ -464,7 +464,7 @@ fn bar_caption(view: TokenActivityView, values: &[i64]) -> Line<'static> {
.into()
}
/// Dim footer that surfaces the other `/tokens` views and emphasizes the
/// Dim footer that surfaces the other `/usage` views and emphasizes the
/// active one, so the weekly/cumulative modes are discoverable from the card.
fn view_footer(active: TokenActivityView) -> Line<'static> {
let mut spans = vec![Span::styled(" ", label_style())];
@@ -743,7 +743,7 @@ impl ChatWidget {
///
/// Returns `true` when the pending request matched and moved into the completed
/// slot. Late responses return `false`, including responses for cards replaced
/// by a newer `/tokens` invocation or cleared during transcript changes.
/// by a newer `/usage` invocation or cleared during transcript changes.
pub(crate) fn finish_token_activity_refresh(
&mut self,
request_id: u64,
+5 -5
View File
@@ -45,7 +45,7 @@ pub enum SlashCommand {
Diff,
Mention,
Status,
Tokens,
Usage,
DebugConfig,
Title,
Statusline,
@@ -99,7 +99,7 @@ impl SlashCommand {
SlashCommand::Skills => "use skills to improve how Codex performs specific tasks",
SlashCommand::Hooks => "view and manage lifecycle hooks",
SlashCommand::Status => "show current session configuration and token usage",
SlashCommand::Tokens => "show account token activity",
SlashCommand::Usage => "show account usage activity",
SlashCommand::DebugConfig => "show config layers and requirement sources for debugging",
SlashCommand::Title => "configure which items appear in the terminal title",
SlashCommand::Statusline => "configure which items appear in the status line",
@@ -159,7 +159,7 @@ impl SlashCommand {
| SlashCommand::Keymap
| SlashCommand::Mcp
| SlashCommand::Raw
| SlashCommand::Tokens
| SlashCommand::Usage
| SlashCommand::Pets
| SlashCommand::Side
| SlashCommand::Btw
@@ -177,7 +177,7 @@ impl SlashCommand {
| SlashCommand::Diff
| SlashCommand::Mention
| SlashCommand::Status
| SlashCommand::Tokens
| SlashCommand::Usage
| SlashCommand::Ide
)
}
@@ -214,7 +214,7 @@ impl SlashCommand {
| SlashCommand::Skills
| SlashCommand::Hooks
| SlashCommand::Status
| SlashCommand::Tokens
| SlashCommand::Usage
| SlashCommand::DebugConfig
| SlashCommand::Ps
| SlashCommand::Stop