1pub const PATTERN_ENGINE_VERSION: u32 = 2;
8
9pub mod alembic;
10pub mod ansible;
11pub mod argocd;
12pub mod artisan;
13pub mod aws;
14pub mod bazel;
15pub mod buf;
16pub mod bun;
17pub mod cargo;
18pub mod clang;
19pub mod cmake;
20pub mod composer;
21pub mod cosign;
22pub mod curl;
23pub mod dbt;
24pub mod deno;
25pub mod deploy;
26pub mod deps_cmd;
27pub mod docker;
28pub mod dotnet;
29pub mod env_filter;
30pub mod eslint;
31pub mod fd;
32pub mod find;
33pub mod flutter;
34pub mod flyway;
35pub mod gem;
36pub mod gh;
37pub mod git;
38pub mod glab;
39pub mod golang;
40pub mod grep;
41pub mod grype;
42pub mod helm;
43pub mod jj;
44pub mod json_schema;
45pub mod just;
46pub mod kubectl;
47pub mod linkerd;
48pub mod log_dedup;
49pub mod ls;
50pub mod make;
51pub mod maven;
52pub mod mise;
53pub mod mix;
54pub mod mlflow;
55pub mod mypy;
56pub mod mysql;
57pub mod next_build;
58pub mod ninja;
59pub mod npm;
60pub mod ollama;
61pub mod php;
62pub mod pip;
63pub mod playwright;
64pub mod pnpm;
65pub mod poetry;
66pub mod prettier;
67pub mod prisma;
68pub mod psql;
69pub mod pulumi;
70pub mod pytest;
71pub mod ruby;
72pub mod ruff;
73pub mod semgrep;
74pub mod spark;
75pub mod swift;
76pub mod swiftlint;
77pub mod syft;
78pub mod sysinfo;
79pub mod systemd;
80pub mod terraform;
81pub mod test;
82pub mod trivy;
83pub mod typescript;
84pub mod wget;
85pub mod zig;
86
87use crate::core::tokens::count_tokens;
88
89pub fn compress_output(command: &str, output: &str) -> Option<String> {
90 let policy = crate::shell::output_policy::classify(command, &[]);
95 if policy.is_protected() {
96 return None;
97 }
98
99 let stripped;
100 let clean_output = {
101 let s = crate::core::compressor::strip_ansi(output);
102 if s.len() < output.len() {
103 stripped = s;
104 &stripped
105 } else {
106 output
107 }
108 };
109
110 if let Some(engine) = crate::core::filters::FilterEngine::load()
111 && let Some(filtered) = engine.apply(command, clean_output)
112 {
113 return shorter_only(filtered, output);
114 }
115
116 if has_vcs_owner(command) {
125 return try_specific_pattern(command, clean_output)
132 .filter(|c| !c.trim().is_empty())
133 .filter(|c| !inflates_tokens(c, output));
134 }
135
136 if let Some(compressed) = try_specific_pattern(command, clean_output)
137 && let Some(r) = shorter_only(compressed, output)
138 {
139 return Some(r);
140 }
141
142 if let Some(r) = json_schema::compress(clean_output)
143 && let Some(r) = shorter_only(r, output)
144 {
145 return Some(r);
146 }
147
148 if let Some(r) = log_dedup::compress(clean_output)
149 && let Some(r) = shorter_only(r, output)
150 {
151 return Some(r);
152 }
153
154 if let Some(r) = test::compress(clean_output)
155 && let Some(r) = shorter_only(r, output)
156 {
157 return Some(r);
158 }
159
160 None
161}
162
163pub(crate) fn has_vcs_owner(command: &str) -> bool {
167 let c = command.trim_start().to_ascii_lowercase();
168 c.starts_with("git ")
169 || c.starts_with("jj ")
170 || c.starts_with("gh ")
171 || c.starts_with("glab ")
172 || c.starts_with("hg ")
173}
174
175fn normalize_shell_tokens(text: &str) -> String {
177 text.split_whitespace().collect::<Vec<_>>().join(" ")
178}
179
180fn inflates_tokens(compressed: &str, original: &str) -> bool {
185 count_tokens(compressed) > count_tokens(original)
186}
187
188fn shorter_only(compressed: String, original: &str) -> Option<String> {
189 let orig_n = normalize_shell_tokens(original);
190 let comp_n = normalize_shell_tokens(&compressed);
191 let ct_c = count_tokens(&comp_n);
192 let ct_o = count_tokens(&orig_n);
193 if ct_c < ct_o || (ct_c == ct_o && comp_n.len() < orig_n.len()) {
194 Some(compressed)
195 } else {
196 None
197 }
198}
199
200pub fn try_specific_pattern(cmd: &str, output: &str) -> Option<String> {
201 let cl = cmd.to_ascii_lowercase();
202 let c = cl.as_str();
203
204 if c.starts_with("git ") {
205 return git::compress(c, output);
206 }
207 if c.starts_with("gh ") {
208 return gh::compress(c, output);
209 }
210 if c.starts_with("glab ") {
211 return glab::try_glab_pattern(c, output);
212 }
213 if c == "terraform" || c.starts_with("terraform ") {
214 return terraform::compress(c, output);
215 }
216 if c == "make" || c.starts_with("make ") {
217 return make::compress(c, output);
218 }
219 if c == "just" || c.starts_with("just ") {
220 return just::compress(c, output);
221 }
222 if c.starts_with("mvn ")
223 || c.starts_with("./mvnw ")
224 || c.starts_with("mvnw ")
225 || c.starts_with("gradle ")
226 || c.starts_with("./gradlew ")
227 || c.starts_with("gradlew ")
228 {
229 return maven::compress(c, output);
230 }
231 if c.starts_with("kubectl ") || c.starts_with("k ") {
232 return kubectl::compress(c, output);
233 }
234 if c.starts_with("helm ") {
235 return helm::compress(c, output);
236 }
237 if c.starts_with("pnpm ") {
238 return pnpm::compress(c, output);
239 }
240 if c.starts_with("bun ") || c.starts_with("bunx ") {
241 return bun::compress(c, output);
242 }
243 if c.starts_with("deno ") {
244 return deno::compress(c, output);
245 }
246 if c.starts_with("npm ") || c.starts_with("yarn ") {
247 return npm::compress(c, output);
248 }
249 if c.starts_with("cargo ") {
250 return cargo::compress(c, output);
251 }
252 if c.starts_with("docker ") || c.starts_with("docker-compose ") {
253 return docker::compress(c, output);
254 }
255 if c.starts_with("pip ") || c.starts_with("pip3 ") || c.starts_with("python -m pip") {
256 return pip::compress(c, output);
257 }
258 if c.starts_with("mypy") || c.starts_with("python -m mypy") || c.starts_with("dmypy ") {
259 return mypy::compress(c, output);
260 }
261 if c.starts_with("pytest") || c.starts_with("python -m pytest") {
262 return pytest::compress(c, output).or_else(|| test::compress(output));
263 }
264 if c.starts_with("ruff ") {
265 return ruff::compress(c, output);
266 }
267 if c.starts_with("eslint")
268 || c.starts_with("npx eslint")
269 || c.starts_with("biome ")
270 || c.starts_with("stylelint")
271 {
272 return eslint::compress(c, output);
273 }
274 if c.starts_with("prettier") || c.starts_with("npx prettier") {
275 return prettier::compress(output);
276 }
277 if c.starts_with("go ") || c.starts_with("golangci-lint") || c.starts_with("golint") {
278 return golang::compress(c, output);
279 }
280 if c.starts_with("playwright")
281 || c.starts_with("npx playwright")
282 || c.starts_with("cypress")
283 || c.starts_with("npx cypress")
284 {
285 return playwright::compress(c, output);
286 }
287 if c.starts_with("vitest") || c.starts_with("npx vitest") || c.starts_with("pnpm vitest") {
288 return test::compress(output);
289 }
290 if c.starts_with("next ")
291 || c.starts_with("npx next")
292 || c.starts_with("vite ")
293 || c.starts_with("npx vite")
294 || c.starts_with("vp ")
295 || c.starts_with("vite-plus ")
296 {
297 return next_build::compress(c, output);
298 }
299 if c.starts_with("tsc") || c.contains("typescript") {
300 return typescript::compress(output);
301 }
302 if c.starts_with("rubocop")
303 || c.starts_with("bundle ")
304 || c.starts_with("rake ")
305 || c.starts_with("rails test")
306 || c.starts_with("rspec")
307 {
308 return ruby::compress(c, output);
309 }
310 if c.starts_with("grep ") || c.starts_with("rg ") {
311 return grep::compress(output);
312 }
313 if c.starts_with("find ") {
314 return find::compress(output);
315 }
316 if c.starts_with("fd ") || c.starts_with("fdfind ") {
317 return fd::compress(output);
318 }
319 if c.starts_with("ls ") || c == "ls" {
320 return ls::compress(output);
321 }
322 if c.starts_with("curl ") {
323 return curl::compress_with_cmd(c, output);
324 }
325 if c.starts_with("wget ") {
326 return wget::compress(output);
327 }
328 if c == "env" || c.starts_with("env ") || c.starts_with("printenv") {
329 return env_filter::compress(output);
330 }
331 if c.starts_with("dotnet ") {
332 return dotnet::compress(c, output);
333 }
334 if c.starts_with("flutter ")
335 || (c.starts_with("dart ") && (c.contains(" analyze") || c.ends_with(" analyze")))
336 {
337 return flutter::compress(c, output);
338 }
339 if c.starts_with("poetry ")
340 || c.starts_with("uv ")
341 || c.starts_with("conda ")
342 || c.starts_with("mamba ")
343 || c.starts_with("pipx ")
344 {
345 return poetry::compress(c, output);
346 }
347 if c.starts_with("aws ") {
348 return aws::compress(c, output);
349 }
350 if c.starts_with("psql ") || c.starts_with("pg_") {
351 return psql::compress(c, output);
352 }
353 if c.starts_with("mysql ") || c.starts_with("mariadb ") {
354 return mysql::compress(c, output);
355 }
356 if c.starts_with("prisma ") || c.starts_with("npx prisma") {
357 return prisma::compress(c, output);
358 }
359 if c.starts_with("swift ") {
360 return swift::compress(c, output);
361 }
362 if c.starts_with("zig ") {
363 return zig::compress(c, output);
364 }
365 if c.starts_with("cmake ") || c.starts_with("ctest") {
366 return cmake::compress(c, output);
367 }
368 if c.starts_with("ninja") {
369 return ninja::compress(c, output);
370 }
371 if c.starts_with("ansible") || c.starts_with("ansible-playbook") {
372 return ansible::compress(c, output);
373 }
374 if c.starts_with("composer ") {
375 return composer::compress(c, output);
376 }
377 if c.starts_with("php artisan") || c.starts_with("artisan ") {
378 return artisan::compress(c, output);
379 }
380 if c.starts_with("./vendor/bin/pest") || c.starts_with("pest ") {
381 return artisan::compress("php artisan test", output);
382 }
383 if c.starts_with("mix ") || c.starts_with("iex ") {
384 return mix::compress(c, output);
385 }
386 if c.starts_with("bazel ") || c.starts_with("blaze ") {
387 return bazel::compress(c, output);
388 }
389 if c.starts_with("systemctl ") || c.starts_with("journalctl") {
390 return systemd::compress(c, output);
391 }
392 if c.starts_with("jest") || c.starts_with("npx jest") || c.starts_with("pnpm jest") {
393 return test::compress(output);
394 }
395 if c.starts_with("mocha") || c.starts_with("npx mocha") {
396 return test::compress(output);
397 }
398 if c.starts_with("tofu ") {
399 return terraform::compress(c, output);
400 }
401 if c.starts_with("ps ") || c == "ps" {
402 return sysinfo::compress_ps(output);
403 }
404 if c.starts_with("df ") || c == "df" {
405 return sysinfo::compress_df(output);
406 }
407 if c.starts_with("du ") || c == "du" {
408 return sysinfo::compress_du(output);
409 }
410 if c.starts_with("ping ") {
411 return sysinfo::compress_ping(output);
412 }
413 if c.starts_with("jq ") || c == "jq" {
414 return json_schema::compress(output);
415 }
416 if c.starts_with("hadolint") {
417 return eslint::compress(c, output);
418 }
419 if c.starts_with("yamllint") || c.starts_with("npx yamllint") {
420 return eslint::compress(c, output);
421 }
422 if c.starts_with("markdownlint") || c.starts_with("npx markdownlint") {
423 return eslint::compress(c, output);
424 }
425 if c.starts_with("oxlint") || c.starts_with("npx oxlint") {
426 return eslint::compress(c, output);
427 }
428 if c.starts_with("pyright") || c.starts_with("basedpyright") {
429 return mypy::compress(c, output);
430 }
431 if c.starts_with("turbo ") || c.starts_with("npx turbo") {
432 return npm::compress(c, output);
433 }
434 if c.starts_with("nx ") || c.starts_with("npx nx") {
435 return npm::compress(c, output);
436 }
437 if c.starts_with("clang++ ") || c.starts_with("clang ") {
438 return clang::compress(c, output);
439 }
440 if c.starts_with("gcc ")
441 || c.starts_with("g++ ")
442 || c.starts_with("cc ")
443 || c.starts_with("c++ ")
444 {
445 return cmake::compress(c, output);
446 }
447
448 if c == "dbt" || c.starts_with("dbt ") {
450 return dbt::compress(c, output);
451 }
452 if c == "alembic" || c.starts_with("alembic ") {
453 return alembic::compress(c, output);
454 }
455 if c == "flyway" || c.starts_with("flyway ") {
456 return flyway::compress(c, output);
457 }
458 if c.starts_with("spark-submit") || c.starts_with("spark-sql") || c.starts_with("pyspark") {
459 return spark::compress(c, output);
460 }
461
462 if c == "ollama" || c.starts_with("ollama ") {
464 return ollama::compress(c, output);
465 }
466 if c.starts_with("mlflow ") {
467 return mlflow::compress(c, output);
468 }
469
470 if c.starts_with("semgrep ") {
472 return semgrep::compress(c, output);
473 }
474 if c.starts_with("trivy ") {
475 return trivy::compress(c, output);
476 }
477 if c.starts_with("grype ") {
478 return grype::compress(c, output);
479 }
480 if c.starts_with("syft ") {
481 return syft::compress(c, output);
482 }
483 if c.starts_with("cosign ") {
484 return cosign::compress(c, output);
485 }
486 if c.starts_with("swiftlint") {
487 return swiftlint::compress(c, output);
488 }
489
490 if c == "jj" || c.starts_with("jj ") {
492 return jj::compress(c, output);
493 }
494 if c == "mise" || c.starts_with("mise ") {
495 return mise::compress(c, output);
496 }
497 if c == "buf" || c.starts_with("buf ") {
498 return buf::compress(c, output);
499 }
500 if c.starts_with("gem ") {
501 return gem::compress(c, output);
502 }
503
504 if c == "pulumi" || c.starts_with("pulumi ") {
506 return pulumi::compress(c, output);
507 }
508 if c.starts_with("linkerd ") {
509 return linkerd::compress(c, output);
510 }
511 if c.starts_with("argocd ") {
512 return argocd::compress(c, output);
513 }
514 if c == "vercel"
515 || c.starts_with("vercel ")
516 || c == "fly"
517 || c.starts_with("fly ")
518 || c.starts_with("flyctl ")
519 || c.starts_with("wrangler ")
520 || c.starts_with("skaffold ")
521 || c.starts_with("supabase ")
522 {
523 return deploy::compress(c, output);
524 }
525
526 None
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532
533 #[test]
534 fn routes_git_commands() {
535 let output = "On branch main\nnothing to commit";
536 assert!(compress_output("git status", output).is_some());
537 }
538
539 #[test]
540 fn vcs_path_never_inflates_tokens() {
541 let output = " Abu a\nAa aa_A00A\n\n-";
547 if let Some(compressed) = compress_output("git status", output) {
548 assert!(
549 count_tokens(&compressed) <= count_tokens(output),
550 "VCS compress inflated: {} > {}",
551 count_tokens(&compressed),
552 count_tokens(output),
553 );
554 }
555 }
556
557 #[test]
558 fn routes_cargo_commands() {
559 let output = " Compiling lean-ctx v2.1.1\n Finished `release` profile [optimized] target(s) in 30.5s";
560 assert!(compress_output("cargo build --release", output).is_some());
561 }
562
563 #[test]
564 fn routes_npm_commands() {
565 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";
566 assert!(compress_output("npm install", output).is_some());
567 }
568
569 #[test]
570 fn routes_docker_commands() {
571 let output = "CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES";
572 assert!(compress_output("docker ps", output).is_none());
575 let build_output =
576 "Step 1/5 : FROM node:18\n ---> abc123\nStep 2/5 : COPY . .\nSuccessfully built def456";
577 assert!(compress_output("docker build .", build_output).is_some());
578 }
579
580 #[test]
581 fn routes_mypy_commands() {
582 let output = "src/main.py:10: error: Missing return [return]\nFound 1 error in 1 file (checked 3 source files)";
583 assert!(compress_output("mypy .", output).is_some());
584 assert!(compress_output("python -m mypy src/", output).is_some());
585 }
586
587 #[test]
588 fn routes_pytest_commands() {
589 let output = "===== test session starts =====\ncollected 5 items\ntest_main.py ..... [100%]\n===== 5 passed in 0.5s =====";
590 assert!(compress_output("pytest", output).is_some());
591 assert!(compress_output("python -m pytest tests/", output).is_some());
592 }
593
594 #[test]
595 fn routes_data_domain() {
596 let dbt =
597 "20:14:02 Found 12 models\n20:14:20 Done. PASS=11 WARN=0 ERROR=1 SKIP=0 TOTAL=12";
598 assert!(
599 compress_output("dbt run", dbt).is_some(),
600 "dbt routed+compressible"
601 );
602 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";
603 assert!(compress_output("alembic upgrade head", alembic).is_some());
604 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";
605 assert!(compress_output("flyway migrate", flyway).is_some());
606 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";
607 assert!(compress_output("spark-submit app.py", spark).is_some());
608 }
609
610 #[test]
611 fn routes_ai_domain() {
612 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";
613 assert!(compress_output("ollama list", ollama).is_some());
614 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 ===";
615 assert!(compress_output("mlflow run .", mlflow).is_some());
616 }
617
618 #[test]
619 fn routes_security_domain() {
620 let trivy = "2024-01-01T12:00:00.000Z\tINFO\tscanning\nnginx:latest (debian 12.1)\n=====\nTotal: 45 (LOW: 20, HIGH: 8, CRITICAL: 2)";
621 assert!(compress_output("trivy image nginx", trivy).is_some());
622 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";
623 assert!(compress_output("grype nginx", grype).is_some());
624 let syft = "NAME VERSION TYPE\nadduser 3.118 deb\napt 2.6.1 deb\nlodash 4.17.21 npm";
625 assert!(compress_output("syft nginx", syft).is_some());
626 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.";
627 assert!(compress_output("semgrep scan", semgrep).is_some());
628 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.";
629 assert!(compress_output("swiftlint", swiftlint).is_some());
630 }
631
632 #[test]
633 fn routes_vcs_toolchain_domain() {
634 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~";
635 assert!(compress_output("jj log", jj).is_some());
636 let mise = "node 20.10.0 ~/.config/mise/config.toml\npython 3.12.0 ~/.tool-versions\nrust 1.75.0 ~/.config/mise/config.toml";
637 assert!(compress_output("mise ls", mise).is_some());
638 let buf_lines: Vec<String> = (0..30)
639 .map(|i| format!("proto/f{i}.proto:{i}:1:Field name should be lower_snake_case here."))
640 .collect();
641 let buf = buf_lines.join("\n");
642 assert!(compress_output("buf lint", &buf).is_some());
643 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";
644 assert!(compress_output("gem install rails", gem).is_some());
645 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";
646 assert!(compress_output("uv add pandas", uv).is_some());
647 }
648
649 #[test]
650 fn routes_edge_infra_domain() {
651 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";
652 assert!(compress_output("pulumi up", pulumi).is_some());
653 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 ×";
654 assert!(compress_output("linkerd check", linkerd).is_some());
655 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";
656 assert!(compress_output("argocd app get myapp", argocd).is_some());
657 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]";
658 assert!(compress_output("vercel deploy --prod", vercel).is_some());
659 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";
660 assert!(compress_output("wrangler deploy", wrangler).is_some());
661 }
662
663 #[test]
664 fn unknown_command_returns_none() {
665 assert!(compress_output("some-unknown-tool --version", "v1.0").is_none());
666 }
667
668 #[test]
669 fn case_insensitive_routing() {
670 let output = "On branch main\nnothing to commit";
671 assert!(compress_output("Git Status", output).is_some());
672 assert!(compress_output("GIT STATUS", output).is_some());
673 }
674
675 #[test]
676 fn routes_vp_and_vite_plus() {
677 let output = " VITE v5.0.0 ready in 200 ms\n\n -> Local: http://localhost:5173/\n -> Network: http://192.168.1.2:5173/";
678 assert!(compress_output("vp build", output).is_some());
679 assert!(compress_output("vite-plus build", output).is_some());
680 }
681
682 #[test]
683 fn routes_bunx_commands() {
684 let output = "1 pass tests\n0 fail tests\n3 skip tests\nDone 12ms\nsome extra line\nmore output here";
685 let result = compress_output("bunx test", output);
686 assert!(
687 result.is_some(),
688 "bunx should compress when output is large enough"
689 );
690 assert!(result.unwrap().contains("bun test: 1 passed"));
691 }
692
693 #[test]
694 fn routes_deno_task() {
695 let output = "Task dev deno run --allow-net server.ts\nListening on http://localhost:8000";
696 assert!(try_specific_pattern("deno task dev", output).is_some());
697 }
698
699 #[test]
700 fn routes_jest_commands() {
701 let output = "PASS tests/main.test.js\nTest Suites: 1 passed, 1 total\nTests: 5 passed, 5 total\nTime: 2.5 s";
702 assert!(try_specific_pattern("jest", output).is_some());
703 assert!(try_specific_pattern("npx jest --coverage", output).is_some());
704 }
705
706 #[test]
707 fn routes_mocha_commands() {
708 let output = " 3 passing (50ms)\n 1 failing\n\n 1) Array #indexOf():\n Error: expected -1 to equal 0";
709 assert!(try_specific_pattern("mocha", output).is_some());
710 assert!(try_specific_pattern("npx mocha tests/", output).is_some());
711 }
712
713 #[test]
714 fn routes_tofu_commands() {
715 let output = "Initializing the backend...\nInitializing provider plugins...\nTerraform has been successfully initialized!";
716 assert!(try_specific_pattern("tofu init", output).is_some());
717 }
718
719 #[test]
720 fn routes_ps_commands() {
721 let mut lines = vec!["USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND".to_string()];
722 for i in 0..20 {
723 lines.push(format!("user {i} 0.0 0.1 1234 123 ? S 10:00 0:00 proc_{i}"));
724 }
725 let output = lines.join("\n");
726 assert!(try_specific_pattern("ps aux", &output).is_some());
727 }
728
729 #[test]
730 fn routes_ping_commands() {
731 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";
732 assert!(try_specific_pattern("ping -c 3 google.com", output).is_some());
733 }
734
735 #[test]
736 fn routes_jq_to_json_schema() {
737 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}]}";
738 assert!(try_specific_pattern("jq '.items' data.json", output).is_some());
739 }
740
741 #[test]
742 fn routes_linting_tools() {
743 let lint_output = "src/main.py:10: error: Missing return\nsrc/main.py:20: error: Unused var\nFound 2 errors";
744 assert!(try_specific_pattern("hadolint Dockerfile", lint_output).is_some());
745 assert!(try_specific_pattern("oxlint src/", lint_output).is_some());
746 assert!(try_specific_pattern("pyright src/", lint_output).is_some());
747 assert!(try_specific_pattern("basedpyright src/", lint_output).is_some());
748 }
749
750 #[test]
751 fn routes_fd_commands() {
752 let output = "src/main.rs\nsrc/lib.rs\nsrc/util/helpers.rs\nsrc/util/math.rs\ntests/integration.rs\n";
753 assert!(try_specific_pattern("fd --extension rs", output).is_some());
754 assert!(try_specific_pattern("fdfind .rs", output).is_some());
755 }
756
757 #[test]
758 fn routes_just_commands() {
759 let output = "Available recipes:\n build\n test\n lint\n";
760 assert!(try_specific_pattern("just --list", output).is_some());
761 assert!(try_specific_pattern("just build", output).is_some());
762 }
763
764 #[test]
765 fn routes_ninja_commands() {
766 let output = "[1/10] Compiling foo.c\n[10/10] Linking app\n";
767 assert!(try_specific_pattern("ninja", output).is_some());
768 assert!(try_specific_pattern("ninja -j4", output).is_some());
769 }
770
771 #[test]
772 fn routes_clang_commands() {
773 let output =
774 "src/main.c:10:5: error: use of undeclared identifier 'foo'\n1 error generated.\n";
775 assert!(try_specific_pattern("clang src/main.c", output).is_some());
776 assert!(try_specific_pattern("clang++ -std=c++17 main.cpp", output).is_some());
777 }
778
779 #[test]
780 fn routes_cargo_run() {
781 let output = " Compiling foo v0.1.0\n Finished `dev` profile\nHello, world!";
782 assert!(try_specific_pattern("cargo run", output).is_some());
783 }
784
785 #[test]
786 fn routes_cargo_bench() {
787 let output = " Compiling foo v0.1.0\ntest bench_parse ... bench: 1234 ns/iter";
788 assert!(try_specific_pattern("cargo bench", output).is_some());
789 }
790
791 #[test]
792 fn routes_build_tools() {
793 let build_output = " Compiling foo v0.1.0\n Finished release [optimized]";
794 assert!(try_specific_pattern("gcc -o main main.c", build_output).is_some());
795 assert!(try_specific_pattern("g++ -o main main.cpp", build_output).is_some());
796 }
797
798 #[test]
799 fn routes_monorepo_tools() {
800 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";
801 assert!(try_specific_pattern("turbo install", output).is_some());
802 assert!(try_specific_pattern("nx install", output).is_some());
803 }
804
805 #[test]
806 fn gh_api_passthrough_never_compresses() {
807 let huge = "line\n".repeat(5000);
808 assert!(
809 compress_output("gh api repos/owner/repo/actions/jobs/123/logs", &huge).is_none(),
810 "gh api must never be compressed, even for large output"
811 );
812 assert!(compress_output("gh api repos/owner/repo/actions/runs/123/logs", &huge).is_none());
813 }
814
815 #[test]
816 fn gh_log_flags_passthrough() {
817 let huge = "line\n".repeat(5000);
818 assert!(compress_output("gh run view 123 --log-failed", &huge).is_none());
819 assert!(compress_output("gh run view 123 --log", &huge).is_none());
820 }
821
822 #[test]
823 fn gh_structured_commands_still_compress() {
824 let output = "On branch main\nnothing to commit";
825 assert!(try_specific_pattern("gh pr list", output).is_some());
826 assert!(try_specific_pattern("gh run list", output).is_some());
827 }
828}