@hasna/terminal 2.3.0 → 2.3.2

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 (267) hide show
  1. package/dist/App.js +404 -0
  2. package/dist/Browse.js +79 -0
  3. package/dist/FuzzyPicker.js +47 -0
  4. package/dist/Onboarding.js +51 -0
  5. package/dist/Spinner.js +12 -0
  6. package/dist/StatusBar.js +49 -0
  7. package/dist/ai.js +322 -0
  8. package/dist/cache.js +41 -0
  9. package/dist/cli.js +64 -16
  10. package/dist/command-rewriter.js +64 -0
  11. package/dist/command-validator.js +86 -0
  12. package/dist/compression.js +107 -0
  13. package/dist/context-hints.js +275 -0
  14. package/dist/diff-cache.js +107 -0
  15. package/dist/discover.js +212 -0
  16. package/dist/economy.js +123 -0
  17. package/dist/expand-store.js +38 -0
  18. package/dist/file-cache.js +72 -0
  19. package/dist/file-index.js +62 -0
  20. package/dist/history.js +62 -0
  21. package/dist/lazy-executor.js +54 -0
  22. package/dist/line-dedup.js +59 -0
  23. package/dist/loop-detector.js +75 -0
  24. package/dist/mcp/install.js +98 -0
  25. package/dist/mcp/server.js +569 -0
  26. package/dist/noise-filter.js +86 -0
  27. package/dist/output-processor.js +129 -0
  28. package/dist/output-router.js +41 -0
  29. package/dist/output-store.js +111 -0
  30. package/dist/parsers/base.js +2 -0
  31. package/dist/parsers/build.js +64 -0
  32. package/dist/parsers/errors.js +101 -0
  33. package/dist/parsers/files.js +78 -0
  34. package/dist/parsers/git.js +99 -0
  35. package/dist/parsers/index.js +48 -0
  36. package/dist/parsers/tests.js +89 -0
  37. package/dist/providers/anthropic.js +39 -0
  38. package/dist/providers/base.js +4 -0
  39. package/dist/providers/cerebras.js +95 -0
  40. package/dist/providers/groq.js +95 -0
  41. package/dist/providers/index.js +73 -0
  42. package/dist/providers/xai.js +95 -0
  43. package/dist/recipes/model.js +20 -0
  44. package/dist/recipes/storage.js +136 -0
  45. package/dist/search/content-search.js +68 -0
  46. package/dist/search/file-search.js +61 -0
  47. package/dist/search/filters.js +34 -0
  48. package/dist/search/index.js +5 -0
  49. package/dist/search/semantic.js +320 -0
  50. package/dist/session-boot.js +59 -0
  51. package/dist/session-context.js +55 -0
  52. package/dist/sessions-db.js +173 -0
  53. package/dist/smart-display.js +286 -0
  54. package/dist/snapshots.js +51 -0
  55. package/dist/supervisor.js +112 -0
  56. package/dist/test-watchlist.js +131 -0
  57. package/dist/tool-profiles.js +122 -0
  58. package/dist/tree.js +94 -0
  59. package/dist/usage-cache.js +65 -0
  60. package/package.json +8 -1
  61. package/src/ai.ts +8 -0
  62. package/src/cli.tsx +57 -18
  63. package/src/output-processor.ts +6 -1
  64. package/src/output-store.ts +58 -12
  65. package/src/tool-profiles.ts +139 -0
  66. package/.claude/scheduled_tasks.lock +0 -1
  67. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  68. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  69. package/CONTRIBUTING.md +0 -80
  70. package/benchmarks/benchmark.mjs +0 -115
  71. package/imported_modules.txt +0 -0
  72. package/temp/rtk/.claude/agents/code-reviewer.md +0 -221
  73. package/temp/rtk/.claude/agents/debugger.md +0 -519
  74. package/temp/rtk/.claude/agents/rtk-testing-specialist.md +0 -461
  75. package/temp/rtk/.claude/agents/rust-rtk.md +0 -511
  76. package/temp/rtk/.claude/agents/technical-writer.md +0 -355
  77. package/temp/rtk/.claude/commands/diagnose.md +0 -352
  78. package/temp/rtk/.claude/commands/test-routing.md +0 -362
  79. package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +0 -16
  80. package/temp/rtk/.claude/hooks/rtk-rewrite.sh +0 -70
  81. package/temp/rtk/.claude/hooks/rtk-suggest.sh +0 -152
  82. package/temp/rtk/.claude/rules/cli-testing.md +0 -526
  83. package/temp/rtk/.claude/skills/issue-triage/SKILL.md +0 -348
  84. package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +0 -134
  85. package/temp/rtk/.claude/skills/performance.md +0 -435
  86. package/temp/rtk/.claude/skills/pr-triage/SKILL.md +0 -315
  87. package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +0 -71
  88. package/temp/rtk/.claude/skills/repo-recap.md +0 -206
  89. package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +0 -78
  90. package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +0 -124
  91. package/temp/rtk/.claude/skills/security-guardian.md +0 -503
  92. package/temp/rtk/.claude/skills/ship.md +0 -404
  93. package/temp/rtk/.github/workflows/benchmark.yml +0 -34
  94. package/temp/rtk/.github/workflows/dco-check.yaml +0 -12
  95. package/temp/rtk/.github/workflows/release-please.yml +0 -51
  96. package/temp/rtk/.github/workflows/release.yml +0 -343
  97. package/temp/rtk/.github/workflows/security-check.yml +0 -135
  98. package/temp/rtk/.github/workflows/validate-docs.yml +0 -78
  99. package/temp/rtk/.release-please-manifest.json +0 -3
  100. package/temp/rtk/ARCHITECTURE.md +0 -1491
  101. package/temp/rtk/CHANGELOG.md +0 -640
  102. package/temp/rtk/CLAUDE.md +0 -605
  103. package/temp/rtk/CONTRIBUTING.md +0 -199
  104. package/temp/rtk/Cargo.lock +0 -1668
  105. package/temp/rtk/Cargo.toml +0 -64
  106. package/temp/rtk/Formula/rtk.rb +0 -43
  107. package/temp/rtk/INSTALL.md +0 -390
  108. package/temp/rtk/LICENSE +0 -21
  109. package/temp/rtk/README.md +0 -386
  110. package/temp/rtk/README_es.md +0 -159
  111. package/temp/rtk/README_fr.md +0 -197
  112. package/temp/rtk/README_ja.md +0 -159
  113. package/temp/rtk/README_ko.md +0 -159
  114. package/temp/rtk/README_zh.md +0 -167
  115. package/temp/rtk/ROADMAP.md +0 -15
  116. package/temp/rtk/SECURITY.md +0 -217
  117. package/temp/rtk/TEST_EXEC_TIME.md +0 -102
  118. package/temp/rtk/build.rs +0 -57
  119. package/temp/rtk/docs/AUDIT_GUIDE.md +0 -432
  120. package/temp/rtk/docs/FEATURES.md +0 -1410
  121. package/temp/rtk/docs/TROUBLESHOOTING.md +0 -309
  122. package/temp/rtk/docs/filter-workflow.md +0 -102
  123. package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
  124. package/temp/rtk/docs/tracking.md +0 -583
  125. package/temp/rtk/hooks/opencode-rtk.ts +0 -39
  126. package/temp/rtk/hooks/rtk-awareness.md +0 -29
  127. package/temp/rtk/hooks/rtk-rewrite.sh +0 -61
  128. package/temp/rtk/hooks/test-rtk-rewrite.sh +0 -442
  129. package/temp/rtk/install.sh +0 -124
  130. package/temp/rtk/release-please-config.json +0 -10
  131. package/temp/rtk/scripts/benchmark.sh +0 -592
  132. package/temp/rtk/scripts/check-installation.sh +0 -162
  133. package/temp/rtk/scripts/install-local.sh +0 -37
  134. package/temp/rtk/scripts/rtk-economics.sh +0 -137
  135. package/temp/rtk/scripts/test-all.sh +0 -561
  136. package/temp/rtk/scripts/test-aristote.sh +0 -227
  137. package/temp/rtk/scripts/test-tracking.sh +0 -79
  138. package/temp/rtk/scripts/update-readme-metrics.sh +0 -32
  139. package/temp/rtk/scripts/validate-docs.sh +0 -73
  140. package/temp/rtk/src/aws_cmd.rs +0 -880
  141. package/temp/rtk/src/binlog.rs +0 -1645
  142. package/temp/rtk/src/cargo_cmd.rs +0 -1727
  143. package/temp/rtk/src/cc_economics.rs +0 -1157
  144. package/temp/rtk/src/ccusage.rs +0 -340
  145. package/temp/rtk/src/config.rs +0 -187
  146. package/temp/rtk/src/container.rs +0 -855
  147. package/temp/rtk/src/curl_cmd.rs +0 -134
  148. package/temp/rtk/src/deps.rs +0 -268
  149. package/temp/rtk/src/diff_cmd.rs +0 -367
  150. package/temp/rtk/src/discover/mod.rs +0 -274
  151. package/temp/rtk/src/discover/provider.rs +0 -388
  152. package/temp/rtk/src/discover/registry.rs +0 -2022
  153. package/temp/rtk/src/discover/report.rs +0 -202
  154. package/temp/rtk/src/discover/rules.rs +0 -667
  155. package/temp/rtk/src/display_helpers.rs +0 -402
  156. package/temp/rtk/src/dotnet_cmd.rs +0 -1771
  157. package/temp/rtk/src/dotnet_format_report.rs +0 -133
  158. package/temp/rtk/src/dotnet_trx.rs +0 -593
  159. package/temp/rtk/src/env_cmd.rs +0 -204
  160. package/temp/rtk/src/filter.rs +0 -462
  161. package/temp/rtk/src/filters/README.md +0 -52
  162. package/temp/rtk/src/filters/ansible-playbook.toml +0 -34
  163. package/temp/rtk/src/filters/basedpyright.toml +0 -47
  164. package/temp/rtk/src/filters/biome.toml +0 -45
  165. package/temp/rtk/src/filters/brew-install.toml +0 -37
  166. package/temp/rtk/src/filters/composer-install.toml +0 -40
  167. package/temp/rtk/src/filters/df.toml +0 -16
  168. package/temp/rtk/src/filters/dotnet-build.toml +0 -64
  169. package/temp/rtk/src/filters/du.toml +0 -16
  170. package/temp/rtk/src/filters/fail2ban-client.toml +0 -15
  171. package/temp/rtk/src/filters/gcc.toml +0 -49
  172. package/temp/rtk/src/filters/gcloud.toml +0 -22
  173. package/temp/rtk/src/filters/hadolint.toml +0 -24
  174. package/temp/rtk/src/filters/helm.toml +0 -29
  175. package/temp/rtk/src/filters/iptables.toml +0 -27
  176. package/temp/rtk/src/filters/jj.toml +0 -28
  177. package/temp/rtk/src/filters/jq.toml +0 -24
  178. package/temp/rtk/src/filters/make.toml +0 -41
  179. package/temp/rtk/src/filters/markdownlint.toml +0 -24
  180. package/temp/rtk/src/filters/mix-compile.toml +0 -27
  181. package/temp/rtk/src/filters/mix-format.toml +0 -15
  182. package/temp/rtk/src/filters/mvn-build.toml +0 -44
  183. package/temp/rtk/src/filters/oxlint.toml +0 -43
  184. package/temp/rtk/src/filters/ping.toml +0 -63
  185. package/temp/rtk/src/filters/pio-run.toml +0 -40
  186. package/temp/rtk/src/filters/poetry-install.toml +0 -50
  187. package/temp/rtk/src/filters/pre-commit.toml +0 -35
  188. package/temp/rtk/src/filters/ps.toml +0 -16
  189. package/temp/rtk/src/filters/quarto-render.toml +0 -41
  190. package/temp/rtk/src/filters/rsync.toml +0 -48
  191. package/temp/rtk/src/filters/shellcheck.toml +0 -27
  192. package/temp/rtk/src/filters/shopify-theme.toml +0 -29
  193. package/temp/rtk/src/filters/skopeo.toml +0 -45
  194. package/temp/rtk/src/filters/sops.toml +0 -16
  195. package/temp/rtk/src/filters/ssh.toml +0 -44
  196. package/temp/rtk/src/filters/stat.toml +0 -34
  197. package/temp/rtk/src/filters/swift-build.toml +0 -41
  198. package/temp/rtk/src/filters/systemctl-status.toml +0 -33
  199. package/temp/rtk/src/filters/terraform-plan.toml +0 -35
  200. package/temp/rtk/src/filters/tofu-fmt.toml +0 -16
  201. package/temp/rtk/src/filters/tofu-init.toml +0 -38
  202. package/temp/rtk/src/filters/tofu-plan.toml +0 -35
  203. package/temp/rtk/src/filters/tofu-validate.toml +0 -17
  204. package/temp/rtk/src/filters/trunk-build.toml +0 -39
  205. package/temp/rtk/src/filters/ty.toml +0 -50
  206. package/temp/rtk/src/filters/uv-sync.toml +0 -37
  207. package/temp/rtk/src/filters/xcodebuild.toml +0 -99
  208. package/temp/rtk/src/filters/yamllint.toml +0 -25
  209. package/temp/rtk/src/find_cmd.rs +0 -598
  210. package/temp/rtk/src/format_cmd.rs +0 -386
  211. package/temp/rtk/src/gain.rs +0 -723
  212. package/temp/rtk/src/gh_cmd.rs +0 -1651
  213. package/temp/rtk/src/git.rs +0 -2012
  214. package/temp/rtk/src/go_cmd.rs +0 -592
  215. package/temp/rtk/src/golangci_cmd.rs +0 -254
  216. package/temp/rtk/src/grep_cmd.rs +0 -288
  217. package/temp/rtk/src/gt_cmd.rs +0 -810
  218. package/temp/rtk/src/hook_audit_cmd.rs +0 -283
  219. package/temp/rtk/src/hook_check.rs +0 -171
  220. package/temp/rtk/src/init.rs +0 -1859
  221. package/temp/rtk/src/integrity.rs +0 -537
  222. package/temp/rtk/src/json_cmd.rs +0 -231
  223. package/temp/rtk/src/learn/detector.rs +0 -628
  224. package/temp/rtk/src/learn/mod.rs +0 -119
  225. package/temp/rtk/src/learn/report.rs +0 -184
  226. package/temp/rtk/src/lint_cmd.rs +0 -694
  227. package/temp/rtk/src/local_llm.rs +0 -316
  228. package/temp/rtk/src/log_cmd.rs +0 -248
  229. package/temp/rtk/src/ls.rs +0 -324
  230. package/temp/rtk/src/main.rs +0 -2482
  231. package/temp/rtk/src/mypy_cmd.rs +0 -389
  232. package/temp/rtk/src/next_cmd.rs +0 -241
  233. package/temp/rtk/src/npm_cmd.rs +0 -236
  234. package/temp/rtk/src/parser/README.md +0 -267
  235. package/temp/rtk/src/parser/error.rs +0 -46
  236. package/temp/rtk/src/parser/formatter.rs +0 -336
  237. package/temp/rtk/src/parser/mod.rs +0 -311
  238. package/temp/rtk/src/parser/types.rs +0 -119
  239. package/temp/rtk/src/pip_cmd.rs +0 -302
  240. package/temp/rtk/src/playwright_cmd.rs +0 -479
  241. package/temp/rtk/src/pnpm_cmd.rs +0 -573
  242. package/temp/rtk/src/prettier_cmd.rs +0 -221
  243. package/temp/rtk/src/prisma_cmd.rs +0 -482
  244. package/temp/rtk/src/psql_cmd.rs +0 -382
  245. package/temp/rtk/src/pytest_cmd.rs +0 -384
  246. package/temp/rtk/src/read.rs +0 -217
  247. package/temp/rtk/src/rewrite_cmd.rs +0 -50
  248. package/temp/rtk/src/ruff_cmd.rs +0 -402
  249. package/temp/rtk/src/runner.rs +0 -271
  250. package/temp/rtk/src/summary.rs +0 -297
  251. package/temp/rtk/src/tee.rs +0 -405
  252. package/temp/rtk/src/telemetry.rs +0 -248
  253. package/temp/rtk/src/toml_filter.rs +0 -1655
  254. package/temp/rtk/src/tracking.rs +0 -1416
  255. package/temp/rtk/src/tree.rs +0 -209
  256. package/temp/rtk/src/tsc_cmd.rs +0 -259
  257. package/temp/rtk/src/utils.rs +0 -432
  258. package/temp/rtk/src/verify_cmd.rs +0 -47
  259. package/temp/rtk/src/vitest_cmd.rs +0 -385
  260. package/temp/rtk/src/wc_cmd.rs +0 -401
  261. package/temp/rtk/src/wget_cmd.rs +0 -260
  262. package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +0 -11
  263. package/temp/rtk/tests/fixtures/dotnet/format_changes.json +0 -31
  264. package/temp/rtk/tests/fixtures/dotnet/format_empty.json +0 -1
  265. package/temp/rtk/tests/fixtures/dotnet/format_success.json +0 -12
  266. package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +0 -18
  267. package/tsconfig.json +0 -15
@@ -1,401 +0,0 @@
1
- /// Compact filter for `wc` — strips redundant paths and alignment padding.
2
- ///
3
- /// Compression examples:
4
- /// - `wc file.py` → `30L 96W 978B`
5
- /// - `wc -l file.py` → `30`
6
- /// - `wc -w file.py` → `96`
7
- /// - `wc -c file.py` → `978`
8
- /// - `wc -l *.py` → table with common path prefix stripped
9
- use crate::tracking;
10
- use anyhow::{Context, Result};
11
- use std::process::Command;
12
-
13
- pub fn run(args: &[String], verbose: u8) -> Result<()> {
14
- let timer = tracking::TimedExecution::start();
15
-
16
- let mut cmd = Command::new("wc");
17
- for arg in args {
18
- cmd.arg(arg);
19
- }
20
-
21
- if verbose > 0 {
22
- eprintln!("Running: wc {}", args.join(" "));
23
- }
24
-
25
- let output = cmd.output().context("Failed to run wc")?;
26
- let stdout = String::from_utf8_lossy(&output.stdout);
27
- let stderr = String::from_utf8_lossy(&output.stderr);
28
-
29
- if !output.status.success() {
30
- let msg = if stderr.trim().is_empty() {
31
- stdout.trim().to_string()
32
- } else {
33
- stderr.trim().to_string()
34
- };
35
- eprintln!("FAILED: wc {}", msg);
36
- std::process::exit(output.status.code().unwrap_or(1));
37
- }
38
-
39
- let raw = stdout.to_string();
40
-
41
- // Detect which columns the user requested
42
- let mode = detect_mode(args);
43
- let filtered = filter_wc_output(&raw, &mode);
44
- println!("{}", filtered);
45
-
46
- timer.track(
47
- &format!("wc {}", args.join(" ")),
48
- &format!("rtk wc {}", args.join(" ")),
49
- &raw,
50
- &filtered,
51
- );
52
-
53
- Ok(())
54
- }
55
-
56
- /// Which columns the user requested
57
- #[derive(Debug, PartialEq)]
58
- enum WcMode {
59
- /// Default: lines, words, bytes (3 columns)
60
- Full,
61
- /// Lines only (-l)
62
- Lines,
63
- /// Words only (-w)
64
- Words,
65
- /// Bytes only (-c)
66
- Bytes,
67
- /// Chars only (-m)
68
- Chars,
69
- /// Multiple flags combined — keep compact format
70
- Mixed,
71
- }
72
-
73
- fn detect_mode(args: &[String]) -> WcMode {
74
- let flags: Vec<&str> = args
75
- .iter()
76
- .filter(|a| a.starts_with('-'))
77
- .map(|s| s.as_str())
78
- .collect();
79
-
80
- if flags.is_empty() {
81
- return WcMode::Full;
82
- }
83
-
84
- // Collect all single-char flags (handles combined flags like -lw)
85
- let mut has_l = false;
86
- let mut has_w = false;
87
- let mut has_c = false;
88
- let mut has_m = false;
89
- let mut flag_count = 0;
90
-
91
- for flag in &flags {
92
- for ch in flag.chars().skip(1) {
93
- match ch {
94
- 'l' => {
95
- has_l = true;
96
- flag_count += 1;
97
- }
98
- 'w' => {
99
- has_w = true;
100
- flag_count += 1;
101
- }
102
- 'c' => {
103
- has_c = true;
104
- flag_count += 1;
105
- }
106
- 'm' => {
107
- has_m = true;
108
- flag_count += 1;
109
- }
110
- _ => {}
111
- }
112
- }
113
- }
114
-
115
- if flag_count == 0 {
116
- return WcMode::Full;
117
- }
118
- if flag_count > 1 {
119
- return WcMode::Mixed;
120
- }
121
-
122
- if has_l {
123
- WcMode::Lines
124
- } else if has_w {
125
- WcMode::Words
126
- } else if has_c {
127
- WcMode::Bytes
128
- } else if has_m {
129
- WcMode::Chars
130
- } else {
131
- WcMode::Full
132
- }
133
- }
134
-
135
- fn filter_wc_output(raw: &str, mode: &WcMode) -> String {
136
- let lines: Vec<&str> = raw.trim().lines().collect();
137
-
138
- if lines.is_empty() {
139
- return String::new();
140
- }
141
-
142
- // Single file (one output line, no "total")
143
- if lines.len() == 1 {
144
- return format_single_line(lines[0], mode);
145
- }
146
-
147
- // Multiple files — compact table
148
- format_multi_line(&lines, mode)
149
- }
150
-
151
- /// Format a single wc output line (one file or stdin)
152
- fn format_single_line(line: &str, mode: &WcMode) -> String {
153
- let parts: Vec<&str> = line.split_whitespace().collect();
154
-
155
- match mode {
156
- WcMode::Lines | WcMode::Words | WcMode::Bytes | WcMode::Chars => {
157
- // First number is the only requested column
158
- parts.first().map(|s| s.to_string()).unwrap_or_default()
159
- }
160
- WcMode::Full => {
161
- if parts.len() >= 3 {
162
- format!("{}L {}W {}B", parts[0], parts[1], parts[2])
163
- } else {
164
- line.trim().to_string()
165
- }
166
- }
167
- WcMode::Mixed => {
168
- // Strip file path, keep numbers only
169
- if parts.len() >= 2 {
170
- let last_is_path = parts.last().map_or(false, |p| p.parse::<u64>().is_err());
171
- if last_is_path {
172
- parts[..parts.len() - 1].join(" ")
173
- } else {
174
- parts.join(" ")
175
- }
176
- } else {
177
- line.trim().to_string()
178
- }
179
- }
180
- }
181
- }
182
-
183
- /// Format multiple files as a compact table
184
- fn format_multi_line(lines: &[&str], mode: &WcMode) -> String {
185
- let mut result = Vec::new();
186
-
187
- // Find common directory prefix to shorten paths
188
- let paths: Vec<&str> = lines
189
- .iter()
190
- .filter_map(|line| {
191
- let parts: Vec<&str> = line.split_whitespace().collect();
192
- parts.last().copied()
193
- })
194
- .filter(|p| *p != "total")
195
- .collect();
196
-
197
- let common_prefix = find_common_prefix(&paths);
198
-
199
- for line in lines {
200
- let parts: Vec<&str> = line.split_whitespace().collect();
201
- if parts.is_empty() {
202
- continue;
203
- }
204
-
205
- let is_total = parts.last().map_or(false, |p| *p == "total");
206
-
207
- match mode {
208
- WcMode::Lines | WcMode::Words | WcMode::Bytes | WcMode::Chars => {
209
- if is_total {
210
- result.push(format!("Σ {}", parts.first().unwrap_or(&"0")));
211
- } else {
212
- let name = strip_prefix(parts.last().unwrap_or(&""), &common_prefix);
213
- result.push(format!("{} {}", parts.first().unwrap_or(&"0"), name));
214
- }
215
- }
216
- WcMode::Full => {
217
- if is_total {
218
- result.push(format!(
219
- "Σ {}L {}W {}B",
220
- parts.first().unwrap_or(&"0"),
221
- parts.get(1).unwrap_or(&"0"),
222
- parts.get(2).unwrap_or(&"0"),
223
- ));
224
- } else if parts.len() >= 4 {
225
- let name = strip_prefix(parts[3], &common_prefix);
226
- result.push(format!(
227
- "{}L {}W {}B {}",
228
- parts[0], parts[1], parts[2], name
229
- ));
230
- } else {
231
- result.push(line.trim().to_string());
232
- }
233
- }
234
- WcMode::Mixed => {
235
- if is_total {
236
- let nums: Vec<&str> = parts[..parts.len() - 1].to_vec();
237
- result.push(format!("Σ {}", nums.join(" ")));
238
- } else if parts.len() >= 2 {
239
- let last_is_path = parts.last().map_or(false, |p| p.parse::<u64>().is_err());
240
- if last_is_path {
241
- let name = strip_prefix(parts.last().unwrap_or(&""), &common_prefix);
242
- let nums: Vec<&str> = parts[..parts.len() - 1].to_vec();
243
- result.push(format!("{} {}", nums.join(" "), name));
244
- } else {
245
- result.push(parts.join(" "));
246
- }
247
- } else {
248
- result.push(line.trim().to_string());
249
- }
250
- }
251
- }
252
- }
253
-
254
- result.join("\n")
255
- }
256
-
257
- /// Find common directory prefix among paths
258
- fn find_common_prefix(paths: &[&str]) -> String {
259
- if paths.len() <= 1 {
260
- return String::new();
261
- }
262
-
263
- let first = paths[0];
264
- let prefix = if let Some(pos) = first.rfind('/') {
265
- &first[..=pos]
266
- } else {
267
- return String::new();
268
- };
269
-
270
- if paths.iter().all(|p| p.starts_with(prefix)) {
271
- return prefix.to_string();
272
- }
273
-
274
- // Try shorter prefixes by removing right-most segments
275
- let mut candidate = prefix.to_string();
276
- while !candidate.is_empty() {
277
- if paths.iter().all(|p| p.starts_with(&candidate)) {
278
- return candidate;
279
- }
280
- if let Some(pos) = candidate[..candidate.len() - 1].rfind('/') {
281
- candidate.truncate(pos + 1);
282
- } else {
283
- return String::new();
284
- }
285
- }
286
- String::new()
287
- }
288
-
289
- /// Strip common prefix from a path
290
- fn strip_prefix<'a>(path: &'a str, prefix: &str) -> &'a str {
291
- if prefix.is_empty() {
292
- return path;
293
- }
294
- path.strip_prefix(prefix).unwrap_or(path)
295
- }
296
-
297
- #[cfg(test)]
298
- mod tests {
299
- use super::*;
300
-
301
- #[test]
302
- fn test_single_file_full() {
303
- let raw = " 30 96 978 scripts/find_duplicate_attrs.py\n";
304
- let result = filter_wc_output(raw, &WcMode::Full);
305
- assert_eq!(result, "30L 96W 978B");
306
- }
307
-
308
- #[test]
309
- fn test_single_file_lines_only() {
310
- let raw = " 30 scripts/find_duplicate_attrs.py\n";
311
- let result = filter_wc_output(raw, &WcMode::Lines);
312
- assert_eq!(result, "30");
313
- }
314
-
315
- #[test]
316
- fn test_single_file_words_only() {
317
- let raw = " 96 scripts/find_duplicate_attrs.py\n";
318
- let result = filter_wc_output(raw, &WcMode::Words);
319
- assert_eq!(result, "96");
320
- }
321
-
322
- #[test]
323
- fn test_stdin_full() {
324
- let raw = " 30 96 978\n";
325
- let result = filter_wc_output(raw, &WcMode::Full);
326
- assert_eq!(result, "30L 96W 978B");
327
- }
328
-
329
- #[test]
330
- fn test_stdin_lines() {
331
- let raw = " 30\n";
332
- let result = filter_wc_output(raw, &WcMode::Lines);
333
- assert_eq!(result, "30");
334
- }
335
-
336
- #[test]
337
- fn test_multi_file_lines() {
338
- let raw = " 30 src/main.rs\n 50 src/lib.rs\n 80 total\n";
339
- let result = filter_wc_output(raw, &WcMode::Lines);
340
- assert_eq!(result, "30 main.rs\n50 lib.rs\nΣ 80");
341
- }
342
-
343
- #[test]
344
- fn test_multi_file_full() {
345
- let raw = " 30 96 978 src/main.rs\n 50 120 1500 src/lib.rs\n 80 216 2478 total\n";
346
- let result = filter_wc_output(raw, &WcMode::Full);
347
- assert_eq!(
348
- result,
349
- "30L 96W 978B main.rs\n50L 120W 1500B lib.rs\nΣ 80L 216W 2478B"
350
- );
351
- }
352
-
353
- #[test]
354
- fn test_detect_mode_full() {
355
- let args: Vec<String> = vec!["file.py".into()];
356
- assert_eq!(detect_mode(&args), WcMode::Full);
357
- }
358
-
359
- #[test]
360
- fn test_detect_mode_lines() {
361
- let args: Vec<String> = vec!["-l".into(), "file.py".into()];
362
- assert_eq!(detect_mode(&args), WcMode::Lines);
363
- }
364
-
365
- #[test]
366
- fn test_detect_mode_mixed() {
367
- let args: Vec<String> = vec!["-lw".into(), "file.py".into()];
368
- assert_eq!(detect_mode(&args), WcMode::Mixed);
369
- }
370
-
371
- #[test]
372
- fn test_detect_mode_separate_flags() {
373
- let args: Vec<String> = vec!["-l".into(), "-w".into(), "file.py".into()];
374
- assert_eq!(detect_mode(&args), WcMode::Mixed);
375
- }
376
-
377
- #[test]
378
- fn test_common_prefix() {
379
- let paths = vec!["src/main.rs", "src/lib.rs", "src/utils.rs"];
380
- assert_eq!(find_common_prefix(&paths), "src/");
381
- }
382
-
383
- #[test]
384
- fn test_no_common_prefix() {
385
- let paths = vec!["main.rs", "lib.rs"];
386
- assert_eq!(find_common_prefix(&paths), "");
387
- }
388
-
389
- #[test]
390
- fn test_deep_common_prefix() {
391
- let paths = vec!["src/cmd/wc.rs", "src/cmd/ls.rs"];
392
- assert_eq!(find_common_prefix(&paths), "src/cmd/");
393
- }
394
-
395
- #[test]
396
- fn test_empty() {
397
- let raw = "";
398
- let result = filter_wc_output(raw, &WcMode::Full);
399
- assert_eq!(result, "");
400
- }
401
- }
@@ -1,260 +0,0 @@
1
- use crate::tracking;
2
- use anyhow::{Context, Result};
3
- use std::process::Command;
4
-
5
- /// Compact wget - strips progress bars, shows only result
6
- pub fn run(url: &str, args: &[String], verbose: u8) -> Result<()> {
7
- let timer = tracking::TimedExecution::start();
8
-
9
- if verbose > 0 {
10
- eprintln!("wget: {}", url);
11
- }
12
-
13
- // Run wget normally but capture output to parse it
14
- let mut cmd_args: Vec<&str> = vec![];
15
-
16
- // Add user args
17
- for arg in args {
18
- cmd_args.push(arg);
19
- }
20
- cmd_args.push(url);
21
-
22
- let output = Command::new("wget")
23
- .args(&cmd_args)
24
- .output()
25
- .context("Failed to run wget")?;
26
-
27
- let stderr = String::from_utf8_lossy(&output.stderr);
28
- let stdout = String::from_utf8_lossy(&output.stdout);
29
-
30
- let raw_output = format!("{}\n{}", stderr, stdout);
31
-
32
- if output.status.success() {
33
- let filename = extract_filename_from_output(&stderr, url, args);
34
- let size = get_file_size(&filename);
35
- let msg = format!(
36
- "⬇️ {} ok | {} | {}",
37
- compact_url(url),
38
- filename,
39
- format_size(size)
40
- );
41
- println!("{}", msg);
42
- timer.track(&format!("wget {}", url), "rtk wget", &raw_output, &msg);
43
- } else {
44
- let error = parse_error(&stderr, &stdout);
45
- let msg = format!("⬇️ {} FAILED: {}", compact_url(url), error);
46
- println!("{}", msg);
47
- timer.track(&format!("wget {}", url), "rtk wget", &raw_output, &msg);
48
- }
49
-
50
- Ok(())
51
- }
52
-
53
- /// Run wget and output to stdout (for piping)
54
- pub fn run_stdout(url: &str, args: &[String], verbose: u8) -> Result<()> {
55
- let timer = tracking::TimedExecution::start();
56
-
57
- if verbose > 0 {
58
- eprintln!("wget: {} -> stdout", url);
59
- }
60
-
61
- let mut cmd_args = vec!["-q", "-O", "-"];
62
- for arg in args {
63
- cmd_args.push(arg);
64
- }
65
- cmd_args.push(url);
66
-
67
- let output = Command::new("wget")
68
- .args(&cmd_args)
69
- .output()
70
- .context("Failed to run wget")?;
71
-
72
- if output.status.success() {
73
- let content = String::from_utf8_lossy(&output.stdout);
74
- let lines: Vec<&str> = content.lines().collect();
75
- let total = lines.len();
76
- let raw_output = content.to_string();
77
-
78
- let mut rtk_output = String::new();
79
- if total > 20 {
80
- rtk_output.push_str(&format!(
81
- "⬇️ {} ok | {} lines | {}\n",
82
- compact_url(url),
83
- total,
84
- format_size(output.stdout.len() as u64)
85
- ));
86
- rtk_output.push_str("--- first 10 lines ---\n");
87
- for line in lines.iter().take(10) {
88
- rtk_output.push_str(&format!("{}\n", truncate_line(line, 100)));
89
- }
90
- rtk_output.push_str(&format!("... +{} more lines", total - 10));
91
- } else {
92
- rtk_output.push_str(&format!("⬇️ {} ok | {} lines\n", compact_url(url), total));
93
- for line in &lines {
94
- rtk_output.push_str(&format!("{}\n", line));
95
- }
96
- }
97
- print!("{}", rtk_output);
98
- timer.track(
99
- &format!("wget -O - {}", url),
100
- "rtk wget -o",
101
- &raw_output,
102
- &rtk_output,
103
- );
104
- } else {
105
- let stderr = String::from_utf8_lossy(&output.stderr);
106
- let error = parse_error(&stderr, "");
107
- let msg = format!("⬇️ {} FAILED: {}", compact_url(url), error);
108
- println!("{}", msg);
109
- timer.track(&format!("wget -O - {}", url), "rtk wget -o", &stderr, &msg);
110
- }
111
-
112
- Ok(())
113
- }
114
-
115
- fn extract_filename_from_output(stderr: &str, url: &str, args: &[String]) -> String {
116
- // Check for -O argument first
117
- for (i, arg) in args.iter().enumerate() {
118
- if arg == "-O" || arg == "--output-document" {
119
- if let Some(name) = args.get(i + 1) {
120
- return name.clone();
121
- }
122
- }
123
- if let Some(name) = arg.strip_prefix("-O") {
124
- return name.to_string();
125
- }
126
- }
127
-
128
- // Parse wget output for "Sauvegarde en" or "Saving to"
129
- for line in stderr.lines() {
130
- // French: Sauvegarde en : « filename »
131
- if line.contains("Sauvegarde en") || line.contains("Saving to") {
132
- // Use char-based parsing to handle Unicode properly
133
- let chars: Vec<char> = line.chars().collect();
134
- let mut start_idx = None;
135
- let mut end_idx = None;
136
-
137
- for (i, c) in chars.iter().enumerate() {
138
- if *c == '«' || (*c == '\'' && start_idx.is_none()) {
139
- start_idx = Some(i);
140
- }
141
- if *c == '»' || (*c == '\'' && start_idx.is_some()) {
142
- end_idx = Some(i);
143
- }
144
- }
145
-
146
- if let (Some(s), Some(e)) = (start_idx, end_idx) {
147
- if e > s + 1 {
148
- let filename: String = chars[s + 1..e].iter().collect();
149
- return filename.trim().to_string();
150
- }
151
- }
152
- }
153
- }
154
-
155
- // Fallback: extract from URL
156
- let path = url.rsplit("://").next().unwrap_or(url);
157
- let filename = path
158
- .rsplit('/')
159
- .next()
160
- .unwrap_or("index.html")
161
- .split('?')
162
- .next()
163
- .unwrap_or("index.html");
164
-
165
- if filename.is_empty() || !filename.contains('.') {
166
- "index.html".to_string()
167
- } else {
168
- filename.to_string()
169
- }
170
- }
171
-
172
- fn get_file_size(filename: &str) -> u64 {
173
- std::fs::metadata(filename).map(|m| m.len()).unwrap_or(0)
174
- }
175
-
176
- fn format_size(bytes: u64) -> String {
177
- if bytes == 0 {
178
- return "?".to_string();
179
- }
180
- if bytes < 1024 {
181
- format!("{}B", bytes)
182
- } else if bytes < 1024 * 1024 {
183
- format!("{:.1}KB", bytes as f64 / 1024.0)
184
- } else if bytes < 1024 * 1024 * 1024 {
185
- format!("{:.1}MB", bytes as f64 / (1024.0 * 1024.0))
186
- } else {
187
- format!("{:.1}GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
188
- }
189
- }
190
-
191
- fn compact_url(url: &str) -> String {
192
- // Remove protocol
193
- let without_proto = url
194
- .strip_prefix("https://")
195
- .or_else(|| url.strip_prefix("http://"))
196
- .unwrap_or(url);
197
-
198
- // Truncate if too long
199
- let chars: Vec<char> = without_proto.chars().collect();
200
- if chars.len() <= 50 {
201
- without_proto.to_string()
202
- } else {
203
- let prefix: String = chars[..25].iter().collect();
204
- let suffix: String = chars[chars.len() - 20..].iter().collect();
205
- format!("{}...{}", prefix, suffix)
206
- }
207
- }
208
-
209
- fn parse_error(stderr: &str, stdout: &str) -> String {
210
- // Common wget error patterns
211
- let combined = format!("{}\n{}", stderr, stdout);
212
-
213
- if combined.contains("404") {
214
- return "404 Not Found".to_string();
215
- }
216
- if combined.contains("403") {
217
- return "403 Forbidden".to_string();
218
- }
219
- if combined.contains("401") {
220
- return "401 Unauthorized".to_string();
221
- }
222
- if combined.contains("500") {
223
- return "500 Server Error".to_string();
224
- }
225
- if combined.contains("Connection refused") {
226
- return "Connection refused".to_string();
227
- }
228
- if combined.contains("unable to resolve") || combined.contains("Name or service not known") {
229
- return "DNS lookup failed".to_string();
230
- }
231
- if combined.contains("timed out") {
232
- return "Connection timed out".to_string();
233
- }
234
- if combined.contains("SSL") || combined.contains("certificate") {
235
- return "SSL/TLS error".to_string();
236
- }
237
-
238
- // Return first meaningful line
239
- for line in stderr.lines() {
240
- let trimmed = line.trim();
241
- if !trimmed.is_empty() && !trimmed.starts_with("--") {
242
- if trimmed.len() > 60 {
243
- let t: String = trimmed.chars().take(60).collect();
244
- return format!("{}...", t);
245
- }
246
- return trimmed.to_string();
247
- }
248
- }
249
-
250
- "Unknown error".to_string()
251
- }
252
-
253
- fn truncate_line(line: &str, max: usize) -> String {
254
- if line.len() <= max {
255
- line.to_string()
256
- } else {
257
- let t: String = line.chars().take(max.saturating_sub(3)).collect();
258
- format!("{}...", t)
259
- }
260
- }
@@ -1,11 +0,0 @@
1
- Determining projects to restore...
2
- All projects are up-to-date for restore.
3
- /private/tmp/RtkDotnetSmoke/Broken.cs(7,17): error CS1525: Invalid expression term ';' [/private/tmp/RtkDotnetSmoke/RtkDotnetSmoke.csproj]
4
-
5
- Build FAILED.
6
-
7
- /private/tmp/RtkDotnetSmoke/Broken.cs(7,17): error CS1525: Invalid expression term ';' [/private/tmp/RtkDotnetSmoke/RtkDotnetSmoke.csproj]
8
- 0 Warning(s)
9
- 1 Error(s)
10
-
11
- Time Elapsed 00:00:00.76
@@ -1,31 +0,0 @@
1
- [
2
- {
3
- "FileName": "Program.cs",
4
- "FilePath": "src/Program.cs",
5
- "FileChanges": [
6
- {
7
- "LineNumber": 42,
8
- "CharNumber": 17,
9
- "DiagnosticId": "WHITESPACE",
10
- "FormatDescription": "Fix whitespace"
11
- }
12
- ]
13
- },
14
- {
15
- "FileName": "Utils.cs",
16
- "FilePath": "src/Utils.cs",
17
- "FileChanges": [
18
- {
19
- "LineNumber": 15,
20
- "CharNumber": 8,
21
- "DiagnosticId": "IDE0055",
22
- "FormatDescription": "Fix formatting"
23
- }
24
- ]
25
- },
26
- {
27
- "FileName": "Tests.cs",
28
- "FilePath": "tests/Tests.cs",
29
- "FileChanges": []
30
- }
31
- ]