greentic_dev/
cli.rs

1use std::path::PathBuf;
2
3use clap::{Args, Parser, Subcommand, ValueEnum};
4
5#[derive(Parser, Debug)]
6#[command(name = "greentic-dev")]
7#[command(version)]
8#[command(about = "Greentic developer tooling CLI")]
9pub struct Cli {
10    #[command(subcommand)]
11    pub command: Command,
12}
13
14#[derive(Subcommand, Debug)]
15pub enum Command {
16    /// Flow tooling (validate, lint, bundle inspection)
17    #[command(subcommand)]
18    Flow(FlowCommand),
19    /// Pack tooling (build deterministic packs, run locally)
20    #[command(subcommand)]
21    Pack(PackCommand),
22    /// Component tooling (scaffolding, validation, diagnostics)
23    #[command(subcommand)]
24    Component(ComponentCommand),
25    /// Manage greentic-dev configuration
26    #[command(subcommand)]
27    Config(ConfigCommand),
28    /// MCP tooling (feature = "mcp")
29    #[cfg(feature = "mcp")]
30    #[command(subcommand)]
31    Mcp(McpCommand),
32}
33
34#[derive(Subcommand, Debug)]
35pub enum FlowCommand {
36    /// Validate a flow YAML file and emit the canonical bundle JSON
37    Validate(FlowValidateArgs),
38}
39
40#[derive(Args, Debug)]
41pub struct FlowValidateArgs {
42    /// Path to the flow definition (YAML)
43    #[arg(short = 'f', long = "file")]
44    pub file: PathBuf,
45    /// Emit compact JSON instead of pretty-printing
46    #[arg(long = "json")]
47    pub json: bool,
48}
49
50#[derive(Subcommand, Debug)]
51pub enum PackCommand {
52    /// Build a deterministic .gtpack from a validated flow bundle
53    Build(PackBuildArgs),
54    /// Execute a pack locally with mocks/telemetry support
55    Run(PackRunArgs),
56}
57
58#[derive(Args, Debug)]
59pub struct PackBuildArgs {
60    /// Path to the flow definition (YAML)
61    #[arg(short = 'f', long = "file")]
62    pub file: PathBuf,
63    /// Output path for the generated pack
64    #[arg(short = 'o', long = "out")]
65    pub out: PathBuf,
66    /// Signing mode for the generated pack
67    #[arg(long = "sign", default_value = "dev", value_enum)]
68    pub sign: PackSignArg,
69    /// Optional path to pack metadata (pack.toml)
70    #[arg(long = "meta")]
71    pub meta: Option<PathBuf>,
72    /// Directory containing local component builds
73    #[arg(long = "component-dir", value_name = "DIR")]
74    pub component_dir: Option<PathBuf>,
75}
76
77#[derive(Args, Debug)]
78pub struct PackRunArgs {
79    /// Path to the pack (.gtpack) to execute
80    #[arg(short = 'p', long = "pack")]
81    pub pack: PathBuf,
82    /// Flow entry identifier override
83    #[arg(long = "entry")]
84    pub entry: Option<String>,
85    /// JSON payload to use as run input
86    #[arg(long = "input")]
87    pub input: Option<String>,
88    /// Enforcement policy for pack signatures
89    #[arg(long = "policy", default_value = "devok", value_enum)]
90    pub policy: RunPolicyArg,
91    /// OTLP collector endpoint (optional)
92    #[arg(long = "otlp")]
93    pub otlp: Option<String>,
94    /// Comma-separated list of allowed outbound hosts
95    #[arg(long = "allow")]
96    pub allow: Option<String>,
97    /// Mocks toggle
98    #[arg(long = "mocks", default_value = "on", value_enum)]
99    pub mocks: MockSettingArg,
100    /// Directory to persist run artifacts (transcripts, logs)
101    #[arg(long = "artifacts")]
102    pub artifacts: Option<PathBuf>,
103}
104
105#[derive(Subcommand, Debug)]
106pub enum ComponentCommand {
107    /// Inspect a component and print metadata
108    Inspect(ComponentInspectArgs),
109    /// List component templates exported by greentic-component
110    Templates(ComponentTemplatesArgs),
111    /// Scaffold a new component via greentic-component
112    New(ComponentNewArgs),
113    /// Validate a component workspace via greentic-component
114    Validate(ComponentValidateArgs),
115    /// Run diagnostics for a component workspace via greentic-component
116    Doctor(ComponentDoctorArgs),
117}
118
119#[derive(Args, Debug, Clone)]
120pub struct ComponentInspectArgs {
121    /// Path or identifier for the component
122    pub target: String,
123    /// Emit compact JSON instead of pretty output
124    #[arg(long = "json")]
125    pub json: bool,
126}
127
128#[derive(Args, Debug, Clone, Default)]
129pub struct ComponentTemplatesArgs {
130    /// Emit JSON for the templates payload
131    #[arg(long = "json")]
132    pub json: bool,
133    /// Enable telemetry collection by the greentic-component tool
134    #[arg(long = "telemetry")]
135    pub telemetry: bool,
136}
137
138#[derive(Args, Debug, Clone, Default)]
139pub struct ComponentNewArgs {
140    /// Component name (used for the scaffold directory)
141    #[arg(long = "name", value_name = "NAME")]
142    pub name: Option<String>,
143    /// Target directory for the scaffold
144    #[arg(long = "path", value_name = "DIR")]
145    pub path: Option<PathBuf>,
146    /// Template identifier from `greentic-component templates`
147    #[arg(long = "template", value_name = "ID")]
148    pub template: Option<String>,
149    /// Reverse-DNS organization identifier (e.g., ai.greentic)
150    #[arg(long = "org", value_name = "ORG")]
151    pub org: Option<String>,
152    /// Version for the scaffolded component (semver)
153    #[arg(long = "version", value_name = "SEMVER")]
154    pub version: Option<String>,
155    /// License identifier (SPDX ID)
156    #[arg(long = "license", value_name = "ID")]
157    pub license: Option<String>,
158    /// WIT world name to target
159    #[arg(long = "wit-world", value_name = "WORLD")]
160    pub wit_world: Option<String>,
161    /// Run in non-interactive mode (no prompts)
162    #[arg(long = "non-interactive")]
163    pub non_interactive: bool,
164    /// Skip the compile check that runs after scaffolding
165    #[arg(long = "no-check")]
166    pub no_check: bool,
167    /// Emit JSON output for the scaffold result
168    #[arg(long = "json")]
169    pub json: bool,
170    /// Enable telemetry collection by the greentic-component tool
171    #[arg(long = "telemetry")]
172    pub telemetry: bool,
173}
174
175#[derive(Args, Debug, Clone, Default)]
176pub struct ComponentValidateArgs {
177    /// Path to the component workspace (defaults to current directory)
178    #[arg(long = "path", value_name = "DIR")]
179    pub path: Option<PathBuf>,
180    /// Enable telemetry collection by the greentic-component tool
181    #[arg(long = "telemetry")]
182    pub telemetry: bool,
183}
184
185#[derive(Args, Debug, Clone, Default)]
186pub struct ComponentDoctorArgs {
187    /// Path to the component workspace (defaults to current directory)
188    #[arg(long = "path", value_name = "DIR")]
189    pub path: Option<PathBuf>,
190    /// Enable telemetry collection by the greentic-component tool
191    #[arg(long = "telemetry")]
192    pub telemetry: bool,
193}
194
195#[cfg(feature = "mcp")]
196#[derive(Subcommand, Debug)]
197pub enum McpCommand {
198    /// Inspect MCP provider metadata
199    Doctor(McpDoctorArgs),
200}
201
202#[cfg(feature = "mcp")]
203#[derive(Args, Debug)]
204pub struct McpDoctorArgs {
205    /// MCP provider identifier or config path
206    pub provider: String,
207    /// Emit compact JSON instead of pretty output
208    #[arg(long = "json")]
209    pub json: bool,
210}
211
212#[derive(Subcommand, Debug)]
213pub enum ConfigCommand {
214    /// Set a key in ~/.greentic/config.toml (e.g. defaults.component.org)
215    Set(ConfigSetArgs),
216}
217
218#[derive(Args, Debug)]
219pub struct ConfigSetArgs {
220    /// Config key path (e.g. defaults.component.org)
221    pub key: String,
222    /// Value to assign to the key (stored as a string)
223    pub value: String,
224    /// Override config file path (default: ~/.greentic/config.toml)
225    #[arg(long = "file")]
226    pub file: Option<PathBuf>,
227}
228
229#[derive(Copy, Clone, Debug, ValueEnum)]
230pub enum PackSignArg {
231    Dev,
232    None,
233}
234
235#[derive(Copy, Clone, Debug, ValueEnum)]
236pub enum RunPolicyArg {
237    Strict,
238    Devok,
239}
240
241#[derive(Copy, Clone, Debug, ValueEnum)]
242pub enum MockSettingArg {
243    On,
244    Off,
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use clap::Parser;
251
252    #[test]
253    fn parses_component_new_arguments() {
254        let cli = Cli::parse_from([
255            "greentic-dev",
256            "component",
257            "new",
258            "--name",
259            "demo",
260            "--path",
261            "./demo",
262            "--template",
263            "rust",
264            "--org",
265            "ai.greentic",
266            "--version",
267            "0.1.0",
268            "--license",
269            "Apache-2.0",
270            "--wit-world",
271            "component:demo",
272            "--non-interactive",
273            "--no-check",
274            "--json",
275            "--telemetry",
276        ]);
277
278        let Command::Component(ComponentCommand::New(args)) = cli.command else {
279            panic!("expected component new variant");
280        };
281        assert_eq!(args.name.as_deref(), Some("demo"));
282        assert_eq!(
283            args.path.as_ref().map(|p| p.display().to_string()),
284            Some("./demo".into())
285        );
286        assert_eq!(args.template.as_deref(), Some("rust"));
287        assert_eq!(args.org.as_deref(), Some("ai.greentic"));
288        assert_eq!(args.version.as_deref(), Some("0.1.0"));
289        assert_eq!(args.license.as_deref(), Some("Apache-2.0"));
290        assert_eq!(args.wit_world.as_deref(), Some("component:demo"));
291        assert!(args.non_interactive);
292        assert!(args.no_check);
293        assert!(args.json);
294        assert!(args.telemetry);
295    }
296
297    #[test]
298    fn parses_component_templates_flag() {
299        let cli = Cli::parse_from([
300            "greentic-dev",
301            "component",
302            "templates",
303            "--json",
304            "--telemetry",
305        ]);
306        let Command::Component(ComponentCommand::Templates(args)) = cli.command else {
307            panic!("expected component templates variant");
308        };
309        assert!(args.json);
310        assert!(args.telemetry);
311    }
312
313    #[test]
314    fn parses_config_set_command() {
315        let cli = Cli::parse_from([
316            "greentic-dev",
317            "config",
318            "set",
319            "defaults.component.org",
320            "ai.greentic",
321            "--file",
322            "/tmp/config.toml",
323        ]);
324        let Command::Config(ConfigCommand::Set(args)) = cli.command else {
325            panic!("expected config set variant");
326        };
327        assert_eq!(args.key, "defaults.component.org");
328        assert_eq!(args.value, "ai.greentic");
329        assert_eq!(
330            args.file.as_ref().map(|p| p.display().to_string()),
331            Some("/tmp/config.toml".into())
332        );
333    }
334}