Skip to main content

greentic_dev/
cli.rs

1use std::{ffi::OsString, path::PathBuf};
2
3use crate::secrets_cli::SecretsCommand;
4use clap::{Args, Parser, Subcommand, ValueEnum};
5use greentic_component::cmd::{
6    build::BuildArgs as ComponentBuildArgs, doctor::DoctorArgs as ComponentDoctorArgs,
7    flow::FlowCommand as ComponentFlowCommand, hash::HashArgs as ComponentHashArgs,
8    inspect::InspectArgs as ComponentInspectArgs, new::NewArgs as ComponentNewArgs,
9    store::StoreCommand as ComponentStoreCommand,
10    templates::TemplatesArgs as ComponentTemplatesArgs,
11};
12
13#[derive(Parser, Debug)]
14#[command(name = "greentic-dev")]
15#[command(version)]
16#[command(about = "Greentic developer tooling CLI")]
17pub struct Cli {
18    #[command(subcommand)]
19    pub command: Command,
20}
21
22#[derive(Subcommand, Debug)]
23pub enum Command {
24    /// Flow passthrough (greentic-flow)
25    Flow(PassthroughArgs),
26    /// Pack passthrough (greentic-pack; pack run uses greentic-dev runner, help uses greentic-runner-cli)
27    Pack(PassthroughArgs),
28    /// Component passthrough (greentic-component)
29    Component(PassthroughArgs),
30    /// Manage greentic-dev configuration
31    #[command(subcommand)]
32    Config(ConfigCommand),
33    /// MCP tooling
34    #[command(subcommand)]
35    Mcp(McpCommand),
36    /// GUI passthrough (greentic-gui)
37    Gui(PassthroughArgs),
38    /// Secrets convenience wrappers
39    #[command(subcommand)]
40    Secrets(SecretsCommand),
41    /// Decode a CBOR file to text
42    Cbor(CborArgs),
43}
44
45#[derive(Args, Debug, Clone)]
46#[command(disable_help_flag = true)]
47pub struct PassthroughArgs {
48    /// Arguments passed directly to the underlying command
49    #[arg(
50        value_name = "ARGS",
51        trailing_var_arg = true,
52        allow_hyphen_values = true
53    )]
54    pub args: Vec<OsString>,
55}
56
57#[derive(Subcommand, Debug)]
58pub enum FlowCommand {
59    /// Doctor validates a flow YAML file via greentic-flow doctor (passthrough)
60    Doctor(FlowDoctorArgs),
61    /// Create a new flow skeleton at the given path
62    New(FlowPassthroughArgs),
63    /// Update flow metadata in-place without overwriting nodes
64    Update(FlowPassthroughArgs),
65    /// Add a configured component step to a flow via config-flow
66    AddStep(Box<FlowAddStepArgs>),
67    /// Update an existing node (rerun config/default with overrides)
68    UpdateStep(FlowPassthroughArgs),
69    /// Delete a node and optionally splice routing
70    DeleteStep(FlowPassthroughArgs),
71    /// Attach or repair a sidecar component binding without changing flow nodes
72    BindComponent(FlowPassthroughArgs),
73}
74
75#[derive(Args, Debug)]
76pub struct FlowDoctorArgs {
77    /// Arguments passed directly to `greentic-flow doctor`
78    #[arg(
79        value_name = "ARGS",
80        trailing_var_arg = true,
81        allow_hyphen_values = true
82    )]
83    pub passthrough: Vec<String>,
84}
85
86#[derive(Args, Debug)]
87pub struct FlowPassthroughArgs {
88    /// Arguments passed directly to `greentic-flow`
89    #[arg(
90        value_name = "ARGS",
91        trailing_var_arg = true,
92        allow_hyphen_values = true
93    )]
94    pub passthrough: Vec<String>,
95}
96
97#[derive(Args, Debug)]
98pub struct FlowAddStepArgs {
99    /// Flow file to modify (e.g., flows/main.ygtc)
100    #[arg(long = "flow")]
101    pub flow_path: PathBuf,
102    /// Optional anchor node id; defaults to entrypoint or first node.
103    #[arg(long = "after")]
104    pub after: Option<String>,
105    /// Mode for add-step (default or config)
106    #[arg(long = "mode", value_enum, default_value = "default")]
107    pub mode: FlowAddStepMode,
108    /// Component id (default mode).
109    #[arg(long = "component")]
110    pub component_id: Option<String>,
111    /// Remote component reference (oci://, repo://, store://) for sidecar binding.
112    #[arg(long = "component-ref")]
113    pub component_ref: Option<String>,
114    /// Local wasm path for sidecar binding (relative to the flow file).
115    #[arg(long = "local-wasm")]
116    pub local_wasm: Option<PathBuf>,
117    /// Pin the component (resolve tag to digest or hash local wasm).
118    #[arg(long = "pin")]
119    pub pin: bool,
120    /// Optional pack alias for the new node.
121    #[arg(long = "pack-alias")]
122    pub pack_alias: Option<String>,
123    /// Optional operation for the new node.
124    #[arg(long = "operation")]
125    pub operation: Option<String>,
126    /// Payload JSON for the new node (default mode).
127    #[arg(long = "payload", default_value = "{}")]
128    pub payload: String,
129    /// Routing shorthand: make the new node terminal (out).
130    #[arg(long = "routing-out")]
131    pub routing_out: bool,
132    /// Routing shorthand: reply to origin.
133    #[arg(long = "routing-reply")]
134    pub routing_reply: bool,
135    /// Route to a specific node id.
136    #[arg(long = "routing-next")]
137    pub routing_next: Option<String>,
138    /// Route to multiple node ids (comma-separated).
139    #[arg(long = "routing-multi-to")]
140    pub routing_multi_to: Option<String>,
141    /// Explicit routing JSON file (escape hatch).
142    #[arg(long = "routing-json")]
143    pub routing_json: Option<PathBuf>,
144    /// Explicitly thread to the anchor’s existing targets (default if no routing flag is given).
145    #[arg(long = "routing-to-anchor")]
146    pub routing_to_anchor: bool,
147    /// Config flow file to execute (config mode).
148    #[arg(long = "config-flow")]
149    pub config_flow: Option<PathBuf>,
150    /// Answers JSON for config mode.
151    #[arg(long = "answers")]
152    pub answers: Option<String>,
153    /// Answers file (JSON) for config mode.
154    #[arg(long = "answers-file")]
155    pub answers_file: Option<PathBuf>,
156    /// Allow cycles/back-edges during insertion.
157    #[arg(long = "allow-cycles")]
158    pub allow_cycles: bool,
159    /// Write back to the flow file instead of stdout.
160    #[arg(long = "write")]
161    pub write: bool,
162    /// Validate only without writing output.
163    #[arg(long = "validate-only")]
164    pub validate_only: bool,
165    /// Optional component manifest paths for catalog validation.
166    #[arg(long = "manifest")]
167    pub manifests: Vec<PathBuf>,
168    /// Optional explicit node id hint.
169    #[arg(long = "node-id")]
170    pub node_id: Option<String>,
171    /// Verbose passthrough logging.
172    #[arg(long = "verbose")]
173    pub verbose: bool,
174}
175
176#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
177pub enum FlowAddStepMode {
178    Default,
179    Config,
180}
181
182#[derive(Subcommand, Debug)]
183pub enum GuiCommand {
184    /// Serve GUI packs locally via greentic-gui
185    Serve(GuiServeArgs),
186    /// Stage a local GUI pack from static assets
187    PackDev(GuiPackDevArgs),
188}
189
190#[derive(Args, Debug)]
191pub struct GuiServeArgs {
192    /// Path to gui-dev.yaml (defaults to discovery order)
193    #[arg(long = "config")]
194    pub config: Option<PathBuf>,
195    /// Address to bind (default: 127.0.0.1:8080)
196    #[arg(long = "bind")]
197    pub bind: Option<String>,
198    /// Domain reported to greentic-gui (default: localhost:8080)
199    #[arg(long = "domain")]
200    pub domain: Option<String>,
201    /// Override greentic-gui binary path (otherwise PATH is used)
202    #[arg(long = "gui-bin")]
203    pub gui_bin: Option<PathBuf>,
204    /// Disable cargo fallback when greentic-gui binary is missing
205    #[arg(long = "no-cargo-fallback")]
206    pub no_cargo_fallback: bool,
207    /// Open a browser after the server starts
208    #[arg(long = "open-browser")]
209    pub open_browser: bool,
210}
211
212#[derive(Args, Debug, Clone)]
213pub struct GuiPackDevArgs {
214    /// Directory containing built/static assets to stage
215    #[arg(long = "dir")]
216    pub dir: PathBuf,
217    /// Output directory for the staged pack (must be empty or absent)
218    #[arg(long = "output")]
219    pub output: PathBuf,
220    /// Kind of GUI pack to generate (controls manifest shape)
221    #[arg(long = "kind", value_enum, default_value = "layout")]
222    pub kind: GuiPackKind,
223    /// Entrypoint HTML file (relative to assets) for layout/feature manifests
224    #[arg(long = "entrypoint", default_value = "index.html")]
225    pub entrypoint: String,
226    /// Optional manifest to copy instead of generating one
227    #[arg(long = "manifest")]
228    pub manifest: Option<PathBuf>,
229    /// Feature route (only used when kind=feature)
230    #[arg(long = "feature-route")]
231    pub feature_route: Option<String>,
232    /// Feature HTML file (relative to assets; kind=feature)
233    #[arg(long = "feature-html", default_value = "index.html")]
234    pub feature_html: String,
235    /// Mark the feature route as authenticated (kind=feature)
236    #[arg(long = "feature-authenticated")]
237    pub feature_authenticated: bool,
238    /// Optional build command to run before staging
239    #[arg(long = "build-cmd")]
240    pub build_cmd: Option<String>,
241    /// Skip running the build command even if provided
242    #[arg(long = "no-build")]
243    pub no_build: bool,
244}
245
246#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
247pub enum GuiPackKind {
248    Layout,
249    Feature,
250}
251
252#[derive(Subcommand, Debug)]
253pub enum PackCommand {
254    /// Delegate to greentic-pack build
255    Build(PackcArgs),
256    /// Delegate to greentic-pack lint
257    Lint(PackcArgs),
258    /// Delegate to greentic-pack components (sync pack.yaml from components/)
259    Components(PackcArgs),
260    /// Delegate to greentic-pack update (sync pack.yaml components + flows)
261    Update(PackcArgs),
262    /// Delegate to greentic-pack new
263    New(PackcArgs),
264    /// Delegate to greentic-pack sign
265    Sign(PackcArgs),
266    /// Delegate to greentic-pack verify
267    Verify(PackcArgs),
268    /// Delegate to greentic-pack gui helpers
269    Gui(PackcArgs),
270    /// Inspect/doctor a .gtpack (or directory via temporary build)
271    Inspect(PackInspectArgs),
272    /// Doctor a .gtpack (or directory via temporary build)
273    Doctor(PackInspectArgs),
274    /// Generate a deployment plan
275    Plan(PackPlanArgs),
276    /// Events helpers (legacy)
277    #[command(subcommand)]
278    Events(PackEventsCommand),
279    /// Delegate to greentic-pack config (resolve config with provenance)
280    Config(PackcArgs),
281    /// Execute a pack locally with mocks/telemetry support
282    Run(PackRunArgs),
283    /// Initialize a pack workspace from a remote coordinate
284    Init(PackInitArgs),
285    /// Register or update a provider declaration in the pack manifest extension
286    NewProvider(PackNewProviderArgs),
287}
288
289#[derive(Args, Debug)]
290pub struct PackRunArgs {
291    /// Path to the pack (.gtpack) to execute
292    #[arg(short = 'p', long = "pack")]
293    pub pack: PathBuf,
294    /// Flow entry identifier override
295    #[arg(long = "entry")]
296    pub entry: Option<String>,
297    /// JSON payload to use as run input
298    #[arg(long = "input")]
299    pub input: Option<String>,
300    /// Emit JSON output
301    #[arg(long = "json")]
302    pub json: bool,
303    /// Offline mode (disable network/proxy)
304    #[arg(long = "offline")]
305    pub offline: bool,
306    /// Use mock executor (internal/testing)
307    #[arg(long = "mock-exec", hide = true)]
308    pub mock_exec: bool,
309    /// Allow external calls in mock executor (default: false)
310    #[arg(long = "allow-external", hide = true)]
311    pub allow_external: bool,
312    /// Return mocked external responses when external calls are allowed (mock exec only)
313    #[arg(long = "mock-external", hide = true)]
314    pub mock_external: bool,
315    /// Path to JSON payload used for mocked external responses (mock exec only)
316    #[arg(long = "mock-external-payload", hide = true)]
317    pub mock_external_payload: Option<PathBuf>,
318    /// Secrets seed file applied to the mock secrets store (mock exec only)
319    #[arg(long = "secrets-seed", hide = true)]
320    pub secrets_seed: Option<PathBuf>,
321    /// Enforcement policy for pack signatures
322    #[arg(long = "policy", default_value = "devok", value_enum)]
323    pub policy: RunPolicyArg,
324    /// OTLP collector endpoint (optional)
325    #[arg(long = "otlp")]
326    pub otlp: Option<String>,
327    /// Comma-separated list of allowed outbound hosts
328    #[arg(long = "allow")]
329    pub allow: Option<String>,
330    /// Mocks toggle
331    #[arg(long = "mocks", default_value = "on", value_enum)]
332    pub mocks: MockSettingArg,
333    /// Directory to persist run artifacts (transcripts, logs)
334    #[arg(long = "artifacts")]
335    pub artifacts: Option<PathBuf>,
336}
337
338#[derive(Args, Debug)]
339pub struct PackInitArgs {
340    /// Remote pack coordinate (e.g. pack://org/name@1.0.0)
341    pub from: String,
342    /// Distributor profile to use (overrides GREENTIC_DISTRIBUTOR_PROFILE/env config)
343    #[arg(long = "profile")]
344    pub profile: Option<String>,
345}
346
347#[derive(Args, Debug)]
348pub struct PackNewProviderArgs {
349    /// Path to a pack source directory, manifest.cbor, or .gtpack archive
350    #[arg(long = "pack")]
351    pub pack: PathBuf,
352    /// Provider identifier (stored as provider_type)
353    #[arg(long = "id")]
354    pub id: String,
355    /// Runtime reference in the form component_ref::export@world
356    #[arg(long = "runtime")]
357    pub runtime: String,
358    /// Optional provider kind (stored in capabilities)
359    #[arg(long = "kind")]
360    pub kind: Option<String>,
361    /// Optional external manifest/config reference (relative path)
362    #[arg(long = "manifest")]
363    pub manifest: Option<PathBuf>,
364    /// When set, do not write changes to disk
365    #[arg(long = "dry-run")]
366    pub dry_run: bool,
367    /// Overwrite an existing provider with the same id
368    #[arg(long = "force")]
369    pub force: bool,
370    /// Emit JSON for the resulting provider declaration
371    #[arg(long = "json")]
372    pub json: bool,
373    /// Scaffold provider manifest files if supported
374    #[arg(long = "scaffold-files")]
375    pub scaffold_files: bool,
376}
377
378#[derive(Args, Debug, Clone, Default)]
379#[command(disable_help_flag = true)]
380pub struct PackcArgs {
381    /// Arguments passed directly to the `greentic-pack` command
382    #[arg(
383        value_name = "ARGS",
384        trailing_var_arg = true,
385        allow_hyphen_values = true
386    )]
387    pub passthrough: Vec<String>,
388}
389
390#[derive(Args, Debug)]
391pub struct PackInspectArgs {
392    /// Path to the .gtpack file or pack directory
393    #[arg(value_name = "PATH")]
394    pub path: PathBuf,
395    /// Signature policy to enforce
396    #[arg(long, value_enum, default_value = "devok")]
397    pub policy: PackPolicyArg,
398    /// Emit JSON output
399    #[arg(long)]
400    pub json: bool,
401}
402
403#[derive(Subcommand, Debug)]
404pub enum PackEventsCommand {
405    /// List event providers declared in a pack
406    List(PackEventsListArgs),
407}
408
409#[derive(Args, Debug)]
410pub struct PackEventsListArgs {
411    /// Path to a .gtpack archive or pack source directory.
412    #[arg(value_name = "PATH")]
413    pub path: PathBuf,
414    /// Output format: table (default), json, yaml.
415    #[arg(long, value_enum, default_value = "table")]
416    pub format: PackEventsFormatArg,
417    /// When set, print additional diagnostics (for directory builds).
418    #[arg(long)]
419    pub verbose: bool,
420}
421
422#[derive(Args, Debug)]
423pub struct PackPlanArgs {
424    /// Path to a .gtpack archive or pack source directory.
425    #[arg(value_name = "PATH")]
426    pub input: PathBuf,
427    /// Tenant identifier to embed in the plan.
428    #[arg(long, default_value = "tenant-local")]
429    pub tenant: String,
430    /// Environment identifier to embed in the plan.
431    #[arg(long, default_value = "local")]
432    pub environment: String,
433    /// Emit compact JSON output instead of pretty-printing.
434    #[arg(long)]
435    pub json: bool,
436    /// When set, print additional diagnostics (for directory builds).
437    #[arg(long)]
438    pub verbose: bool,
439}
440
441#[derive(Subcommand, Debug, Clone)]
442pub enum ComponentCommand {
443    /// Add a remote component to the current workspace via the distributor
444    Add(ComponentAddArgs),
445    /// Scaffold a new Greentic component project
446    New(ComponentNewArgs),
447    /// List available component templates
448    Templates(ComponentTemplatesArgs),
449    /// Run component doctor checks
450    Doctor(ComponentDoctorArgs),
451    /// Inspect manifests and describe payloads
452    Inspect(ComponentInspectArgs),
453    /// Recompute manifest hashes
454    Hash(ComponentHashArgs),
455    /// Build component wasm + scaffold config flows
456    Build(ComponentBuildArgs),
457    /// Flow utilities (config flow scaffolding)
458    #[command(subcommand)]
459    Flow(ComponentFlowCommand),
460    /// Interact with the component store
461    #[command(subcommand)]
462    Store(ComponentStoreCommand),
463}
464
465#[derive(Args, Debug, Clone)]
466pub struct ComponentAddArgs {
467    /// Remote component coordinate (e.g. component://org/name@^1.0)
468    pub coordinate: String,
469    /// Distributor profile to use (overrides GREENTIC_DISTRIBUTOR_PROFILE/env config)
470    #[arg(long = "profile")]
471    pub profile: Option<String>,
472    /// Resolution intent (dev or runtime)
473    #[arg(long = "intent", default_value = "dev", value_enum)]
474    pub intent: DevIntentArg,
475}
476
477#[derive(Subcommand, Debug)]
478pub enum McpCommand {
479    /// Inspect MCP provider metadata
480    Doctor(McpDoctorArgs),
481}
482
483#[derive(Args, Debug)]
484pub struct McpDoctorArgs {
485    /// MCP provider identifier or config path
486    pub provider: String,
487    /// Emit compact JSON instead of pretty output
488    #[arg(long = "json")]
489    pub json: bool,
490}
491
492#[derive(Subcommand, Debug)]
493pub enum ConfigCommand {
494    /// Set a key in greentic-dev config (e.g. defaults.component.org)
495    Set(ConfigSetArgs),
496}
497
498#[derive(Args, Debug)]
499pub struct ConfigSetArgs {
500    /// Config key path (e.g. defaults.component.org)
501    pub key: String,
502    /// Value to assign to the key (stored as a string)
503    pub value: String,
504    /// Override config file path (default: $XDG_CONFIG_HOME/greentic-dev/config.toml)
505    #[arg(long = "file")]
506    pub file: Option<PathBuf>,
507}
508
509#[derive(Args, Debug)]
510pub struct CborArgs {
511    /// Path to the CBOR file to decode
512    #[arg(value_name = "PATH")]
513    pub path: PathBuf,
514}
515
516#[derive(Copy, Clone, Debug, ValueEnum)]
517pub enum PackSignArg {
518    Dev,
519    None,
520}
521
522#[derive(Copy, Clone, Debug, ValueEnum)]
523pub enum PackPolicyArg {
524    Devok,
525    Strict,
526}
527
528#[derive(Copy, Clone, Debug, ValueEnum)]
529pub enum RunPolicyArg {
530    Strict,
531    Devok,
532}
533
534#[derive(Copy, Clone, Debug, ValueEnum)]
535pub enum VerifyPolicyArg {
536    Strict,
537    Devok,
538}
539
540#[derive(Copy, Clone, Debug, ValueEnum)]
541pub enum MockSettingArg {
542    On,
543    Off,
544}
545
546#[derive(Copy, Clone, Debug, ValueEnum)]
547pub enum PackEventsFormatArg {
548    Table,
549    Json,
550    Yaml,
551}
552
553#[derive(Copy, Clone, Debug, ValueEnum)]
554pub enum ConfigFlowModeArg {
555    Default,
556    Custom,
557}
558#[derive(Copy, Clone, Debug, ValueEnum)]
559pub enum DevIntentArg {
560    Dev,
561    Runtime,
562}
563
564#[cfg(test)]
565mod tests {}