harmont_cli/
dispatcher.rs1#![allow(
9 clippy::print_stderr,
10 reason = "this is a top-level dispatch site; ExitInfo.message is user-facing output to stderr"
11)]
12
13use std::collections::BTreeMap;
14
15use anyhow::{Context, Result};
16use hm_plugin_protocol::{ExitInfo, SubcommandInput};
17
18use crate::error::HmError;
19use crate::plugin::{PluginRegistry, RegistryConfig};
20
21pub async fn run(argv: Vec<String>) -> Result<i32> {
30 let verb = argv
31 .first()
32 .cloned()
33 .ok_or_else(|| anyhow::anyhow!("dispatcher called with empty argv (clap bug)"))?;
34
35 let registry = PluginRegistry::load(RegistryConfig {
36 auto_discover: true,
37 extra_paths: vec![],
38 embedded: vec![
39 (
40 "harmont-docker",
41 crate::plugin::embedded::DOCKER_PLUGIN_WASM,
42 ),
43 (
44 "harmont-output-human",
45 crate::plugin::embedded::OUTPUT_HUMAN_PLUGIN_WASM,
46 ),
47 (
48 "harmont-output-json",
49 crate::plugin::embedded::OUTPUT_JSON_PLUGIN_WASM,
50 ),
51 ("harmont-cloud", crate::plugin::embedded::CLOUD_PLUGIN_WASM),
52 ],
53 pool_sizes: BTreeMap::new(),
54 })
55 .context("load plugin registry")?;
56
57 let idx = registry
58 .subcommand_index
59 .get(&verb)
60 .copied()
61 .ok_or_else(|| HmError::UnknownVerb {
62 verb: verb.clone(),
63 available: registry.subcommand_index.keys().cloned().collect(),
64 })?;
65
66 let plugin = registry
67 .get(idx)
68 .context("plugin moved away during dispatch")?;
69
70 let env: BTreeMap<String, String> = std::env::vars()
71 .filter(|(k, _)| k.starts_with("HARMONT_"))
72 .collect();
73
74 let input = SubcommandInput {
75 verb_path: argv.clone(),
76 args: serde_json::Value::Null, env,
78 };
79
80 let info: ExitInfo = plugin
81 .call_capability("hm_subcommand_run", &input)
82 .await
83 .with_context(|| format!("invoke plugin for verb '{verb}'"))?;
84
85 if let Some(msg) = info.message {
86 eprintln!("{msg}");
87 }
88 Ok(info.exit_code)
89}