Skip to content
Open
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
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ All notable changes to rtk (Rust Token Killer) will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.37.1](https://github.com/rtk-ai/rtk/compare/v0.37.0...v0.37.1) (2026-04-18)


### Bug Fixes

* **docs:** user facing docs ([c8d6878](https://github.com/rtk-ai/rtk/commit/c8d68787fb8b31c52125e9fc7ea62e0aa590485f))

## [0.37.0](https://github.com/rtk-ai/rtk/compare/v0.36.0...v0.37.0) (2026-04-17)


### Features

* **discover:** handle more npm/npx/pnpm/pnpx patterns ([9e96caa](https://github.com/rtk-ai/rtk/commit/9e96caa0a18a95c84da82ba57716a9d3ef86d0c8))
* **refacto-core:** binary hook w/ native cmd exec + streaming ([e7b7f9a](https://github.com/rtk-ai/rtk/commit/e7b7f9ab665a0f7303d41d23ad156d24e5e8964e))


### Bug Fixes

* **docs:** use release please changelog no manual ([7591a14](https://github.com/rtk-ai/rtk/commit/7591a14e4ceb732ab7ca160ac01a852926abe77a))
* isolate cursor hook tests from local settings (determinist) ([d8ddefe](https://github.com/rtk-ai/rtk/commit/d8ddefe78efe25c35bb2a2f9083f2eacb9dd7274))
* P0+P1 fixes from pre-merge review of hook engine ([df8e035](https://github.com/rtk-ai/rtk/commit/df8e03558d4d6cc2f5cbac91c63ab1b3b51d3bcd))
* P0+P1 fixes from pre-merge review of hook engine ([d34389c](https://github.com/rtk-ai/rtk/commit/d34389c3d0936c2b0790e14f450bb50a28a7edf7))
* rename ship.md to ship/SKILL.md to match develop ([5916ecd](https://github.com/rtk-ai/rtk/commit/5916ecd86fb319c2519a0b4fb2891309833a3bb4))
* **runner:** preserve fd separation on command failure ([e92d099](https://github.com/rtk-ai/rtk/commit/e92d0993c93f0b732316dfa932d265aeca7488d6))
* **stream:** missing stderr fields ([a1d46f3](https://github.com/rtk-ai/rtk/commit/a1d46f39c291e3356b9c26a062bde05ba1de591a))
## [0.36.0](https://github.com/rtk-ai/rtk/compare/v0.35.0...v0.36.0) (2026-04-13)


Expand Down
85 changes: 51 additions & 34 deletions src/analytics/gain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use std::path::PathBuf;

#[allow(clippy::too_many_arguments)]
pub fn run(
project: bool, // added: per-project scope flag
project: bool,
agent: Option<String>,
graph: bool,
history: bool,
quota: bool,
Expand All @@ -27,7 +28,8 @@ pub fn run(
_verbose: u8,
) -> Result<()> {
let tracker = Tracker::new().context("Failed to initialize tracking database")?;
let project_scope = resolve_project_scope(project)?; // added: resolve project path
let project_scope = resolve_project_scope(project)?;
let agent_filter = agent.as_deref();

if failures {
return show_failures(&tracker);
Expand All @@ -42,7 +44,8 @@ pub fn run(
weekly,
monthly,
all,
project_scope.as_deref(), // added: pass project scope
project_scope.as_deref(),
agent_filter,
);
}
"csv" => {
Expand All @@ -52,14 +55,15 @@ pub fn run(
weekly,
monthly,
all,
project_scope.as_deref(), // added: pass project scope
project_scope.as_deref(),
agent_filter,
);
}
_ => {} // Continue with text format
}

let summary = tracker
.get_summary_filtered(project_scope.as_deref()) // changed: use filtered variant
.get_summary_filtered(project_scope.as_deref(), agent_filter)
.context("Failed to load token savings summary from database")?;

if summary.total_commands == 0 {
Expand All @@ -70,18 +74,20 @@ pub fn run(

// Default view (summary)
if !daily && !weekly && !monthly && !all {
// added: scope-aware styled header // changed: merged upstream styled + project scope
let title = if project_scope.is_some() {
"RTK Token Savings (Project Scope)"
} else {
"RTK Token Savings (Global Scope)"
let title = match (project_scope.is_some(), agent_filter) {
(true, Some(a)) => format!("RTK Token Savings (Project + Agent: {a})"),
(true, None) => "RTK Token Savings (Project Scope)".to_string(),
(false, Some(a)) => format!("RTK Token Savings (Agent: {a})"),
(false, None) => "RTK Token Savings (Global Scope)".to_string(),
};
println!("{}", styled(title, true));
println!("{}", styled(&title, true));
println!("{}", "═".repeat(60));
// added: show project path when scoped
if let Some(ref scope) = project_scope {
println!("Scope: {}", shorten_path(scope));
}
if let Some(a) = agent_filter {
println!("Agent: {a}");
}
println!();

// added: KPI-style aligned output
Expand Down Expand Up @@ -226,7 +232,7 @@ pub fn run(
}

if history {
let recent = tracker.get_recent_filtered(10, project_scope.as_deref())?; // changed: filtered
let recent = tracker.get_recent_filtered(10, project_scope.as_deref(), agent_filter)?;
if !recent.is_empty() {
println!("{}", styled("Recent Commands", true)); // added: styled header
println!("──────────────────────────────────────────────────────────");
Expand Down Expand Up @@ -289,15 +295,15 @@ pub fn run(

// Time breakdown views
if all || daily {
print_daily_full(&tracker, project_scope.as_deref())?; // changed: pass project scope
print_daily_full(&tracker, project_scope.as_deref(), agent_filter)?;
}

if all || weekly {
print_weekly(&tracker, project_scope.as_deref())?; // changed: pass project scope
print_weekly(&tracker, project_scope.as_deref(), agent_filter)?;
}

if all || monthly {
print_monthly(&tracker, project_scope.as_deref())?; // changed: pass project scope
print_monthly(&tracker, project_scope.as_deref(), agent_filter)?;
}

Ok(())
Expand Down Expand Up @@ -460,23 +466,32 @@ fn print_ascii_graph(data: &[(String, usize)]) {
}
}

fn print_daily_full(tracker: &Tracker, project_scope: Option<&str>) -> Result<()> {
// changed: add project scope
let days = tracker.get_all_days_filtered(project_scope)?; // changed: use filtered variant
fn print_daily_full(
tracker: &Tracker,
project_scope: Option<&str>,
agent_filter: Option<&str>,
) -> Result<()> {
let days = tracker.get_all_days_filtered(project_scope, agent_filter)?;
print_period_table(&days);
Ok(())
}

fn print_weekly(tracker: &Tracker, project_scope: Option<&str>) -> Result<()> {
// changed: add project scope
let weeks = tracker.get_by_week_filtered(project_scope)?; // changed: use filtered variant
fn print_weekly(
tracker: &Tracker,
project_scope: Option<&str>,
agent_filter: Option<&str>,
) -> Result<()> {
let weeks = tracker.get_by_week_filtered(project_scope, agent_filter)?;
print_period_table(&weeks);
Ok(())
}

fn print_monthly(tracker: &Tracker, project_scope: Option<&str>) -> Result<()> {
// changed: add project scope
let months = tracker.get_by_month_filtered(project_scope)?; // changed: use filtered variant
fn print_monthly(
tracker: &Tracker,
project_scope: Option<&str>,
agent_filter: Option<&str>,
) -> Result<()> {
let months = tracker.get_by_month_filtered(project_scope, agent_filter)?;
print_period_table(&months);
Ok(())
}
Expand Down Expand Up @@ -509,10 +524,11 @@ fn export_json(
weekly: bool,
monthly: bool,
all: bool,
project_scope: Option<&str>, // added: project scope
project_scope: Option<&str>,
agent_filter: Option<&str>,
) -> Result<()> {
let summary = tracker
.get_summary_filtered(project_scope) // changed: use filtered variant
.get_summary_filtered(project_scope, agent_filter)
.context("Failed to load token savings summary from database")?;

let export = ExportData {
Expand All @@ -526,17 +542,17 @@ fn export_json(
avg_time_ms: summary.avg_time_ms,
},
daily: if all || daily {
Some(tracker.get_all_days_filtered(project_scope)?) // changed: use filtered
Some(tracker.get_all_days_filtered(project_scope, agent_filter)?)
} else {
None
},
weekly: if all || weekly {
Some(tracker.get_by_week_filtered(project_scope)?) // changed: use filtered
Some(tracker.get_by_week_filtered(project_scope, agent_filter)?)
} else {
None
},
monthly: if all || monthly {
Some(tracker.get_by_month_filtered(project_scope)?) // changed: use filtered
Some(tracker.get_by_month_filtered(project_scope, agent_filter)?)
} else {
None
},
Expand All @@ -554,10 +570,11 @@ fn export_csv(
weekly: bool,
monthly: bool,
all: bool,
project_scope: Option<&str>, // added: project scope
project_scope: Option<&str>,
agent_filter: Option<&str>,
) -> Result<()> {
if all || daily {
let days = tracker.get_all_days_filtered(project_scope)?; // changed: use filtered
let days = tracker.get_all_days_filtered(project_scope, agent_filter)?;
println!("# Daily Data");
println!("date,commands,input_tokens,output_tokens,saved_tokens,savings_pct,total_time_ms,avg_time_ms");
for day in days {
Expand All @@ -577,7 +594,7 @@ fn export_csv(
}

if all || weekly {
let weeks = tracker.get_by_week_filtered(project_scope)?; // changed: use filtered
let weeks = tracker.get_by_week_filtered(project_scope, agent_filter)?;
println!("# Weekly Data");
println!(
"week_start,week_end,commands,input_tokens,output_tokens,saved_tokens,savings_pct,total_time_ms,avg_time_ms"
Expand All @@ -600,7 +617,7 @@ fn export_csv(
}

if all || monthly {
let months = tracker.get_by_month_filtered(project_scope)?; // changed: use filtered
let months = tracker.get_by_month_filtered(project_scope, agent_filter)?;
println!("# Monthly Data");
println!("month,commands,input_tokens,output_tokens,saved_tokens,savings_pct,total_time_ms,avg_time_ms");
for month in months {
Expand Down
Loading