1pub const PATTERN_ENGINE_VERSION: u32 = 1;
5
6pub mod alembic;
7pub mod ansible;
8pub mod argocd;
9pub mod artisan;
10pub mod aws;
11pub mod bazel;
12pub mod buf;
13pub mod bun;
14pub mod cargo;
15pub mod clang;
16pub mod cmake;
17pub mod composer;
18pub mod cosign;
19pub mod curl;
20pub mod dbt;
21pub mod deno;
22pub mod deploy;
23pub mod deps_cmd;
24pub mod docker;
25pub mod dotnet;
26pub mod env_filter;
27pub mod eslint;
28pub mod fd;
29pub mod find;
30pub mod flutter;
31pub mod flyway;
32pub mod gem;
33pub mod gh;
34pub mod git;
35pub mod glab;
36pub mod golang;
37pub mod grep;
38pub mod grype;
39pub mod helm;
40pub mod jj;
41pub mod json_schema;
42pub mod just;
43pub mod kubectl;
44pub mod linkerd;
45pub mod log_dedup;
46pub mod ls;
47pub mod make;
48pub mod maven;
49pub mod mise;
50pub mod mix;
51pub mod mlflow;
52pub mod mypy;
53pub mod mysql;
54pub mod next_build;
55pub mod ninja;
56pub mod npm;
57pub mod ollama;
58pub mod php;
59pub mod pip;
60pub mod playwright;
61pub mod pnpm;
62pub mod poetry;
63pub mod prettier;
64pub mod prisma;
65pub mod psql;
66pub mod pulumi;
67pub mod pytest;
68pub mod ruby;
69pub mod ruff;
70pub mod semgrep;
71pub mod spark;
72pub mod swift;
73pub mod swiftlint;
74pub mod syft;
75pub mod sysinfo;
76pub mod systemd;
77pub mod terraform;
78pub mod test;
79pub mod trivy;
80pub mod typescript;
81pub mod wget;
82pub mod zig;
83
84use crate::core::tokens::count_tokens;
85
86pub fn compress_output(command: &str, output: &str) -> Option<String> {
87 let policy = crate::shell::output_policy::classify(command, &[]);
92 if policy.is_protected() {
93 return None;
94 }
95
96 let stripped;
97 let clean_output = {
98 let s = crate::core::compressor::strip_ansi(output);
99 if s.len() < output.len() {
100 stripped = s;
101 &stripped
102 } else {
103 output
104 }
105 };
106
107 if let Some(engine) = crate::core::filters::FilterEngine::load()
108 && let Some(filtered) = engine.apply(command, clean_output)
109 {
110 return shorter_only(filtered, output);
111 }
112
113 if has_vcs_owner(command) {
122 return try_specific_pattern(command, clean_output)
129 .filter(|c| !c.trim().is_empty())
130 .filter(|c| !inflates_tokens(c, output));
131 }
132
133 if let Some(compressed) = try_specific_pattern(command, clean_output)
134 && let Some(r) = shorter_only(compressed, output)
135 {
136 return Some(r);
137 }
138
139 if let Some(r) = json_schema::compress(clean_output)
140 && let Some(r) = shorter_only(r, output)
141 {
142 return Some(r);
143 }
144
145 if let Some(r) = log_dedup::compress(clean_output)
146 && let Some(r) = shorter_only(r, output)
147 {
148 return Some(r);
149 }
150
151 if let Some(r) = test::compress(clean_output)
152 && let Some(r) = shorter_only(r, output)
153 {
154 return Some(r);
155 }
156
157 None
158}
159
160pub(crate) fn has_vcs_owner(command: &str) -> bool {
164 let c = command.trim_start().to_ascii_lowercase();
165 c.starts_with("git ")
166 || c.starts_with("jj ")
167 || c.starts_with("gh ")
168 || c.starts_with("glab ")
169 || c.starts_with("hg ")
170}
171
172fn normalize_shell_tokens(text: &str) -> String {
174 text.split_whitespace().collect::<Vec<_>>().join(" ")
175}
176
177fn inflates_tokens(compressed: &str, original: &str) -> bool {
182 count_tokens(compressed) > count_tokens(original)
183}
184
185fn shorter_only(compressed: String, original: &str) -> Option<String> {
186 let orig_n = normalize_shell_tokens(original);
187 let comp_n = normalize_shell_tokens(&compressed);
188 let ct_c = count_tokens(&comp_n);
189 let ct_o = count_tokens(&orig_n);
190 if ct_c < ct_o || (ct_c == ct_o && comp_n.len() < orig_n.len()) {
191 Some(compressed)
192 } else {
193 None
194 }
195}
196
197pub fn try_specific_pattern(cmd: &str, output: &str) -> Option<String> {
198 let cl = cmd.to_ascii_lowercase();
199 let c = cl.as_str();
200
201 if c.starts_with("git ") {
202 return git::compress(c, output);
203 }
204 if c.starts_with("gh ") {
205 return gh::compress(c, output);
206 }
207 if c.starts_with("glab ") {
208 return glab::try_glab_pattern(c, output);
209 }
210 if c == "terraform" || c.starts_with("terraform ") {
211 return terraform::compress(c, output);
212 }
213 if c == "make" || c.starts_with("make ") {
214 return make::compress(c, output);
215 }
216 if c == "just" || c.starts_with("just ") {
217 return just::compress(c, output);
218 }
219 if c.starts_with("mvn ")
220 || c.starts_with("./mvnw ")
221 || c.starts_with("mvnw ")
222 || c.starts_with("gradle ")
223 || c.starts_with("./gradlew ")
224 || c.starts_with("gradlew ")
225 {
226 return maven::compress(c, output);
227 }
228 if c.starts_with("kubectl ") || c.starts_with("k ") {
229 return kubectl::compress(c, output);
230 }
231 if c.starts_with("helm ") {
232 return helm::compress(c, output);
233 }
234 if c.starts_with("pnpm ") {
235 return pnpm::compress(c, output);
236 }
237 if c.starts_with("bun ") || c.starts_with("bunx ") {
238 return bun::compress(c, output);
239 }
240 if c.starts_with("deno ") {
241 return deno::compress(c, output);
242 }
243 if c.starts_with("npm ") || c.starts_with("yarn ") {
244 return npm::compress(c, output);
245 }
246 if c.starts_with("cargo ") {
247 return cargo::compress(c, output);
248 }
249 if c.starts_with("docker ") || c.starts_with("docker-compose ") {
250 return docker::compress(c, output);
251 }
252 if c.starts_with("pip ") || c.starts_with("pip3 ") || c.starts_with("python -m pip") {
253 return pip::compress(c, output);
254 }
255 if c.starts_with("mypy") || c.starts_with("python -m mypy") || c.starts_with("dmypy ") {
256 return mypy::compress(c, output);
257 }
258 if c.starts_with("pytest") || c.starts_with("python -m pytest") {
259 return pytest::compress(c, output).or_else(|| test::compress(output));
260 }
261 if c.starts_with("ruff ") {
262 return ruff::compress(c, output);
263 }
264 if c.starts_with("eslint")
265 || c.starts_with("npx eslint")
266 || c.starts_with("biome ")
267 || c.starts_with("stylelint")
268 {
269 return eslint::compress(c, output);
270 }
271 if c.starts_with("prettier") || c.starts_with("npx prettier") {
272 return prettier::compress(output);
273 }
274 if c.starts_with("go ") || c.starts_with("golangci-lint") || c.starts_with("golint") {
275 return golang::compress(c, output);
276 }
277 if c.starts_with("playwright")
278 || c.starts_with("npx playwright")
279 || c.starts_with("cypress")
280 || c.starts_with("npx cypress")
281 {
282 return playwright::compress(c, output);
283 }
284 if c.starts_with("vitest") || c.starts_with("npx vitest") || c.starts_with("pnpm vitest") {
285 return test::compress(output);
286 }
287 if c.starts_with("next ")
288 || c.starts_with("npx next")
289 || c.starts_with("vite ")
290 || c.starts_with("npx vite")
291 || c.starts_with("vp ")
292 || c.starts_with("vite-plus ")
293 {
294 return next_build::compress(c, output);
295 }
296 if c.starts_with("tsc") || c.contains("typescript") {
297 return typescript::compress(output);
298 }
299 if c.starts_with("rubocop")
300 || c.starts_with("bundle ")
301 || c.starts_with("rake ")
302 || c.starts_with("rails test")
303 || c.starts_with("rspec")
304 {
305 return ruby::compress(c, output);
306 }
307 if c.starts_with("grep ") || c.starts_with("rg ") {
308 return grep::compress(output);
309 }
310 if c.starts_with("find ") {
311 return find::compress(output);
312 }
313 if c.starts_with("fd ") || c.starts_with("fdfind ") {
314 return fd::compress(output);
315 }
316 if c.starts_with("ls ") || c == "ls" {
317 return ls::compress(output);
318 }
319 if c.starts_with("curl ") {
320 return curl::compress_with_cmd(c, output);
321 }
322 if c.starts_with("wget ") {
323 return wget::compress(output);
324 }
325 if c == "env" || c.starts_with("env ") || c.starts_with("printenv") {
326 return env_filter::compress(output);
327 }
328 if c.starts_with("dotnet ") {
329 return dotnet::compress(c, output);
330 }
331 if c.starts_with("flutter ")
332 || (c.starts_with("dart ") && (c.contains(" analyze") || c.ends_with(" analyze")))
333 {
334 return flutter::compress(c, output);
335 }
336 if c.starts_with("poetry ")
337 || c.starts_with("uv ")
338 || c.starts_with("conda ")
339 || c.starts_with("mamba ")
340 || c.starts_with("pipx ")
341 {
342 return poetry::compress(c, output);
343 }
344 if c.starts_with("aws ") {
345 return aws::compress(c, output);
346 }
347 if c.starts_with("psql ") || c.starts_with("pg_") {
348 return psql::compress(c, output);
349 }
350 if c.starts_with("mysql ") || c.starts_with("mariadb ") {
351 return mysql::compress(c, output);
352 }
353 if c.starts_with("prisma ") || c.starts_with("npx prisma") {
354 return prisma::compress(c, output);
355 }
356 if c.starts_with("swift ") {
357 return swift::compress(c, output);
358 }
359 if c.starts_with("zig ") {
360 return zig::compress(c, output);
361 }
362 if c.starts_with("cmake ") || c.starts_with("ctest") {
363 return cmake::compress(c, output);
364 }
365 if c.starts_with("ninja") {
366 return ninja::compress(c, output);
367 }
368 if c.starts_with("ansible") || c.starts_with("ansible-playbook") {
369 return ansible::compress(c, output);
370 }
371 if c.starts_with("composer ") {
372 return composer::compress(c, output);
373 }
374 if c.starts_with("php artisan") || c.starts_with("artisan ") {
375 return artisan::compress(c, output);
376 }
377 if c.starts_with("./vendor/bin/pest") || c.starts_with("pest ") {
378 return artisan::compress("php artisan test", output);
379 }
380 if c.starts_with("mix ") || c.starts_with("iex ") {
381 return mix::compress(c, output);
382 }
383 if c.starts_with("bazel ") || c.starts_with("blaze ") {
384 return bazel::compress(c, output);
385 }
386 if c.starts_with("systemctl ") || c.starts_with("journalctl") {
387 return systemd::compress(c, output);
388 }
389 if c.starts_with("jest") || c.starts_with("npx jest") || c.starts_with("pnpm jest") {
390 return test::compress(output);
391 }
392 if c.starts_with("mocha") || c.starts_with("npx mocha") {
393 return test::compress(output);
394 }
395 if c.starts_with("tofu ") {
396 return terraform::compress(c, output);
397 }
398 if c.starts_with("ps ") || c == "ps" {
399 return sysinfo::compress_ps(output);
400 }
401 if c.starts_with("df ") || c == "df" {
402 return sysinfo::compress_df(output);
403 }
404 if c.starts_with("du ") || c == "du" {
405 return sysinfo::compress_du(output);
406 }
407 if c.starts_with("ping ") {
408 return sysinfo::compress_ping(output);
409 }
410 if c.starts_with("jq ") || c == "jq" {
411 return json_schema::compress(output);
412 }
413 if c.starts_with("hadolint") {
414 return eslint::compress(c, output);
415 }
416 if c.starts_with("yamllint") || c.starts_with("npx yamllint") {
417 return eslint::compress(c, output);
418 }
419 if c.starts_with("markdownlint") || c.starts_with("npx markdownlint") {
420 return eslint::compress(c, output);
421 }
422 if c.starts_with("oxlint") || c.starts_with("npx oxlint") {
423 return eslint::compress(c, output);
424 }
425 if c.starts_with("pyright") || c.starts_with("basedpyright") {
426 return mypy::compress(c, output);
427 }
428 if c.starts_with("turbo ") || c.starts_with("npx turbo") {
429 return npm::compress(c, output);
430 }
431 if c.starts_with("nx ") || c.starts_with("npx nx") {
432 return npm::compress(c, output);
433 }
434 if c.starts_with("clang++ ") || c.starts_with("clang ") {
435 return clang::compress(c, output);
436 }
437 if c.starts_with("gcc ")
438 || c.starts_with("g++ ")
439 || c.starts_with("cc ")
440 || c.starts_with("c++ ")
441 {
442 return cmake::compress(c, output);
443 }
444
445 if c == "dbt" || c.starts_with("dbt ") {
447 return dbt::compress(c, output);
448 }
449 if c == "alembic" || c.starts_with("alembic ") {
450 return alembic::compress(c, output);
451 }
452 if c == "flyway" || c.starts_with("flyway ") {
453 return flyway::compress(c, output);
454 }
455 if c.starts_with("spark-submit") || c.starts_with("spark-sql") || c.starts_with("pyspark") {
456 return spark::compress(c, output);
457 }
458
459 if c == "ollama" || c.starts_with("ollama ") {
461 return ollama::compress(c, output);
462 }
463 if c.starts_with("mlflow ") {
464 return mlflow::compress(c, output);
465 }
466
467 if c.starts_with("semgrep ") {
469 return semgrep::compress(c, output);
470 }
471 if c.starts_with("trivy ") {
472 return trivy::compress(c, output);
473 }
474 if c.starts_with("grype ") {
475 return grype::compress(c, output);
476 }
477 if c.starts_with("syft ") {
478 return syft::compress(c, output);
479 }
480 if c.starts_with("cosign ") {
481 return cosign::compress(c, output);
482 }
483 if c.starts_with("swiftlint") {
484 return swiftlint::compress(c, output);
485 }
486
487 if c == "jj" || c.starts_with("jj ") {
489 return jj::compress(c, output);
490 }
491 if c == "mise" || c.starts_with("mise ") {
492 return mise::compress(c, output);
493 }
494 if c == "buf" || c.starts_with("buf ") {
495 return buf::compress(c, output);
496 }
497 if c.starts_with("gem ") {
498 return gem::compress(c, output);
499 }
500
501 if c == "pulumi" || c.starts_with("pulumi ") {
503 return pulumi::compress(c, output);
504 }
505 if c.starts_with("linkerd ") {
506 return linkerd::compress(c, output);
507 }
508 if c.starts_with("argocd ") {
509 return argocd::compress(c, output);
510 }
511 if c == "vercel"
512 || c.starts_with("vercel ")
513 || c == "fly"
514 || c.starts_with("fly ")
515 || c.starts_with("flyctl ")
516 || c.starts_with("wrangler ")
517 || c.starts_with("skaffold ")
518 || c.starts_with("supabase ")
519 {
520 return deploy::compress(c, output);
521 }
522
523 None
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529
530 #[test]
531 fn routes_git_commands() {
532 let output = "On branch main\nnothing to commit";
533 assert!(compress_output("git status", output).is_some());
534 }
535
536 #[test]
537 fn vcs_path_never_inflates_tokens() {
538 let output = " Abu a\nAa aa_A00A\n\n-";
544 if let Some(compressed) = compress_output("git status", output) {
545 assert!(
546 count_tokens(&compressed) <= count_tokens(output),
547 "VCS compress inflated: {} > {}",
548 count_tokens(&compressed),
549 count_tokens(output),
550 );
551 }
552 }
553
554 #[test]
555 fn routes_cargo_commands() {
556 let output = " Compiling lean-ctx v2.1.1\n Finished `release` profile [optimized] target(s) in 30.5s";
557 assert!(compress_output("cargo build --release", output).is_some());
558 }
559
560 #[test]
561 fn routes_npm_commands() {
562 let output = "added 150 packages, and audited 151 packages in 5s\n\n25 packages are looking for funding\n run `npm fund` for details\n\nfound 0 vulnerabilities";
563 assert!(compress_output("npm install", output).is_some());
564 }
565
566 #[test]
567 fn routes_docker_commands() {
568 let output = "CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES";
569 assert!(compress_output("docker ps", output).is_none());
572 let build_output =
573 "Step 1/5 : FROM node:18\n ---> abc123\nStep 2/5 : COPY . .\nSuccessfully built def456";
574 assert!(compress_output("docker build .", build_output).is_some());
575 }
576
577 #[test]
578 fn routes_mypy_commands() {
579 let output = "src/main.py:10: error: Missing return [return]\nFound 1 error in 1 file (checked 3 source files)";
580 assert!(compress_output("mypy .", output).is_some());
581 assert!(compress_output("python -m mypy src/", output).is_some());
582 }
583
584 #[test]
585 fn routes_pytest_commands() {
586 let output = "===== test session starts =====\ncollected 5 items\ntest_main.py ..... [100%]\n===== 5 passed in 0.5s =====";
587 assert!(compress_output("pytest", output).is_some());
588 assert!(compress_output("python -m pytest tests/", output).is_some());
589 }
590
591 #[test]
592 fn routes_data_domain() {
593 let dbt =
594 "20:14:02 Found 12 models\n20:14:20 Done. PASS=11 WARN=0 ERROR=1 SKIP=0 TOTAL=12";
595 assert!(
596 compress_output("dbt run", dbt).is_some(),
597 "dbt routed+compressible"
598 );
599 let alembic = "INFO [alembic.runtime.migration] Context impl PostgresqlImpl.\nINFO [alembic.runtime.migration] Running upgrade -> a1b2c3, create users table\nINFO [alembic.runtime.migration] Running upgrade a1b2c3 -> d4e5f6, add email index";
600 assert!(compress_output("alembic upgrade head", alembic).is_some());
601 let flyway = "Flyway Community Edition 9.22.0 by Redgate\nDatabase: jdbc:postgresql://localhost/db\nMigrating schema \"public\" to version \"5 - add orders\"\nSuccessfully applied 1 migration to schema \"public\", now at version v5";
602 assert!(compress_output("flyway migrate", flyway).is_some());
603 let spark = "23/01/01 12:00:00 INFO SparkContext: Running Spark version 3.4.0\n23/01/01 12:00:01 INFO ResourceUtils: none configured\n23/01/01 12:00:10 INFO DAGScheduler: Job 0 finished: collect, took 5.1 s\n23/01/01 12:00:15 ERROR Executor: Exception in task 0.0";
604 assert!(compress_output("spark-submit app.py", spark).is_some());
605 }
606
607 #[test]
608 fn routes_ai_domain() {
609 let ollama = "NAME ID SIZE MODIFIED\nllama3.2:latest a80c4f17acd5 2.0 GB 3 days ago\nqwen2.5-coder:7b 2b0496514337 4.7 GB 2 weeks ago";
610 assert!(compress_output("ollama list", ollama).is_some());
611 let mlflow = "2024/01/01 12:00:01 INFO mlflow.projects.backend.local: === Running command 'python train.py' ===\nCollecting numpy==1.26.0\nDownloading numpy-1.26.0.whl (18.2 MB)\n2024/01/01 12:00:30 INFO mlflow.projects: === Run (ID 'abc123def456') succeeded ===";
612 assert!(compress_output("mlflow run .", mlflow).is_some());
613 }
614
615 #[test]
616 fn routes_security_domain() {
617 let trivy = "2024-01-01T12:00:00.000Z\tINFO\tscanning\nnginx:latest (debian 12.1)\n=====\nTotal: 45 (LOW: 20, HIGH: 8, CRITICAL: 2)";
618 assert!(compress_output("trivy image nginx", trivy).is_some());
619 let grype = "NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY\nlibssl1.1 1.1.1n 1.1.1w deb CVE-2023-1234 Critical\nzlib1g 1.2.11 1.2.13 deb CVE-2022-5678 High";
620 assert!(compress_output("grype nginx", grype).is_some());
621 let syft = "NAME VERSION TYPE\nadduser 3.118 deb\napt 2.6.1 deb\nlodash 4.17.21 npm";
622 assert!(compress_output("syft nginx", syft).is_some());
623 let semgrep = "Scanning 120 files.\n src/app.py\n python.security.dangerous-subprocess-use\n Detected subprocess.\n 42┆ subprocess.call(x)\nRan 450 rules on 120 files: 1 findings.";
624 assert!(compress_output("semgrep scan", semgrep).is_some());
625 let swiftlint = "Linting Swift files in current working directory\nLinting 'A.swift' (1/3)\nLinting 'B.swift' (2/3)\nLinting 'C.swift' (3/3)\n/path/A.swift:10:5: warning: Line Length Violation: Line should be 120 chars or less (line_length)\n/path/A.swift:22:1: warning: Trailing Whitespace Violation: no trailing whitespace (trailing_whitespace)\n/path/B.swift:5:1: error: Force Cast Violation: avoid force casts (force_cast)\n/path/C.swift:8:3: warning: Todo Violation: resolve TODOs (todo)\nDone linting! Found 4 violations, 1 serious in 3 files.";
626 assert!(compress_output("swiftlint", swiftlint).is_some());
627 }
628
629 #[test]
630 fn routes_vcs_toolchain_domain() {
631 let jj = "@ qpvuntsm user@host.com 2024-01-01 12:00:00 1234abcd\n│ add feature x\n○ zzzzmmmm user@host.com 2024-01-01 11:00:00 main 5678efab\n│ initial commit\n~";
632 assert!(compress_output("jj log", jj).is_some());
633 let mise = "node 20.10.0 ~/.config/mise/config.toml\npython 3.12.0 ~/.tool-versions\nrust 1.75.0 ~/.config/mise/config.toml";
634 assert!(compress_output("mise ls", mise).is_some());
635 let buf_lines: Vec<String> = (0..30)
636 .map(|i| format!("proto/f{i}.proto:{i}:1:Field name should be lower_snake_case here."))
637 .collect();
638 let buf = buf_lines.join("\n");
639 assert!(compress_output("buf lint", &buf).is_some());
640 let gem = "Fetching rails-7.1.0.gem\nSuccessfully installed activesupport-7.1.0\nSuccessfully installed rails-7.1.0\nParsing documentation for rails-7.1.0\nInstalling ri documentation for rails-7.1.0\nDone installing documentation for rails after 3 seconds\n2 gems installed";
641 assert!(compress_output("gem install rails", gem).is_some());
642 let uv = "Resolved 42 packages in 120ms\nDownloading numpy (18.2MiB)\n 100%|████████| 18.2M/18.2M [00:01<00:00, 15.3MiB/s]\nDownloading pandas (12.1MiB)\n 100%|████████| 12.1M/12.1M [00:00<00:00, 14.1MiB/s]\nPrepared 5 packages in 1.2s\nInstalled 5 packages in 30ms\n + numpy==1.26.0\n + pandas==2.1.0";
643 assert!(compress_output("uv add pandas", uv).is_some());
644 }
645
646 #[test]
647 fn routes_edge_infra_domain() {
648 let pulumi = "Updating (dev):\n Type Name Status\n + pulumi:pulumi:Stack proj created\n + aws:s3:Bucket b1 created\n + aws:s3:Bucket b2 created\n + aws:s3:Bucket b3 created\n + aws:lambda:Function fn created\n\nOutputs:\n url: \"https://x.example.com\"\n\nResources:\n + 5 created\n 10 unchanged\n\nDuration: 35s";
649 assert!(compress_output("pulumi up", pulumi).is_some());
650 let linkerd = "kubernetes-api\n--------------\n√ can initialize the client\n√ can query the Kubernetes API\n√ is running the minimum kubectl version\n\nlinkerd-existence\n-----------------\n√ 'linkerd-config' config map exists\n× control plane pods are ready\n some pods are not ready\n\nStatus check results are ×";
651 assert!(compress_output("linkerd check", linkerd).is_some());
652 let argocd = "Name: argocd/myapp\nProject: default\nSync Status: Synced\nHealth Status: Healthy\n\nGROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE\n Service ns s1 Synced Healthy\n Service ns s2 Synced Healthy\n Service ns s3 Synced Healthy\napps Deployment ns d1 OutOfSync Progressing";
653 assert!(compress_output("argocd app get myapp", argocd).is_some());
654 let vercel = "Vercel CLI 33.0.0\nInstalling dependencies...\nadded 420 packages in 12s\nBuilding...\nCompiling pages\nCollecting page data\nGenerating static pages\nProduction: https://my-app.vercel.app [45s]";
655 assert!(compress_output("vercel deploy --prod", vercel).is_some());
656 let wrangler = "wrangler 3.0.0\n-------------------\nyour worker has access to the following bindings:\n- KV Namespaces:\n - CACHE: abc123\nTotal Upload: 1.2 MiB / gzip: 0.4 MiB\nUploaded my-worker (3.5 sec)\nPublished my-worker (1.2 sec)\n https://my-worker.example.workers.dev";
657 assert!(compress_output("wrangler deploy", wrangler).is_some());
658 }
659
660 #[test]
661 fn unknown_command_returns_none() {
662 assert!(compress_output("some-unknown-tool --version", "v1.0").is_none());
663 }
664
665 #[test]
666 fn case_insensitive_routing() {
667 let output = "On branch main\nnothing to commit";
668 assert!(compress_output("Git Status", output).is_some());
669 assert!(compress_output("GIT STATUS", output).is_some());
670 }
671
672 #[test]
673 fn routes_vp_and_vite_plus() {
674 let output = " VITE v5.0.0 ready in 200 ms\n\n -> Local: http://localhost:5173/\n -> Network: http://192.168.1.2:5173/";
675 assert!(compress_output("vp build", output).is_some());
676 assert!(compress_output("vite-plus build", output).is_some());
677 }
678
679 #[test]
680 fn routes_bunx_commands() {
681 let output = "1 pass tests\n0 fail tests\n3 skip tests\nDone 12ms\nsome extra line\nmore output here";
682 let result = compress_output("bunx test", output);
683 assert!(
684 result.is_some(),
685 "bunx should compress when output is large enough"
686 );
687 assert!(result.unwrap().contains("bun test: 1 passed"));
688 }
689
690 #[test]
691 fn routes_deno_task() {
692 let output = "Task dev deno run --allow-net server.ts\nListening on http://localhost:8000";
693 assert!(try_specific_pattern("deno task dev", output).is_some());
694 }
695
696 #[test]
697 fn routes_jest_commands() {
698 let output = "PASS tests/main.test.js\nTest Suites: 1 passed, 1 total\nTests: 5 passed, 5 total\nTime: 2.5 s";
699 assert!(try_specific_pattern("jest", output).is_some());
700 assert!(try_specific_pattern("npx jest --coverage", output).is_some());
701 }
702
703 #[test]
704 fn routes_mocha_commands() {
705 let output = " 3 passing (50ms)\n 1 failing\n\n 1) Array #indexOf():\n Error: expected -1 to equal 0";
706 assert!(try_specific_pattern("mocha", output).is_some());
707 assert!(try_specific_pattern("npx mocha tests/", output).is_some());
708 }
709
710 #[test]
711 fn routes_tofu_commands() {
712 let output = "Initializing the backend...\nInitializing provider plugins...\nTerraform has been successfully initialized!";
713 assert!(try_specific_pattern("tofu init", output).is_some());
714 }
715
716 #[test]
717 fn routes_ps_commands() {
718 let mut lines = vec!["USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND".to_string()];
719 for i in 0..20 {
720 lines.push(format!("user {i} 0.0 0.1 1234 123 ? S 10:00 0:00 proc_{i}"));
721 }
722 let output = lines.join("\n");
723 assert!(try_specific_pattern("ps aux", &output).is_some());
724 }
725
726 #[test]
727 fn routes_ping_commands() {
728 let output = "PING google.com (1.2.3.4): 56 data bytes\n64 bytes from 1.2.3.4: icmp_seq=0 ttl=116 time=12ms\n3 packets transmitted, 3 packets received, 0.0% packet loss\nrtt min/avg/max/stddev = 11/12/13/1 ms";
729 assert!(try_specific_pattern("ping -c 3 google.com", output).is_some());
730 }
731
732 #[test]
733 fn routes_jq_to_json_schema() {
734 let output = "{\"name\": \"test\", \"version\": \"1.0\", \"items\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}, {\"id\": 4}, {\"id\": 5}, {\"id\": 6}, {\"id\": 7}, {\"id\": 8}, {\"id\": 9}, {\"id\": 10}]}";
735 assert!(try_specific_pattern("jq '.items' data.json", output).is_some());
736 }
737
738 #[test]
739 fn routes_linting_tools() {
740 let lint_output = "src/main.py:10: error: Missing return\nsrc/main.py:20: error: Unused var\nFound 2 errors";
741 assert!(try_specific_pattern("hadolint Dockerfile", lint_output).is_some());
742 assert!(try_specific_pattern("oxlint src/", lint_output).is_some());
743 assert!(try_specific_pattern("pyright src/", lint_output).is_some());
744 assert!(try_specific_pattern("basedpyright src/", lint_output).is_some());
745 }
746
747 #[test]
748 fn routes_fd_commands() {
749 let output = "src/main.rs\nsrc/lib.rs\nsrc/util/helpers.rs\nsrc/util/math.rs\ntests/integration.rs\n";
750 assert!(try_specific_pattern("fd --extension rs", output).is_some());
751 assert!(try_specific_pattern("fdfind .rs", output).is_some());
752 }
753
754 #[test]
755 fn routes_just_commands() {
756 let output = "Available recipes:\n build\n test\n lint\n";
757 assert!(try_specific_pattern("just --list", output).is_some());
758 assert!(try_specific_pattern("just build", output).is_some());
759 }
760
761 #[test]
762 fn routes_ninja_commands() {
763 let output = "[1/10] Compiling foo.c\n[10/10] Linking app\n";
764 assert!(try_specific_pattern("ninja", output).is_some());
765 assert!(try_specific_pattern("ninja -j4", output).is_some());
766 }
767
768 #[test]
769 fn routes_clang_commands() {
770 let output =
771 "src/main.c:10:5: error: use of undeclared identifier 'foo'\n1 error generated.\n";
772 assert!(try_specific_pattern("clang src/main.c", output).is_some());
773 assert!(try_specific_pattern("clang++ -std=c++17 main.cpp", output).is_some());
774 }
775
776 #[test]
777 fn routes_cargo_run() {
778 let output = " Compiling foo v0.1.0\n Finished `dev` profile\nHello, world!";
779 assert!(try_specific_pattern("cargo run", output).is_some());
780 }
781
782 #[test]
783 fn routes_cargo_bench() {
784 let output = " Compiling foo v0.1.0\ntest bench_parse ... bench: 1234 ns/iter";
785 assert!(try_specific_pattern("cargo bench", output).is_some());
786 }
787
788 #[test]
789 fn routes_build_tools() {
790 let build_output = " Compiling foo v0.1.0\n Finished release [optimized]";
791 assert!(try_specific_pattern("gcc -o main main.c", build_output).is_some());
792 assert!(try_specific_pattern("g++ -o main main.cpp", build_output).is_some());
793 }
794
795 #[test]
796 fn routes_monorepo_tools() {
797 let output = "npm warn deprecated inflight@1.0.6\nnpm warn deprecated rimraf@3.0.2\nadded 150 packages, and audited 151 packages in 5s\n\n25 packages are looking for funding\n run `npm fund` for details\n\nfound 0 vulnerabilities";
798 assert!(try_specific_pattern("turbo install", output).is_some());
799 assert!(try_specific_pattern("nx install", output).is_some());
800 }
801
802 #[test]
803 fn gh_api_passthrough_never_compresses() {
804 let huge = "line\n".repeat(5000);
805 assert!(
806 compress_output("gh api repos/owner/repo/actions/jobs/123/logs", &huge).is_none(),
807 "gh api must never be compressed, even for large output"
808 );
809 assert!(compress_output("gh api repos/owner/repo/actions/runs/123/logs", &huge).is_none());
810 }
811
812 #[test]
813 fn gh_log_flags_passthrough() {
814 let huge = "line\n".repeat(5000);
815 assert!(compress_output("gh run view 123 --log-failed", &huge).is_none());
816 assert!(compress_output("gh run view 123 --log", &huge).is_none());
817 }
818
819 #[test]
820 fn gh_structured_commands_still_compress() {
821 let output = "On branch main\nnothing to commit";
822 assert!(try_specific_pattern("gh pr list", output).is_some());
823 assert!(try_specific_pattern("gh run list", output).is_some());
824 }
825}