Skip to main content

foundry_cli/introspect/
document.rs

1//! Serializable types describing a Foundry binary's command surface.
2//!
3//! See [`docs/agents/spec.md`](../../../../docs/agents/spec.md) for the
4//! contract these types implement.
5
6use serde::{Deserialize, Serialize};
7use std::borrow::Cow;
8
9/// Top-level introspection document.
10#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
11pub struct IntrospectDocument {
12    /// Stable logical schema id, e.g. `foundry:introspect@v1`.
13    pub schema_id: String,
14    /// Schema version for the introspect document.
15    pub schema_version: u32,
16    /// Information about the binary being introspected.
17    pub binary: BinaryInfo,
18    /// Tree of commands exposed by the binary.
19    pub commands: Vec<CommandInfo>,
20}
21
22/// Information about the binary itself.
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24pub struct BinaryInfo {
25    /// Binary name (`forge`, `cast`, `anvil`, `chisel`).
26    pub name: String,
27    /// Short version string.
28    pub version: String,
29    /// Long version string with build metadata.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub long_version: Option<String>,
32    /// Description of the binary.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub description: Option<String>,
35    /// Args accepted by every command (clap `global = true`).
36    #[serde(default, skip_serializing_if = "Vec::is_empty")]
37    pub global_args: Vec<ArgInfo>,
38}
39
40/// Information about a single command (or group) in the CLI tree.
41#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
42pub struct CommandInfo {
43    /// Stable machine identifier (e.g. `forge.build`).
44    pub command_id: String,
45    /// Whether `command_id` is pinned in the per-binary registry (frozen) or
46    /// derived from the clap path (provisional and may shift on CLI renames).
47    pub command_id_stable: bool,
48    /// Clap path components (e.g. `["forge", "build"]`).
49    pub path: Vec<String>,
50    /// Visible aliases for this command.
51    pub aliases: Vec<String>,
52    /// Short, single-line summary.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub summary: Option<String>,
55    /// Long description (multi-line allowed).
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub description: Option<String>,
58    /// Arguments declared directly on this command.
59    pub args: Vec<ArgInfo>,
60    /// Subcommands of this command.
61    pub subcommands: Vec<Self>,
62    /// Capabilities reported for agent consumers.
63    pub capabilities: Capabilities,
64    /// Whether `capabilities` was authored in the registry. When `false`,
65    /// every capability field is a non-authoritative default and consumers
66    /// MUST treat side-effects, project requirement, etc. as unknown.
67    pub capabilities_declared: bool,
68    /// Command-specific exit codes (in addition to the global table).
69    pub exit_codes: Vec<ExitCodeInfo>,
70    /// Whether this command is hidden in the human-facing help.
71    #[serde(default, skip_serializing_if = "is_false")]
72    pub hidden: bool,
73}
74
75/// Capability flags exposed for agent consumers.
76#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
77pub struct Capabilities {
78    /// What the command emits when run in machine mode.
79    pub output_mode: OutputMode,
80    /// Stable schema id for the envelope payload.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub result_schema_ref: Option<Cow<'static, str>>,
83    /// Stable schema id for stream event records.
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub event_schema_ref: Option<Cow<'static, str>>,
86    /// Stable schema id for session-record startup/state.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub session_schema_ref: Option<Cow<'static, str>>,
89    /// Whether the command can take input on stdin via `--input -`.
90    pub reads_stdin: bool,
91    /// Whether the command supports `--output PATH`.
92    pub supports_output_path: bool,
93    /// Whether the command requires a Foundry project to run.
94    pub requires_project: bool,
95    /// Coarse classification of the command's side effects.
96    pub side_effects: SideEffects,
97    /// Whether the command can stream output for an extended period.
98    pub long_running: bool,
99    /// Whether the command opens a session that persists beyond a single call.
100    pub stateful: bool,
101}
102
103impl Capabilities {
104    /// Const-constructible default suitable for use in `static` registries.
105    pub const NONE: Self = Self {
106        output_mode: OutputMode::None,
107        result_schema_ref: None,
108        event_schema_ref: None,
109        session_schema_ref: None,
110        reads_stdin: false,
111        supports_output_path: false,
112        requires_project: false,
113        side_effects: SideEffects::None,
114        long_running: false,
115        stateful: false,
116    };
117}
118
119impl Default for Capabilities {
120    fn default() -> Self {
121        Self::NONE
122    }
123}
124
125/// Output mode under machine mode.
126#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(rename_all = "snake_case")]
128pub enum OutputMode {
129    /// No machine-mode contract yet; output is human-only.
130    None,
131    /// Pre-existing `--json` shape predating this contract.
132    LegacyJson,
133    /// Single terminal `JsonEnvelope<T>` on stdout.
134    Envelope,
135    /// Newline-delimited JSON event records on stdout.
136    Stream,
137    /// Long-running session (e.g. `anvil`); emits a `session_start` record.
138    Session,
139}
140
141/// Coarse classification of a command's side effects.
142///
143/// Reports only the highest-impact effect (e.g. a chain-writing command that
144/// also writes files reports `ChainWrite`); it is not an exhaustive set.
145#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
146#[serde(rename_all = "snake_case")]
147pub enum SideEffects {
148    /// Pure: reads only (e.g. `cast tx`).
149    None,
150    /// Writes files on the local filesystem.
151    FsWrite,
152    /// Performs network reads (RPC, HTTP).
153    Network,
154    /// Submits transactions or otherwise mutates chain state.
155    ChainWrite,
156    /// Spawns a long-running server (e.g. `anvil`).
157    SpawnServer,
158}
159
160/// Information about a single command argument.
161#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
162pub struct ArgInfo {
163    /// Argument identifier (clap arg id).
164    pub name: String,
165    /// Argument kind.
166    pub kind: ArgKind,
167    /// Best-effort classification of the value type.
168    pub value_type: ValueType,
169    /// Help text, when present.
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub help: Option<String>,
172    /// Long form (`--foo`), if any.
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub long: Option<String>,
175    /// Short form (`-f`), if any.
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub short: Option<char>,
178    /// All visible aliases.
179    pub aliases: Vec<String>,
180    /// Bound environment variable, if any.
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub env: Option<String>,
183    /// Default value, if any.
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub default: Option<String>,
186    /// Permitted values for value-enum arguments.
187    pub possible_values: Vec<String>,
188    /// Whether the argument is required.
189    pub required: bool,
190    /// Whether the argument can be supplied multiple times.
191    pub repeatable: bool,
192    /// Other arguments this argument conflicts with.
193    pub conflicts_with: Vec<String>,
194    /// Help heading the argument is grouped under.
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub help_heading: Option<String>,
197    /// Whether the argument is hidden in human help.
198    #[serde(default, skip_serializing_if = "is_false")]
199    pub hidden: bool,
200}
201
202/// Argument shape.
203#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(rename_all = "snake_case")]
205pub enum ArgKind {
206    /// Boolean flag (`--quiet`).
207    Flag,
208    /// Option that takes a value (`--rpc-url URL`).
209    Option,
210    /// Positional argument.
211    Positional,
212}
213
214/// Best-effort classification of an argument's value type, for agent UI hints.
215#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(rename_all = "snake_case")]
217pub enum ValueType {
218    Bool,
219    String,
220    Integer,
221    Path,
222    Url,
223    Address,
224    Selector,
225    Hex,
226    Json,
227    Other,
228}
229
230/// Documented exit code for a command, beyond the global table.
231#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
232pub struct ExitCodeInfo {
233    /// Numeric process exit code.
234    pub code: i32,
235    /// Stable name (e.g. `TestFailure`).
236    pub name: Cow<'static, str>,
237    /// Description of when this code is emitted.
238    pub description: Cow<'static, str>,
239}
240
241#[allow(clippy::trivially_copy_pass_by_ref)]
242const fn is_false(b: &bool) -> bool {
243    !*b
244}