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 (delegates to `greentic-component`)
23    Component(ComponentPassthroughArgs),
24    /// Manage greentic-dev configuration
25    #[command(subcommand)]
26    Config(ConfigCommand),
27    /// MCP tooling (feature = "mcp")
28    #[cfg(feature = "mcp")]
29    #[command(subcommand)]
30    Mcp(McpCommand),
31}
32
33#[derive(Subcommand, Debug)]
34pub enum FlowCommand {
35    /// Validate a flow YAML file and emit the canonical bundle JSON
36    Validate(FlowValidateArgs),
37}
38
39#[derive(Args, Debug)]
40pub struct FlowValidateArgs {
41    /// Path to the flow definition (YAML)
42    #[arg(short = 'f', long = "file")]
43    pub file: PathBuf,
44    /// Emit compact JSON instead of pretty-printing
45    #[arg(long = "json")]
46    pub json: bool,
47}
48
49#[derive(Subcommand, Debug)]
50pub enum PackCommand {
51    /// Build a deterministic .gtpack from a validated flow bundle
52    Build(PackBuildArgs),
53    /// Execute a pack locally with mocks/telemetry support
54    Run(PackRunArgs),
55    /// Verify a built pack archive (.gtpack)
56    Verify(PackVerifyArgs),
57    /// Scaffold a pack workspace via the `packc` CLI
58    New(PackNewArgs),
59}
60
61#[derive(Args, Debug)]
62pub struct PackBuildArgs {
63    /// Path to the flow definition (YAML)
64    #[arg(short = 'f', long = "file")]
65    pub file: PathBuf,
66    /// Output path for the generated pack
67    #[arg(short = 'o', long = "out")]
68    pub out: PathBuf,
69    /// Signing mode for the generated pack
70    #[arg(long = "sign", default_value = "dev", value_enum)]
71    pub sign: PackSignArg,
72    /// Optional path to pack metadata (pack.toml)
73    #[arg(long = "meta")]
74    pub meta: Option<PathBuf>,
75    /// Directory containing local component builds
76    #[arg(long = "component-dir", value_name = "DIR")]
77    pub component_dir: Option<PathBuf>,
78}
79
80#[derive(Args, Debug)]
81pub struct PackRunArgs {
82    /// Path to the pack (.gtpack) to execute
83    #[arg(short = 'p', long = "pack")]
84    pub pack: PathBuf,
85    /// Flow entry identifier override
86    #[arg(long = "entry")]
87    pub entry: Option<String>,
88    /// JSON payload to use as run input
89    #[arg(long = "input")]
90    pub input: Option<String>,
91    /// Enforcement policy for pack signatures
92    #[arg(long = "policy", default_value = "devok", value_enum)]
93    pub policy: RunPolicyArg,
94    /// OTLP collector endpoint (optional)
95    #[arg(long = "otlp")]
96    pub otlp: Option<String>,
97    /// Comma-separated list of allowed outbound hosts
98    #[arg(long = "allow")]
99    pub allow: Option<String>,
100    /// Mocks toggle
101    #[arg(long = "mocks", default_value = "on", value_enum)]
102    pub mocks: MockSettingArg,
103    /// Directory to persist run artifacts (transcripts, logs)
104    #[arg(long = "artifacts")]
105    pub artifacts: Option<PathBuf>,
106}
107
108#[derive(Args, Debug)]
109pub struct PackVerifyArgs {
110    /// Path to the pack (.gtpack) to verify
111    #[arg(short = 'p', long = "pack")]
112    pub pack: PathBuf,
113    /// Verification policy for signatures
114    #[arg(long = "policy", default_value = "devok", value_enum)]
115    pub policy: VerifyPolicyArg,
116    /// Emit the manifest JSON on success
117    #[arg(long = "json")]
118    pub json: bool,
119}
120
121#[derive(Args, Debug, Clone, Default)]
122#[command(disable_help_flag = true)]
123pub struct PackNewArgs {
124    /// Arguments passed directly to the `packc new` command
125    #[arg(
126        value_name = "ARGS",
127        trailing_var_arg = true,
128        allow_hyphen_values = true
129    )]
130    pub passthrough: Vec<String>,
131}
132
133#[derive(Args, Debug, Clone, Default)]
134pub struct ComponentPassthroughArgs {
135    /// Arguments passed directly to the `greentic-component` CLI
136    #[arg(
137        value_name = "ARGS",
138        trailing_var_arg = true,
139        allow_hyphen_values = true
140    )]
141    pub passthrough: Vec<String>,
142}
143
144#[cfg(feature = "mcp")]
145#[derive(Subcommand, Debug)]
146pub enum McpCommand {
147    /// Inspect MCP provider metadata
148    Doctor(McpDoctorArgs),
149}
150
151#[cfg(feature = "mcp")]
152#[derive(Args, Debug)]
153pub struct McpDoctorArgs {
154    /// MCP provider identifier or config path
155    pub provider: String,
156    /// Emit compact JSON instead of pretty output
157    #[arg(long = "json")]
158    pub json: bool,
159}
160
161#[derive(Subcommand, Debug)]
162pub enum ConfigCommand {
163    /// Set a key in ~/.greentic/config.toml (e.g. defaults.component.org)
164    Set(ConfigSetArgs),
165}
166
167#[derive(Args, Debug)]
168pub struct ConfigSetArgs {
169    /// Config key path (e.g. defaults.component.org)
170    pub key: String,
171    /// Value to assign to the key (stored as a string)
172    pub value: String,
173    /// Override config file path (default: ~/.greentic/config.toml)
174    #[arg(long = "file")]
175    pub file: Option<PathBuf>,
176}
177
178#[derive(Copy, Clone, Debug, ValueEnum)]
179pub enum PackSignArg {
180    Dev,
181    None,
182}
183
184#[derive(Copy, Clone, Debug, ValueEnum)]
185pub enum RunPolicyArg {
186    Strict,
187    Devok,
188}
189
190#[derive(Copy, Clone, Debug, ValueEnum)]
191pub enum VerifyPolicyArg {
192    Strict,
193    Devok,
194}
195
196#[derive(Copy, Clone, Debug, ValueEnum)]
197pub enum MockSettingArg {
198    On,
199    Off,
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use clap::Parser;
206
207    #[test]
208    fn parses_component_passthrough_args() {
209        let cli = Cli::parse_from([
210            "greentic-dev",
211            "component",
212            "new",
213            "--name",
214            "demo",
215            "--json",
216        ]);
217        let Command::Component(args) = cli.command else {
218            panic!("expected component passthrough variant");
219        };
220        assert_eq!(
221            args.passthrough,
222            vec![
223                "new".to_string(),
224                "--name".into(),
225                "demo".into(),
226                "--json".into()
227            ]
228        );
229    }
230
231    #[test]
232    fn parses_pack_new_args() {
233        let cli = Cli::parse_from(["greentic-dev", "pack", "new", "--name", "demo-pack"]);
234        let Command::Pack(PackCommand::New(args)) = cli.command else {
235            panic!("expected pack new variant");
236        };
237        assert_eq!(
238            args.passthrough,
239            vec!["--name".to_string(), "demo-pack".to_string()]
240        );
241    }
242
243    #[test]
244    fn parses_config_set_command() {
245        let cli = Cli::parse_from([
246            "greentic-dev",
247            "config",
248            "set",
249            "defaults.component.org",
250            "ai.greentic",
251            "--file",
252            "/tmp/config.toml",
253        ]);
254        let Command::Config(ConfigCommand::Set(args)) = cli.command else {
255            panic!("expected config set variant");
256        };
257        assert_eq!(args.key, "defaults.component.org");
258        assert_eq!(args.value, "ai.greentic");
259        assert_eq!(
260            args.file.as_ref().map(|p| p.display().to_string()),
261            Some("/tmp/config.toml".into())
262        );
263    }
264}