Skip to main content

hypha/cli/
run.rs

1use std::io::Write;
2use std::process::ExitCode;
3
4use crate::api::Output;
5use crate::{cache, config, mycelium, skill_admin, spore, synapse, visitor};
6
7use super::*;
8
9fn emit_cli_json(message: &str) {
10    let mut stdout = std::io::stdout();
11    let _ = stdout.write_all(message.as_bytes());
12    let _ = stdout.write_all(b"\n");
13}
14
15fn build_runtime() -> Result<tokio::runtime::Runtime, ExitCode> {
16    tokio::runtime::Runtime::new().map_err(|e| {
17        emit_cli_json(&render_cli_json(&cli_error_value(
18            &format!("Failed to create async runtime: {}", e),
19            "retry the command; if it repeats, check available system resources",
20        )));
21        ExitCode::FAILURE
22    })
23}
24
25fn emit_startup(out: &Output, cli: &Cli, log: &agent_first_data::LogFilters) {
26    if log.enabled("startup") {
27        let mut args = serde_json::to_value(&cli.command)
28            .unwrap_or_else(|_| serde_json::Value::Object(Default::default()));
29        if let Some(obj) = args.as_object_mut() {
30            obj.insert(
31                "output".to_string(),
32                serde_json::Value::String(cli.output.clone()),
33            );
34            if let Ok(log_value) = serde_json::to_value(log.as_slice()) {
35                obj.insert("log".to_string(), log_value);
36            }
37        }
38        out.startup(args);
39    }
40}
41
42pub fn execute(cli: Cli) -> ExitCode {
43    let output_format = agent_first_data::cli_parse_output(&cli.output).unwrap_or_else(|e| {
44        emit_cli_json(&render_cli_json(&cli_error_value(
45            &e,
46            "use --output json, --output yaml, or --output plain",
47        )));
48        std::process::exit(2);
49    });
50
51    let log = agent_first_data::cli_parse_log_filters(&cli.log);
52    let out = Output::new(output_format);
53    emit_startup(&out, &cli, &log);
54
55    let rt = match build_runtime() {
56        Ok(rt) => rt,
57        Err(code) => return code,
58    };
59
60    match cli.command {
61        Commands::Sense { uri, id } => {
62            rt.block_on(visitor::handle_sense(&out, &uri, id.as_deref()))
63        }
64
65        Commands::Taste {
66            uri,
67            verdict,
68            notes,
69            synapse,
70            synapse_token_secret,
71            domain,
72        } => rt.block_on(visitor::handle_taste(
73            &out,
74            &uri,
75            verdict,
76            notes.as_deref(),
77            synapse.as_deref(),
78            synapse_token_secret.as_deref(),
79            domain.as_deref(),
80        )),
81
82        Commands::Spawn {
83            uri,
84            directory,
85            vcs,
86            dist,
87            bond,
88        } => rt.block_on(visitor::handle_spawn(
89            &out,
90            &uri,
91            directory.as_deref(),
92            vcs.map(|v| v.as_str()),
93            dist.map(|d| d.as_str()),
94            bond,
95        )),
96
97        Commands::Grow {
98            dist,
99            synapse,
100            synapse_token_secret,
101            bond,
102        } => rt.block_on(visitor::handle_grow(
103            &out,
104            None,
105            dist.map(|d| d.as_str()),
106            bond,
107            synapse.as_deref(),
108            synapse_token_secret.as_deref(),
109        )),
110
111        Commands::Absorb {
112            uris,
113            discover,
114            synapse,
115            synapse_token_secret,
116            max_depth,
117        } => rt.block_on(visitor::handle_absorb(
118            &out,
119            uris,
120            discover,
121            synapse.as_deref(),
122            synapse_token_secret.as_deref(),
123            max_depth,
124        )),
125
126        Commands::Bond { clean, status } => {
127            rt.block_on(visitor::handle_bond_fetch(&out, clean, status))
128        }
129
130        Commands::Replicate {
131            uris,
132            refs,
133            domain,
134            site_path,
135        } => rt.block_on(spore::handle_replicate(
136            &out,
137            uris,
138            refs,
139            &domain,
140            site_path.as_deref(),
141        )),
142
143        Commands::Hatch {
144            id,
145            version,
146            name,
147            domain,
148            synopsis,
149            intent,
150            mutations,
151            license,
152            command,
153        } => match command {
154            Some(HatchCommands::Bond { command }) => match command {
155                HatchBondCommands::Set {
156                    uri,
157                    relation,
158                    id,
159                    reason,
160                    with_entries,
161                } => spore::handle_bond_set(&out, &uri, relation, id, reason, with_entries),
162                HatchBondCommands::Remove { uri, relation } => {
163                    spore::handle_bond_remove(&out, uri, relation)
164                }
165                HatchBondCommands::Clear => spore::handle_bond_clear(&out),
166                HatchBondCommands::Sync {
167                    relation,
168                    spec,
169                    domain,
170                    site_path,
171                    check,
172                } => spore::handle_bond_sync(&out, relation, &spec, domain, site_path, check),
173            },
174            Some(HatchCommands::Tree { command }) => match command {
175                HatchTreeCommands::Set {
176                    algorithm,
177                    exclude_names,
178                    follow_rules,
179                } => spore::handle_tree_set(&out, algorithm, exclude_names, follow_rules),
180                HatchTreeCommands::Show => spore::handle_tree_show(&out),
181            },
182            None => spore::handle_hatch(
183                &out,
184                spore::HatchArgs {
185                    id,
186                    version,
187                    name,
188                    domain,
189                    synopsis,
190                    intent,
191                    mutations,
192                    license,
193                },
194            ),
195        },
196
197        Commands::Release {
198            domain,
199            source,
200            site_path,
201            dist_git,
202            dist_ref,
203            archive,
204            dry_run,
205        } => spore::handle_release(
206            &out,
207            spore::ReleaseArgs {
208                domain: &domain,
209                source,
210                site_path: site_path.as_deref(),
211                dist_git,
212                dist_ref,
213                archive: &archive,
214                dry_run,
215            },
216        ),
217
218        Commands::Lineage {
219            uri,
220            direction,
221            synapse,
222            synapse_token_secret,
223            max_depth,
224        } => rt.block_on(visitor::handle_lineage(
225            &out,
226            &uri,
227            direction.map(|d| d.as_str()),
228            synapse.as_deref(),
229            synapse_token_secret.as_deref(),
230            max_depth,
231        )),
232
233        Commands::Search {
234            query,
235            synapse,
236            synapse_token_secret,
237            domain,
238            license,
239            bonds,
240            limit,
241        } => rt.block_on(visitor::handle_search(
242            &out,
243            &query,
244            synapse.as_deref(),
245            synapse_token_secret.as_deref(),
246            domain.as_deref(),
247            license.as_deref(),
248            bonds.as_deref(),
249            limit,
250        )),
251
252        Commands::Mycelium { action } => match action {
253            MyceliumAction::Root {
254                domain,
255                hub,
256                site_path,
257                name,
258                synopsis,
259                bio,
260                endpoints_base,
261            } => mycelium::handle_init(
262                &out,
263                mycelium::InitArgs {
264                    domain: domain.as_deref(),
265                    hub: hub.as_deref(),
266                    site_path: site_path.as_deref(),
267                    name: name.as_deref(),
268                    synopsis: synopsis.as_deref(),
269                    bio: bio.as_deref(),
270                    endpoints_base: endpoints_base.as_deref(),
271                },
272            ),
273            MyceliumAction::Nutrient { command } => match command {
274                NutrientCommands::Add {
275                    domain,
276                    method_type,
277                    with_entries,
278                    site_path,
279                } => mycelium::handle_nutrient_add(
280                    &out,
281                    &domain,
282                    &method_type,
283                    with_entries,
284                    site_path.as_deref(),
285                ),
286                NutrientCommands::Remove {
287                    domain,
288                    method_type,
289                    site_path,
290                } => mycelium::handle_nutrient_remove(
291                    &out,
292                    &domain,
293                    &method_type,
294                    site_path.as_deref(),
295                ),
296                NutrientCommands::Clear { domain, site_path } => {
297                    mycelium::handle_nutrient_clear(&out, &domain, site_path.as_deref())
298                }
299            },
300            MyceliumAction::Spore { command } => match command {
301                SporeCommands::Yank {
302                    id,
303                    domain,
304                    site_path,
305                    purge,
306                } => mycelium::handle_spore_yank(
307                    &out,
308                    domain.as_deref(),
309                    &id,
310                    site_path.as_deref(),
311                    purge,
312                ),
313                SporeCommands::Unyank {
314                    id,
315                    domain,
316                    site_path,
317                } => mycelium::handle_spore_unyank(
318                    &out,
319                    domain.as_deref(),
320                    &id,
321                    site_path.as_deref(),
322                ),
323            },
324            MyceliumAction::Status {
325                domain,
326                site_path,
327                id,
328            } => mycelium::handle_status(
329                &out,
330                domain.as_deref(),
331                site_path.as_deref(),
332                id.as_deref(),
333            ),
334            MyceliumAction::Pulse {
335                synapse,
336                synapse_token_secret,
337                file,
338            } => rt.block_on(mycelium::handle_pulse(
339                &out,
340                synapse.as_deref(),
341                synapse_token_secret.as_deref(),
342                &file,
343            )),
344            MyceliumAction::Serve {
345                domain,
346                site_path,
347                port,
348            } => mycelium::handle_serve(&out, domain.as_deref(), site_path.as_deref(), port),
349        },
350
351        Commands::Synapse { action } => match action {
352            SynapseAction::Discover {
353                synapse,
354                synapse_token_secret,
355            } => rt.block_on(synapse::handle_discover(
356                &out,
357                synapse.as_deref(),
358                synapse_token_secret.as_deref(),
359            )),
360            SynapseAction::List => synapse::handle_list(&out),
361            SynapseAction::Health {
362                synapse,
363                synapse_token_secret,
364            } => rt.block_on(synapse::handle_info(
365                &out,
366                synapse.as_deref(),
367                synapse_token_secret.as_deref(),
368            )),
369            SynapseAction::Add { url } => synapse::handle_add(&out, &url),
370            SynapseAction::Remove { domain } => synapse::handle_remove(&out, &domain),
371            SynapseAction::Use { domain } => synapse::handle_use(&out, &domain),
372            SynapseAction::Config {
373                domain,
374                token_secret,
375            } => synapse::handle_config(&out, &domain, token_secret.as_deref()),
376        },
377
378        Commands::Cache { action } => match action {
379            CacheAction::List => cache::handle_list(&out),
380            CacheAction::Clean { all } => cache::handle_clean(&out, all),
381            CacheAction::Path { uri } => cache::handle_path(&out, &uri),
382        },
383
384        Commands::Config { action } => match action {
385            ConfigAction::List => config::handle_list(&out),
386            ConfigAction::Set { key, value } => config::handle_set(&out, &key, &value),
387        },
388
389        Commands::Skill { action } => skill_admin::handle_skill(&out, action),
390    }
391}