use clap::{Args, Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(name = "cargo", bin_name = "cargo", version)]
pub struct Cargo {
#[command(subcommand)]
pub command: CargoCommand,
}
#[derive(Subcommand, Debug)]
pub enum CargoCommand {
Brief(BriefDirect),
}
#[derive(Parser, Debug)]
#[command(
version,
about = "Visibility-aware Rust API extractor for AI agents",
after_help = "Run `cargo brief --help` for AI agent quick guide, or `cargo brief <cmd> --help` for details.",
after_long_help = "\
QUICK GUIDE — which subcommand for which task:
\"What's the signature of X?\" → search [--members]
\"Show a module's full API surface\" → api [--depth N] [--compact]
\"Where is X defined? Show source\" → code [--refs]
\"What modules exist in this crate?\" → summary
\"What features does crate X have?\" → features [-C <crate>]
\"How is X used in practice?\" → examples [--tests]
\"Find structural patterns in AST\" → ts '<s-expr>'
\"Who calls X? All references\" → lsp references <symbol>
\"What breaks if I change X?\" → lsp blast-radius <symbol> [--depth N]
\"What does X call / who calls X?\" → lsp call-hierarchy <symbol> [--outgoing]
TYPICAL WORKFLOW:
cargo brief summary self # 1. overview of modules
cargo brief api self::some_module # 2. drill into a module
cargo brief search self SomeType --members # 3. find a specific item
cargo brief code fn some_function --refs # 4. read source + references
cargo brief lsp references some_function # 5. cross-crate reference tracking
REMOTE CRATES (-C):
cargo brief -C summary tokio@1 # explore an unfamiliar crate
cargo brief -C features serde@1 # inspect a crate's feature graph
cargo brief -C -F rt,net api tokio@1 net # feature-gated APIs need -F
cargo brief -C search serde@1 Serialize # search a crates.io dependency
TIPS:
- Feature-gated items: run `features` to see what flags are available, then -F.
- api output annotates gated items (// requires feature \"...\") even when enabled.
- Smart-case: all-lowercase pattern = case-insensitive, any uppercase = exact.
- `code` searches ALL workspace members by default (not just current package).
- `lsp` resolves symbols by name. Workspace items are found instantly;
external deps (e.g., hecs::World) use usage-site fallback (slower).
Use unique names; common methods like \"new\" may still be ambiguous.
- `lsp` spawns a persistent rust-analyzer daemon; first query may be slow
while indexing. Use `lsp touch` to pre-warm.
Run `cargo brief <subcommand> --help` for subcommand-specific options and examples."
)]
pub struct BriefDirect {
#[arg(short = 'C', long, global = true)]
pub crates: bool,
#[arg(
short = 'F',
long,
value_name = "FEATURES",
global = true,
requires = "crates"
)]
pub features: Option<String>,
#[arg(long, global = true, requires = "crates")]
pub no_default_features: bool,
#[arg(long, global = true, requires = "crates")]
pub no_cache: bool,
#[command(subcommand)]
pub command: BriefCommand,
}
impl BriefDirect {
pub fn remote_opts(&self) -> RemoteOpts {
RemoteOpts {
crates: self.crates,
features: self.features.clone(),
no_default_features: self.no_default_features,
no_cache: self.no_cache,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RemoteOpts {
pub crates: bool,
pub features: Option<String>,
pub no_default_features: bool,
pub no_cache: bool,
}
#[derive(Subcommand, Debug, Clone)]
pub enum BriefCommand {
#[command(after_help = "\
EXAMPLES:
# Browse the current crate's API (run inside a Cargo project)
cargo brief api
# Browse a specific module in the current crate
cargo brief api self::net::tcp
# Inspect a crates.io dependency (cached after first run)
cargo brief -C api serde@1 --compact
cargo brief -C -F rt,net,io-util api tokio@1
# Browse a specific module of a remote crate
cargo brief -C api tokio@1::net
cargo brief -C -F net api tokio@1 net
# Reduce output verbosity for large crates
cargo brief -C api tokio@1 --compact
cargo brief -C api tokio@1 --doc-lines 1
RESOLUTION RULES:
The <TARGET> argument is resolved as follows:
1. \"self\" → current package (cwd-based detection)
2. \"self::mod\" → current package, specific module
3. \"crate::mod\" → named crate + module in one argument
4. \"src/foo.rs\" → file path auto-converted to module path
5. \"crate_name\" → workspace package (hyphen/underscore normalized)
6. \"unknown_name\" → treated as package name (use \"self::mod\" for modules)
With -C, TARGET is the crate spec (e.g., serde@1, tokio@1.0).
The [MODULE_PATH] argument also accepts file paths (e.g., src/foo.rs).")]
Api(ApiArgs),
#[command(after_help = "\
EXAMPLES:
# Substring search (smart-case: all-lowercase = insensitive)
cargo brief -C search axum@0.8 Router route
cargo brief -C search bevy ShaderRef Material
# OR-match with comma, methods-of
cargo brief search self \"EventReader,EventWriter\"
cargo brief -C search bytes@1 --methods-of Bytes
# Glob, exact match, exclusion
cargo brief search bevy \"Shader*Ref\" # * = 0+ chars, ? = 1 char
cargo brief search bevy \"=Router\" # final :: segment only
cargo brief search bevy -- spawn -test # -- needed for -prefix args
cargo brief search bevy \"*Plugin*,*Resource* -test\"
PATTERN SYNTAX:
Smart-case: all-lowercase → case-insensitive, any uppercase → case-sensitive.
Space = AND, comma = OR. Multiple args are joined with spaces.
Operators (per token):
word substring — path contains \"word\"
w*ld glob — * matches 0+ chars, ? matches 1 char (full-path anchored)
=Name exact — final path segment (after last ::) equals \"Name\"
-term exclude — remove matches (works with substring, glob, or -=exact)
Exclusions are global across all OR groups.")]
Search(SearchArgs),
#[command(after_help = "\
EXAMPLES:
# List example files with their module docs
cargo brief examples self
cargo brief -C examples tokio@1
# Grep for a pattern in example files
cargo brief examples self spawn
cargo brief -C examples hecs spawn_at --context 3
# Multiple patterns are AND-matched (no quotes needed)
cargo brief examples self spawn async
# Include tests and benches directories
cargo brief -C examples serde --tests --benches derive
MATCHING:
Multiple pattern arguments are joined with spaces (AND-matched).
Smart-case: all-lowercase pattern = case-insensitive, any uppercase = case-sensitive.
Without a pattern, lists files with their //! doc comments.")]
Examples(ExamplesArgs),
#[command(after_help = "\
EXAMPLES:
# Summarize the current crate
cargo brief summary self
# Summarize a remote crate
cargo brief -C -F full summary tokio@1
# Summarize a specific module
cargo brief -C summary bevy bevy::ecs
RESOLUTION RULES:
The <TARGET> argument is resolved as follows:
1. \"self\" → current package (cwd-based detection)
2. \"self::mod\" → current package, specific module
3. \"crate::mod\" → named crate + module in one argument
4. \"src/foo.rs\" → file path auto-converted to module path
5. \"crate_name\" → workspace package (hyphen/underscore normalized)
6. \"unknown_name\" → treated as package name
With -C, TARGET is the crate spec (e.g., serde@1, tokio@1.0).
The [MODULE_PATH] argument also accepts file paths (e.g., src/foo.rs).")]
Summary(SummaryArgs),
#[command(after_help = "\
EXAMPLES:
# Find all function definitions
cargo brief ts self '(function_item)'
# Capture function names
cargo brief ts self '(function_item name: (identifier) @name)' --captures
# Find impl blocks for a specific trait
cargo brief ts self '(impl_item trait: (type_identifier) @t (#eq? @t \"MyTrait\"))'
# Find struct definitions with their fields
cargo brief ts self '(struct_item name: (type_identifier) @name body: (field_declaration_list) @fields)' --captures
# Find functions returning Result
cargo brief ts self '(function_item return_type: (generic_type type: (type_identifier) @r (#eq? @r \"Result\")))'
# Find let bindings with type annotations
cargo brief ts self '(let_declaration pattern: (_) @pat type: (_) @type)' --captures
# Find all call expressions to a specific function
cargo brief ts self '(call_expression function: (identifier) @fn (#eq? @fn \"spawn\"))'
# Location-only output for large result sets
cargo brief ts self '(function_item)' --quiet
# Limit results
cargo brief ts self '(function_item)' --limit 10
# Only search src/ (skip tests/examples/benches)
cargo brief ts self '(function_item)' --src-only
# Query a remote crate's source
cargo brief -C ts serde@1 '(struct_item)'
QUERY SYNTAX:
Tree-sitter S-expression patterns match AST nodes by type and structure.
Explore the AST interactively: https://tree-sitter.github.io/tree-sitter/playground
NODE TYPES (common Rust constructs):
Items: function_item, struct_item, enum_item, impl_item, trait_item,
type_item, const_item, static_item, macro_definition,
use_declaration, mod_item
Expressions: call_expression, method_call_expression, field_expression,
match_expression, if_expression, closure_expression,
return_expression, await_expression
Statements: let_declaration, expression_statement, assignment_expression
Types: type_identifier, generic_type, reference_type, array_type,
function_type, tuple_type
Other: attribute_item, line_comment, block_comment, string_literal,
identifier, field_identifier
CAPTURES:
@name binds a node. In default mode, the outermost matched node is shown
(captures used only in predicates don't affect output). With --captures,
each @name: text pair is shown separately. Capture-less queries work too —
an internal @_match capture is auto-added.
PREDICATES:
(#eq? @cap \"value\") — exact string match on captured node text
(#match? @cap \"regex\") — regex match on captured node text
(#not-eq? @cap \"value\") — negated exact match
(#any-of? @cap \"a\" \"b\") — match any of the listed strings
TIPS:
- Rust async functions are function_item with modifiers, not a separate type
- Use (type_identifier) for type names, (identifier) for variable/fn names
- Nested captures: (struct_item (field_declaration_list
(field_declaration name: (field_identifier) @field)))
- Wildcards: (_) matches any single node type
- #[derive(...)] is: (attribute_item (attribute (identifier) @a (#eq? @a \"derive\")))")]
Ts(TsArgs),
#[command(after_help = "\
EXAMPLES:
# Search current workspace (TARGET omitted — defaults to \"self\")
cargo brief code spawn
cargo brief code struct Commands
# Search a specific crate in the workspace
cargo brief code my-crate fn spawn
cargo brief code my-crate struct Commands
# Remote crate (TARGET required with -C)
cargo brief -C code serde@1 struct Serializer
# Smart-case: all-lowercase = case-insensitive
cargo brief code pubstruct # finds PubStruct
cargo brief code PubStruct # case-sensitive
# Show definitions + references
cargo brief code fn spawn --refs
# References only
cargo brief code spawn --refs-only
# Scope to items inside a type
cargo brief code --in Commands fn new
cargo brief code --in PubStruct field
# Quiet mode (location only, no source text)
cargo brief code fn spawn -q
# Limit results
cargo brief code fn spawn --limit 5
POSITIONAL ARGS:
1 arg: code NAME search all workspace members, all kinds
(error if NAME is a kind keyword — use 2-arg form instead)
2 args: code KIND NAME if first arg is a kind keyword (see below)
code TARGET NAME otherwise: search named crate, all kinds
3 args: code TARGET KIND NAME search named crate, filter by kind
Disambiguation: if the first of two args matches a kind keyword, it is
treated as KIND (not TARGET). Use the 3-arg form to force a target that
happens to shadow a kind keyword.
TARGET RESOLUTION:
\"self\" (default when TARGET omitted):
Searches ALL workspace members — not just the current package.
This differs from other subcommands where \"self\" = current package,
because code is a source-level lookup tool where project-wide search
is the common case.
Named crate:
Searches only that specific package (hyphen/underscore normalized).
With -C (remote):
TARGET is a crates.io spec (e.g., serde@1). Implicit \"self\" is not
allowed — you must provide an explicit TARGET.
DEPENDENCY SEARCH:
Default: workspace members + accessible dependencies (via rustdoc JSON
reachability analysis; requires nightly).
--no-deps: workspace members (or named target) only.
--all-deps: workspace members + all direct dependencies (via cargo
metadata; no nightly needed, wider but noisier).
ITEM KINDS:
fn, struct, enum, trait, field, type, impl, macro, const, use
Omit KIND to search all kinds (except use, to reduce noise).
OUTPUT FORMAT:
Each match is printed as:
@<file>:<line> — source location
in <crate>::<module>[, <parent>] — module path + parent context
(e.g., impl Type, trait Trait)
<source text> — full item definition
With --quiet (-q), only the location and module-path lines are shown.
NAME MATCHING:
Smart-case: all-lowercase pattern = case-insensitive, any uppercase =
case-sensitive. The name must match the item's identifier exactly (not
a substring).
REFERENCE SEARCH (--refs, --refs-only):
After definitions, grep for literal name occurrences across the same
source files. Output uses * markers on match lines with 2 lines of
surrounding context. --refs-only skips definitions entirely.
--limit applies to definitions (--refs) or grep matches (--refs-only).
PARENT SCOPING (--in <TYPE>):
Filter definitions to items inside a specific type, impl block, or
trait. Matches the type identifier with smart-case rules.
--in Commands matches impl Commands, impl T for Commands, etc.
--in commands case-insensitive match
Top-level items (not inside any type) are excluded.")]
Code(CodeArgs),
#[command(after_help = "\
EXAMPLES:
# Show feature graph for the current workspace crate
cargo brief features
# Show feature graph for a specific workspace member
cargo brief features my-crate
# Show feature graph for a crates.io crate (requires -C)
cargo brief -C features serde@1
cargo brief -C features tokio@1")]
Features(FeaturesArgs),
#[command(after_help = "\
EXAMPLES:
# Clear all cached workspaces
cargo brief clean
# Clear caches for a specific crate
cargo brief clean serde")]
Clean(CleanArgs),
#[command(after_help = "\
EXAMPLES:
# Find all references to a symbol across the workspace
cargo brief lsp references resolve_symbol
cargo brief lsp references Foo::bar -q # quiet: locations only
# Blast radius: direct + transitive callers (\"what breaks if I change X?\")
cargo brief lsp blast-radius handle_request
cargo brief lsp blast-radius handle_request --depth 3
# Call hierarchy: who calls X (incoming) / what does X call (outgoing)
cargo brief lsp call-hierarchy spawn
cargo brief lsp call-hierarchy spawn --outgoing -q
# Daemon lifecycle (daemon auto-starts on first query)
cargo brief lsp touch # pre-warm rust-analyzer
cargo brief lsp status # check if running
cargo brief lsp stop # shut down daemon
SYMBOL RESOLUTION:
Symbols are resolved in two stages:
1. workspace/symbol search (fast, finds workspace-defined items)
2. Fallback: grep workspace source for usage sites, then resolve via
textDocument/definition (slower, finds external deps like hecs::World)
Tips:
- Qualified names work: \"hecs::World\", \"App::new\", \"MyStruct::method\"
- Common names like \"new\" or \"get\" may return Ambiguous (many matches).
Use a qualified form to narrow down.
- call-hierarchy and blast-radius work on functions/methods, not types.
Use `references` for tracking struct/enum/trait usage.
- If resolution still fails, try `code --refs <name>` as a last resort.
NOTE:
The LSP daemon spawns automatically on first query. Initial indexing may
take time; subsequent queries are fast. Use `lsp touch` to pre-warm.")]
Lsp(LspArgs),
}
#[derive(Args, Debug, Clone)]
pub struct TargetArgs {
#[arg(value_name = "TARGET", default_value = "self")]
pub crate_name: String,
pub module_path: Option<String>,
#[arg(long, help_heading = "Local Workspace")]
pub at_package: Option<String>,
#[arg(long, help_heading = "Local Workspace")]
pub at_mod: Option<String>,
#[arg(long, help_heading = "Local Workspace")]
pub manifest_path: Option<String>,
}
#[derive(Args, Debug, Clone)]
pub struct FilterArgs {
#[arg(long, help_heading = "Filtering")]
pub no_structs: bool,
#[arg(long, help_heading = "Filtering")]
pub no_enums: bool,
#[arg(long, help_heading = "Filtering")]
pub no_traits: bool,
#[arg(long, help_heading = "Filtering")]
pub no_functions: bool,
#[arg(long, help_heading = "Filtering")]
pub no_aliases: bool,
#[arg(long, help_heading = "Filtering")]
pub no_constants: bool,
#[arg(long, help_heading = "Filtering")]
pub no_unions: bool,
#[arg(long, help_heading = "Filtering")]
pub no_macros: bool,
#[arg(long, help_heading = "Filtering")]
pub no_docs: bool,
#[arg(long, help_heading = "Filtering")]
pub no_crate_docs: bool,
#[arg(long, value_name = "N", help_heading = "Filtering")]
pub doc_lines: Option<usize>,
#[arg(long, help_heading = "Filtering")]
pub compact: bool,
#[arg(long, help_heading = "Filtering")]
pub verbose_metadata: bool,
#[arg(long)]
pub all: bool,
#[arg(long, help_heading = "Filtering")]
pub no_feature_gates: bool,
}
#[derive(Args, Debug, Clone)]
pub struct GlobalArgs {
#[arg(long, default_value = "nightly", help_heading = "Advanced")]
pub toolchain: String,
#[arg(short, long)]
pub verbose: bool,
}
#[derive(Args, Debug, Clone)]
pub struct ApiArgs {
#[command(flatten)]
pub target: TargetArgs,
#[command(flatten)]
pub filter: FilterArgs,
#[command(flatten)]
pub global: GlobalArgs,
#[arg(long, default_value = "1")]
pub depth: u32,
#[arg(long)]
pub recursive: bool,
#[arg(long)]
pub no_expand_glob: bool,
}
#[derive(Args, Debug, Clone)]
pub struct SearchArgs {
#[arg(value_name = "TARGET", default_value = "self")]
pub crate_name: String,
#[arg(value_name = "PATTERN", num_args = 0..)]
pub patterns: Vec<String>,
#[command(flatten)]
pub filter: FilterArgs,
#[command(flatten)]
pub global: GlobalArgs,
#[arg(long, help_heading = "Local Workspace")]
pub at_package: Option<String>,
#[arg(long, help_heading = "Local Workspace")]
pub at_mod: Option<String>,
#[arg(long, help_heading = "Local Workspace")]
pub manifest_path: Option<String>,
#[arg(long, value_name = "[OFFSET:]N")]
pub limit: Option<String>,
#[arg(long, value_name = "TYPE")]
pub methods_of: Option<String>,
#[arg(long, value_name = "KINDS", help_heading = "Filtering")]
pub search_kind: Option<String>,
#[arg(long)]
pub members: bool,
}
impl SearchArgs {
pub fn pattern(&self) -> String {
self.patterns.join(" ")
}
}
#[derive(Args, Debug, Clone)]
pub struct ExamplesArgs {
#[arg(value_name = "TARGET", default_value = "self")]
pub crate_name: String,
#[arg(value_name = "PATTERN", num_args = 0..)]
pub patterns: Vec<String>,
#[command(flatten)]
pub global: GlobalArgs,
#[arg(long, help_heading = "Local Workspace")]
pub manifest_path: Option<String>,
#[arg(long, default_value = "2")]
pub context: String,
#[arg(long, num_args(0..=1), default_missing_value = "999", value_name = "DEPTH")]
pub tests: Option<u32>,
#[arg(long, num_args(0..=1), default_missing_value = "999", value_name = "DEPTH")]
pub benches: Option<u32>,
}
#[derive(Args, Debug, Clone)]
pub struct TsArgs {
#[arg(value_name = "TARGET")]
pub crate_name: String,
#[arg(value_name = "QUERY")]
pub query: String,
#[command(flatten)]
pub global: GlobalArgs,
#[arg(long, help_heading = "Local Workspace")]
pub manifest_path: Option<String>,
#[arg(long)]
pub captures: bool,
#[arg(long, default_value = "0")]
pub context: String,
#[arg(long)]
pub src_only: bool,
#[arg(long, value_name = "[OFFSET:]N")]
pub limit: Option<String>,
#[arg(short = 'q', long)]
pub quiet: bool,
}
#[derive(Args, Debug, Clone)]
pub struct CodeArgs {
#[arg(value_name = "ARGS", num_args = 1..=3)]
pub args: Vec<String>,
#[command(flatten)]
pub global: GlobalArgs,
#[arg(long, help_heading = "Local Workspace")]
pub manifest_path: Option<String>,
#[arg(long)]
pub src_only: bool,
#[arg(long)]
pub no_deps: bool,
#[arg(long, conflicts_with = "no_deps")]
pub all_deps: bool,
#[arg(long, value_name = "[OFFSET:]N")]
pub limit: Option<String>,
#[arg(short = 'q', long)]
pub quiet: bool,
#[arg(long)]
pub refs: bool,
#[arg(long, conflicts_with = "refs")]
pub refs_only: bool,
#[arg(long, value_name = "TYPE")]
pub in_type: Option<String>,
}
#[derive(Args, Debug, Clone)]
pub struct SummaryArgs {
#[command(flatten)]
pub target: TargetArgs,
#[command(flatten)]
pub global: GlobalArgs,
}
#[derive(Args, Debug, Clone)]
pub struct FeaturesArgs {
#[arg(value_name = "CRATE", default_value = "self")]
pub crate_name: String,
#[command(flatten)]
pub global: GlobalArgs,
#[arg(long, help_heading = "Local Workspace")]
pub manifest_path: Option<String>,
}
#[derive(Args, Debug, Clone)]
pub struct CleanArgs {
#[arg(value_name = "SPEC")]
pub spec: Option<String>,
}
#[derive(Args, Debug, Clone)]
pub struct LspArgs {
#[command(subcommand)]
pub command: LspCommand,
#[command(flatten)]
pub global: GlobalArgs,
#[arg(long, help_heading = "Local Workspace")]
pub manifest_path: Option<String>,
}
#[derive(Subcommand, Debug, Clone)]
pub enum LspCommand {
Touch {
#[arg(long)]
no_wait: bool,
},
Stop,
Status,
References {
symbol: String,
#[arg(long, short)]
quiet: bool,
},
BlastRadius {
symbol: String,
#[arg(long, default_value = "1")]
depth: u32,
#[arg(long, short)]
quiet: bool,
},
CallHierarchy {
symbol: String,
#[arg(long)]
outgoing: bool,
#[arg(long, short)]
quiet: bool,
},
}
impl ExamplesArgs {
pub fn pattern(&self) -> Option<String> {
if self.patterns.is_empty() {
None
} else {
Some(self.patterns.join(" "))
}
}
}