@hasna/terminal 2.3.0 → 2.3.1

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.
Files changed (202) hide show
  1. package/dist/cli.js +64 -16
  2. package/package.json +1 -1
  3. package/src/ai.ts +8 -0
  4. package/src/cli.tsx +57 -18
  5. package/src/output-processor.ts +6 -1
  6. package/src/output-store.ts +58 -12
  7. package/src/tool-profiles.ts +139 -0
  8. package/temp/rtk/.claude/agents/code-reviewer.md +0 -221
  9. package/temp/rtk/.claude/agents/debugger.md +0 -519
  10. package/temp/rtk/.claude/agents/rtk-testing-specialist.md +0 -461
  11. package/temp/rtk/.claude/agents/rust-rtk.md +0 -511
  12. package/temp/rtk/.claude/agents/technical-writer.md +0 -355
  13. package/temp/rtk/.claude/commands/diagnose.md +0 -352
  14. package/temp/rtk/.claude/commands/test-routing.md +0 -362
  15. package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +0 -16
  16. package/temp/rtk/.claude/hooks/rtk-rewrite.sh +0 -70
  17. package/temp/rtk/.claude/hooks/rtk-suggest.sh +0 -152
  18. package/temp/rtk/.claude/rules/cli-testing.md +0 -526
  19. package/temp/rtk/.claude/skills/issue-triage/SKILL.md +0 -348
  20. package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +0 -134
  21. package/temp/rtk/.claude/skills/performance.md +0 -435
  22. package/temp/rtk/.claude/skills/pr-triage/SKILL.md +0 -315
  23. package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +0 -71
  24. package/temp/rtk/.claude/skills/repo-recap.md +0 -206
  25. package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +0 -78
  26. package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +0 -124
  27. package/temp/rtk/.claude/skills/security-guardian.md +0 -503
  28. package/temp/rtk/.claude/skills/ship.md +0 -404
  29. package/temp/rtk/.github/workflows/benchmark.yml +0 -34
  30. package/temp/rtk/.github/workflows/dco-check.yaml +0 -12
  31. package/temp/rtk/.github/workflows/release-please.yml +0 -51
  32. package/temp/rtk/.github/workflows/release.yml +0 -343
  33. package/temp/rtk/.github/workflows/security-check.yml +0 -135
  34. package/temp/rtk/.github/workflows/validate-docs.yml +0 -78
  35. package/temp/rtk/.release-please-manifest.json +0 -3
  36. package/temp/rtk/ARCHITECTURE.md +0 -1491
  37. package/temp/rtk/CHANGELOG.md +0 -640
  38. package/temp/rtk/CLAUDE.md +0 -605
  39. package/temp/rtk/CONTRIBUTING.md +0 -199
  40. package/temp/rtk/Cargo.lock +0 -1668
  41. package/temp/rtk/Cargo.toml +0 -64
  42. package/temp/rtk/Formula/rtk.rb +0 -43
  43. package/temp/rtk/INSTALL.md +0 -390
  44. package/temp/rtk/LICENSE +0 -21
  45. package/temp/rtk/README.md +0 -386
  46. package/temp/rtk/README_es.md +0 -159
  47. package/temp/rtk/README_fr.md +0 -197
  48. package/temp/rtk/README_ja.md +0 -159
  49. package/temp/rtk/README_ko.md +0 -159
  50. package/temp/rtk/README_zh.md +0 -167
  51. package/temp/rtk/ROADMAP.md +0 -15
  52. package/temp/rtk/SECURITY.md +0 -217
  53. package/temp/rtk/TEST_EXEC_TIME.md +0 -102
  54. package/temp/rtk/build.rs +0 -57
  55. package/temp/rtk/docs/AUDIT_GUIDE.md +0 -432
  56. package/temp/rtk/docs/FEATURES.md +0 -1410
  57. package/temp/rtk/docs/TROUBLESHOOTING.md +0 -309
  58. package/temp/rtk/docs/filter-workflow.md +0 -102
  59. package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
  60. package/temp/rtk/docs/tracking.md +0 -583
  61. package/temp/rtk/hooks/opencode-rtk.ts +0 -39
  62. package/temp/rtk/hooks/rtk-awareness.md +0 -29
  63. package/temp/rtk/hooks/rtk-rewrite.sh +0 -61
  64. package/temp/rtk/hooks/test-rtk-rewrite.sh +0 -442
  65. package/temp/rtk/install.sh +0 -124
  66. package/temp/rtk/release-please-config.json +0 -10
  67. package/temp/rtk/scripts/benchmark.sh +0 -592
  68. package/temp/rtk/scripts/check-installation.sh +0 -162
  69. package/temp/rtk/scripts/install-local.sh +0 -37
  70. package/temp/rtk/scripts/rtk-economics.sh +0 -137
  71. package/temp/rtk/scripts/test-all.sh +0 -561
  72. package/temp/rtk/scripts/test-aristote.sh +0 -227
  73. package/temp/rtk/scripts/test-tracking.sh +0 -79
  74. package/temp/rtk/scripts/update-readme-metrics.sh +0 -32
  75. package/temp/rtk/scripts/validate-docs.sh +0 -73
  76. package/temp/rtk/src/aws_cmd.rs +0 -880
  77. package/temp/rtk/src/binlog.rs +0 -1645
  78. package/temp/rtk/src/cargo_cmd.rs +0 -1727
  79. package/temp/rtk/src/cc_economics.rs +0 -1157
  80. package/temp/rtk/src/ccusage.rs +0 -340
  81. package/temp/rtk/src/config.rs +0 -187
  82. package/temp/rtk/src/container.rs +0 -855
  83. package/temp/rtk/src/curl_cmd.rs +0 -134
  84. package/temp/rtk/src/deps.rs +0 -268
  85. package/temp/rtk/src/diff_cmd.rs +0 -367
  86. package/temp/rtk/src/discover/mod.rs +0 -274
  87. package/temp/rtk/src/discover/provider.rs +0 -388
  88. package/temp/rtk/src/discover/registry.rs +0 -2022
  89. package/temp/rtk/src/discover/report.rs +0 -202
  90. package/temp/rtk/src/discover/rules.rs +0 -667
  91. package/temp/rtk/src/display_helpers.rs +0 -402
  92. package/temp/rtk/src/dotnet_cmd.rs +0 -1771
  93. package/temp/rtk/src/dotnet_format_report.rs +0 -133
  94. package/temp/rtk/src/dotnet_trx.rs +0 -593
  95. package/temp/rtk/src/env_cmd.rs +0 -204
  96. package/temp/rtk/src/filter.rs +0 -462
  97. package/temp/rtk/src/filters/README.md +0 -52
  98. package/temp/rtk/src/filters/ansible-playbook.toml +0 -34
  99. package/temp/rtk/src/filters/basedpyright.toml +0 -47
  100. package/temp/rtk/src/filters/biome.toml +0 -45
  101. package/temp/rtk/src/filters/brew-install.toml +0 -37
  102. package/temp/rtk/src/filters/composer-install.toml +0 -40
  103. package/temp/rtk/src/filters/df.toml +0 -16
  104. package/temp/rtk/src/filters/dotnet-build.toml +0 -64
  105. package/temp/rtk/src/filters/du.toml +0 -16
  106. package/temp/rtk/src/filters/fail2ban-client.toml +0 -15
  107. package/temp/rtk/src/filters/gcc.toml +0 -49
  108. package/temp/rtk/src/filters/gcloud.toml +0 -22
  109. package/temp/rtk/src/filters/hadolint.toml +0 -24
  110. package/temp/rtk/src/filters/helm.toml +0 -29
  111. package/temp/rtk/src/filters/iptables.toml +0 -27
  112. package/temp/rtk/src/filters/jj.toml +0 -28
  113. package/temp/rtk/src/filters/jq.toml +0 -24
  114. package/temp/rtk/src/filters/make.toml +0 -41
  115. package/temp/rtk/src/filters/markdownlint.toml +0 -24
  116. package/temp/rtk/src/filters/mix-compile.toml +0 -27
  117. package/temp/rtk/src/filters/mix-format.toml +0 -15
  118. package/temp/rtk/src/filters/mvn-build.toml +0 -44
  119. package/temp/rtk/src/filters/oxlint.toml +0 -43
  120. package/temp/rtk/src/filters/ping.toml +0 -63
  121. package/temp/rtk/src/filters/pio-run.toml +0 -40
  122. package/temp/rtk/src/filters/poetry-install.toml +0 -50
  123. package/temp/rtk/src/filters/pre-commit.toml +0 -35
  124. package/temp/rtk/src/filters/ps.toml +0 -16
  125. package/temp/rtk/src/filters/quarto-render.toml +0 -41
  126. package/temp/rtk/src/filters/rsync.toml +0 -48
  127. package/temp/rtk/src/filters/shellcheck.toml +0 -27
  128. package/temp/rtk/src/filters/shopify-theme.toml +0 -29
  129. package/temp/rtk/src/filters/skopeo.toml +0 -45
  130. package/temp/rtk/src/filters/sops.toml +0 -16
  131. package/temp/rtk/src/filters/ssh.toml +0 -44
  132. package/temp/rtk/src/filters/stat.toml +0 -34
  133. package/temp/rtk/src/filters/swift-build.toml +0 -41
  134. package/temp/rtk/src/filters/systemctl-status.toml +0 -33
  135. package/temp/rtk/src/filters/terraform-plan.toml +0 -35
  136. package/temp/rtk/src/filters/tofu-fmt.toml +0 -16
  137. package/temp/rtk/src/filters/tofu-init.toml +0 -38
  138. package/temp/rtk/src/filters/tofu-plan.toml +0 -35
  139. package/temp/rtk/src/filters/tofu-validate.toml +0 -17
  140. package/temp/rtk/src/filters/trunk-build.toml +0 -39
  141. package/temp/rtk/src/filters/ty.toml +0 -50
  142. package/temp/rtk/src/filters/uv-sync.toml +0 -37
  143. package/temp/rtk/src/filters/xcodebuild.toml +0 -99
  144. package/temp/rtk/src/filters/yamllint.toml +0 -25
  145. package/temp/rtk/src/find_cmd.rs +0 -598
  146. package/temp/rtk/src/format_cmd.rs +0 -386
  147. package/temp/rtk/src/gain.rs +0 -723
  148. package/temp/rtk/src/gh_cmd.rs +0 -1651
  149. package/temp/rtk/src/git.rs +0 -2012
  150. package/temp/rtk/src/go_cmd.rs +0 -592
  151. package/temp/rtk/src/golangci_cmd.rs +0 -254
  152. package/temp/rtk/src/grep_cmd.rs +0 -288
  153. package/temp/rtk/src/gt_cmd.rs +0 -810
  154. package/temp/rtk/src/hook_audit_cmd.rs +0 -283
  155. package/temp/rtk/src/hook_check.rs +0 -171
  156. package/temp/rtk/src/init.rs +0 -1859
  157. package/temp/rtk/src/integrity.rs +0 -537
  158. package/temp/rtk/src/json_cmd.rs +0 -231
  159. package/temp/rtk/src/learn/detector.rs +0 -628
  160. package/temp/rtk/src/learn/mod.rs +0 -119
  161. package/temp/rtk/src/learn/report.rs +0 -184
  162. package/temp/rtk/src/lint_cmd.rs +0 -694
  163. package/temp/rtk/src/local_llm.rs +0 -316
  164. package/temp/rtk/src/log_cmd.rs +0 -248
  165. package/temp/rtk/src/ls.rs +0 -324
  166. package/temp/rtk/src/main.rs +0 -2482
  167. package/temp/rtk/src/mypy_cmd.rs +0 -389
  168. package/temp/rtk/src/next_cmd.rs +0 -241
  169. package/temp/rtk/src/npm_cmd.rs +0 -236
  170. package/temp/rtk/src/parser/README.md +0 -267
  171. package/temp/rtk/src/parser/error.rs +0 -46
  172. package/temp/rtk/src/parser/formatter.rs +0 -336
  173. package/temp/rtk/src/parser/mod.rs +0 -311
  174. package/temp/rtk/src/parser/types.rs +0 -119
  175. package/temp/rtk/src/pip_cmd.rs +0 -302
  176. package/temp/rtk/src/playwright_cmd.rs +0 -479
  177. package/temp/rtk/src/pnpm_cmd.rs +0 -573
  178. package/temp/rtk/src/prettier_cmd.rs +0 -221
  179. package/temp/rtk/src/prisma_cmd.rs +0 -482
  180. package/temp/rtk/src/psql_cmd.rs +0 -382
  181. package/temp/rtk/src/pytest_cmd.rs +0 -384
  182. package/temp/rtk/src/read.rs +0 -217
  183. package/temp/rtk/src/rewrite_cmd.rs +0 -50
  184. package/temp/rtk/src/ruff_cmd.rs +0 -402
  185. package/temp/rtk/src/runner.rs +0 -271
  186. package/temp/rtk/src/summary.rs +0 -297
  187. package/temp/rtk/src/tee.rs +0 -405
  188. package/temp/rtk/src/telemetry.rs +0 -248
  189. package/temp/rtk/src/toml_filter.rs +0 -1655
  190. package/temp/rtk/src/tracking.rs +0 -1416
  191. package/temp/rtk/src/tree.rs +0 -209
  192. package/temp/rtk/src/tsc_cmd.rs +0 -259
  193. package/temp/rtk/src/utils.rs +0 -432
  194. package/temp/rtk/src/verify_cmd.rs +0 -47
  195. package/temp/rtk/src/vitest_cmd.rs +0 -385
  196. package/temp/rtk/src/wc_cmd.rs +0 -401
  197. package/temp/rtk/src/wget_cmd.rs +0 -260
  198. package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +0 -11
  199. package/temp/rtk/tests/fixtures/dotnet/format_changes.json +0 -31
  200. package/temp/rtk/tests/fixtures/dotnet/format_empty.json +0 -1
  201. package/temp/rtk/tests/fixtures/dotnet/format_success.json +0 -12
  202. package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +0 -18
@@ -1,2482 +0,0 @@
1
- mod aws_cmd;
2
- mod binlog;
3
- mod cargo_cmd;
4
- mod cc_economics;
5
- mod ccusage;
6
- mod config;
7
- mod container;
8
- mod curl_cmd;
9
- mod deps;
10
- mod diff_cmd;
11
- mod discover;
12
- mod display_helpers;
13
- mod dotnet_cmd;
14
- mod dotnet_format_report;
15
- mod dotnet_trx;
16
- mod env_cmd;
17
- mod filter;
18
- mod find_cmd;
19
- mod format_cmd;
20
- mod gain;
21
- mod gh_cmd;
22
- mod git;
23
- mod go_cmd;
24
- mod golangci_cmd;
25
- mod grep_cmd;
26
- mod gt_cmd;
27
- mod hook_audit_cmd;
28
- mod hook_check;
29
- mod init;
30
- mod integrity;
31
- mod json_cmd;
32
- mod learn;
33
- mod lint_cmd;
34
- mod local_llm;
35
- mod log_cmd;
36
- mod ls;
37
- mod mypy_cmd;
38
- mod next_cmd;
39
- mod npm_cmd;
40
- mod parser;
41
- mod pip_cmd;
42
- mod playwright_cmd;
43
- mod pnpm_cmd;
44
- mod prettier_cmd;
45
- mod prisma_cmd;
46
- mod psql_cmd;
47
- mod pytest_cmd;
48
- mod read;
49
- mod rewrite_cmd;
50
- mod ruff_cmd;
51
- mod runner;
52
- mod summary;
53
- mod tee;
54
- mod telemetry;
55
- mod toml_filter;
56
- mod tracking;
57
- mod tree;
58
- mod tsc_cmd;
59
- mod utils;
60
- mod verify_cmd;
61
- mod vitest_cmd;
62
- mod wc_cmd;
63
- mod wget_cmd;
64
-
65
- use anyhow::{Context, Result};
66
- use clap::error::ErrorKind;
67
- use clap::{Parser, Subcommand};
68
- use std::ffi::OsString;
69
- use std::path::{Path, PathBuf};
70
-
71
- #[derive(Parser)]
72
- #[command(
73
- name = "rtk",
74
- version,
75
- about = "Rust Token Killer - Minimize LLM token consumption",
76
- long_about = "A high-performance CLI proxy designed to filter and summarize system outputs before they reach your LLM context."
77
- )]
78
- struct Cli {
79
- #[command(subcommand)]
80
- command: Commands,
81
-
82
- /// Verbosity level (-v, -vv, -vvv)
83
- #[arg(short, long, action = clap::ArgAction::Count, global = true)]
84
- verbose: u8,
85
-
86
- /// Ultra-compact mode: ASCII icons, inline format (Level 2 optimizations)
87
- #[arg(short = 'u', long, global = true)]
88
- ultra_compact: bool,
89
-
90
- /// Set SKIP_ENV_VALIDATION=1 for child processes (Next.js, tsc, lint, prisma)
91
- #[arg(long = "skip-env", global = true)]
92
- skip_env: bool,
93
- }
94
-
95
- #[derive(Subcommand)]
96
- enum Commands {
97
- /// List directory contents with token-optimized output (proxy to native ls)
98
- Ls {
99
- /// Arguments passed to ls (supports all native ls flags like -l, -a, -h, -R)
100
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
101
- args: Vec<String>,
102
- },
103
-
104
- /// Directory tree with token-optimized output (proxy to native tree)
105
- Tree {
106
- /// Arguments passed to tree (supports all native tree flags like -L, -d, -a)
107
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
108
- args: Vec<String>,
109
- },
110
-
111
- /// Read file with intelligent filtering
112
- Read {
113
- /// File to read
114
- file: PathBuf,
115
- /// Filter: none, minimal, aggressive
116
- #[arg(short, long, default_value = "minimal")]
117
- level: filter::FilterLevel,
118
- /// Max lines
119
- #[arg(short, long, conflicts_with = "tail_lines")]
120
- max_lines: Option<usize>,
121
- /// Keep only last N lines
122
- #[arg(long, conflicts_with = "max_lines")]
123
- tail_lines: Option<usize>,
124
- /// Show line numbers
125
- #[arg(short = 'n', long)]
126
- line_numbers: bool,
127
- },
128
-
129
- /// Generate 2-line technical summary (heuristic-based)
130
- Smart {
131
- /// File to analyze
132
- file: PathBuf,
133
- /// Model: heuristic
134
- #[arg(short, long, default_value = "heuristic")]
135
- model: String,
136
- /// Force model download
137
- #[arg(long)]
138
- force_download: bool,
139
- },
140
-
141
- /// Git commands with compact output
142
- Git {
143
- /// Change to directory before executing (like git -C <path>, can be repeated)
144
- #[arg(short = 'C', action = clap::ArgAction::Append)]
145
- directory: Vec<String>,
146
-
147
- /// Git configuration override (like git -c key=value, can be repeated)
148
- #[arg(short = 'c', action = clap::ArgAction::Append)]
149
- config_override: Vec<String>,
150
-
151
- /// Set the path to the .git directory
152
- #[arg(long = "git-dir")]
153
- git_dir: Option<String>,
154
-
155
- /// Set the path to the working tree
156
- #[arg(long = "work-tree")]
157
- work_tree: Option<String>,
158
-
159
- /// Disable pager (like git --no-pager)
160
- #[arg(long = "no-pager")]
161
- no_pager: bool,
162
-
163
- /// Skip optional locks (like git --no-optional-locks)
164
- #[arg(long = "no-optional-locks")]
165
- no_optional_locks: bool,
166
-
167
- /// Treat repository as bare (like git --bare)
168
- #[arg(long)]
169
- bare: bool,
170
-
171
- /// Treat pathspecs literally (like git --literal-pathspecs)
172
- #[arg(long = "literal-pathspecs")]
173
- literal_pathspecs: bool,
174
-
175
- #[command(subcommand)]
176
- command: GitCommands,
177
- },
178
-
179
- /// GitHub CLI (gh) commands with token-optimized output
180
- Gh {
181
- /// Subcommand: pr, issue, run, repo
182
- subcommand: String,
183
- /// Additional arguments
184
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
185
- args: Vec<String>,
186
- },
187
-
188
- /// AWS CLI with compact output (force JSON, compress)
189
- Aws {
190
- /// AWS service subcommand (e.g., sts, s3, ec2, ecs, rds, cloudformation)
191
- subcommand: String,
192
- /// Additional arguments
193
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
194
- args: Vec<String>,
195
- },
196
-
197
- /// PostgreSQL client with compact output (strip borders, compress tables)
198
- Psql {
199
- /// psql arguments
200
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
201
- args: Vec<String>,
202
- },
203
-
204
- /// pnpm commands with ultra-compact output
205
- Pnpm {
206
- #[command(subcommand)]
207
- command: PnpmCommands,
208
- },
209
-
210
- /// Run command and show only errors/warnings
211
- Err {
212
- /// Command to run
213
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
214
- command: Vec<String>,
215
- },
216
-
217
- /// Run tests and show only failures
218
- Test {
219
- /// Test command (e.g. cargo test)
220
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
221
- command: Vec<String>,
222
- },
223
-
224
- /// Show JSON structure without values
225
- Json {
226
- /// JSON file
227
- file: PathBuf,
228
- /// Max depth
229
- #[arg(short, long, default_value = "5")]
230
- depth: usize,
231
- },
232
-
233
- /// Summarize project dependencies
234
- Deps {
235
- /// Project path
236
- #[arg(default_value = ".")]
237
- path: PathBuf,
238
- },
239
-
240
- /// Show environment variables (filtered, sensitive masked)
241
- Env {
242
- /// Filter by name (e.g. PATH, AWS)
243
- #[arg(short, long)]
244
- filter: Option<String>,
245
- /// Show all (include sensitive)
246
- #[arg(long)]
247
- show_all: bool,
248
- },
249
-
250
- /// Find files with compact tree output (accepts native find flags like -name, -type)
251
- Find {
252
- /// All find arguments (supports both RTK and native find syntax)
253
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
254
- args: Vec<String>,
255
- },
256
-
257
- /// Ultra-condensed diff (only changed lines)
258
- Diff {
259
- /// First file or - for stdin (unified diff)
260
- file1: PathBuf,
261
- /// Second file (optional if stdin)
262
- file2: Option<PathBuf>,
263
- },
264
-
265
- /// Filter and deduplicate log output
266
- Log {
267
- /// Log file (omit for stdin)
268
- file: Option<PathBuf>,
269
- },
270
-
271
- /// .NET commands with compact output (build/test/restore/format)
272
- Dotnet {
273
- #[command(subcommand)]
274
- command: DotnetCommands,
275
- },
276
-
277
- /// Docker commands with compact output
278
- Docker {
279
- #[command(subcommand)]
280
- command: DockerCommands,
281
- },
282
-
283
- /// Kubectl commands with compact output
284
- Kubectl {
285
- #[command(subcommand)]
286
- command: KubectlCommands,
287
- },
288
-
289
- /// Run command and show heuristic summary
290
- Summary {
291
- /// Command to run and summarize
292
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
293
- command: Vec<String>,
294
- },
295
-
296
- /// Compact grep - strips whitespace, truncates, groups by file
297
- Grep {
298
- /// Pattern to search
299
- pattern: String,
300
- /// Path to search in
301
- #[arg(default_value = ".")]
302
- path: String,
303
- /// Max line length
304
- #[arg(short = 'l', long, default_value = "80")]
305
- max_len: usize,
306
- /// Max results to show
307
- #[arg(short, long, default_value = "50")]
308
- max: usize,
309
- /// Show only match context (not full line)
310
- #[arg(short, long)]
311
- context_only: bool,
312
- /// Filter by file type (e.g., ts, py, rust)
313
- #[arg(short = 't', long)]
314
- file_type: Option<String>,
315
- /// Show line numbers (always on, accepted for grep/rg compatibility)
316
- #[arg(short = 'n', long)]
317
- line_numbers: bool,
318
- /// Extra ripgrep arguments (e.g., -i, -A 3, -w, --glob)
319
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
320
- extra_args: Vec<String>,
321
- },
322
-
323
- /// Initialize rtk instructions in CLAUDE.md
324
- Init {
325
- /// Add to global ~/.claude/CLAUDE.md instead of local
326
- #[arg(short, long)]
327
- global: bool,
328
-
329
- /// Install OpenCode plugin (in addition to Claude Code)
330
- #[arg(long)]
331
- opencode: bool,
332
-
333
- /// Show current configuration
334
- #[arg(long)]
335
- show: bool,
336
-
337
- /// Inject full instructions into CLAUDE.md (legacy mode)
338
- #[arg(long = "claude-md", group = "mode")]
339
- claude_md: bool,
340
-
341
- /// Hook only, no RTK.md
342
- #[arg(long = "hook-only", group = "mode")]
343
- hook_only: bool,
344
-
345
- /// Auto-patch settings.json without prompting
346
- #[arg(long = "auto-patch", group = "patch")]
347
- auto_patch: bool,
348
-
349
- /// Skip settings.json patching (print manual instructions)
350
- #[arg(long = "no-patch", group = "patch")]
351
- no_patch: bool,
352
-
353
- /// Remove all RTK artifacts (hook, RTK.md, CLAUDE.md reference, settings.json entry)
354
- #[arg(long)]
355
- uninstall: bool,
356
- },
357
-
358
- /// Download with compact output (strips progress bars)
359
- Wget {
360
- /// URL to download
361
- url: String,
362
- /// Output to stdout instead of file
363
- #[arg(short = 'O', long)]
364
- stdout: bool,
365
- /// Additional wget arguments
366
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
367
- args: Vec<String>,
368
- },
369
-
370
- /// Word/line/byte count with compact output (strips paths and padding)
371
- Wc {
372
- /// Arguments passed to wc (files, flags like -l, -w, -c)
373
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
374
- args: Vec<String>,
375
- },
376
-
377
- /// Show token savings summary and history
378
- Gain {
379
- /// Filter statistics to current project (current working directory) // added
380
- #[arg(short, long)]
381
- project: bool,
382
- /// Show ASCII graph of daily savings
383
- #[arg(short, long)]
384
- graph: bool,
385
- /// Show recent command history
386
- #[arg(short = 'H', long)]
387
- history: bool,
388
- /// Show monthly quota savings estimate
389
- #[arg(short, long)]
390
- quota: bool,
391
- /// Subscription tier for quota calculation: pro, 5x, 20x
392
- #[arg(short, long, default_value = "20x", requires = "quota")]
393
- tier: String,
394
- /// Show detailed daily breakdown (all days)
395
- #[arg(short, long)]
396
- daily: bool,
397
- /// Show weekly breakdown
398
- #[arg(short, long)]
399
- weekly: bool,
400
- /// Show monthly breakdown
401
- #[arg(short, long)]
402
- monthly: bool,
403
- /// Show all time breakdowns (daily + weekly + monthly)
404
- #[arg(short, long)]
405
- all: bool,
406
- /// Output format: text, json, csv
407
- #[arg(short, long, default_value = "text")]
408
- format: String,
409
- /// Show parse failure log (commands that fell back to raw execution)
410
- #[arg(short = 'F', long)]
411
- failures: bool,
412
- },
413
-
414
- /// Claude Code economics: spending (ccusage) vs savings (rtk) analysis
415
- CcEconomics {
416
- /// Show detailed daily breakdown
417
- #[arg(short, long)]
418
- daily: bool,
419
- /// Show weekly breakdown
420
- #[arg(short, long)]
421
- weekly: bool,
422
- /// Show monthly breakdown
423
- #[arg(short, long)]
424
- monthly: bool,
425
- /// Show all time breakdowns (daily + weekly + monthly)
426
- #[arg(short, long)]
427
- all: bool,
428
- /// Output format: text, json, csv
429
- #[arg(short, long, default_value = "text")]
430
- format: String,
431
- },
432
-
433
- /// Show or create configuration file
434
- Config {
435
- /// Create default config file
436
- #[arg(long)]
437
- create: bool,
438
- },
439
-
440
- /// Vitest commands with compact output
441
- Vitest {
442
- #[command(subcommand)]
443
- command: VitestCommands,
444
- },
445
-
446
- /// Prisma commands with compact output (no ASCII art)
447
- Prisma {
448
- #[command(subcommand)]
449
- command: PrismaCommands,
450
- },
451
-
452
- /// TypeScript compiler with grouped error output
453
- Tsc {
454
- /// TypeScript compiler arguments
455
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
456
- args: Vec<String>,
457
- },
458
-
459
- /// Next.js build with compact output
460
- Next {
461
- /// Next.js build arguments
462
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
463
- args: Vec<String>,
464
- },
465
-
466
- /// ESLint with grouped rule violations
467
- Lint {
468
- /// Linter arguments
469
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
470
- args: Vec<String>,
471
- },
472
-
473
- /// Prettier format checker with compact output
474
- Prettier {
475
- /// Prettier arguments (e.g., --check, --write)
476
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
477
- args: Vec<String>,
478
- },
479
-
480
- /// Universal format checker (prettier, black, ruff format)
481
- Format {
482
- /// Formatter arguments (auto-detects formatter from project files)
483
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
484
- args: Vec<String>,
485
- },
486
-
487
- /// Playwright E2E tests with compact output
488
- Playwright {
489
- /// Playwright arguments
490
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
491
- args: Vec<String>,
492
- },
493
-
494
- /// Cargo commands with compact output
495
- Cargo {
496
- #[command(subcommand)]
497
- command: CargoCommands,
498
- },
499
-
500
- /// npm run with filtered output (strip boilerplate)
501
- Npm {
502
- /// npm run arguments (script name + options)
503
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
504
- args: Vec<String>,
505
- },
506
-
507
- /// npx with intelligent routing (tsc, eslint, prisma -> specialized filters)
508
- Npx {
509
- /// npx arguments (command + options)
510
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
511
- args: Vec<String>,
512
- },
513
-
514
- /// Curl with auto-JSON detection and schema output
515
- Curl {
516
- /// Curl arguments (URL + options)
517
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
518
- args: Vec<String>,
519
- },
520
-
521
- /// Discover missed RTK savings from Claude Code history
522
- Discover {
523
- /// Filter by project path (substring match)
524
- #[arg(short, long)]
525
- project: Option<String>,
526
- /// Max commands per section
527
- #[arg(short, long, default_value = "15")]
528
- limit: usize,
529
- /// Scan all projects (default: current project only)
530
- #[arg(short, long)]
531
- all: bool,
532
- /// Limit to sessions from last N days
533
- #[arg(short, long, default_value = "30")]
534
- since: u64,
535
- /// Output format: text, json
536
- #[arg(short, long, default_value = "text")]
537
- format: String,
538
- },
539
-
540
- /// Learn CLI corrections from Claude Code error history
541
- Learn {
542
- /// Filter by project path (substring match)
543
- #[arg(short, long)]
544
- project: Option<String>,
545
- /// Scan all projects (default: current project only)
546
- #[arg(short, long)]
547
- all: bool,
548
- /// Limit to sessions from last N days
549
- #[arg(short, long, default_value = "30")]
550
- since: u64,
551
- /// Output format: text, json
552
- #[arg(short, long, default_value = "text")]
553
- format: String,
554
- /// Generate .claude/rules/cli-corrections.md file
555
- #[arg(short, long)]
556
- write_rules: bool,
557
- /// Minimum confidence threshold (0.0-1.0)
558
- #[arg(long, default_value = "0.6")]
559
- min_confidence: f64,
560
- /// Minimum occurrences to include in report
561
- #[arg(long, default_value = "1")]
562
- min_occurrences: usize,
563
- },
564
-
565
- /// Execute command without filtering but track usage
566
- Proxy {
567
- /// Command and arguments to execute
568
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
569
- args: Vec<OsString>,
570
- },
571
-
572
- /// Verify hook integrity and run TOML filter inline tests
573
- Verify {
574
- /// Run tests only for this filter name
575
- #[arg(long)]
576
- filter: Option<String>,
577
- /// Fail if any filter has no inline tests (CI mode)
578
- #[arg(long)]
579
- require_all: bool,
580
- },
581
-
582
- /// Ruff linter/formatter with compact output
583
- Ruff {
584
- /// Ruff arguments (e.g., check, format --check)
585
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
586
- args: Vec<String>,
587
- },
588
-
589
- /// Pytest test runner with compact output
590
- Pytest {
591
- /// Pytest arguments
592
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
593
- args: Vec<String>,
594
- },
595
-
596
- /// Mypy type checker with grouped error output
597
- Mypy {
598
- /// Mypy arguments
599
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
600
- args: Vec<String>,
601
- },
602
-
603
- /// Pip package manager with compact output (auto-detects uv)
604
- Pip {
605
- /// Pip arguments (e.g., list, outdated, install)
606
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
607
- args: Vec<String>,
608
- },
609
-
610
- /// Go commands with compact output
611
- Go {
612
- #[command(subcommand)]
613
- command: GoCommands,
614
- },
615
-
616
- /// Graphite (gt) stacked PR commands with compact output
617
- Gt {
618
- #[command(subcommand)]
619
- command: GtCommands,
620
- },
621
-
622
- /// golangci-lint with compact output
623
- #[command(name = "golangci-lint")]
624
- GolangciLint {
625
- /// golangci-lint arguments
626
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
627
- args: Vec<String>,
628
- },
629
-
630
- /// Show hook rewrite audit metrics (requires RTK_HOOK_AUDIT=1)
631
- #[command(name = "hook-audit")]
632
- HookAudit {
633
- /// Show entries from last N days (0 = all time)
634
- #[arg(short, long, default_value = "7")]
635
- since: u64,
636
- },
637
-
638
- /// Rewrite a raw command to its RTK equivalent (single source of truth for hooks)
639
- ///
640
- /// Exits 0 and prints the rewritten command if supported.
641
- /// Exits 1 with no output if the command has no RTK equivalent.
642
- ///
643
- /// Used by Claude Code, Gemini CLI, and other LLM hooks:
644
- /// REWRITTEN=$(rtk rewrite "$CMD") || exit 0
645
- Rewrite {
646
- /// Raw command to rewrite (e.g. "git status", "cargo test && git push")
647
- /// Accepts multiple args: `rtk rewrite ls -al` is equivalent to `rtk rewrite "ls -al"`
648
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
649
- args: Vec<String>,
650
- },
651
- }
652
-
653
- #[derive(Subcommand)]
654
- enum GitCommands {
655
- /// Condensed diff output
656
- Diff {
657
- /// Git arguments (supports all git diff flags like --stat, --cached, etc)
658
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
659
- args: Vec<String>,
660
- },
661
- /// One-line commit history
662
- Log {
663
- /// Git arguments (supports all git log flags like --oneline, --graph, --all)
664
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
665
- args: Vec<String>,
666
- },
667
- /// Compact status (supports all git status flags)
668
- Status {
669
- /// Git arguments (supports all git status flags like --porcelain, --short, -s)
670
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
671
- args: Vec<String>,
672
- },
673
- /// Compact show (commit summary + stat + compacted diff)
674
- Show {
675
- /// Git arguments (supports all git show flags)
676
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
677
- args: Vec<String>,
678
- },
679
- /// Add files → "ok ✓"
680
- Add {
681
- /// Files and flags to add (supports all git add flags like -A, -p, --all, etc)
682
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
683
- args: Vec<String>,
684
- },
685
- /// Commit → "ok ✓ \<hash\>"
686
- Commit {
687
- /// Git commit arguments (supports -a, -m, --amend, --allow-empty, etc)
688
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
689
- args: Vec<String>,
690
- },
691
- /// Push → "ok ✓ \<branch\>"
692
- Push {
693
- /// Git push arguments (supports -u, remote, branch, etc.)
694
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
695
- args: Vec<String>,
696
- },
697
- /// Pull → "ok ✓ \<stats\>"
698
- Pull {
699
- /// Git pull arguments (supports --rebase, remote, branch, etc.)
700
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
701
- args: Vec<String>,
702
- },
703
- /// Compact branch listing (current/local/remote)
704
- Branch {
705
- /// Git branch arguments (supports -d, -D, -m, etc.)
706
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
707
- args: Vec<String>,
708
- },
709
- /// Fetch → "ok fetched (N new refs)"
710
- Fetch {
711
- /// Git fetch arguments
712
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
713
- args: Vec<String>,
714
- },
715
- /// Stash management (list, show, pop, apply, drop)
716
- Stash {
717
- /// Subcommand: list, show, pop, apply, drop, push
718
- subcommand: Option<String>,
719
- /// Additional arguments
720
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
721
- args: Vec<String>,
722
- },
723
- /// Compact worktree listing
724
- Worktree {
725
- /// Git worktree arguments (add, remove, prune, or empty for list)
726
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
727
- args: Vec<String>,
728
- },
729
- /// Passthrough: runs any unsupported git subcommand directly
730
- #[command(external_subcommand)]
731
- Other(Vec<OsString>),
732
- }
733
-
734
- #[derive(Subcommand)]
735
- enum PnpmCommands {
736
- /// List installed packages (ultra-dense)
737
- List {
738
- /// Depth level (default: 0)
739
- #[arg(short, long, default_value = "0")]
740
- depth: usize,
741
- /// Additional pnpm arguments
742
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
743
- args: Vec<String>,
744
- },
745
- /// Show outdated packages (condensed: "pkg: old → new")
746
- Outdated {
747
- /// Additional pnpm arguments
748
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
749
- args: Vec<String>,
750
- },
751
- /// Install packages (filter progress bars)
752
- Install {
753
- /// Packages to install
754
- packages: Vec<String>,
755
- /// Additional pnpm arguments
756
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
757
- args: Vec<String>,
758
- },
759
- /// Build (generic passthrough, no framework-specific filter)
760
- Build {
761
- /// Additional build arguments
762
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
763
- args: Vec<String>,
764
- },
765
- /// Typecheck (delegates to tsc filter)
766
- Typecheck {
767
- /// Additional typecheck arguments
768
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
769
- args: Vec<String>,
770
- },
771
- /// Passthrough: runs any unsupported pnpm subcommand directly
772
- #[command(external_subcommand)]
773
- Other(Vec<OsString>),
774
- }
775
-
776
- #[derive(Subcommand)]
777
- enum DockerCommands {
778
- /// List running containers
779
- Ps,
780
- /// List images
781
- Images,
782
- /// Show container logs (deduplicated)
783
- Logs { container: String },
784
- /// Docker Compose commands with compact output
785
- Compose {
786
- #[command(subcommand)]
787
- command: ComposeCommands,
788
- },
789
- /// Passthrough: runs any unsupported docker subcommand directly
790
- #[command(external_subcommand)]
791
- Other(Vec<OsString>),
792
- }
793
-
794
- #[derive(Subcommand)]
795
- enum ComposeCommands {
796
- /// List compose services (compact)
797
- Ps,
798
- /// Show compose logs (deduplicated)
799
- Logs {
800
- /// Optional service name
801
- service: Option<String>,
802
- },
803
- /// Build compose services (summary)
804
- Build {
805
- /// Optional service name
806
- service: Option<String>,
807
- },
808
- /// Passthrough: runs any unsupported compose subcommand directly
809
- #[command(external_subcommand)]
810
- Other(Vec<OsString>),
811
- }
812
-
813
- #[derive(Subcommand)]
814
- enum KubectlCommands {
815
- /// List pods
816
- Pods {
817
- #[arg(short, long)]
818
- namespace: Option<String>,
819
- /// All namespaces
820
- #[arg(short = 'A', long)]
821
- all: bool,
822
- },
823
- /// List services
824
- Services {
825
- #[arg(short, long)]
826
- namespace: Option<String>,
827
- /// All namespaces
828
- #[arg(short = 'A', long)]
829
- all: bool,
830
- },
831
- /// Show pod logs (deduplicated)
832
- Logs {
833
- pod: String,
834
- #[arg(short, long)]
835
- container: Option<String>,
836
- },
837
- /// Passthrough: runs any unsupported kubectl subcommand directly
838
- #[command(external_subcommand)]
839
- Other(Vec<OsString>),
840
- }
841
-
842
- #[derive(Subcommand)]
843
- enum VitestCommands {
844
- /// Run tests with filtered output (90% token reduction)
845
- Run {
846
- /// Additional vitest arguments
847
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
848
- args: Vec<String>,
849
- },
850
- }
851
-
852
- #[derive(Subcommand)]
853
- enum PrismaCommands {
854
- /// Generate Prisma Client (strip ASCII art)
855
- Generate {
856
- /// Additional prisma arguments
857
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
858
- args: Vec<String>,
859
- },
860
- /// Manage migrations
861
- Migrate {
862
- #[command(subcommand)]
863
- command: PrismaMigrateCommands,
864
- },
865
- /// Push schema to database
866
- DbPush {
867
- /// Additional prisma arguments
868
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
869
- args: Vec<String>,
870
- },
871
- }
872
-
873
- #[derive(Subcommand)]
874
- enum PrismaMigrateCommands {
875
- /// Create and apply migration
876
- Dev {
877
- /// Migration name
878
- #[arg(short, long)]
879
- name: Option<String>,
880
- /// Additional arguments
881
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
882
- args: Vec<String>,
883
- },
884
- /// Check migration status
885
- Status {
886
- /// Additional arguments
887
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
888
- args: Vec<String>,
889
- },
890
- /// Deploy migrations to production
891
- Deploy {
892
- /// Additional arguments
893
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
894
- args: Vec<String>,
895
- },
896
- }
897
-
898
- #[derive(Subcommand)]
899
- enum CargoCommands {
900
- /// Build with compact output (strip Compiling lines, keep errors)
901
- Build {
902
- /// Additional cargo build arguments
903
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
904
- args: Vec<String>,
905
- },
906
- /// Test with failures-only output
907
- Test {
908
- /// Additional cargo test arguments
909
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
910
- args: Vec<String>,
911
- },
912
- /// Clippy with warnings grouped by lint rule
913
- Clippy {
914
- /// Additional cargo clippy arguments
915
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
916
- args: Vec<String>,
917
- },
918
- /// Check with compact output (strip Checking lines, keep errors)
919
- Check {
920
- /// Additional cargo check arguments
921
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
922
- args: Vec<String>,
923
- },
924
- /// Install with compact output (strip dep compilation, keep installed/errors)
925
- Install {
926
- /// Additional cargo install arguments
927
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
928
- args: Vec<String>,
929
- },
930
- /// Nextest with failures-only output
931
- Nextest {
932
- /// Additional cargo nextest arguments (e.g., run, list, --lib)
933
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
934
- args: Vec<String>,
935
- },
936
- /// Passthrough: runs any unsupported cargo subcommand directly
937
- #[command(external_subcommand)]
938
- Other(Vec<OsString>),
939
- }
940
-
941
- #[derive(Subcommand)]
942
- enum DotnetCommands {
943
- /// Build with compact output
944
- Build {
945
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
946
- args: Vec<String>,
947
- },
948
- /// Test with compact output
949
- Test {
950
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
951
- args: Vec<String>,
952
- },
953
- /// Restore with compact output
954
- Restore {
955
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
956
- args: Vec<String>,
957
- },
958
- /// Format with compact output
959
- Format {
960
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
961
- args: Vec<String>,
962
- },
963
- /// Passthrough: runs any unsupported dotnet subcommand directly
964
- #[command(external_subcommand)]
965
- Other(Vec<OsString>),
966
- }
967
-
968
- #[derive(Subcommand)]
969
- enum GoCommands {
970
- /// Run tests with compact output (90% token reduction via JSON streaming)
971
- Test {
972
- /// Additional go test arguments
973
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
974
- args: Vec<String>,
975
- },
976
- /// Build with compact output (errors only)
977
- Build {
978
- /// Additional go build arguments
979
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
980
- args: Vec<String>,
981
- },
982
- /// Vet with compact output
983
- Vet {
984
- /// Additional go vet arguments
985
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
986
- args: Vec<String>,
987
- },
988
- /// Passthrough: runs any unsupported go subcommand directly
989
- #[command(external_subcommand)]
990
- Other(Vec<OsString>),
991
- }
992
-
993
- /// RTK-only subcommands that should never fall back to raw execution.
994
- /// If Clap fails to parse these, show the Clap error directly.
995
- const RTK_META_COMMANDS: &[&str] = &[
996
- "gain",
997
- "discover",
998
- "learn",
999
- "init",
1000
- "config",
1001
- "proxy",
1002
- "hook-audit",
1003
- "cc-economics",
1004
- "verify",
1005
- ];
1006
-
1007
- fn run_fallback(parse_error: clap::Error) -> Result<()> {
1008
- let args: Vec<String> = std::env::args().skip(1).collect();
1009
-
1010
- // No args → show Clap's error (user ran just "rtk" with bad syntax)
1011
- if args.is_empty() {
1012
- parse_error.exit();
1013
- }
1014
-
1015
- // RTK meta-commands should never fall back to raw execution.
1016
- // e.g. `rtk gain --badtypo` should show Clap's error, not try to run `gain` from $PATH.
1017
- if RTK_META_COMMANDS.contains(&args[0].as_str()) {
1018
- parse_error.exit();
1019
- }
1020
-
1021
- let raw_command = args.join(" ");
1022
- let error_message = utils::strip_ansi(&parse_error.to_string());
1023
-
1024
- // Start timer before execution to capture actual command runtime
1025
- let timer = tracking::TimedExecution::start();
1026
-
1027
- // TOML filter lookup — bypass with RTK_NO_TOML=1
1028
- // Use basename of args[0] so absolute paths (/usr/bin/make) still match "^make\b".
1029
- let lookup_cmd = {
1030
- let base = std::path::Path::new(&args[0])
1031
- .file_name()
1032
- .map(|n| n.to_string_lossy().into_owned())
1033
- .unwrap_or_else(|| args[0].clone());
1034
- std::iter::once(base.as_str())
1035
- .chain(args[1..].iter().map(|s| s.as_str()))
1036
- .collect::<Vec<_>>()
1037
- .join(" ")
1038
- };
1039
- let toml_match = if std::env::var("RTK_NO_TOML").ok().as_deref() == Some("1") {
1040
- None
1041
- } else {
1042
- toml_filter::find_matching_filter(&lookup_cmd)
1043
- };
1044
-
1045
- if let Some(filter) = toml_match {
1046
- // TOML match: capture stdout for filtering
1047
- let result = std::process::Command::new(&args[0])
1048
- .args(&args[1..])
1049
- .stdin(std::process::Stdio::inherit())
1050
- .stdout(std::process::Stdio::piped()) // capture
1051
- .stderr(std::process::Stdio::inherit()) // stderr always direct
1052
- .output();
1053
-
1054
- match result {
1055
- Ok(output) => {
1056
- let stdout_raw = String::from_utf8_lossy(&output.stdout);
1057
-
1058
- // Tee raw output BEFORE filtering on failure — lets LLM re-read if needed
1059
- let tee_hint = if !output.status.success() {
1060
- tee::tee_and_hint(&stdout_raw, &raw_command, output.status.code().unwrap_or(1))
1061
- } else {
1062
- None
1063
- };
1064
-
1065
- let filtered = toml_filter::apply_filter(filter, &stdout_raw);
1066
- println!("{}", filtered);
1067
- if let Some(hint) = tee_hint {
1068
- println!("{}", hint);
1069
- }
1070
-
1071
- timer.track(
1072
- &raw_command,
1073
- &format!("rtk:toml {}", raw_command),
1074
- &stdout_raw,
1075
- &filtered,
1076
- );
1077
- tracking::record_parse_failure_silent(&raw_command, &error_message, true);
1078
-
1079
- if !output.status.success() {
1080
- std::process::exit(output.status.code().unwrap_or(1));
1081
- }
1082
- }
1083
- Err(e) => {
1084
- // Command not found — same behaviour as no-TOML path
1085
- tracking::record_parse_failure_silent(&raw_command, &error_message, false);
1086
- eprintln!("[rtk: {}]", e);
1087
- std::process::exit(127);
1088
- }
1089
- }
1090
- } else {
1091
- // No TOML match: original passthrough behaviour (Stdio::inherit, streaming)
1092
- let status = std::process::Command::new(&args[0])
1093
- .args(&args[1..])
1094
- .stdin(std::process::Stdio::inherit())
1095
- .stdout(std::process::Stdio::inherit())
1096
- .stderr(std::process::Stdio::inherit())
1097
- .status();
1098
-
1099
- match status {
1100
- Ok(s) => {
1101
- timer.track_passthrough(&raw_command, &format!("rtk fallback: {}", raw_command));
1102
-
1103
- tracking::record_parse_failure_silent(&raw_command, &error_message, true);
1104
-
1105
- if !s.success() {
1106
- std::process::exit(s.code().unwrap_or(1));
1107
- }
1108
- }
1109
- Err(e) => {
1110
- tracking::record_parse_failure_silent(&raw_command, &error_message, false);
1111
- // Command not found or other OS error — single message, no duplicate Clap error
1112
- eprintln!("[rtk: {}]", e);
1113
- std::process::exit(127);
1114
- }
1115
- }
1116
- }
1117
-
1118
- Ok(())
1119
- }
1120
-
1121
- #[derive(Subcommand)]
1122
- enum GtCommands {
1123
- /// Compact stack log output
1124
- Log {
1125
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
1126
- args: Vec<String>,
1127
- },
1128
- /// Compact submit output
1129
- Submit {
1130
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
1131
- args: Vec<String>,
1132
- },
1133
- /// Compact sync output
1134
- Sync {
1135
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
1136
- args: Vec<String>,
1137
- },
1138
- /// Compact restack output
1139
- Restack {
1140
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
1141
- args: Vec<String>,
1142
- },
1143
- /// Compact create output
1144
- Create {
1145
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
1146
- args: Vec<String>,
1147
- },
1148
- /// Branch info and management
1149
- Branch {
1150
- #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
1151
- args: Vec<String>,
1152
- },
1153
- /// Passthrough: git-passthrough detection or direct gt execution
1154
- #[command(external_subcommand)]
1155
- Other(Vec<OsString>),
1156
- }
1157
-
1158
- /// Split a string into shell-like tokens, respecting single and double quotes.
1159
- /// e.g. `git log --format="%H %s"` → ["git", "log", "--format=%H %s"]
1160
- fn shell_split(input: &str) -> Vec<String> {
1161
- let mut tokens = Vec::new();
1162
- let mut current = String::new();
1163
- let mut chars = input.chars().peekable();
1164
- let mut in_single = false;
1165
- let mut in_double = false;
1166
-
1167
- while let Some(c) = chars.next() {
1168
- match c {
1169
- '\'' if !in_double => in_single = !in_single,
1170
- '"' if !in_single => in_double = !in_double,
1171
- ' ' | '\t' if !in_single && !in_double => {
1172
- if !current.is_empty() {
1173
- tokens.push(std::mem::take(&mut current));
1174
- }
1175
- }
1176
- _ => current.push(c),
1177
- }
1178
- }
1179
- if !current.is_empty() {
1180
- tokens.push(current);
1181
- }
1182
- tokens
1183
- }
1184
-
1185
- fn main() -> Result<()> {
1186
- // Fire-and-forget telemetry ping (1/day, non-blocking)
1187
- telemetry::maybe_ping();
1188
-
1189
- let cli = match Cli::try_parse() {
1190
- Ok(cli) => cli,
1191
- Err(e) => {
1192
- if matches!(e.kind(), ErrorKind::DisplayHelp | ErrorKind::DisplayVersion) {
1193
- e.exit();
1194
- }
1195
- return run_fallback(e);
1196
- }
1197
- };
1198
-
1199
- // Warn if installed hook is outdated/missing (1/day, non-blocking).
1200
- // Skip for Gain — it shows its own inline hook warning.
1201
- if !matches!(cli.command, Commands::Gain { .. }) {
1202
- hook_check::maybe_warn();
1203
- }
1204
-
1205
- // Runtime integrity check for operational commands.
1206
- // Meta commands (init, gain, verify, config, etc.) skip the check
1207
- // because they don't go through the hook pipeline.
1208
- if is_operational_command(&cli.command) {
1209
- integrity::runtime_check()?;
1210
- }
1211
-
1212
- match cli.command {
1213
- Commands::Ls { args } => {
1214
- ls::run(&args, cli.verbose)?;
1215
- }
1216
-
1217
- Commands::Tree { args } => {
1218
- tree::run(&args, cli.verbose)?;
1219
- }
1220
-
1221
- Commands::Read {
1222
- file,
1223
- level,
1224
- max_lines,
1225
- tail_lines,
1226
- line_numbers,
1227
- } => {
1228
- if file == Path::new("-") {
1229
- read::run_stdin(level, max_lines, tail_lines, line_numbers, cli.verbose)?;
1230
- } else {
1231
- read::run(
1232
- &file,
1233
- level,
1234
- max_lines,
1235
- tail_lines,
1236
- line_numbers,
1237
- cli.verbose,
1238
- )?;
1239
- }
1240
- }
1241
-
1242
- Commands::Smart {
1243
- file,
1244
- model,
1245
- force_download,
1246
- } => {
1247
- local_llm::run(&file, &model, force_download, cli.verbose)?;
1248
- }
1249
-
1250
- Commands::Git {
1251
- directory,
1252
- config_override,
1253
- git_dir,
1254
- work_tree,
1255
- no_pager,
1256
- no_optional_locks,
1257
- bare,
1258
- literal_pathspecs,
1259
- command,
1260
- } => {
1261
- // Build global git args (inserted between "git" and subcommand)
1262
- let mut global_args: Vec<String> = Vec::new();
1263
- for dir in &directory {
1264
- global_args.push("-C".to_string());
1265
- global_args.push(dir.clone());
1266
- }
1267
- for cfg in &config_override {
1268
- global_args.push("-c".to_string());
1269
- global_args.push(cfg.clone());
1270
- }
1271
- if let Some(ref dir) = git_dir {
1272
- global_args.push("--git-dir".to_string());
1273
- global_args.push(dir.clone());
1274
- }
1275
- if let Some(ref tree) = work_tree {
1276
- global_args.push("--work-tree".to_string());
1277
- global_args.push(tree.clone());
1278
- }
1279
- if no_pager {
1280
- global_args.push("--no-pager".to_string());
1281
- }
1282
- if no_optional_locks {
1283
- global_args.push("--no-optional-locks".to_string());
1284
- }
1285
- if bare {
1286
- global_args.push("--bare".to_string());
1287
- }
1288
- if literal_pathspecs {
1289
- global_args.push("--literal-pathspecs".to_string());
1290
- }
1291
-
1292
- match command {
1293
- GitCommands::Diff { args } => {
1294
- git::run(
1295
- git::GitCommand::Diff,
1296
- &args,
1297
- None,
1298
- cli.verbose,
1299
- &global_args,
1300
- )?;
1301
- }
1302
- GitCommands::Log { args } => {
1303
- git::run(git::GitCommand::Log, &args, None, cli.verbose, &global_args)?;
1304
- }
1305
- GitCommands::Status { args } => {
1306
- git::run(
1307
- git::GitCommand::Status,
1308
- &args,
1309
- None,
1310
- cli.verbose,
1311
- &global_args,
1312
- )?;
1313
- }
1314
- GitCommands::Show { args } => {
1315
- git::run(
1316
- git::GitCommand::Show,
1317
- &args,
1318
- None,
1319
- cli.verbose,
1320
- &global_args,
1321
- )?;
1322
- }
1323
- GitCommands::Add { args } => {
1324
- git::run(git::GitCommand::Add, &args, None, cli.verbose, &global_args)?;
1325
- }
1326
- GitCommands::Commit { args } => {
1327
- git::run(
1328
- git::GitCommand::Commit,
1329
- &args,
1330
- None,
1331
- cli.verbose,
1332
- &global_args,
1333
- )?;
1334
- }
1335
- GitCommands::Push { args } => {
1336
- git::run(
1337
- git::GitCommand::Push,
1338
- &args,
1339
- None,
1340
- cli.verbose,
1341
- &global_args,
1342
- )?;
1343
- }
1344
- GitCommands::Pull { args } => {
1345
- git::run(
1346
- git::GitCommand::Pull,
1347
- &args,
1348
- None,
1349
- cli.verbose,
1350
- &global_args,
1351
- )?;
1352
- }
1353
- GitCommands::Branch { args } => {
1354
- git::run(
1355
- git::GitCommand::Branch,
1356
- &args,
1357
- None,
1358
- cli.verbose,
1359
- &global_args,
1360
- )?;
1361
- }
1362
- GitCommands::Fetch { args } => {
1363
- git::run(
1364
- git::GitCommand::Fetch,
1365
- &args,
1366
- None,
1367
- cli.verbose,
1368
- &global_args,
1369
- )?;
1370
- }
1371
- GitCommands::Stash { subcommand, args } => {
1372
- git::run(
1373
- git::GitCommand::Stash { subcommand },
1374
- &args,
1375
- None,
1376
- cli.verbose,
1377
- &global_args,
1378
- )?;
1379
- }
1380
- GitCommands::Worktree { args } => {
1381
- git::run(
1382
- git::GitCommand::Worktree,
1383
- &args,
1384
- None,
1385
- cli.verbose,
1386
- &global_args,
1387
- )?;
1388
- }
1389
- GitCommands::Other(args) => {
1390
- git::run_passthrough(&args, &global_args, cli.verbose)?;
1391
- }
1392
- }
1393
- }
1394
-
1395
- Commands::Gh { subcommand, args } => {
1396
- gh_cmd::run(&subcommand, &args, cli.verbose, cli.ultra_compact)?;
1397
- }
1398
-
1399
- Commands::Aws { subcommand, args } => {
1400
- aws_cmd::run(&subcommand, &args, cli.verbose)?;
1401
- }
1402
-
1403
- Commands::Psql { args } => {
1404
- psql_cmd::run(&args, cli.verbose)?;
1405
- }
1406
-
1407
- Commands::Pnpm { command } => match command {
1408
- PnpmCommands::List { depth, args } => {
1409
- pnpm_cmd::run(pnpm_cmd::PnpmCommand::List { depth }, &args, cli.verbose)?;
1410
- }
1411
- PnpmCommands::Outdated { args } => {
1412
- pnpm_cmd::run(pnpm_cmd::PnpmCommand::Outdated, &args, cli.verbose)?;
1413
- }
1414
- PnpmCommands::Install { packages, args } => {
1415
- pnpm_cmd::run(
1416
- pnpm_cmd::PnpmCommand::Install { packages },
1417
- &args,
1418
- cli.verbose,
1419
- )?;
1420
- }
1421
- PnpmCommands::Build { args } => {
1422
- let mut build_args: Vec<String> = vec!["build".into()];
1423
- build_args.extend(args);
1424
- let os_args: Vec<OsString> = build_args.into_iter().map(OsString::from).collect();
1425
- pnpm_cmd::run_passthrough(&os_args, cli.verbose)?;
1426
- }
1427
- PnpmCommands::Typecheck { args } => {
1428
- tsc_cmd::run(&args, cli.verbose)?;
1429
- }
1430
- PnpmCommands::Other(args) => {
1431
- pnpm_cmd::run_passthrough(&args, cli.verbose)?;
1432
- }
1433
- },
1434
-
1435
- Commands::Err { command } => {
1436
- let cmd = command.join(" ");
1437
- runner::run_err(&cmd, cli.verbose)?;
1438
- }
1439
-
1440
- Commands::Test { command } => {
1441
- let cmd = command.join(" ");
1442
- runner::run_test(&cmd, cli.verbose)?;
1443
- }
1444
-
1445
- Commands::Json { file, depth } => {
1446
- if file == Path::new("-") {
1447
- json_cmd::run_stdin(depth, cli.verbose)?;
1448
- } else {
1449
- json_cmd::run(&file, depth, cli.verbose)?;
1450
- }
1451
- }
1452
-
1453
- Commands::Deps { path } => {
1454
- deps::run(&path, cli.verbose)?;
1455
- }
1456
-
1457
- Commands::Env { filter, show_all } => {
1458
- env_cmd::run(filter.as_deref(), show_all, cli.verbose)?;
1459
- }
1460
-
1461
- Commands::Find { args } => {
1462
- find_cmd::run_from_args(&args, cli.verbose)?;
1463
- }
1464
-
1465
- Commands::Diff { file1, file2 } => {
1466
- if let Some(f2) = file2 {
1467
- diff_cmd::run(&file1, &f2, cli.verbose)?;
1468
- } else {
1469
- diff_cmd::run_stdin(cli.verbose)?;
1470
- }
1471
- }
1472
-
1473
- Commands::Log { file } => {
1474
- if let Some(f) = file {
1475
- log_cmd::run_file(&f, cli.verbose)?;
1476
- } else {
1477
- log_cmd::run_stdin(cli.verbose)?;
1478
- }
1479
- }
1480
-
1481
- Commands::Dotnet { command } => match command {
1482
- DotnetCommands::Build { args } => {
1483
- dotnet_cmd::run_build(&args, cli.verbose)?;
1484
- }
1485
- DotnetCommands::Test { args } => {
1486
- dotnet_cmd::run_test(&args, cli.verbose)?;
1487
- }
1488
- DotnetCommands::Restore { args } => {
1489
- dotnet_cmd::run_restore(&args, cli.verbose)?;
1490
- }
1491
- DotnetCommands::Format { args } => {
1492
- dotnet_cmd::run_format(&args, cli.verbose)?;
1493
- }
1494
- DotnetCommands::Other(args) => {
1495
- dotnet_cmd::run_passthrough(&args, cli.verbose)?;
1496
- }
1497
- },
1498
-
1499
- Commands::Docker { command } => match command {
1500
- DockerCommands::Ps => {
1501
- container::run(container::ContainerCmd::DockerPs, &[], cli.verbose)?;
1502
- }
1503
- DockerCommands::Images => {
1504
- container::run(container::ContainerCmd::DockerImages, &[], cli.verbose)?;
1505
- }
1506
- DockerCommands::Logs { container: c } => {
1507
- container::run(container::ContainerCmd::DockerLogs, &[c], cli.verbose)?;
1508
- }
1509
- DockerCommands::Compose { command: compose } => match compose {
1510
- ComposeCommands::Ps => {
1511
- container::run_compose_ps(cli.verbose)?;
1512
- }
1513
- ComposeCommands::Logs { service } => {
1514
- container::run_compose_logs(service.as_deref(), cli.verbose)?;
1515
- }
1516
- ComposeCommands::Build { service } => {
1517
- container::run_compose_build(service.as_deref(), cli.verbose)?;
1518
- }
1519
- ComposeCommands::Other(args) => {
1520
- container::run_compose_passthrough(&args, cli.verbose)?;
1521
- }
1522
- },
1523
- DockerCommands::Other(args) => {
1524
- container::run_docker_passthrough(&args, cli.verbose)?;
1525
- }
1526
- },
1527
-
1528
- Commands::Kubectl { command } => match command {
1529
- KubectlCommands::Pods { namespace, all } => {
1530
- let mut args: Vec<String> = Vec::new();
1531
- if all {
1532
- args.push("-A".to_string());
1533
- } else if let Some(n) = namespace {
1534
- args.push("-n".to_string());
1535
- args.push(n);
1536
- }
1537
- container::run(container::ContainerCmd::KubectlPods, &args, cli.verbose)?;
1538
- }
1539
- KubectlCommands::Services { namespace, all } => {
1540
- let mut args: Vec<String> = Vec::new();
1541
- if all {
1542
- args.push("-A".to_string());
1543
- } else if let Some(n) = namespace {
1544
- args.push("-n".to_string());
1545
- args.push(n);
1546
- }
1547
- container::run(container::ContainerCmd::KubectlServices, &args, cli.verbose)?;
1548
- }
1549
- KubectlCommands::Logs { pod, container: c } => {
1550
- let mut args = vec![pod];
1551
- if let Some(cont) = c {
1552
- args.push("-c".to_string());
1553
- args.push(cont);
1554
- }
1555
- container::run(container::ContainerCmd::KubectlLogs, &args, cli.verbose)?;
1556
- }
1557
- KubectlCommands::Other(args) => {
1558
- container::run_kubectl_passthrough(&args, cli.verbose)?;
1559
- }
1560
- },
1561
-
1562
- Commands::Summary { command } => {
1563
- let cmd = command.join(" ");
1564
- summary::run(&cmd, cli.verbose)?;
1565
- }
1566
-
1567
- Commands::Grep {
1568
- pattern,
1569
- path,
1570
- max_len,
1571
- max,
1572
- context_only,
1573
- file_type,
1574
- line_numbers: _, // no-op: line numbers always enabled in grep_cmd::run
1575
- extra_args,
1576
- } => {
1577
- grep_cmd::run(
1578
- &pattern,
1579
- &path,
1580
- max_len,
1581
- max,
1582
- context_only,
1583
- file_type.as_deref(),
1584
- &extra_args,
1585
- cli.verbose,
1586
- )?;
1587
- }
1588
-
1589
- Commands::Init {
1590
- global,
1591
- opencode,
1592
- show,
1593
- claude_md,
1594
- hook_only,
1595
- auto_patch,
1596
- no_patch,
1597
- uninstall,
1598
- } => {
1599
- if show {
1600
- init::show_config()?;
1601
- } else if uninstall {
1602
- init::uninstall(global, cli.verbose)?;
1603
- } else {
1604
- let install_opencode = opencode;
1605
- let install_claude = !opencode;
1606
-
1607
- let patch_mode = if auto_patch {
1608
- init::PatchMode::Auto
1609
- } else if no_patch {
1610
- init::PatchMode::Skip
1611
- } else {
1612
- init::PatchMode::Ask
1613
- };
1614
- init::run(
1615
- global,
1616
- install_claude,
1617
- install_opencode,
1618
- claude_md,
1619
- hook_only,
1620
- patch_mode,
1621
- cli.verbose,
1622
- )?;
1623
- }
1624
- }
1625
-
1626
- Commands::Wget { url, stdout, args } => {
1627
- if stdout {
1628
- wget_cmd::run_stdout(&url, &args, cli.verbose)?;
1629
- } else {
1630
- wget_cmd::run(&url, &args, cli.verbose)?;
1631
- }
1632
- }
1633
-
1634
- Commands::Wc { args } => {
1635
- wc_cmd::run(&args, cli.verbose)?;
1636
- }
1637
-
1638
- Commands::Gain {
1639
- project, // added
1640
- graph,
1641
- history,
1642
- quota,
1643
- tier,
1644
- daily,
1645
- weekly,
1646
- monthly,
1647
- all,
1648
- format,
1649
- failures,
1650
- } => {
1651
- gain::run(
1652
- project, // added: pass project flag
1653
- graph,
1654
- history,
1655
- quota,
1656
- &tier,
1657
- daily,
1658
- weekly,
1659
- monthly,
1660
- all,
1661
- &format,
1662
- failures,
1663
- cli.verbose,
1664
- )?;
1665
- }
1666
-
1667
- Commands::CcEconomics {
1668
- daily,
1669
- weekly,
1670
- monthly,
1671
- all,
1672
- format,
1673
- } => {
1674
- cc_economics::run(daily, weekly, monthly, all, &format, cli.verbose)?;
1675
- }
1676
-
1677
- Commands::Config { create } => {
1678
- if create {
1679
- let path = config::Config::create_default()?;
1680
- println!("Created: {}", path.display());
1681
- } else {
1682
- config::show_config()?;
1683
- }
1684
- }
1685
-
1686
- Commands::Vitest { command } => match command {
1687
- VitestCommands::Run { args } => {
1688
- vitest_cmd::run(vitest_cmd::VitestCommand::Run, &args, cli.verbose)?;
1689
- }
1690
- },
1691
-
1692
- Commands::Prisma { command } => match command {
1693
- PrismaCommands::Generate { args } => {
1694
- prisma_cmd::run(prisma_cmd::PrismaCommand::Generate, &args, cli.verbose)?;
1695
- }
1696
- PrismaCommands::Migrate { command } => match command {
1697
- PrismaMigrateCommands::Dev { name, args } => {
1698
- prisma_cmd::run(
1699
- prisma_cmd::PrismaCommand::Migrate {
1700
- subcommand: prisma_cmd::MigrateSubcommand::Dev { name },
1701
- },
1702
- &args,
1703
- cli.verbose,
1704
- )?;
1705
- }
1706
- PrismaMigrateCommands::Status { args } => {
1707
- prisma_cmd::run(
1708
- prisma_cmd::PrismaCommand::Migrate {
1709
- subcommand: prisma_cmd::MigrateSubcommand::Status,
1710
- },
1711
- &args,
1712
- cli.verbose,
1713
- )?;
1714
- }
1715
- PrismaMigrateCommands::Deploy { args } => {
1716
- prisma_cmd::run(
1717
- prisma_cmd::PrismaCommand::Migrate {
1718
- subcommand: prisma_cmd::MigrateSubcommand::Deploy,
1719
- },
1720
- &args,
1721
- cli.verbose,
1722
- )?;
1723
- }
1724
- },
1725
- PrismaCommands::DbPush { args } => {
1726
- prisma_cmd::run(prisma_cmd::PrismaCommand::DbPush, &args, cli.verbose)?;
1727
- }
1728
- },
1729
-
1730
- Commands::Tsc { args } => {
1731
- tsc_cmd::run(&args, cli.verbose)?;
1732
- }
1733
-
1734
- Commands::Next { args } => {
1735
- next_cmd::run(&args, cli.verbose)?;
1736
- }
1737
-
1738
- Commands::Lint { args } => {
1739
- lint_cmd::run(&args, cli.verbose)?;
1740
- }
1741
-
1742
- Commands::Prettier { args } => {
1743
- prettier_cmd::run(&args, cli.verbose)?;
1744
- }
1745
-
1746
- Commands::Format { args } => {
1747
- format_cmd::run(&args, cli.verbose)?;
1748
- }
1749
-
1750
- Commands::Playwright { args } => {
1751
- playwright_cmd::run(&args, cli.verbose)?;
1752
- }
1753
-
1754
- Commands::Cargo { command } => match command {
1755
- CargoCommands::Build { args } => {
1756
- cargo_cmd::run(cargo_cmd::CargoCommand::Build, &args, cli.verbose)?;
1757
- }
1758
- CargoCommands::Test { args } => {
1759
- cargo_cmd::run(cargo_cmd::CargoCommand::Test, &args, cli.verbose)?;
1760
- }
1761
- CargoCommands::Clippy { args } => {
1762
- cargo_cmd::run(cargo_cmd::CargoCommand::Clippy, &args, cli.verbose)?;
1763
- }
1764
- CargoCommands::Check { args } => {
1765
- cargo_cmd::run(cargo_cmd::CargoCommand::Check, &args, cli.verbose)?;
1766
- }
1767
- CargoCommands::Install { args } => {
1768
- cargo_cmd::run(cargo_cmd::CargoCommand::Install, &args, cli.verbose)?;
1769
- }
1770
- CargoCommands::Nextest { args } => {
1771
- cargo_cmd::run(cargo_cmd::CargoCommand::Nextest, &args, cli.verbose)?;
1772
- }
1773
- CargoCommands::Other(args) => {
1774
- cargo_cmd::run_passthrough(&args, cli.verbose)?;
1775
- }
1776
- },
1777
-
1778
- Commands::Npm { args } => {
1779
- npm_cmd::run(&args, cli.verbose, cli.skip_env)?;
1780
- }
1781
-
1782
- Commands::Curl { args } => {
1783
- curl_cmd::run(&args, cli.verbose)?;
1784
- }
1785
-
1786
- Commands::Discover {
1787
- project,
1788
- limit,
1789
- all,
1790
- since,
1791
- format,
1792
- } => {
1793
- discover::run(project.as_deref(), all, since, limit, &format, cli.verbose)?;
1794
- }
1795
-
1796
- Commands::Learn {
1797
- project,
1798
- all,
1799
- since,
1800
- format,
1801
- write_rules,
1802
- min_confidence,
1803
- min_occurrences,
1804
- } => {
1805
- learn::run(
1806
- project,
1807
- all,
1808
- since,
1809
- format,
1810
- write_rules,
1811
- min_confidence,
1812
- min_occurrences,
1813
- )?;
1814
- }
1815
-
1816
- Commands::Npx { args } => {
1817
- if args.is_empty() {
1818
- anyhow::bail!("npx requires a command argument");
1819
- }
1820
-
1821
- // Intelligent routing: delegate to specialized filters
1822
- match args[0].as_str() {
1823
- "tsc" | "typescript" => {
1824
- tsc_cmd::run(&args[1..], cli.verbose)?;
1825
- }
1826
- "eslint" => {
1827
- lint_cmd::run(&args[1..], cli.verbose)?;
1828
- }
1829
- "prisma" => {
1830
- // Route to prisma_cmd based on subcommand
1831
- if args.len() > 1 {
1832
- let prisma_args: Vec<String> = args[2..].to_vec();
1833
- match args[1].as_str() {
1834
- "generate" => {
1835
- prisma_cmd::run(
1836
- prisma_cmd::PrismaCommand::Generate,
1837
- &prisma_args,
1838
- cli.verbose,
1839
- )?;
1840
- }
1841
- "db" if args.len() > 2 && args[2] == "push" => {
1842
- prisma_cmd::run(
1843
- prisma_cmd::PrismaCommand::DbPush,
1844
- &args[3..],
1845
- cli.verbose,
1846
- )?;
1847
- }
1848
- _ => {
1849
- // Passthrough other prisma subcommands
1850
- let timer = tracking::TimedExecution::start();
1851
- let mut cmd = std::process::Command::new("npx");
1852
- for arg in &args {
1853
- cmd.arg(arg);
1854
- }
1855
- let status = cmd.status().context("Failed to run npx prisma")?;
1856
- let args_str = args.join(" ");
1857
- timer.track_passthrough(
1858
- &format!("npx {}", args_str),
1859
- &format!("rtk npx {} (passthrough)", args_str),
1860
- );
1861
- if !status.success() {
1862
- std::process::exit(status.code().unwrap_or(1));
1863
- }
1864
- }
1865
- }
1866
- } else {
1867
- let timer = tracking::TimedExecution::start();
1868
- let status = std::process::Command::new("npx")
1869
- .arg("prisma")
1870
- .status()
1871
- .context("Failed to run npx prisma")?;
1872
- timer.track_passthrough("npx prisma", "rtk npx prisma (passthrough)");
1873
- if !status.success() {
1874
- std::process::exit(status.code().unwrap_or(1));
1875
- }
1876
- }
1877
- }
1878
- "next" => {
1879
- next_cmd::run(&args[1..], cli.verbose)?;
1880
- }
1881
- "prettier" => {
1882
- prettier_cmd::run(&args[1..], cli.verbose)?;
1883
- }
1884
- "playwright" => {
1885
- playwright_cmd::run(&args[1..], cli.verbose)?;
1886
- }
1887
- _ => {
1888
- // Generic passthrough with npm boilerplate filter
1889
- npm_cmd::run(&args, cli.verbose, cli.skip_env)?;
1890
- }
1891
- }
1892
- }
1893
-
1894
- Commands::Ruff { args } => {
1895
- ruff_cmd::run(&args, cli.verbose)?;
1896
- }
1897
-
1898
- Commands::Pytest { args } => {
1899
- pytest_cmd::run(&args, cli.verbose)?;
1900
- }
1901
-
1902
- Commands::Mypy { args } => {
1903
- mypy_cmd::run(&args, cli.verbose)?;
1904
- }
1905
-
1906
- Commands::Pip { args } => {
1907
- pip_cmd::run(&args, cli.verbose)?;
1908
- }
1909
-
1910
- Commands::Go { command } => match command {
1911
- GoCommands::Test { args } => {
1912
- go_cmd::run_test(&args, cli.verbose)?;
1913
- }
1914
- GoCommands::Build { args } => {
1915
- go_cmd::run_build(&args, cli.verbose)?;
1916
- }
1917
- GoCommands::Vet { args } => {
1918
- go_cmd::run_vet(&args, cli.verbose)?;
1919
- }
1920
- GoCommands::Other(args) => {
1921
- go_cmd::run_other(&args, cli.verbose)?;
1922
- }
1923
- },
1924
-
1925
- Commands::Gt { command } => match command {
1926
- GtCommands::Log { args } => {
1927
- gt_cmd::run_log(&args, cli.verbose)?;
1928
- }
1929
- GtCommands::Submit { args } => {
1930
- gt_cmd::run_submit(&args, cli.verbose)?;
1931
- }
1932
- GtCommands::Sync { args } => {
1933
- gt_cmd::run_sync(&args, cli.verbose)?;
1934
- }
1935
- GtCommands::Restack { args } => {
1936
- gt_cmd::run_restack(&args, cli.verbose)?;
1937
- }
1938
- GtCommands::Create { args } => {
1939
- gt_cmd::run_create(&args, cli.verbose)?;
1940
- }
1941
- GtCommands::Branch { args } => {
1942
- gt_cmd::run_branch(&args, cli.verbose)?;
1943
- }
1944
- GtCommands::Other(args) => {
1945
- gt_cmd::run_other(&args, cli.verbose)?;
1946
- }
1947
- },
1948
-
1949
- Commands::GolangciLint { args } => {
1950
- golangci_cmd::run(&args, cli.verbose)?;
1951
- }
1952
-
1953
- Commands::HookAudit { since } => {
1954
- hook_audit_cmd::run(since, cli.verbose)?;
1955
- }
1956
-
1957
- Commands::Rewrite { args } => {
1958
- let cmd = args.join(" ");
1959
- rewrite_cmd::run(&cmd)?;
1960
- }
1961
-
1962
- Commands::Proxy { args } => {
1963
- use std::io::{Read, Write};
1964
- use std::process::{Command, Stdio};
1965
- use std::thread;
1966
-
1967
- if args.is_empty() {
1968
- anyhow::bail!(
1969
- "proxy requires a command to execute\nUsage: rtk proxy <command> [args...]"
1970
- );
1971
- }
1972
-
1973
- let timer = tracking::TimedExecution::start();
1974
-
1975
- // If a single quoted arg contains spaces, split it respecting quotes (#388).
1976
- // e.g. rtk proxy 'head -50 file.php' → cmd=head, args=["-50", "file.php"]
1977
- // e.g. rtk proxy 'git log --format="%H %s"' → cmd=git, args=["log", "--format=%H %s"]
1978
- let (cmd_name, cmd_args): (String, Vec<String>) = if args.len() == 1 {
1979
- let full = args[0].to_string_lossy();
1980
- let parts = shell_split(&full);
1981
- if parts.len() > 1 {
1982
- (parts[0].clone(), parts[1..].to_vec())
1983
- } else {
1984
- (full.into_owned(), vec![])
1985
- }
1986
- } else {
1987
- (
1988
- args[0].to_string_lossy().into_owned(),
1989
- args[1..]
1990
- .iter()
1991
- .map(|s| s.to_string_lossy().into_owned())
1992
- .collect(),
1993
- )
1994
- };
1995
-
1996
- if cli.verbose > 0 {
1997
- eprintln!("Proxy mode: {} {}", cmd_name, cmd_args.join(" "));
1998
- }
1999
-
2000
- let mut child = Command::new(&cmd_name)
2001
- .args(&cmd_args)
2002
- .stdout(Stdio::piped())
2003
- .stderr(Stdio::piped())
2004
- .spawn()
2005
- .context(format!("Failed to execute command: {}", cmd_name))?;
2006
-
2007
- let stdout_pipe = child
2008
- .stdout
2009
- .take()
2010
- .context("Failed to capture child stdout")?;
2011
- let stderr_pipe = child
2012
- .stderr
2013
- .take()
2014
- .context("Failed to capture child stderr")?;
2015
-
2016
- let stdout_handle = thread::spawn(move || -> std::io::Result<Vec<u8>> {
2017
- let mut reader = stdout_pipe;
2018
- let mut captured = Vec::new();
2019
- let mut buf = [0u8; 8192];
2020
-
2021
- loop {
2022
- let count = reader.read(&mut buf)?;
2023
- if count == 0 {
2024
- break;
2025
- }
2026
- captured.extend_from_slice(&buf[..count]);
2027
- let mut out = std::io::stdout().lock();
2028
- out.write_all(&buf[..count])?;
2029
- out.flush()?;
2030
- }
2031
-
2032
- Ok(captured)
2033
- });
2034
-
2035
- let stderr_handle = thread::spawn(move || -> std::io::Result<Vec<u8>> {
2036
- let mut reader = stderr_pipe;
2037
- let mut captured = Vec::new();
2038
- let mut buf = [0u8; 8192];
2039
-
2040
- loop {
2041
- let count = reader.read(&mut buf)?;
2042
- if count == 0 {
2043
- break;
2044
- }
2045
- captured.extend_from_slice(&buf[..count]);
2046
- let mut err = std::io::stderr().lock();
2047
- err.write_all(&buf[..count])?;
2048
- err.flush()?;
2049
- }
2050
-
2051
- Ok(captured)
2052
- });
2053
-
2054
- let status = child
2055
- .wait()
2056
- .context(format!("Failed waiting for command: {}", cmd_name))?;
2057
-
2058
- let stdout_bytes = stdout_handle
2059
- .join()
2060
- .map_err(|_| anyhow::anyhow!("stdout streaming thread panicked"))??;
2061
- let stderr_bytes = stderr_handle
2062
- .join()
2063
- .map_err(|_| anyhow::anyhow!("stderr streaming thread panicked"))??;
2064
-
2065
- let stdout = String::from_utf8_lossy(&stdout_bytes);
2066
- let stderr = String::from_utf8_lossy(&stderr_bytes);
2067
- let full_output = format!("{}{}", stdout, stderr);
2068
-
2069
- // Track usage (input = output since no filtering)
2070
- timer.track(
2071
- &format!("{} {}", cmd_name, cmd_args.join(" ")),
2072
- &format!("rtk proxy {} {}", cmd_name, cmd_args.join(" ")),
2073
- &full_output,
2074
- &full_output,
2075
- );
2076
-
2077
- // Exit with same code as child process
2078
- if !status.success() {
2079
- std::process::exit(status.code().unwrap_or(1));
2080
- }
2081
- }
2082
-
2083
- Commands::Verify {
2084
- filter,
2085
- require_all,
2086
- } => {
2087
- if filter.is_some() {
2088
- // Filter-specific mode: run only that filter's tests
2089
- verify_cmd::run(filter, require_all)?;
2090
- } else {
2091
- // Default or --require-all: always run integrity check first
2092
- integrity::run_verify(cli.verbose)?;
2093
- verify_cmd::run(None, require_all)?;
2094
- }
2095
- }
2096
- }
2097
-
2098
- Ok(())
2099
- }
2100
-
2101
- /// Returns true for commands that are invoked via the hook pipeline
2102
- /// (i.e., commands that process rewritten shell commands).
2103
- /// Meta commands (init, gain, verify, etc.) are excluded because
2104
- /// they are run directly by the user, not through the hook.
2105
- /// Returns true for commands that go through the hook pipeline
2106
- /// and therefore require integrity verification.
2107
- ///
2108
- /// SECURITY: whitelist pattern — new commands are NOT integrity-checked
2109
- /// until explicitly added here. A forgotten command fails open (no check)
2110
- /// rather than creating false confidence about what's protected.
2111
- fn is_operational_command(cmd: &Commands) -> bool {
2112
- matches!(
2113
- cmd,
2114
- Commands::Ls { .. }
2115
- | Commands::Tree { .. }
2116
- | Commands::Read { .. }
2117
- | Commands::Smart { .. }
2118
- | Commands::Git { .. }
2119
- | Commands::Gh { .. }
2120
- | Commands::Pnpm { .. }
2121
- | Commands::Err { .. }
2122
- | Commands::Test { .. }
2123
- | Commands::Json { .. }
2124
- | Commands::Deps { .. }
2125
- | Commands::Env { .. }
2126
- | Commands::Find { .. }
2127
- | Commands::Diff { .. }
2128
- | Commands::Log { .. }
2129
- | Commands::Dotnet { .. }
2130
- | Commands::Docker { .. }
2131
- | Commands::Kubectl { .. }
2132
- | Commands::Summary { .. }
2133
- | Commands::Grep { .. }
2134
- | Commands::Wget { .. }
2135
- | Commands::Vitest { .. }
2136
- | Commands::Prisma { .. }
2137
- | Commands::Tsc { .. }
2138
- | Commands::Next { .. }
2139
- | Commands::Lint { .. }
2140
- | Commands::Prettier { .. }
2141
- | Commands::Playwright { .. }
2142
- | Commands::Cargo { .. }
2143
- | Commands::Npm { .. }
2144
- | Commands::Npx { .. }
2145
- | Commands::Curl { .. }
2146
- | Commands::Ruff { .. }
2147
- | Commands::Pytest { .. }
2148
- | Commands::Pip { .. }
2149
- | Commands::Go { .. }
2150
- | Commands::GolangciLint { .. }
2151
- | Commands::Gt { .. }
2152
- )
2153
- }
2154
-
2155
- #[cfg(test)]
2156
- mod tests {
2157
- use super::*;
2158
- use clap::Parser;
2159
-
2160
- #[test]
2161
- fn test_git_commit_single_message() {
2162
- let cli = Cli::try_parse_from(["rtk", "git", "commit", "-m", "fix: typo"]).unwrap();
2163
- match cli.command {
2164
- Commands::Git {
2165
- command: GitCommands::Commit { args },
2166
- ..
2167
- } => {
2168
- assert_eq!(args, vec!["-m", "fix: typo"]);
2169
- }
2170
- _ => panic!("Expected Git Commit command"),
2171
- }
2172
- }
2173
-
2174
- #[test]
2175
- fn test_git_commit_multiple_messages() {
2176
- let cli = Cli::try_parse_from([
2177
- "rtk",
2178
- "git",
2179
- "commit",
2180
- "-m",
2181
- "feat: add support",
2182
- "-m",
2183
- "Body paragraph here.",
2184
- ])
2185
- .unwrap();
2186
- match cli.command {
2187
- Commands::Git {
2188
- command: GitCommands::Commit { args },
2189
- ..
2190
- } => {
2191
- assert_eq!(
2192
- args,
2193
- vec!["-m", "feat: add support", "-m", "Body paragraph here."]
2194
- );
2195
- }
2196
- _ => panic!("Expected Git Commit command"),
2197
- }
2198
- }
2199
-
2200
- // #327: git commit -am "msg" was rejected by Clap
2201
- #[test]
2202
- fn test_git_commit_am_flag() {
2203
- let cli = Cli::try_parse_from(["rtk", "git", "commit", "-am", "quick fix"]).unwrap();
2204
- match cli.command {
2205
- Commands::Git {
2206
- command: GitCommands::Commit { args },
2207
- ..
2208
- } => {
2209
- assert_eq!(args, vec!["-am", "quick fix"]);
2210
- }
2211
- _ => panic!("Expected Git Commit command"),
2212
- }
2213
- }
2214
-
2215
- #[test]
2216
- fn test_git_commit_amend() {
2217
- let cli =
2218
- Cli::try_parse_from(["rtk", "git", "commit", "--amend", "-m", "new msg"]).unwrap();
2219
- match cli.command {
2220
- Commands::Git {
2221
- command: GitCommands::Commit { args },
2222
- ..
2223
- } => {
2224
- assert_eq!(args, vec!["--amend", "-m", "new msg"]);
2225
- }
2226
- _ => panic!("Expected Git Commit command"),
2227
- }
2228
- }
2229
-
2230
- #[test]
2231
- fn test_git_global_options_parsing() {
2232
- let cli =
2233
- Cli::try_parse_from(["rtk", "git", "--no-pager", "--no-optional-locks", "status"])
2234
- .unwrap();
2235
- match cli.command {
2236
- Commands::Git {
2237
- no_pager,
2238
- no_optional_locks,
2239
- bare,
2240
- literal_pathspecs,
2241
- ..
2242
- } => {
2243
- assert!(no_pager);
2244
- assert!(no_optional_locks);
2245
- assert!(!bare);
2246
- assert!(!literal_pathspecs);
2247
- }
2248
- _ => panic!("Expected Git command"),
2249
- }
2250
- }
2251
-
2252
- #[test]
2253
- fn test_git_commit_long_flag_multiple() {
2254
- let cli = Cli::try_parse_from([
2255
- "rtk",
2256
- "git",
2257
- "commit",
2258
- "--message",
2259
- "title",
2260
- "--message",
2261
- "body",
2262
- "--message",
2263
- "footer",
2264
- ])
2265
- .unwrap();
2266
- match cli.command {
2267
- Commands::Git {
2268
- command: GitCommands::Commit { args },
2269
- ..
2270
- } => {
2271
- assert_eq!(
2272
- args,
2273
- vec![
2274
- "--message",
2275
- "title",
2276
- "--message",
2277
- "body",
2278
- "--message",
2279
- "footer"
2280
- ]
2281
- );
2282
- }
2283
- _ => panic!("Expected Git Commit command"),
2284
- }
2285
- }
2286
-
2287
- #[test]
2288
- fn test_try_parse_valid_git_status() {
2289
- let result = Cli::try_parse_from(["rtk", "git", "status"]);
2290
- assert!(result.is_ok(), "git status should parse successfully");
2291
- }
2292
-
2293
- #[test]
2294
- fn test_try_parse_help_is_display_help() {
2295
- match Cli::try_parse_from(["rtk", "--help"]) {
2296
- Err(e) => assert_eq!(e.kind(), ErrorKind::DisplayHelp),
2297
- Ok(_) => panic!("Expected DisplayHelp error"),
2298
- }
2299
- }
2300
-
2301
- #[test]
2302
- fn test_try_parse_version_is_display_version() {
2303
- match Cli::try_parse_from(["rtk", "--version"]) {
2304
- Err(e) => assert_eq!(e.kind(), ErrorKind::DisplayVersion),
2305
- Ok(_) => panic!("Expected DisplayVersion error"),
2306
- }
2307
- }
2308
-
2309
- #[test]
2310
- fn test_try_parse_unknown_subcommand_is_error() {
2311
- match Cli::try_parse_from(["rtk", "nonexistent-command"]) {
2312
- Err(e) => assert!(!matches!(
2313
- e.kind(),
2314
- ErrorKind::DisplayHelp | ErrorKind::DisplayVersion
2315
- )),
2316
- Ok(_) => panic!("Expected parse error for unknown subcommand"),
2317
- }
2318
- }
2319
-
2320
- #[test]
2321
- fn test_try_parse_git_with_dash_c_succeeds() {
2322
- let result = Cli::try_parse_from(["rtk", "git", "-C", "/path", "status"]);
2323
- assert!(
2324
- result.is_ok(),
2325
- "git -C /path status should parse successfully"
2326
- );
2327
- if let Ok(cli) = result {
2328
- match cli.command {
2329
- Commands::Git { directory, .. } => {
2330
- assert_eq!(directory, vec!["/path"]);
2331
- }
2332
- _ => panic!("Expected Git command"),
2333
- }
2334
- }
2335
- }
2336
-
2337
- #[test]
2338
- fn test_gain_failures_flag_parses() {
2339
- let result = Cli::try_parse_from(["rtk", "gain", "--failures"]);
2340
- assert!(result.is_ok());
2341
- if let Ok(cli) = result {
2342
- match cli.command {
2343
- Commands::Gain { failures, .. } => assert!(failures),
2344
- _ => panic!("Expected Gain command"),
2345
- }
2346
- }
2347
- }
2348
-
2349
- #[test]
2350
- fn test_gain_failures_short_flag_parses() {
2351
- let result = Cli::try_parse_from(["rtk", "gain", "-F"]);
2352
- assert!(result.is_ok());
2353
- if let Ok(cli) = result {
2354
- match cli.command {
2355
- Commands::Gain { failures, .. } => assert!(failures),
2356
- _ => panic!("Expected Gain command"),
2357
- }
2358
- }
2359
- }
2360
-
2361
- #[test]
2362
- fn test_meta_commands_reject_bad_flags() {
2363
- // RTK meta-commands should produce parse errors (not fall through to raw execution).
2364
- // Skip "proxy" because it uses trailing_var_arg (accepts any args by design).
2365
- for cmd in RTK_META_COMMANDS {
2366
- if *cmd == "proxy" {
2367
- continue;
2368
- }
2369
- let result = Cli::try_parse_from(["rtk", cmd, "--nonexistent-flag-xyz"]);
2370
- assert!(
2371
- result.is_err(),
2372
- "Meta-command '{}' with bad flag should fail to parse",
2373
- cmd
2374
- );
2375
- }
2376
- }
2377
-
2378
- #[test]
2379
- fn test_meta_command_list_is_complete() {
2380
- // Verify all meta-commands are in the guard list by checking they parse with valid syntax
2381
- let meta_cmds_that_parse = [
2382
- vec!["rtk", "gain"],
2383
- vec!["rtk", "discover"],
2384
- vec!["rtk", "learn"],
2385
- vec!["rtk", "init"],
2386
- vec!["rtk", "config"],
2387
- vec!["rtk", "proxy", "echo", "hi"],
2388
- vec!["rtk", "hook-audit"],
2389
- vec!["rtk", "cc-economics"],
2390
- ];
2391
- for args in &meta_cmds_that_parse {
2392
- let result = Cli::try_parse_from(args.iter());
2393
- assert!(
2394
- result.is_ok(),
2395
- "Meta-command {:?} should parse successfully",
2396
- args
2397
- );
2398
- }
2399
- }
2400
-
2401
- #[test]
2402
- fn test_shell_split_simple() {
2403
- assert_eq!(
2404
- shell_split("head -50 file.php"),
2405
- vec!["head", "-50", "file.php"]
2406
- );
2407
- }
2408
-
2409
- #[test]
2410
- fn test_shell_split_double_quotes() {
2411
- assert_eq!(
2412
- shell_split(r#"git log --format="%H %s""#),
2413
- vec!["git", "log", "--format=%H %s"]
2414
- );
2415
- }
2416
-
2417
- #[test]
2418
- fn test_shell_split_single_quotes() {
2419
- assert_eq!(
2420
- shell_split("grep -r 'hello world' ."),
2421
- vec!["grep", "-r", "hello world", "."]
2422
- );
2423
- }
2424
-
2425
- #[test]
2426
- fn test_shell_split_single_word() {
2427
- assert_eq!(shell_split("ls"), vec!["ls"]);
2428
- }
2429
-
2430
- #[test]
2431
- fn test_shell_split_empty() {
2432
- let result: Vec<String> = shell_split("");
2433
- assert!(result.is_empty());
2434
- }
2435
-
2436
- #[test]
2437
- fn test_rewrite_clap_multi_args() {
2438
- // This is the bug KuSh reported: `rtk rewrite ls -al` failed because
2439
- // Clap rejected `-al` as an unknown flag. With trailing_var_arg + allow_hyphen_values,
2440
- // multiple args are accepted and joined into a single command string.
2441
- let cases = vec![
2442
- vec!["rtk", "rewrite", "ls", "-al"],
2443
- vec!["rtk", "rewrite", "git", "status"],
2444
- vec!["rtk", "rewrite", "npm", "exec"],
2445
- vec!["rtk", "rewrite", "cargo", "test"],
2446
- vec!["rtk", "rewrite", "du", "-sh", "."],
2447
- vec!["rtk", "rewrite", "head", "-50", "file.txt"],
2448
- ];
2449
- for args in &cases {
2450
- let result = Cli::try_parse_from(args.iter());
2451
- assert!(
2452
- result.is_ok(),
2453
- "rtk rewrite {:?} should parse (was failing before trailing_var_arg fix)",
2454
- &args[2..]
2455
- );
2456
- if let Ok(cli) = result {
2457
- match cli.command {
2458
- Commands::Rewrite { ref args } => {
2459
- assert!(args.len() >= 2, "rewrite args should capture all tokens");
2460
- }
2461
- _ => panic!("expected Rewrite command"),
2462
- }
2463
- }
2464
- }
2465
- }
2466
-
2467
- #[test]
2468
- fn test_rewrite_clap_quoted_single_arg() {
2469
- // Quoted form: `rtk rewrite "git status"` — single arg containing spaces
2470
- let result = Cli::try_parse_from(["rtk", "rewrite", "git status"]);
2471
- assert!(result.is_ok());
2472
- if let Ok(cli) = result {
2473
- match cli.command {
2474
- Commands::Rewrite { ref args } => {
2475
- assert_eq!(args.len(), 1);
2476
- assert_eq!(args[0], "git status");
2477
- }
2478
- _ => panic!("expected Rewrite command"),
2479
- }
2480
- }
2481
- }
2482
- }