@hasna/terminal 2.0.5 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +52 -21
- package/package.json +1 -1
- package/src/ai.ts +77 -130
- package/src/cli.tsx +51 -21
- package/src/command-validator.ts +11 -0
- package/src/context-hints.ts +291 -0
- package/src/discover.ts +238 -0
- package/src/economy.ts +53 -0
- package/src/output-processor.ts +7 -18
- package/src/output-store.ts +65 -0
- package/src/providers/base.ts +3 -1
- package/src/providers/groq.ts +108 -0
- package/src/providers/index.ts +26 -2
- package/src/providers/providers.test.ts +4 -2
- package/src/providers/xai.ts +108 -0
- package/src/sessions-db.ts +81 -0
- package/temp/rtk/.claude/agents/code-reviewer.md +221 -0
- package/temp/rtk/.claude/agents/debugger.md +519 -0
- package/temp/rtk/.claude/agents/rtk-testing-specialist.md +461 -0
- package/temp/rtk/.claude/agents/rust-rtk.md +511 -0
- package/temp/rtk/.claude/agents/technical-writer.md +355 -0
- package/temp/rtk/.claude/commands/diagnose.md +352 -0
- package/temp/rtk/.claude/commands/test-routing.md +362 -0
- package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +16 -0
- package/temp/rtk/.claude/hooks/rtk-rewrite.sh +70 -0
- package/temp/rtk/.claude/hooks/rtk-suggest.sh +152 -0
- package/temp/rtk/.claude/rules/cli-testing.md +526 -0
- package/temp/rtk/.claude/skills/issue-triage/SKILL.md +348 -0
- package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +134 -0
- package/temp/rtk/.claude/skills/performance.md +435 -0
- package/temp/rtk/.claude/skills/pr-triage/SKILL.md +315 -0
- package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +71 -0
- package/temp/rtk/.claude/skills/repo-recap.md +206 -0
- package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +78 -0
- package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +124 -0
- package/temp/rtk/.claude/skills/security-guardian.md +503 -0
- package/temp/rtk/.claude/skills/ship.md +404 -0
- package/temp/rtk/.github/workflows/benchmark.yml +34 -0
- package/temp/rtk/.github/workflows/dco-check.yaml +12 -0
- package/temp/rtk/.github/workflows/release-please.yml +51 -0
- package/temp/rtk/.github/workflows/release.yml +343 -0
- package/temp/rtk/.github/workflows/security-check.yml +135 -0
- package/temp/rtk/.github/workflows/validate-docs.yml +78 -0
- package/temp/rtk/.release-please-manifest.json +3 -0
- package/temp/rtk/ARCHITECTURE.md +1491 -0
- package/temp/rtk/CHANGELOG.md +640 -0
- package/temp/rtk/CLAUDE.md +605 -0
- package/temp/rtk/CONTRIBUTING.md +199 -0
- package/temp/rtk/Cargo.lock +1668 -0
- package/temp/rtk/Cargo.toml +64 -0
- package/temp/rtk/Formula/rtk.rb +43 -0
- package/temp/rtk/INSTALL.md +390 -0
- package/temp/rtk/LICENSE +21 -0
- package/temp/rtk/README.md +386 -0
- package/temp/rtk/README_es.md +159 -0
- package/temp/rtk/README_fr.md +197 -0
- package/temp/rtk/README_ja.md +159 -0
- package/temp/rtk/README_ko.md +159 -0
- package/temp/rtk/README_zh.md +167 -0
- package/temp/rtk/ROADMAP.md +15 -0
- package/temp/rtk/SECURITY.md +217 -0
- package/temp/rtk/TEST_EXEC_TIME.md +102 -0
- package/temp/rtk/build.rs +57 -0
- package/temp/rtk/docs/AUDIT_GUIDE.md +432 -0
- package/temp/rtk/docs/FEATURES.md +1410 -0
- package/temp/rtk/docs/TROUBLESHOOTING.md +309 -0
- package/temp/rtk/docs/filter-workflow.md +102 -0
- package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
- package/temp/rtk/docs/tracking.md +583 -0
- package/temp/rtk/hooks/opencode-rtk.ts +39 -0
- package/temp/rtk/hooks/rtk-awareness.md +29 -0
- package/temp/rtk/hooks/rtk-rewrite.sh +61 -0
- package/temp/rtk/hooks/test-rtk-rewrite.sh +442 -0
- package/temp/rtk/install.sh +124 -0
- package/temp/rtk/release-please-config.json +10 -0
- package/temp/rtk/scripts/benchmark.sh +592 -0
- package/temp/rtk/scripts/check-installation.sh +162 -0
- package/temp/rtk/scripts/install-local.sh +37 -0
- package/temp/rtk/scripts/rtk-economics.sh +137 -0
- package/temp/rtk/scripts/test-all.sh +561 -0
- package/temp/rtk/scripts/test-aristote.sh +227 -0
- package/temp/rtk/scripts/test-tracking.sh +79 -0
- package/temp/rtk/scripts/update-readme-metrics.sh +32 -0
- package/temp/rtk/scripts/validate-docs.sh +73 -0
- package/temp/rtk/src/aws_cmd.rs +880 -0
- package/temp/rtk/src/binlog.rs +1645 -0
- package/temp/rtk/src/cargo_cmd.rs +1727 -0
- package/temp/rtk/src/cc_economics.rs +1157 -0
- package/temp/rtk/src/ccusage.rs +340 -0
- package/temp/rtk/src/config.rs +187 -0
- package/temp/rtk/src/container.rs +855 -0
- package/temp/rtk/src/curl_cmd.rs +134 -0
- package/temp/rtk/src/deps.rs +268 -0
- package/temp/rtk/src/diff_cmd.rs +367 -0
- package/temp/rtk/src/discover/mod.rs +274 -0
- package/temp/rtk/src/discover/provider.rs +388 -0
- package/temp/rtk/src/discover/registry.rs +2022 -0
- package/temp/rtk/src/discover/report.rs +202 -0
- package/temp/rtk/src/discover/rules.rs +667 -0
- package/temp/rtk/src/display_helpers.rs +402 -0
- package/temp/rtk/src/dotnet_cmd.rs +1771 -0
- package/temp/rtk/src/dotnet_format_report.rs +133 -0
- package/temp/rtk/src/dotnet_trx.rs +593 -0
- package/temp/rtk/src/env_cmd.rs +204 -0
- package/temp/rtk/src/filter.rs +462 -0
- package/temp/rtk/src/filters/README.md +52 -0
- package/temp/rtk/src/filters/ansible-playbook.toml +34 -0
- package/temp/rtk/src/filters/basedpyright.toml +47 -0
- package/temp/rtk/src/filters/biome.toml +45 -0
- package/temp/rtk/src/filters/brew-install.toml +37 -0
- package/temp/rtk/src/filters/composer-install.toml +40 -0
- package/temp/rtk/src/filters/df.toml +16 -0
- package/temp/rtk/src/filters/dotnet-build.toml +64 -0
- package/temp/rtk/src/filters/du.toml +16 -0
- package/temp/rtk/src/filters/fail2ban-client.toml +15 -0
- package/temp/rtk/src/filters/gcc.toml +49 -0
- package/temp/rtk/src/filters/gcloud.toml +22 -0
- package/temp/rtk/src/filters/hadolint.toml +24 -0
- package/temp/rtk/src/filters/helm.toml +29 -0
- package/temp/rtk/src/filters/iptables.toml +27 -0
- package/temp/rtk/src/filters/jj.toml +28 -0
- package/temp/rtk/src/filters/jq.toml +24 -0
- package/temp/rtk/src/filters/make.toml +41 -0
- package/temp/rtk/src/filters/markdownlint.toml +24 -0
- package/temp/rtk/src/filters/mix-compile.toml +27 -0
- package/temp/rtk/src/filters/mix-format.toml +15 -0
- package/temp/rtk/src/filters/mvn-build.toml +44 -0
- package/temp/rtk/src/filters/oxlint.toml +43 -0
- package/temp/rtk/src/filters/ping.toml +63 -0
- package/temp/rtk/src/filters/pio-run.toml +40 -0
- package/temp/rtk/src/filters/poetry-install.toml +50 -0
- package/temp/rtk/src/filters/pre-commit.toml +35 -0
- package/temp/rtk/src/filters/ps.toml +16 -0
- package/temp/rtk/src/filters/quarto-render.toml +41 -0
- package/temp/rtk/src/filters/rsync.toml +48 -0
- package/temp/rtk/src/filters/shellcheck.toml +27 -0
- package/temp/rtk/src/filters/shopify-theme.toml +29 -0
- package/temp/rtk/src/filters/skopeo.toml +45 -0
- package/temp/rtk/src/filters/sops.toml +16 -0
- package/temp/rtk/src/filters/ssh.toml +44 -0
- package/temp/rtk/src/filters/stat.toml +34 -0
- package/temp/rtk/src/filters/swift-build.toml +41 -0
- package/temp/rtk/src/filters/systemctl-status.toml +33 -0
- package/temp/rtk/src/filters/terraform-plan.toml +35 -0
- package/temp/rtk/src/filters/tofu-fmt.toml +16 -0
- package/temp/rtk/src/filters/tofu-init.toml +38 -0
- package/temp/rtk/src/filters/tofu-plan.toml +35 -0
- package/temp/rtk/src/filters/tofu-validate.toml +17 -0
- package/temp/rtk/src/filters/trunk-build.toml +39 -0
- package/temp/rtk/src/filters/ty.toml +50 -0
- package/temp/rtk/src/filters/uv-sync.toml +37 -0
- package/temp/rtk/src/filters/xcodebuild.toml +99 -0
- package/temp/rtk/src/filters/yamllint.toml +25 -0
- package/temp/rtk/src/find_cmd.rs +598 -0
- package/temp/rtk/src/format_cmd.rs +386 -0
- package/temp/rtk/src/gain.rs +723 -0
- package/temp/rtk/src/gh_cmd.rs +1651 -0
- package/temp/rtk/src/git.rs +2012 -0
- package/temp/rtk/src/go_cmd.rs +592 -0
- package/temp/rtk/src/golangci_cmd.rs +254 -0
- package/temp/rtk/src/grep_cmd.rs +288 -0
- package/temp/rtk/src/gt_cmd.rs +810 -0
- package/temp/rtk/src/hook_audit_cmd.rs +283 -0
- package/temp/rtk/src/hook_check.rs +171 -0
- package/temp/rtk/src/init.rs +1859 -0
- package/temp/rtk/src/integrity.rs +537 -0
- package/temp/rtk/src/json_cmd.rs +231 -0
- package/temp/rtk/src/learn/detector.rs +628 -0
- package/temp/rtk/src/learn/mod.rs +119 -0
- package/temp/rtk/src/learn/report.rs +184 -0
- package/temp/rtk/src/lint_cmd.rs +694 -0
- package/temp/rtk/src/local_llm.rs +316 -0
- package/temp/rtk/src/log_cmd.rs +248 -0
- package/temp/rtk/src/ls.rs +324 -0
- package/temp/rtk/src/main.rs +2482 -0
- package/temp/rtk/src/mypy_cmd.rs +389 -0
- package/temp/rtk/src/next_cmd.rs +241 -0
- package/temp/rtk/src/npm_cmd.rs +236 -0
- package/temp/rtk/src/parser/README.md +267 -0
- package/temp/rtk/src/parser/error.rs +46 -0
- package/temp/rtk/src/parser/formatter.rs +336 -0
- package/temp/rtk/src/parser/mod.rs +311 -0
- package/temp/rtk/src/parser/types.rs +119 -0
- package/temp/rtk/src/pip_cmd.rs +302 -0
- package/temp/rtk/src/playwright_cmd.rs +479 -0
- package/temp/rtk/src/pnpm_cmd.rs +573 -0
- package/temp/rtk/src/prettier_cmd.rs +221 -0
- package/temp/rtk/src/prisma_cmd.rs +482 -0
- package/temp/rtk/src/psql_cmd.rs +382 -0
- package/temp/rtk/src/pytest_cmd.rs +384 -0
- package/temp/rtk/src/read.rs +217 -0
- package/temp/rtk/src/rewrite_cmd.rs +50 -0
- package/temp/rtk/src/ruff_cmd.rs +402 -0
- package/temp/rtk/src/runner.rs +271 -0
- package/temp/rtk/src/summary.rs +297 -0
- package/temp/rtk/src/tee.rs +405 -0
- package/temp/rtk/src/telemetry.rs +248 -0
- package/temp/rtk/src/toml_filter.rs +1655 -0
- package/temp/rtk/src/tracking.rs +1416 -0
- package/temp/rtk/src/tree.rs +209 -0
- package/temp/rtk/src/tsc_cmd.rs +259 -0
- package/temp/rtk/src/utils.rs +432 -0
- package/temp/rtk/src/verify_cmd.rs +47 -0
- package/temp/rtk/src/vitest_cmd.rs +385 -0
- package/temp/rtk/src/wc_cmd.rs +401 -0
- package/temp/rtk/src/wget_cmd.rs +260 -0
- package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +11 -0
- package/temp/rtk/tests/fixtures/dotnet/format_changes.json +31 -0
- package/temp/rtk/tests/fixtures/dotnet/format_empty.json +1 -0
- package/temp/rtk/tests/fixtures/dotnet/format_success.json +12 -0
- package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +18 -0
- package/dist/App.js +0 -404
- package/dist/Browse.js +0 -79
- package/dist/FuzzyPicker.js +0 -47
- package/dist/Onboarding.js +0 -51
- package/dist/Spinner.js +0 -12
- package/dist/StatusBar.js +0 -49
- package/dist/ai.js +0 -368
- package/dist/cache.js +0 -41
- package/dist/command-rewriter.js +0 -64
- package/dist/command-validator.js +0 -77
- package/dist/compression.js +0 -107
- package/dist/diff-cache.js +0 -107
- package/dist/economy.js +0 -79
- package/dist/expand-store.js +0 -38
- package/dist/file-cache.js +0 -72
- package/dist/file-index.js +0 -62
- package/dist/history.js +0 -62
- package/dist/lazy-executor.js +0 -54
- package/dist/line-dedup.js +0 -59
- package/dist/loop-detector.js +0 -75
- package/dist/mcp/install.js +0 -98
- package/dist/mcp/server.js +0 -569
- package/dist/noise-filter.js +0 -86
- package/dist/output-processor.js +0 -136
- package/dist/output-router.js +0 -41
- package/dist/parsers/base.js +0 -2
- package/dist/parsers/build.js +0 -64
- package/dist/parsers/errors.js +0 -101
- package/dist/parsers/files.js +0 -78
- package/dist/parsers/git.js +0 -99
- package/dist/parsers/index.js +0 -48
- package/dist/parsers/tests.js +0 -89
- package/dist/providers/anthropic.js +0 -39
- package/dist/providers/base.js +0 -4
- package/dist/providers/cerebras.js +0 -95
- package/dist/providers/index.js +0 -49
- package/dist/recipes/model.js +0 -20
- package/dist/recipes/storage.js +0 -136
- package/dist/search/content-search.js +0 -68
- package/dist/search/file-search.js +0 -61
- package/dist/search/filters.js +0 -34
- package/dist/search/index.js +0 -5
- package/dist/search/semantic.js +0 -320
- package/dist/session-boot.js +0 -59
- package/dist/session-context.js +0 -55
- package/dist/sessions-db.js +0 -120
- package/dist/smart-display.js +0 -286
- package/dist/snapshots.js +0 -51
- package/dist/supervisor.js +0 -112
- package/dist/test-watchlist.js +0 -131
- package/dist/tree.js +0 -94
- package/dist/usage-cache.js +0 -65
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
use crate::display_helpers::{format_duration, print_period_table};
|
|
2
|
+
use crate::hook_check;
|
|
3
|
+
use crate::tracking::{DayStats, MonthStats, Tracker, WeekStats};
|
|
4
|
+
use crate::utils::format_tokens;
|
|
5
|
+
use anyhow::{Context, Result};
|
|
6
|
+
use colored::Colorize;
|
|
7
|
+
use serde::Serialize;
|
|
8
|
+
use std::io::IsTerminal;
|
|
9
|
+
use std::path::PathBuf;
|
|
10
|
+
|
|
11
|
+
pub fn run(
|
|
12
|
+
project: bool, // added: per-project scope flag
|
|
13
|
+
graph: bool,
|
|
14
|
+
history: bool,
|
|
15
|
+
quota: bool,
|
|
16
|
+
tier: &str,
|
|
17
|
+
daily: bool,
|
|
18
|
+
weekly: bool,
|
|
19
|
+
monthly: bool,
|
|
20
|
+
all: bool,
|
|
21
|
+
format: &str,
|
|
22
|
+
failures: bool,
|
|
23
|
+
_verbose: u8,
|
|
24
|
+
) -> Result<()> {
|
|
25
|
+
let tracker = Tracker::new().context("Failed to initialize tracking database")?;
|
|
26
|
+
let project_scope = resolve_project_scope(project)?; // added: resolve project path
|
|
27
|
+
|
|
28
|
+
if failures {
|
|
29
|
+
return show_failures(&tracker);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle export formats
|
|
33
|
+
match format {
|
|
34
|
+
"json" => {
|
|
35
|
+
return export_json(
|
|
36
|
+
&tracker,
|
|
37
|
+
daily,
|
|
38
|
+
weekly,
|
|
39
|
+
monthly,
|
|
40
|
+
all,
|
|
41
|
+
project_scope.as_deref(), // added: pass project scope
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
"csv" => {
|
|
45
|
+
return export_csv(
|
|
46
|
+
&tracker,
|
|
47
|
+
daily,
|
|
48
|
+
weekly,
|
|
49
|
+
monthly,
|
|
50
|
+
all,
|
|
51
|
+
project_scope.as_deref(), // added: pass project scope
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
_ => {} // Continue with text format
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let summary = tracker
|
|
58
|
+
.get_summary_filtered(project_scope.as_deref()) // changed: use filtered variant
|
|
59
|
+
.context("Failed to load token savings summary from database")?;
|
|
60
|
+
|
|
61
|
+
if summary.total_commands == 0 {
|
|
62
|
+
println!("No tracking data yet.");
|
|
63
|
+
println!("Run some rtk commands to start tracking savings.");
|
|
64
|
+
return Ok(());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Default view (summary)
|
|
68
|
+
if !daily && !weekly && !monthly && !all {
|
|
69
|
+
// added: scope-aware styled header // changed: merged upstream styled + project scope
|
|
70
|
+
let title = if project_scope.is_some() {
|
|
71
|
+
"RTK Token Savings (Project Scope)"
|
|
72
|
+
} else {
|
|
73
|
+
"RTK Token Savings (Global Scope)"
|
|
74
|
+
};
|
|
75
|
+
println!("{}", styled(title, true));
|
|
76
|
+
println!("{}", "═".repeat(60));
|
|
77
|
+
// added: show project path when scoped
|
|
78
|
+
if let Some(ref scope) = project_scope {
|
|
79
|
+
println!("Scope: {}", shorten_path(scope));
|
|
80
|
+
}
|
|
81
|
+
println!();
|
|
82
|
+
|
|
83
|
+
// added: KPI-style aligned output
|
|
84
|
+
print_kpi("Total commands", summary.total_commands.to_string());
|
|
85
|
+
print_kpi("Input tokens", format_tokens(summary.total_input));
|
|
86
|
+
print_kpi("Output tokens", format_tokens(summary.total_output));
|
|
87
|
+
print_kpi(
|
|
88
|
+
"Tokens saved",
|
|
89
|
+
format!(
|
|
90
|
+
"{} ({:.1}%)",
|
|
91
|
+
format_tokens(summary.total_saved),
|
|
92
|
+
summary.avg_savings_pct
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
print_kpi(
|
|
96
|
+
"Total exec time",
|
|
97
|
+
format!(
|
|
98
|
+
"{} (avg {})",
|
|
99
|
+
format_duration(summary.total_time_ms),
|
|
100
|
+
format_duration(summary.avg_time_ms)
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
print_efficiency_meter(summary.avg_savings_pct);
|
|
104
|
+
println!();
|
|
105
|
+
|
|
106
|
+
// Warn about hook issues that silently kill savings (stderr, not stdout)
|
|
107
|
+
match hook_check::status() {
|
|
108
|
+
hook_check::HookStatus::Missing => {
|
|
109
|
+
eprintln!(
|
|
110
|
+
"{}",
|
|
111
|
+
"⚠️ No hook installed — run `rtk init -g` for automatic token savings"
|
|
112
|
+
.yellow()
|
|
113
|
+
);
|
|
114
|
+
eprintln!();
|
|
115
|
+
}
|
|
116
|
+
hook_check::HookStatus::Outdated => {
|
|
117
|
+
eprintln!(
|
|
118
|
+
"{}",
|
|
119
|
+
"⚠️ Hook outdated — run `rtk init -g` to update".yellow()
|
|
120
|
+
);
|
|
121
|
+
eprintln!();
|
|
122
|
+
}
|
|
123
|
+
hook_check::HookStatus::Ok => {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Lightweight RTK_DISABLED bypass check (best-effort, silent on failure)
|
|
127
|
+
if let Some(warning) = check_rtk_disabled_bypass() {
|
|
128
|
+
eprintln!("{}", warning.yellow());
|
|
129
|
+
eprintln!();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if !summary.by_command.is_empty() {
|
|
133
|
+
// added: styled section header
|
|
134
|
+
println!("{}", styled("By Command", true));
|
|
135
|
+
|
|
136
|
+
// added: dynamic column widths for clean alignment
|
|
137
|
+
let cmd_width = 24usize;
|
|
138
|
+
let impact_width = 10usize;
|
|
139
|
+
let count_width = summary
|
|
140
|
+
.by_command
|
|
141
|
+
.iter()
|
|
142
|
+
.map(|(_, count, _, _, _)| count.to_string().len())
|
|
143
|
+
.max()
|
|
144
|
+
.unwrap_or(5)
|
|
145
|
+
.max(5);
|
|
146
|
+
let saved_width = summary
|
|
147
|
+
.by_command
|
|
148
|
+
.iter()
|
|
149
|
+
.map(|(_, _, saved, _, _)| format_tokens(*saved).len())
|
|
150
|
+
.max()
|
|
151
|
+
.unwrap_or(5)
|
|
152
|
+
.max(5);
|
|
153
|
+
let time_width = summary
|
|
154
|
+
.by_command
|
|
155
|
+
.iter()
|
|
156
|
+
.map(|(_, _, _, _, avg_time)| format_duration(*avg_time).len())
|
|
157
|
+
.max()
|
|
158
|
+
.unwrap_or(6)
|
|
159
|
+
.max(6);
|
|
160
|
+
|
|
161
|
+
let table_width = 3
|
|
162
|
+
+ 2
|
|
163
|
+
+ cmd_width
|
|
164
|
+
+ 2
|
|
165
|
+
+ count_width
|
|
166
|
+
+ 2
|
|
167
|
+
+ saved_width
|
|
168
|
+
+ 2
|
|
169
|
+
+ 6
|
|
170
|
+
+ 2
|
|
171
|
+
+ time_width
|
|
172
|
+
+ 2
|
|
173
|
+
+ impact_width;
|
|
174
|
+
println!("{}", "─".repeat(table_width));
|
|
175
|
+
println!(
|
|
176
|
+
"{:>3} {:<cmd_width$} {:>count_width$} {:>saved_width$} {:>6} {:>time_width$} {:<impact_width$}",
|
|
177
|
+
"#", "Command", "Count", "Saved", "Avg%", "Time", "Impact",
|
|
178
|
+
cmd_width = cmd_width, count_width = count_width,
|
|
179
|
+
saved_width = saved_width, time_width = time_width,
|
|
180
|
+
impact_width = impact_width
|
|
181
|
+
);
|
|
182
|
+
println!("{}", "─".repeat(table_width));
|
|
183
|
+
|
|
184
|
+
let max_saved = summary
|
|
185
|
+
.by_command
|
|
186
|
+
.iter()
|
|
187
|
+
.map(|(_, _, saved, _, _)| *saved)
|
|
188
|
+
.max()
|
|
189
|
+
.unwrap_or(1);
|
|
190
|
+
|
|
191
|
+
for (idx, (cmd, count, saved, pct, avg_time)) in summary.by_command.iter().enumerate() {
|
|
192
|
+
let row_idx = format!("{:>2}.", idx + 1);
|
|
193
|
+
let cmd_cell = style_command_cell(&truncate_for_column(cmd, cmd_width)); // added: colored command
|
|
194
|
+
let count_cell = format!("{:>count_width$}", count, count_width = count_width);
|
|
195
|
+
let saved_cell = format!(
|
|
196
|
+
"{:>saved_width$}",
|
|
197
|
+
format_tokens(*saved),
|
|
198
|
+
saved_width = saved_width
|
|
199
|
+
);
|
|
200
|
+
let pct_plain = format!("{:>6}", format!("{pct:.1}%"));
|
|
201
|
+
let pct_cell = colorize_pct_cell(*pct, &pct_plain); // added: color-coded percentage
|
|
202
|
+
let time_cell = format!(
|
|
203
|
+
"{:>time_width$}",
|
|
204
|
+
format_duration(*avg_time),
|
|
205
|
+
time_width = time_width
|
|
206
|
+
);
|
|
207
|
+
let impact = mini_bar(*saved, max_saved, impact_width); // added: impact bar
|
|
208
|
+
println!(
|
|
209
|
+
"{} {} {} {} {} {} {}",
|
|
210
|
+
row_idx, cmd_cell, count_cell, saved_cell, pct_cell, time_cell, impact
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
println!("{}", "─".repeat(table_width));
|
|
214
|
+
println!();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if graph && !summary.by_day.is_empty() {
|
|
218
|
+
println!("{}", styled("Daily Savings (last 30 days)", true)); // added: styled header
|
|
219
|
+
println!("──────────────────────────────────────────────────────────");
|
|
220
|
+
print_ascii_graph(&summary.by_day);
|
|
221
|
+
println!();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if history {
|
|
225
|
+
let recent = tracker.get_recent_filtered(10, project_scope.as_deref())?; // changed: filtered
|
|
226
|
+
if !recent.is_empty() {
|
|
227
|
+
println!("{}", styled("Recent Commands", true)); // added: styled header
|
|
228
|
+
println!("──────────────────────────────────────────────────────────");
|
|
229
|
+
for rec in recent {
|
|
230
|
+
let time = rec.timestamp.format("%m-%d %H:%M");
|
|
231
|
+
let cmd_short = if rec.rtk_cmd.len() > 25 {
|
|
232
|
+
format!("{}...", &rec.rtk_cmd[..22])
|
|
233
|
+
} else {
|
|
234
|
+
rec.rtk_cmd.clone()
|
|
235
|
+
};
|
|
236
|
+
// added: tier indicators by savings level
|
|
237
|
+
let sign = if rec.savings_pct >= 70.0 {
|
|
238
|
+
"▲"
|
|
239
|
+
} else if rec.savings_pct >= 30.0 {
|
|
240
|
+
"■"
|
|
241
|
+
} else {
|
|
242
|
+
"•"
|
|
243
|
+
};
|
|
244
|
+
println!(
|
|
245
|
+
"{} {} {:<25} -{:.0}% ({})",
|
|
246
|
+
time,
|
|
247
|
+
sign,
|
|
248
|
+
cmd_short,
|
|
249
|
+
rec.savings_pct,
|
|
250
|
+
format_tokens(rec.saved_tokens)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
println!();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if quota {
|
|
258
|
+
const ESTIMATED_PRO_MONTHLY: usize = 6_000_000;
|
|
259
|
+
|
|
260
|
+
let (quota_tokens, tier_name) = match tier {
|
|
261
|
+
"pro" => (ESTIMATED_PRO_MONTHLY, "Pro ($20/mo)"),
|
|
262
|
+
"5x" => (ESTIMATED_PRO_MONTHLY * 5, "Max 5x ($100/mo)"),
|
|
263
|
+
"20x" => (ESTIMATED_PRO_MONTHLY * 20, "Max 20x ($200/mo)"),
|
|
264
|
+
_ => (ESTIMATED_PRO_MONTHLY, "Pro ($20/mo)"),
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
let quota_pct = (summary.total_saved as f64 / quota_tokens as f64) * 100.0;
|
|
268
|
+
|
|
269
|
+
println!("{}", styled("Monthly Quota Analysis", true)); // added: styled header
|
|
270
|
+
println!("──────────────────────────────────────────────────────────");
|
|
271
|
+
print_kpi("Subscription tier", tier_name.to_string()); // added: KPI style
|
|
272
|
+
print_kpi("Estimated monthly quota", format_tokens(quota_tokens));
|
|
273
|
+
print_kpi(
|
|
274
|
+
"Tokens saved (lifetime)",
|
|
275
|
+
format_tokens(summary.total_saved),
|
|
276
|
+
);
|
|
277
|
+
print_kpi("Quota preserved", format!("{:.1}%", quota_pct));
|
|
278
|
+
println!();
|
|
279
|
+
println!("Note: Heuristic estimate based on ~44K tokens/5h (Pro baseline)");
|
|
280
|
+
println!(" Actual limits use rolling 5-hour windows, not monthly caps.");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return Ok(());
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Time breakdown views
|
|
287
|
+
if all || daily {
|
|
288
|
+
print_daily_full(&tracker, project_scope.as_deref())?; // changed: pass project scope
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if all || weekly {
|
|
292
|
+
print_weekly(&tracker, project_scope.as_deref())?; // changed: pass project scope
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if all || monthly {
|
|
296
|
+
print_monthly(&tracker, project_scope.as_deref())?; // changed: pass project scope
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
Ok(())
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── Display helpers (TTY-aware) ── // added: entire section
|
|
303
|
+
|
|
304
|
+
/// Format text with bold styling (TTY-aware). // added
|
|
305
|
+
fn styled(text: &str, strong: bool) -> String {
|
|
306
|
+
if !std::io::stdout().is_terminal() {
|
|
307
|
+
return text.to_string();
|
|
308
|
+
}
|
|
309
|
+
if strong {
|
|
310
|
+
text.bold().green().to_string()
|
|
311
|
+
} else {
|
|
312
|
+
text.to_string()
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// Print a key-value pair in KPI layout. // added
|
|
317
|
+
fn print_kpi(label: &str, value: String) {
|
|
318
|
+
println!("{:<18} {}", format!("{label}:"), value);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// Colorize percentage based on savings tier (TTY-aware). // added
|
|
322
|
+
fn colorize_pct_cell(pct: f64, padded: &str) -> String {
|
|
323
|
+
if !std::io::stdout().is_terminal() {
|
|
324
|
+
return padded.to_string();
|
|
325
|
+
}
|
|
326
|
+
if pct >= 70.0 {
|
|
327
|
+
padded.green().bold().to_string()
|
|
328
|
+
} else if pct >= 40.0 {
|
|
329
|
+
padded.yellow().bold().to_string()
|
|
330
|
+
} else {
|
|
331
|
+
padded.red().bold().to_string()
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/// Truncate text to fit column width with ellipsis. // added
|
|
336
|
+
fn truncate_for_column(text: &str, width: usize) -> String {
|
|
337
|
+
if width == 0 {
|
|
338
|
+
return String::new();
|
|
339
|
+
}
|
|
340
|
+
let char_count = text.chars().count();
|
|
341
|
+
if char_count <= width {
|
|
342
|
+
return format!("{:<width$}", text, width = width);
|
|
343
|
+
}
|
|
344
|
+
if width <= 3 {
|
|
345
|
+
return text.chars().take(width).collect();
|
|
346
|
+
}
|
|
347
|
+
let mut out: String = text.chars().take(width - 3).collect();
|
|
348
|
+
out.push_str("...");
|
|
349
|
+
out
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/// Style command names with cyan+bold (TTY-aware). // added
|
|
353
|
+
fn style_command_cell(cmd: &str) -> String {
|
|
354
|
+
if !std::io::stdout().is_terminal() {
|
|
355
|
+
return cmd.to_string();
|
|
356
|
+
}
|
|
357
|
+
cmd.bright_cyan().bold().to_string()
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/// Render a proportional bar chart segment (TTY-aware). // added
|
|
361
|
+
fn mini_bar(value: usize, max: usize, width: usize) -> String {
|
|
362
|
+
if max == 0 || width == 0 {
|
|
363
|
+
return String::new();
|
|
364
|
+
}
|
|
365
|
+
let filled = ((value as f64 / max as f64) * width as f64).round() as usize;
|
|
366
|
+
let filled = filled.min(width);
|
|
367
|
+
let mut bar = "█".repeat(filled);
|
|
368
|
+
bar.push_str(&"░".repeat(width - filled));
|
|
369
|
+
if std::io::stdout().is_terminal() {
|
|
370
|
+
bar.cyan().to_string()
|
|
371
|
+
} else {
|
|
372
|
+
bar
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/// Print an efficiency meter with colored progress bar (TTY-aware). // added
|
|
377
|
+
fn print_efficiency_meter(pct: f64) {
|
|
378
|
+
let width = 24usize;
|
|
379
|
+
let filled = (((pct / 100.0) * width as f64).round() as usize).min(width);
|
|
380
|
+
let meter = format!("{}{}", "█".repeat(filled), "░".repeat(width - filled));
|
|
381
|
+
if std::io::stdout().is_terminal() {
|
|
382
|
+
let pct_str = format!("{pct:.1}%");
|
|
383
|
+
let colored_pct = if pct >= 70.0 {
|
|
384
|
+
pct_str.green().bold().to_string()
|
|
385
|
+
} else if pct >= 40.0 {
|
|
386
|
+
pct_str.yellow().bold().to_string()
|
|
387
|
+
} else {
|
|
388
|
+
pct_str.red().bold().to_string()
|
|
389
|
+
};
|
|
390
|
+
println!("Efficiency meter: {} {}", meter.green(), colored_pct);
|
|
391
|
+
} else {
|
|
392
|
+
println!("Efficiency meter: {} {:.1}%", meter, pct);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/// Resolve project scope from --project flag. // added
|
|
397
|
+
fn resolve_project_scope(project: bool) -> Result<Option<String>> {
|
|
398
|
+
if !project {
|
|
399
|
+
return Ok(None);
|
|
400
|
+
}
|
|
401
|
+
let cwd = std::env::current_dir().context("Failed to resolve current working directory")?;
|
|
402
|
+
let canonical = cwd.canonicalize().unwrap_or(cwd);
|
|
403
|
+
Ok(Some(canonical.to_string_lossy().to_string()))
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/// Shorten long absolute paths for display. // added
|
|
407
|
+
fn shorten_path(path: &str) -> String {
|
|
408
|
+
let path_buf = PathBuf::from(path);
|
|
409
|
+
let comps: Vec<String> = path_buf
|
|
410
|
+
.components()
|
|
411
|
+
.map(|c| c.as_os_str().to_string_lossy().to_string())
|
|
412
|
+
.collect();
|
|
413
|
+
if comps.len() <= 4 {
|
|
414
|
+
return path.to_string();
|
|
415
|
+
}
|
|
416
|
+
let root = comps[0].as_str();
|
|
417
|
+
if root == "/" || root.is_empty() {
|
|
418
|
+
format!("/.../{}/{}", comps[comps.len() - 2], comps[comps.len() - 1])
|
|
419
|
+
} else {
|
|
420
|
+
format!(
|
|
421
|
+
"{}/.../{}/{}",
|
|
422
|
+
root,
|
|
423
|
+
comps[comps.len() - 2],
|
|
424
|
+
comps[comps.len() - 1]
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
fn print_ascii_graph(data: &[(String, usize)]) {
|
|
430
|
+
if data.is_empty() {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
let max_val = data.iter().map(|(_, v)| *v).max().unwrap_or(1);
|
|
435
|
+
let width = 40;
|
|
436
|
+
|
|
437
|
+
for (date, value) in data {
|
|
438
|
+
let date_short = if date.len() >= 10 { &date[5..10] } else { date };
|
|
439
|
+
|
|
440
|
+
let bar_len = if max_val > 0 {
|
|
441
|
+
((*value as f64 / max_val as f64) * width as f64) as usize
|
|
442
|
+
} else {
|
|
443
|
+
0
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
let bar: String = "█".repeat(bar_len);
|
|
447
|
+
let spaces: String = " ".repeat(width - bar_len);
|
|
448
|
+
|
|
449
|
+
println!(
|
|
450
|
+
"{} │{}{} {}",
|
|
451
|
+
date_short,
|
|
452
|
+
bar,
|
|
453
|
+
spaces,
|
|
454
|
+
format_tokens(*value)
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
fn print_daily_full(tracker: &Tracker, project_scope: Option<&str>) -> Result<()> {
|
|
460
|
+
// changed: add project scope
|
|
461
|
+
let days = tracker.get_all_days_filtered(project_scope)?; // changed: use filtered variant
|
|
462
|
+
print_period_table(&days);
|
|
463
|
+
Ok(())
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
fn print_weekly(tracker: &Tracker, project_scope: Option<&str>) -> Result<()> {
|
|
467
|
+
// changed: add project scope
|
|
468
|
+
let weeks = tracker.get_by_week_filtered(project_scope)?; // changed: use filtered variant
|
|
469
|
+
print_period_table(&weeks);
|
|
470
|
+
Ok(())
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
fn print_monthly(tracker: &Tracker, project_scope: Option<&str>) -> Result<()> {
|
|
474
|
+
// changed: add project scope
|
|
475
|
+
let months = tracker.get_by_month_filtered(project_scope)?; // changed: use filtered variant
|
|
476
|
+
print_period_table(&months);
|
|
477
|
+
Ok(())
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
#[derive(Serialize)]
|
|
481
|
+
struct ExportData {
|
|
482
|
+
summary: ExportSummary,
|
|
483
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
484
|
+
daily: Option<Vec<DayStats>>,
|
|
485
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
486
|
+
weekly: Option<Vec<WeekStats>>,
|
|
487
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
488
|
+
monthly: Option<Vec<MonthStats>>,
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
#[derive(Serialize)]
|
|
492
|
+
struct ExportSummary {
|
|
493
|
+
total_commands: usize,
|
|
494
|
+
total_input: usize,
|
|
495
|
+
total_output: usize,
|
|
496
|
+
total_saved: usize,
|
|
497
|
+
avg_savings_pct: f64,
|
|
498
|
+
total_time_ms: u64,
|
|
499
|
+
avg_time_ms: u64,
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
fn export_json(
|
|
503
|
+
tracker: &Tracker,
|
|
504
|
+
daily: bool,
|
|
505
|
+
weekly: bool,
|
|
506
|
+
monthly: bool,
|
|
507
|
+
all: bool,
|
|
508
|
+
project_scope: Option<&str>, // added: project scope
|
|
509
|
+
) -> Result<()> {
|
|
510
|
+
let summary = tracker
|
|
511
|
+
.get_summary_filtered(project_scope) // changed: use filtered variant
|
|
512
|
+
.context("Failed to load token savings summary from database")?;
|
|
513
|
+
|
|
514
|
+
let export = ExportData {
|
|
515
|
+
summary: ExportSummary {
|
|
516
|
+
total_commands: summary.total_commands,
|
|
517
|
+
total_input: summary.total_input,
|
|
518
|
+
total_output: summary.total_output,
|
|
519
|
+
total_saved: summary.total_saved,
|
|
520
|
+
avg_savings_pct: summary.avg_savings_pct,
|
|
521
|
+
total_time_ms: summary.total_time_ms,
|
|
522
|
+
avg_time_ms: summary.avg_time_ms,
|
|
523
|
+
},
|
|
524
|
+
daily: if all || daily {
|
|
525
|
+
Some(tracker.get_all_days_filtered(project_scope)?) // changed: use filtered
|
|
526
|
+
} else {
|
|
527
|
+
None
|
|
528
|
+
},
|
|
529
|
+
weekly: if all || weekly {
|
|
530
|
+
Some(tracker.get_by_week_filtered(project_scope)?) // changed: use filtered
|
|
531
|
+
} else {
|
|
532
|
+
None
|
|
533
|
+
},
|
|
534
|
+
monthly: if all || monthly {
|
|
535
|
+
Some(tracker.get_by_month_filtered(project_scope)?) // changed: use filtered
|
|
536
|
+
} else {
|
|
537
|
+
None
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
let json = serde_json::to_string_pretty(&export)?;
|
|
542
|
+
println!("{}", json);
|
|
543
|
+
|
|
544
|
+
Ok(())
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
fn export_csv(
|
|
548
|
+
tracker: &Tracker,
|
|
549
|
+
daily: bool,
|
|
550
|
+
weekly: bool,
|
|
551
|
+
monthly: bool,
|
|
552
|
+
all: bool,
|
|
553
|
+
project_scope: Option<&str>, // added: project scope
|
|
554
|
+
) -> Result<()> {
|
|
555
|
+
if all || daily {
|
|
556
|
+
let days = tracker.get_all_days_filtered(project_scope)?; // changed: use filtered
|
|
557
|
+
println!("# Daily Data");
|
|
558
|
+
println!("date,commands,input_tokens,output_tokens,saved_tokens,savings_pct,total_time_ms,avg_time_ms");
|
|
559
|
+
for day in days {
|
|
560
|
+
println!(
|
|
561
|
+
"{},{},{},{},{},{:.2},{},{}",
|
|
562
|
+
day.date,
|
|
563
|
+
day.commands,
|
|
564
|
+
day.input_tokens,
|
|
565
|
+
day.output_tokens,
|
|
566
|
+
day.saved_tokens,
|
|
567
|
+
day.savings_pct,
|
|
568
|
+
day.total_time_ms,
|
|
569
|
+
day.avg_time_ms
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
println!();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if all || weekly {
|
|
576
|
+
let weeks = tracker.get_by_week_filtered(project_scope)?; // changed: use filtered
|
|
577
|
+
println!("# Weekly Data");
|
|
578
|
+
println!(
|
|
579
|
+
"week_start,week_end,commands,input_tokens,output_tokens,saved_tokens,savings_pct,total_time_ms,avg_time_ms"
|
|
580
|
+
);
|
|
581
|
+
for week in weeks {
|
|
582
|
+
println!(
|
|
583
|
+
"{},{},{},{},{},{},{:.2},{},{}",
|
|
584
|
+
week.week_start,
|
|
585
|
+
week.week_end,
|
|
586
|
+
week.commands,
|
|
587
|
+
week.input_tokens,
|
|
588
|
+
week.output_tokens,
|
|
589
|
+
week.saved_tokens,
|
|
590
|
+
week.savings_pct,
|
|
591
|
+
week.total_time_ms,
|
|
592
|
+
week.avg_time_ms
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
println!();
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if all || monthly {
|
|
599
|
+
let months = tracker.get_by_month_filtered(project_scope)?; // changed: use filtered
|
|
600
|
+
println!("# Monthly Data");
|
|
601
|
+
println!("month,commands,input_tokens,output_tokens,saved_tokens,savings_pct,total_time_ms,avg_time_ms");
|
|
602
|
+
for month in months {
|
|
603
|
+
println!(
|
|
604
|
+
"{},{},{},{},{},{:.2},{},{}",
|
|
605
|
+
month.month,
|
|
606
|
+
month.commands,
|
|
607
|
+
month.input_tokens,
|
|
608
|
+
month.output_tokens,
|
|
609
|
+
month.saved_tokens,
|
|
610
|
+
month.savings_pct,
|
|
611
|
+
month.total_time_ms,
|
|
612
|
+
month.avg_time_ms
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
Ok(())
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/// Lightweight scan of recent Claude Code sessions for RTK_DISABLED= overuse.
|
|
621
|
+
/// Returns a warning string if bypass rate exceeds 10%, None otherwise.
|
|
622
|
+
/// Silently returns None on any error (missing dirs, permission issues, etc.).
|
|
623
|
+
fn check_rtk_disabled_bypass() -> Option<String> {
|
|
624
|
+
use crate::discover::provider::{ClaudeProvider, SessionProvider};
|
|
625
|
+
use crate::discover::registry::has_rtk_disabled_prefix;
|
|
626
|
+
|
|
627
|
+
let provider = ClaudeProvider;
|
|
628
|
+
|
|
629
|
+
// Quick scan: last 7 days only
|
|
630
|
+
let sessions = provider.discover_sessions(None, Some(7)).ok()?;
|
|
631
|
+
|
|
632
|
+
// Early bail if no sessions or too many (avoid slow scan)
|
|
633
|
+
if sessions.is_empty() || sessions.len() > 200 {
|
|
634
|
+
return None;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
let mut total_bash: usize = 0;
|
|
638
|
+
let mut bypassed: usize = 0;
|
|
639
|
+
|
|
640
|
+
for session_path in &sessions {
|
|
641
|
+
let extracted = match provider.extract_commands(session_path) {
|
|
642
|
+
Ok(cmds) => cmds,
|
|
643
|
+
Err(_) => continue,
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
for ext_cmd in &extracted {
|
|
647
|
+
total_bash += 1;
|
|
648
|
+
if has_rtk_disabled_prefix(&ext_cmd.command) {
|
|
649
|
+
bypassed += 1;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if total_bash == 0 {
|
|
655
|
+
return None;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
let pct = (bypassed as f64 / total_bash as f64) * 100.0;
|
|
659
|
+
if pct > 10.0 {
|
|
660
|
+
Some(format!(
|
|
661
|
+
"⚠️ {} commands ({:.0}%) used RTK_DISABLED=1 unnecessarily — run `rtk discover` for details",
|
|
662
|
+
bypassed, pct
|
|
663
|
+
))
|
|
664
|
+
} else {
|
|
665
|
+
None
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
fn show_failures(tracker: &Tracker) -> Result<()> {
|
|
670
|
+
let summary = tracker
|
|
671
|
+
.get_parse_failure_summary()
|
|
672
|
+
.context("Failed to load parse failure data")?;
|
|
673
|
+
|
|
674
|
+
if summary.total == 0 {
|
|
675
|
+
println!("No parse failures recorded.");
|
|
676
|
+
println!("This means all commands parsed successfully (or fallback hasn't triggered yet).");
|
|
677
|
+
return Ok(());
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
println!("{}", styled("RTK Parse Failures", true));
|
|
681
|
+
println!("{}", "═".repeat(60));
|
|
682
|
+
println!();
|
|
683
|
+
|
|
684
|
+
print_kpi("Total failures", summary.total.to_string());
|
|
685
|
+
print_kpi("Recovery rate", format!("{:.1}%", summary.recovery_rate));
|
|
686
|
+
println!();
|
|
687
|
+
|
|
688
|
+
if !summary.top_commands.is_empty() {
|
|
689
|
+
println!("{}", styled("Top Commands (by frequency)", true));
|
|
690
|
+
println!("{}", "─".repeat(60));
|
|
691
|
+
for (cmd, count) in &summary.top_commands {
|
|
692
|
+
let cmd_display = if cmd.len() > 50 {
|
|
693
|
+
format!("{}...", &cmd[..47])
|
|
694
|
+
} else {
|
|
695
|
+
cmd.clone()
|
|
696
|
+
};
|
|
697
|
+
println!(" {:>4}x {}", count, cmd_display);
|
|
698
|
+
}
|
|
699
|
+
println!();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if !summary.recent.is_empty() {
|
|
703
|
+
println!("{}", styled("Recent Failures (last 10)", true));
|
|
704
|
+
println!("{}", "─".repeat(60));
|
|
705
|
+
for rec in &summary.recent {
|
|
706
|
+
let ts_short = if rec.timestamp.len() >= 16 {
|
|
707
|
+
&rec.timestamp[..16]
|
|
708
|
+
} else {
|
|
709
|
+
&rec.timestamp
|
|
710
|
+
};
|
|
711
|
+
let status = if rec.fallback_succeeded { "ok" } else { "FAIL" };
|
|
712
|
+
let cmd_display = if rec.raw_command.len() > 40 {
|
|
713
|
+
format!("{}...", &rec.raw_command[..37])
|
|
714
|
+
} else {
|
|
715
|
+
rec.raw_command.clone()
|
|
716
|
+
};
|
|
717
|
+
println!(" {} [{}] {}", ts_short, status, cmd_display);
|
|
718
|
+
}
|
|
719
|
+
println!();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
Ok(())
|
|
723
|
+
}
|