Skip to main content

theater_cli/
lib.rs

1pub mod commands;
2pub mod config;
3pub mod error;
4pub mod output;
5pub mod templates;
6pub mod utils;
7
8use clap::{Parser, Subcommand, ValueEnum};
9
10/// Log level for runtime/system logs
11#[derive(Debug, Clone, Copy, ValueEnum, Default)]
12pub enum LogLevel {
13    /// Show error logs only
14    Error,
15    /// Show warning and error logs
16    #[default]
17    Warn,
18    /// Show info, warning, and error logs
19    Info,
20    /// Show debug and above
21    Debug,
22    /// Show all logs including trace
23    Trace,
24}
25
26impl From<LogLevel> for tracing::Level {
27    fn from(level: LogLevel) -> Self {
28        match level {
29            LogLevel::Error => tracing::Level::ERROR,
30            LogLevel::Warn => tracing::Level::WARN,
31            LogLevel::Info => tracing::Level::INFO,
32            LogLevel::Debug => tracing::Level::DEBUG,
33            LogLevel::Trace => tracing::Level::TRACE,
34        }
35    }
36}
37
38/// Theater CLI - A WebAssembly actor system that enables state management,
39/// verification, and flexible interaction patterns.
40#[derive(Debug, Parser)]
41#[command(name = "theater")]
42#[command(author, version, about, long_about = None)]
43pub struct Cli {
44    /// Set the log level for runtime/system logs
45    #[arg(short, long, global = true, value_enum, default_value = "warn")]
46    pub log_level: LogLevel,
47
48    /// Display output in JSON format
49    #[arg(long, global = true)]
50    pub json: bool,
51
52    #[command(subcommand)]
53    pub command: Commands,
54}
55
56#[derive(Debug, Subcommand)]
57pub enum Commands {
58    /// Create a new Theater actor project
59    #[command(name = "create")]
60    Create(commands::create::CreateArgs),
61
62    /// Build a Theater actor to WebAssembly
63    #[command(name = "build")]
64    Build(commands::build::BuildArgs),
65
66    /// Start an actor with a local runtime
67    #[command(name = "start")]
68    Start(commands::start::StartArgs),
69
70    /// List, inspect, and manage event chains
71    #[command(name = "chains")]
72    Chains(commands::chains::ChainsArgs),
73
74    /// Generate shell completion scripts
75    #[command(name = "completion")]
76    Completion(commands::completion::CompletionArgs),
77
78    /// Generate dynamic completions (internal use)
79    #[command(name = "dynamic-completion", hide = true)]
80    DynamicCompletion(commands::dynamic_completion::DynamicCompletionArgs),
81}
82
83/// Run the Theater CLI asynchronously with cancellation support
84pub async fn run(
85    cli: Cli,
86    config: config::Config,
87    shutdown_token: tokio_util::sync::CancellationToken,
88) -> anyhow::Result<()> {
89    // Create output manager
90    let output = output::OutputManager::new(config.output.clone());
91
92    // Create a context that contains shared resources
93    let ctx = CommandContext {
94        config,
95        output,
96        log_level: cli.log_level,
97        json: cli.json,
98        shutdown_token: shutdown_token.clone(),
99    };
100
101    // Execute the command with cancellation support
102    let command_future = async {
103        match &cli.command {
104            Commands::Create(args) => commands::create::execute_async(args, &ctx)
105                .await
106                .map_err(anyhow::Error::from),
107            Commands::Build(args) => commands::build::execute_async(args, &ctx)
108                .await
109                .map_err(anyhow::Error::from),
110            Commands::Start(args) => commands::start::execute_async(args, &ctx)
111                .await
112                .map_err(anyhow::Error::from),
113            Commands::Chains(args) => commands::chains::execute_async(args, &ctx)
114                .await
115                .map_err(anyhow::Error::from),
116            Commands::Completion(args) => commands::completion::execute_async(args, &ctx)
117                .await
118                .map_err(anyhow::Error::from),
119            Commands::DynamicCompletion(args) => {
120                commands::dynamic_completion::execute_async(args, &ctx)
121                    .await
122                    .map_err(anyhow::Error::from)
123            }
124        }
125    };
126
127    // Race the command execution against cancellation
128    let result = tokio::select! {
129        result = command_future => result,
130        _ = shutdown_token.cancelled() => {
131            return Err(anyhow::anyhow!("Operation cancelled"));
132        }
133    };
134
135    // Handle the result
136    match result {
137        Ok(()) => Ok(()),
138        Err(e) => {
139            // Use our enhanced error handling
140            if let Some(cli_error) = e.downcast_ref::<error::CliError>() {
141                ctx.output.error(&cli_error.user_message())?;
142                if ctx.is_verbose() {
143                    eprintln!("\nDebug info: {:?}", cli_error);
144                }
145            } else {
146                ctx.output.error(&format!("Error: {}", e))?;
147                if ctx.is_verbose() {
148                    eprintln!("\nDebug info: {:?}", e);
149                }
150            }
151            std::process::exit(1);
152        }
153    }
154}
155
156/// Shared context for command execution
157pub struct CommandContext {
158    pub config: config::Config,
159    pub output: output::OutputManager,
160    pub log_level: LogLevel,
161    pub json: bool,
162    pub shutdown_token: tokio_util::sync::CancellationToken,
163}
164
165impl CommandContext {
166    /// Returns true if log level is debug or higher (more verbose)
167    pub fn is_verbose(&self) -> bool {
168        matches!(self.log_level, LogLevel::Debug | LogLevel::Trace)
169    }
170}