1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Jonathan Shook
//! The `ct-search` command grammar (see [`crate::cli`]); the `ct-search` bin is
//! a thin parse-and-dispatch wrapper over this `Cli`.
use std::path::PathBuf;
use clap::Parser;
use crate::explain::Format;
use crate::pattern;
use crate::pulse::HeartbeatOpts;
use crate::walk::EntryType;
#[derive(Parser, Debug)]
#[command(
name = "ct-search",
version,
about = "Recursively find files by name, type, size, and content from a chosen root.",
long_about = "ct-search combines the predicates you would otherwise assemble from find, xargs, \
and grep into one declarative command (also reachable as `ct search`). An entry \
matches only when every supplied predicate holds. See `ct-search --explain` for \
agent-oriented documentation."
)]
#[command(group = clap::ArgGroup::new("output_mode")
.args(["list", "summary", "detail", "quiet"])
.multiple(false))]
pub struct Cli {
/// Search root (relative or absolute), independent of the current directory.
#[arg(long, default_value = ".")]
pub base: PathBuf,
/// File-name pattern; '|'-separated alternatives, each substring->glob->regex promoted and anchored to the whole name.
#[arg(long)]
pub name: Option<String>,
/// Restrict to entry kinds: f=file, d=dir, l=symlink (repeatable or comma-joined).
#[arg(long, value_enum, value_delimiter = ',')]
pub r#type: Vec<EntryType>,
/// Content pattern (substring->glob->regex promoted); searches file contents. Accepts file:PATH / text:VALUE; a multi-line pattern matches as a line-anchored literal block.
#[arg(long)]
pub grep: Option<String>,
/// Pin how patterns are interpreted (promotion off): literal, glob, or regex.
#[arg(long, value_enum)]
pub mode: Option<pattern::Mode>,
/// OKF: keep only Markdown concepts whose frontmatter `type` matches this pattern (substring->glob->regex promoted).
#[arg(long)]
pub okf_type: Option<String>,
/// OKF: keep only Markdown concepts whose frontmatter carries all these tags (comma-joined or repeated).
#[arg(long, value_delimiter = ',')]
pub okf_tag: Vec<String>,
/// Size predicate [+|-]N[k|m|g]: +N larger than, -N smaller than, N at least N.
#[arg(long)]
pub size: Option<String>,
/// Include dot-entries (names starting with '.'); default skips them.
#[arg(long)]
pub hidden: bool,
/// Follow symlinks while traversing.
#[arg(long)]
pub follow: bool,
/// Walk gitignored / .ignore files too (the .git directory is always skipped); by default the walk skips what git would.
#[arg(long)]
pub no_ignore: bool,
/// Stop after N matches.
#[arg(long)]
pub limit: Option<usize>,
/// Abort with exit 2 if the search exceeds SECS seconds (fractional allowed).
#[arg(long, value_name = "SECS")]
pub timeout: Option<f64>,
#[command(flatten)]
pub heartbeat: HeartbeatOpts,
/// Question this search answers, framing it as a test; printed as a "== ... ==" banner unless --quiet.
#[arg(long)]
pub question: Option<String>,
/// Verdict expectation over the match count: any|none|N|=N|+N|-N (default: any). Turns the search into a pass/fail test whose exit status follows the verdict.
#[arg(long)]
pub expect: Option<String>,
/// Template written to stdout after the search. Tokens: {RESULT} {QUESTION} {COUNT} {LINES} {BASE} {MATCHES}.
#[arg(long, alias = "emit-stdout")]
pub emit: Option<String>,
/// Template written to stderr after the search (same tokens as --emit).
#[arg(long)]
pub emit_stderr: Option<String>,
/// Output mode: print one matching path per line (default).
#[arg(long)]
pub list: bool,
/// Output mode: print counts only.
#[arg(long)]
pub summary: bool,
/// Output mode: print matches plus, for --grep, each hit as path:line:text.
#[arg(long)]
pub detail: bool,
/// Output mode: print nothing; report via exit status only.
#[arg(long)]
pub quiet: bool,
/// Emit a structured JSON result instead of text (overrides the output mode and --emit).
#[arg(long)]
pub json: bool,
/// Like `--json`, but pretty-printed (indented).
#[arg(long)]
pub json_pretty: bool,
/// Print agent usage docs (md or json) and exit.
#[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
pub explain: Option<Format>,
}