sesame/
main.rs

1//! Open Sesame CLI — platform orchestration for multi-agent desktop control.
2//!
3//! All subcommands connect to the IPC bus, send a request, wait for a
4//! correlated response, format the output, and exit.
5//!
6//! Exit codes:
7//!   0 — success (or child process exit code for `sesame env`)
8//!   1 — error (daemon unreachable, request failed, etc.)
9//!   2 — timeout waiting for response
10
11mod audit;
12mod cli;
13mod clipboard;
14mod doctor;
15mod env;
16mod helpers;
17mod init;
18mod input;
19mod ipc;
20mod launch;
21mod profile;
22mod secrets;
23mod snippets;
24mod ssh;
25mod status;
26mod unlock;
27mod wm;
28mod workspace;
29
30use clap::Parser;
31use owo_colors::OwoColorize;
32
33use cli::*;
34
35#[tokio::main(flavor = "current_thread")]
36async fn main() {
37    let cli = Cli::parse();
38
39    if let Err(e) = run(cli).await {
40        eprintln!("{}: {e:#}", "error".red().bold());
41        std::process::exit(1);
42    }
43}
44
45async fn run(cli: Cli) -> anyhow::Result<()> {
46    match cli.command {
47        Command::Init {
48            no_keybinding,
49            wipe_reset_destroy_all_data,
50            org,
51            ssh_key,
52            password,
53            auth_policy,
54        } => {
55            if wipe_reset_destroy_all_data {
56                init::cmd_wipe()
57            } else {
58                init::cmd_init(no_keybinding, org, ssh_key, password, auth_policy).await
59            }
60        }
61        Command::Status {
62            doctor,
63            output,
64            exit_code,
65            quiet,
66            rotate_keys,
67        } => {
68            if rotate_keys {
69                use owo_colors::OwoColorize;
70                let client = ipc::connect().await?;
71                match ipc::rpc(
72                    &client,
73                    core_types::EventKind::KeyRotationRequest,
74                    core_types::SecurityLevel::Internal,
75                )
76                .await?
77                {
78                    core_types::EventKind::KeyRotationRequestResponse { success: true, .. } => {
79                        println!(
80                            "{} Key rotation initiated. Phase 2 completes in 30 seconds.",
81                            "✓".green().bold()
82                        );
83                        Ok(())
84                    }
85                    core_types::EventKind::KeyRotationRequestResponse {
86                        success: false,
87                        error,
88                    } => {
89                        let reason = error.as_deref().unwrap_or("unknown");
90                        anyhow::bail!("key rotation failed: {reason}");
91                    }
92                    other => anyhow::bail!("unexpected response: {other:?}"),
93                }
94            } else if let Some(categories) = doctor {
95                doctor::cmd_doctor(
96                    &categories,
97                    output.as_deref().unwrap_or("text"),
98                    exit_code || quiet,
99                    quiet,
100                )
101            } else {
102                status::cmd_status().await
103            }
104        }
105        Command::Unlock { profile } => unlock::cmd_unlock(profile).await,
106        Command::Lock { profile } => unlock::cmd_lock(profile).await,
107        Command::Profile(sub) => match sub {
108            ProfileCmd::List => profile::cmd_profile_list().await,
109            ProfileCmd::Activate { name } => profile::cmd_profile_activate(&name).await,
110            ProfileCmd::Deactivate { name } => profile::cmd_profile_deactivate(&name).await,
111            ProfileCmd::Default { name } => profile::cmd_profile_default(&name).await,
112            ProfileCmd::Show { name } => profile::cmd_profile_show(&name),
113        },
114        Command::Ssh(sub) => match sub {
115            SshCmd::Enroll { profile, ssh_key } => ssh::cmd_ssh_enroll(profile, ssh_key).await,
116            SshCmd::List { profile } => ssh::cmd_ssh_list(profile).await,
117            SshCmd::Revoke { profile } => ssh::cmd_ssh_revoke(profile).await,
118        },
119        Command::Secret(sub) => match sub {
120            SecretCmd::Set { profile, key } => secrets::cmd_secret_set(&profile, &key).await,
121            SecretCmd::Get { profile, key } => secrets::cmd_secret_get(&profile, &key).await,
122            SecretCmd::Delete { profile, key, yes } => {
123                secrets::cmd_secret_delete(&profile, &key, yes).await
124            }
125            SecretCmd::List { profile } => secrets::cmd_secret_list(&profile).await,
126        },
127        Command::Audit(sub) => match sub {
128            AuditCmd::Verify => audit::cmd_audit_verify(),
129            AuditCmd::Tail { count, follow } => audit::cmd_audit_tail(count, follow).await,
130        },
131        Command::Wm(sub) => match sub {
132            WmCmd::List => wm::cmd_wm_list().await,
133            WmCmd::Switch { backward } => wm::cmd_wm_switch(backward).await,
134            WmCmd::Focus { window_id } => wm::cmd_wm_focus(&window_id).await,
135            WmCmd::Overlay { launcher, backward } => wm::cmd_wm_overlay(launcher, backward).await,
136            WmCmd::OverlayResident => wm::cmd_wm_overlay_resident().await,
137        },
138        Command::Launch(sub) => match sub {
139            LaunchCmd::Search {
140                query,
141                max_results,
142                profile,
143            } => launch::cmd_launch_search(&query, max_results, profile.as_deref()).await,
144            LaunchCmd::Run { entry_id, profile } => {
145                launch::cmd_launch_run(&entry_id, profile.as_deref()).await
146            }
147        },
148        Command::Clipboard(sub) => match sub {
149            ClipboardCmd::History { profile, limit } => {
150                clipboard::cmd_clipboard_history(&profile, limit).await
151            }
152            ClipboardCmd::Clear { profile } => clipboard::cmd_clipboard_clear(&profile).await,
153            ClipboardCmd::Get { entry_id } => clipboard::cmd_clipboard_get(&entry_id).await,
154        },
155        Command::Input(sub) => match sub {
156            InputCmd::Layers => input::cmd_input_layers().await,
157            InputCmd::Status => input::cmd_input_status().await,
158        },
159        Command::Snippet(sub) => match sub {
160            SnippetCmd::List { profile } => snippets::cmd_snippet_list(&profile).await,
161            SnippetCmd::Expand { profile, trigger } => {
162                snippets::cmd_snippet_expand(&profile, &trigger).await
163            }
164            SnippetCmd::Add {
165                profile,
166                trigger,
167                template,
168            } => snippets::cmd_snippet_add(&profile, &trigger, &template).await,
169        },
170        #[cfg(all(target_os = "linux", feature = "desktop"))]
171        Command::SetupKeybinding { launcher_key } => {
172            platform_linux::cosmic_keys::setup_keybinding(&launcher_key)
173                .map_err(|e| anyhow::anyhow!("{e}"))
174        }
175        #[cfg(all(target_os = "linux", feature = "desktop"))]
176        Command::RemoveKeybinding => {
177            platform_linux::cosmic_keys::remove_keybinding().map_err(|e| anyhow::anyhow!("{e}"))
178        }
179        #[cfg(all(target_os = "linux", feature = "desktop"))]
180        Command::KeybindingStatus => {
181            platform_linux::cosmic_keys::keybinding_status().map_err(|e| anyhow::anyhow!("{e}"))
182        }
183        Command::Clone {
184            url,
185            depth,
186            profile,
187            no_workspace,
188            project,
189            include_forks,
190            include_archived,
191        } => {
192            // Top-level clone delegates to workspace clone with sane defaults.
193            workspace::cmd_workspace(WorkspaceCmd::Clone {
194                url,
195                depth,
196                profile,
197                adopt: true,
198                workspace_init: false,
199                workspace_update: false,
200                no_workspace,
201                force: false,
202                project,
203                include_forks,
204                include_archived,
205            })
206            .await
207        }
208        Command::Env {
209            profile,
210            prefix,
211            command,
212        } => env::cmd_env(profile.as_deref(), prefix.as_deref(), &command).await,
213        Command::Export {
214            profile,
215            format,
216            prefix,
217        } => env::cmd_export(profile.as_deref(), &format, prefix.as_deref()).await,
218        Command::Workspace(sub) => workspace::cmd_workspace(sub).await,
219    }
220}