Repository: candlecorp/wick (language: rust)

Issue:
Wick CLI should exit with code 1 on error
**Is your feature request related to a problem? Please describe.**
When using CLI in automation, it is important to know if the command succeeded or errored.

The command (`wick registry push`) was outputting: `error: a value is required for '<SOURCE>' but none was supplied`

**Propose a solution**
Throw exit code 1 when the command is not executed properly and returns the help text.

**Describe alternatives you've considered**
None

**Additional context**
None


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 21df57172..6b5125fa4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6156,16 +6156,19 @@ dependencies = [
  "atty",
  "clap",
  "dialoguer",
+ "flow-expression-parser",
  "futures",
  "human-panic",
  "jaq-core",
  "markup-converter",
  "nkeys",
  "openssl",
+ "option-utils",
  "regex",
  "rstest",
  "seeded-random",
  "serde_json",
+ "structured-output",
  "test-logger",
  "test_bin",
  "thiserror",
@@ -6334,6 +6337,7 @@ dependencies = [
  "futures",
  "http",
  "once_cell",
+ "option-utils",
  "parking_lot 0.12.1",
  "seeded-random",
  "test-logger",
diff --git a/Cargo.toml b/Cargo.toml
index d6b879aa5..c0476dc8e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,8 @@ exclude = [
   "tests/template",
   "tests/testdata",
   "examples/http/middleware/request",
+  "examples/http/wasm-http-call/wasm-component",
+  "examples/cli/cli-maker",
 ]
 
 [workspace.dependencies]
diff --git a/Justfile b/Justfile
index 16f8da905..eda1241fa 100644
--- a/Justfile
+++ b/Justfile
@@ -56,6 +56,12 @@ unit-tests: _check_nextest _codegen-tests
 integration-tests: _check_nextest _codegen-tests
   cargo nextest run -E 'not (test(slow_test))'
   cargo test --manifest-path tests/template/Cargo.toml
+  just _wick-http-tests
+  just _wick-db-tests
+
+# Run tests via `wick test`
+wick-tests:
+  just _wick-http-tests
   just _wick-db-tests
 
 # Run tests suitable for CI
@@ -167,7 +173,8 @@ _run-wasm-task task:
     "crates/integration/test-http-trigger-component",
     "crates/integration/test-cli-trigger-component",
     "crates/integration/test-cli-with-db",
-    "examples/http/middleware/request"
+    "examples/http/middleware/request",
+    "examples/http/wasm-http-call/wasm-component",
   ]
   for dir in wasm:
     if subprocess.call(["just", "{}/{{task}}".format(dir)]) != 0:
@@ -178,6 +185,9 @@ _wick-db-tests:
   cargo run -p wick-cli -- test ./examples/db/flow-with-postgres.wick
   cargo run -p wick-cli -- test ./examples/db/postgres-numeric.wick
 
+# Run `wick` tests for http components
+_wick-http-tests:
+  cargo run -p wick-cli -- test ./examples/http/wasm-http-call/harness.wick
 
 # Run component-codegen unit tests
 _codegen-tests:
diff --git a/crates/bins/wick/Cargo.toml b/crates/bins/wick/Cargo.toml
index 0f5e0b20b..f872598ed 100644
--- a/crates/bins/wick/Cargo.toml
+++ b/crates/bins/wick/Cargo.toml
@@ -26,6 +26,7 @@ wick-wascap = { workspace = true }
 wick-oci-utils = { workspace = true }
 wick-package = { workspace = true }
 wick-logger = { workspace = true }
+flow-expression-parser = { workspace = true }
 seeded-random = { workspace = true }
 tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
 tracing = { workspace = true }
@@ -41,6 +42,8 @@ atty = { workspace = true }
 jaq-core = { workspace = true }
 markup-converter = { workspace = true }
 openssl = { workspace = true, features = ["vendored"], optional = true }
+option-utils = { workspace = true }
+structured-output = { workspace = true }
 
 [dev-dependencies]
 test_bin = { workspace = true }
diff --git a/crates/bins/wick/src/commands.rs b/crates/bins/wick/src/commands.rs
index 9315ffbc8..2269e1ec7 100644
--- a/crates/bins/wick/src/commands.rs
+++ b/crates/bins/wick/src/commands.rs
@@ -1,7 +1,7 @@
-pub(crate) mod init;
 pub(crate) mod invoke;
 pub(crate) mod key;
 pub(crate) mod list;
+pub(crate) mod new;
 pub(crate) mod query;
 pub(crate) mod registry;
 pub(crate) mod rpc;
@@ -12,6 +12,7 @@ pub(crate) mod wasm;
 
 use clap::{Parser, Subcommand};
 
+use crate::options::GlobalOptions;
 use crate::LoggingOptions;
 
 #[derive(Parser, Debug, Clone)]
@@ -21,6 +22,8 @@ use crate::LoggingOptions;
   version,
 )]
 pub(crate) struct Cli {
+  #[clap(flatten)]
+  pub(crate) output: GlobalOptions,
   #[clap(flatten)]
   pub(crate) logging: LoggingOptions,
   #[clap(subcommand)]
@@ -32,23 +35,23 @@ pub(crate) enum CliCommand {
   // Core commands
   /// Start a persistent host from a manifest.
   #[clap(name = "serve")]
-  Serve(serve::ServeCommand),
+  Serve(serve::Options),
   /// Load a manifest and execute an entrypoint component (temporarily disabled).
   #[clap(name = "run")]
-  Run(run::RunCommand),
+  Run(run::Options),
   /// Invoke a component from a manifest or wasm module.
   #[clap(name = "invoke")]
-  Invoke(invoke::InvokeCommand),
+  Invoke(invoke::Options),
   /// Print the components in a manifest or wasm module.
   #[clap(name = "list")]
-  List(list::ListCommand),
+  List(list::Options),
   /// Execute a component with test data and assert its output.
   #[clap(name = "test")]
-  Test(test::TestCommand),
+  Test(test::Options),
 
-  /// Initialize a new project.
-  #[clap(name = "init")]
-  Init(init::InitCommand),
+  /// Create new app and component configurations.
+  #[clap(subcommand, name = "new")]
+  New(new::SubCommands),
 
   /// Commands for WebAssembly component.
   #[clap(subcommand, name = "wasm")]
@@ -68,7 +71,7 @@ pub(crate) enum CliCommand {
 
   /// Command to query JSON, YAML, or TOML file.
   #[clap(name = "query")]
-  Query(query::QueryCommand),
+  Query(query::Options),
 }
 
 #[cfg(test)]
diff --git a/crates/bins/wick/src/commands/init.rs b/crates/bins/wick/src/commands/init.rs
deleted file mode 100644
index def5d4c62..000000000
--- a/crates/bins/wick/src/commands/init.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use std::path::PathBuf;
-
-use anyhow::Result;
-use clap::Args;
-use wick_config::config::{AppConfiguration, ComponentConfiguration};
-
-#[derive(Debug, Clone, Args)]
-#[clap(rename_all = "kebab-case")]
-pub(crate) struct InitCommand {
-  /// Name of the project.
-  #[clap()]
-  name: String,
-
-  /// Initialize a new component project.
-  #[clap(short = 'c', long = "component", action)]
-  component: bool,
-}
-
-#[allow(clippy::field_reassign_with_default)]
-pub(crate) async fn handle(opts: InitCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
-  let files: Result<Vec<(PathBuf, String)>> = span.in_scope(|| {
-    if opts.component {
-      info!("Initializing wick component project: {}", opts.name);
-      let mut config = ComponentConfiguration::default();
-      config.set_name(opts.name);
-      Ok(vec![("component.wick".into(), config.into_v1_yaml()?)])
-    } else {
-      info!("Initializing wick application: {}", opts.name);
-      let mut config = AppConfiguration::default();
-      config.set_name(opts.name);
-      Ok(vec![("app.wick".into(), config.into_v1_yaml()?)])
-    }
-  });
-  let files = files?;
-
-  for (file, _) in &files {
-    if file.exists() {
-      anyhow::bail!("File already exists: {}", file.display());
-    }
-  }
-
-  for (file, contents) in files {
-    span.in_scope(|| info!("Writing file: {}", file.display()));
-
-    crate::io::write_bytes(file, contents.as_bytes()).await?;
-  }
-
-  Ok(())
-}
diff --git a/crates/bins/wick/src/commands/invoke.rs b/crates/bins/wick/src/commands/invoke.rs
index c5a918d9e..62a2b9c9f 100644
--- a/crates/bins/wick/src/commands/invoke.rs
+++ b/crates/bins/wick/src/commands/invoke.rs
@@ -5,6 +5,8 @@ use std::time::SystemTime;
 use anyhow::Result;
 use clap::Args;
 use seeded_random::Seed;
+use serde_json::json;
+use structured_output::StructuredOutput;
 use wick_component_cli::options::DefaultCliOptions;
 use wick_component_cli::parse_args;
 use wick_config::{FetchOptions, WickConfiguration};
@@ -16,7 +18,8 @@ use crate::utils::{self, merge_config, parse_config_string};
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct InvokeCommand {
+#[group(skip)]
+pub(crate) struct Options {
   #[clap(flatten)]
   wasi: crate::wasm::WasiOptions,
 
@@ -68,7 +71,11 @@ pub(crate) struct InvokeCommand {
   args: Vec<String>,
 }
 
-pub(crate) async fn handle(opts: InvokeCommand, settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+pub(crate) async fn handle(
+  opts: Options,
+  settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
   let configured_creds = settings.credentials.iter().find(|c| opts.path.starts_with(&c.scope));
 
   let (username, password) = get_auth_for_scope(
@@ -174,9 +181,10 @@ pub(crate) async fn handle(opts: InvokeCommand, settings: wick_settings::Setting
     let stream = PacketStream::new(futures::stream::iter(packets));
 
     let stream = host.request(&component, stream, inherent_data).await?;
+
     utils::print_stream_json(stream, &opts.filter, opts.short, opts.raw).await?;
   }
   host.stop().await;
 
-  Ok(())
+  Ok(StructuredOutput::new("", json!({})))
 }
diff --git a/crates/bins/wick/src/commands/key.rs b/crates/bins/wick/src/commands/key.rs
index f9dd6f1a3..f3eedbf59 100644
--- a/crates/bins/wick/src/commands/key.rs
+++ b/crates/bins/wick/src/commands/key.rs
@@ -8,13 +8,13 @@ pub(crate) mod list;
 pub(crate) enum SubCommands {
   /// Generate new signing keys.
   #[clap(name = "gen")]
-  Gen(gen::KeyGenCommand),
+  Gen(gen::Options),
 
   /// List all found keys.
   #[clap(name = "list")]
-  List(list::KeyListCommand),
+  List(list::Options),
 
   /// Read key data.
   #[clap(name = "get")]
-  Get(get::KeyGetCommand),
+  Get(get::Options),
 }
diff --git a/crates/bins/wick/src/commands/key/gen.rs b/crates/bins/wick/src/commands/key/gen.rs
index 70298e4f5..3bcfe95ed 100644
--- a/crates/bins/wick/src/commands/key/gen.rs
+++ b/crates/bins/wick/src/commands/key/gen.rs
@@ -1,12 +1,15 @@
 use anyhow::Result;
 use clap::Args;
 use nkeys::{KeyPair, KeyPairType};
+use serde_json::json;
+use structured_output::StructuredOutput;
 
 use crate::keys::GenerateCommon;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct KeyGenCommand {
+#[group(skip)]
+pub(crate) struct Options {
   /// The type of key to create (e.g. account or module)
   #[clap(action)]
   keytype: KeyPairType,
@@ -16,14 +19,18 @@ pub(crate) struct KeyGenCommand {
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: KeyGenCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
   let _enter = span.enter();
   debug!("Generating {} key", crate::keys::keypair_type_to_string(&opts.keytype));
 
   let kp = KeyPair::new(opts.keytype);
 
-  println!("Public key: {}", kp.public_key());
-  println!("Seed: {}", kp.seed()?);
+  let lines = format!("Public key: {}\nPrivate seed: {}", kp.public_key(), kp.seed()?);
 
-  Ok(())
+  let json = json!({"public_key":kp.public_key(),"private_seed":kp.seed()?});
+  Ok(StructuredOutput::new(lines, json))
 }
diff --git a/crates/bins/wick/src/commands/key/get.rs b/crates/bins/wick/src/commands/key/get.rs
index 13beba56b..03b75a86e 100644
--- a/crates/bins/wick/src/commands/key/get.rs
+++ b/crates/bins/wick/src/commands/key/get.rs
@@ -2,10 +2,13 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use clap::Args;
+use serde_json::json;
+use structured_output::StructuredOutput;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct KeyGetCommand {
+#[group(skip)]
+pub(crate) struct Options {
   /// The filename to read (without path).
   #[clap(action)]
   path: PathBuf,
@@ -16,13 +19,18 @@ pub(crate) struct KeyGetCommand {
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: KeyGetCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
   let _span = span.enter();
   println!("Reading key: {}\n", opts.path.to_string_lossy());
   let kp = crate::keys::get_key(opts.directory, opts.path).await?;
 
-  println!("Public key: {}", kp.public_key());
-  println!("Private seed: {}", kp.seed()?);
+  let lines = format!("Public key: {}\nPrivate seed: {}", kp.public_key(), kp.seed()?);
 
-  Ok(())
+  let json = json!({"public_key":kp.public_key(),"private_seed":kp.seed()?});
+
+  Ok(StructuredOutput::new(lines, json))
 }
diff --git a/crates/bins/wick/src/commands/key/list.rs b/crates/bins/wick/src/commands/key/list.rs
index 25390f685..fda2b35a6 100644
--- a/crates/bins/wick/src/commands/key/list.rs
+++ b/crates/bins/wick/src/commands/key/list.rs
@@ -2,12 +2,15 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use clap::Args;
+use serde_json::json;
+use structured_output::StructuredOutput;
 
 use crate::keys::get_key_files;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct KeyListCommand {
+#[group(skip)]
+pub(crate) struct Options {
   /// Location of key files. Defaults to $WICK_KEYS ($HOME/.wick/keys or %USERPROFILE%/.wick/keys).
   #[clap(long = "directory", env = "WICK_KEYS", action)]
   pub(crate) directory: Option<PathBuf>,
@@ -15,18 +18,16 @@ pub(crate) struct KeyListCommand {
 
 #[allow(clippy::unused_async)]
 pub(crate) async fn handle(
-  opts: KeyListCommand,
+  opts: Options,
   _settings: wick_settings::Settings,
   span: tracing::Span,
-) -> Result<()> {
+) -> Result<StructuredOutput> {
   let _enter = span.enter();
   debug!("Listing keys");
   let (dir, keys) = get_key_files(opts.directory)?;
   info!("Listing keys in {}", dir.to_string_lossy());
 
-  for key in keys {
-    println!("{}", key);
-  }
+  let json = json!({"keys":keys});
 
-  Ok(())
+  Ok(StructuredOutput::new(keys.join("\n"), json))
 }
diff --git a/crates/bins/wick/src/commands/list.rs b/crates/bins/wick/src/commands/list.rs
index c78439bfc..b78213ff6 100644
--- a/crates/bins/wick/src/commands/list.rs
+++ b/crates/bins/wick/src/commands/list.rs
@@ -1,5 +1,10 @@
+use std::fmt::Write;
+
 use anyhow::Result;
 use clap::Args;
+use option_utils::OptionUtils;
+use serde_json::json;
+use structured_output::StructuredOutput;
 use wick_component_cli::options::DefaultCliOptions;
 use wick_config::WickConfiguration;
 use wick_host::ComponentHostBuilder;
@@ -8,19 +13,21 @@ use wick_interface_types::Field;
 use crate::utils::merge_config;
 
 #[derive(Debug, Clone, Args)]
-pub(crate) struct ListCommand {
+#[group(skip)]
+pub(crate) struct Options {
   #[clap(flatten)]
   pub(crate) oci: crate::oci::Options,
 
   /// The path or OCI URL to a wick manifest or wasm file.
   #[clap(action)]
   pub(crate) location: String,
-
-  #[clap(long = "json", action)]
-  pub(crate) json: bool,
 }
 
-pub(crate) async fn handle(opts: ListCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
   let fetch_options = wick_config::config::FetchOptions::new()
     .allow_latest(opts.oci.allow_latest)
     .allow_insecure(&opts.oci.insecure_registries);
@@ -33,26 +40,37 @@ pub(crate) async fn handle(opts: ListCommand, _settings: wick_settings::Settings
 
   let mut config = merge_config(&manifest, &opts.oci, Some(server_options));
   // Disable everything but the mesh
-  config.host_mut().set_rpc(None);
+  config.host_mut().inner_mut(|h| {
+    h.set_rpc(None);
+  });
 
   let mut host = ComponentHostBuilder::default().manifest(config).span(span).build()?;
 
   host.start_engine(None).await?;
   let signature = host.get_signature()?;
 
-  if opts.json {
-    let json = serde_json::to_string(&signature)?;
-    println!("{}", json);
-  } else {
-    fn print_component(label: &str, indent: &str, inputs: &[Field], outputs: &[Field]) {
-      let inputs = inputs.iter().map(|f| f.to_string()).collect::<Vec<_>>().join(", ");
-      let outputs = outputs.iter().map(|f| f.to_string()).collect::<Vec<_>>().join(", ");
-      println!("{}{}({}) -> ({})", indent, label, inputs, outputs);
-    }
-    for op in signature.operations {
-      print!("Component: ");
-      print_component(&op.name, "", &op.inputs, &op.outputs);
-    }
+  let mut output = String::new();
+  for op in &signature.operations {
+    write!(&mut output, "Component: ")?;
+    write_line(&mut output, &op.name, "", &op.inputs, &op.outputs)?;
   }
-  Ok(())
+
+  Ok(StructuredOutput::new(
+    output,
+    json!({
+      "result": signature,
+    }),
+  ))
+}
+
+fn write_line(
+  mut buff: impl Write,
+  label: &str,
+  indent: &str,
+  inputs: &[Field],
+  outputs: &[Field],
+) -> std::fmt::Result {
+  let inputs = inputs.iter().map(|f| f.to_string()).collect::<Vec<_>>().join(", ");
+  let outputs = outputs.iter().map(|f| f.to_string()).collect::<Vec<_>>().join(", ");
+  write!(buff, "{}{}({}) -> ({})", indent, label, inputs, outputs)
 }
diff --git a/crates/bins/wick/src/commands/new.rs b/crates/bins/wick/src/commands/new.rs
new file mode 100644
index 000000000..96c339cfd
--- /dev/null
+++ b/crates/bins/wick/src/commands/new.rs
@@ -0,0 +1,25 @@
+use clap::Subcommand;
+use wick_config::config;
+
+pub(crate) mod app;
+pub(crate) mod component;
+
+#[derive(Subcommand, Debug, Clone)]
+pub(crate) enum SubCommands {
+  /// Invoke a component in a collection.
+  #[clap(subcommand, name = "component", alias = "comp")]
+  Component(component::SubCommands),
+
+  /// Query a collection for a list of its components.
+  #[clap(name = "application", alias = "app")]
+  App(app::Options),
+}
+
+fn generic_metadata(description: &str) -> config::Metadata {
+  config::MetadataBuilder::default()
+    .licenses(["Apache-2.0".to_owned()])
+    .version("0.0.1")
+    .description(Some(description.to_owned()))
+    .build()
+    .unwrap()
+}
diff --git a/crates/bins/wick/src/commands/new/app.rs b/crates/bins/wick/src/commands/new/app.rs
new file mode 100644
index 000000000..bba4d89d6
--- /dev/null
+++ b/crates/bins/wick/src/commands/new/app.rs
@@ -0,0 +1,68 @@
+use anyhow::Result;
+use clap::{Args, ValueEnum};
+use structured_output::StructuredOutput;
+use wick_config::config::{self, AppConfiguration};
+use wick_config::WickConfiguration;
+
+use crate::io::File;
+
+#[derive(Debug, Clone, Copy, ValueEnum)]
+pub(crate) enum TriggerType {
+  Time,
+  Http,
+  Cli,
+}
+
+#[derive(Debug, Clone, Args)]
+#[clap(rename_all = "kebab-case")]
+#[group(skip)]
+pub(crate) struct Options {
+  /// Name of the project.
+  #[clap()]
+  name: String,
+
+  #[clap(short = 't', long = "trigger", value_enum)]
+  /// Triggers to initialize.
+  triggers: Vec<TriggerType>,
+
+  #[clap(long = "dry-run", action)]
+  dry_run: bool,
+}
+
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
+  let files: Result<Vec<File>> = span.in_scope(|| {
+    info!("Initializing wick application: {}", opts.name);
+    let mut config = AppConfiguration::default();
+    config.set_name(opts.name);
+    config.set_metadata(crate::commands::new::generic_metadata("New wick application"));
+
+    for trigger in opts.triggers {
+      match trigger {
+        TriggerType::Time => {
+          config.triggers_mut().push(config::TriggerDefinition::Time(
+            config::app_config::TimeTriggerConfigBuilder::default().build().unwrap(),
+          ));
+        }
+        TriggerType::Http => {
+          config.triggers_mut().push(config::TriggerDefinition::Http(
+            config::HttpTriggerConfigBuilder::default().build().unwrap(),
+          ));
+        }
+        TriggerType::Cli => {
+          config.triggers_mut().push(config::TriggerDefinition::Cli(
+            config::CliConfigBuilder::default().build().unwrap(),
+          ));
+        }
+      }
+    }
+    let config = WickConfiguration::App(config);
+
+    Ok(vec![File::new("app.wick", config.into_v1_yaml()?.into())])
+  });
+
+  Ok(crate::io::init_files(&files?, opts.dry_run).await?)
+}
diff --git a/crates/bins/wick/src/commands/new/component.rs b/crates/bins/wick/src/commands/new/component.rs
new file mode 100644
index 000000000..e18371742
--- /dev/null
+++ b/crates/bins/wick/src/commands/new/component.rs
@@ -0,0 +1,25 @@
+use clap::Subcommand;
+
+pub(crate) mod composite;
+pub(crate) mod http;
+pub(crate) mod sql;
+pub(crate) mod wasmrs;
+
+#[derive(Subcommand, Debug, Clone)]
+pub(crate) enum SubCommands {
+  /// Create a new HTTP client component
+  #[clap(name = "http")]
+  Http(http::Options),
+
+  /// Create a new wick composite component
+  #[clap(name = "composite")]
+  Composite(composite::Options),
+
+  /// Create a new SQL DB component
+  #[clap(name = "sql")]
+  Sql(sql::Options),
+
+  /// Create a new wick wasmrs component
+  #[clap(name = "wasmrs")]
+  WasmRS(wasmrs::Options),
+}
diff --git a/crates/bins/wick/src/commands/new/component/composite.rs b/crates/bins/wick/src/commands/new/component/composite.rs
new file mode 100644
index 000000000..e436655ed
--- /dev/null
+++ b/crates/bins/wick/src/commands/new/component/composite.rs
@@ -0,0 +1,55 @@
+use anyhow::Result;
+use clap::Args;
+use structured_output::StructuredOutput;
+use wick_config::config::{self, ComponentConfiguration, CompositeComponentImplementation, FlowOperationBuilder};
+
+use crate::io::File;
+
+#[derive(Debug, Clone, Args)]
+#[clap(rename_all = "kebab-case")]
+#[group(skip)]
+pub(crate) struct Options {
+  /// Name of the component.
+  #[clap()]
+  name: String,
+
+  #[clap(long = "dry-run", action)]
+  dry_run: bool,
+}
+
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
+  let _span = span.enter();
+  let files: Result<Vec<File>> = span.in_scope(|| {
+    info!("Initializing wick http component: {}", opts.name);
+
+    let mut config = ComponentConfiguration::default();
+    config.set_name(opts.name.clone());
+    config.set_metadata(crate::commands::new::generic_metadata("New composite wick component"));
+
+    let mut component = CompositeComponentImplementation::default();
+
+    component.set_operations([(
+      "operation_name".to_owned(),
+      FlowOperationBuilder::default()
+        .name("operation_name")
+        .expressions(vec!["<>.input -> <>.output".parse().unwrap()])
+        .build()
+        .unwrap(),
+    )]);
+
+    config.set_component(config::ComponentImplementation::Composite(component));
+
+    let config = wick_config::WickConfiguration::Component(config);
+
+    Ok(vec![File::new(
+      format!("{}.wick", opts.name),
+      config.into_v1_yaml()?.into(),
+    )])
+  });
+
+  Ok(crate::io::init_files(&files?, opts.dry_run).await?)
+}
diff --git a/crates/bins/wick/src/commands/new/component/http.rs b/crates/bins/wick/src/commands/new/component/http.rs
new file mode 100644
index 000000000..ba2348139
--- /dev/null
+++ b/crates/bins/wick/src/commands/new/component/http.rs
@@ -0,0 +1,69 @@
+use anyhow::Result;
+use clap::Args;
+use structured_output::StructuredOutput;
+use wick_config::config::components::{Codec, HttpClientComponentConfigBuilder, HttpClientOperationDefinitionBuilder};
+use wick_config::config::{self, ComponentConfiguration, ResourceBinding, ResourceDefinition, UrlResource};
+use wick_interface_types::{Field, Type};
+
+use crate::io::File;
+
+#[derive(Debug, Clone, Args)]
+#[clap(rename_all = "kebab-case")]
+#[group(skip)]
+pub(crate) struct Options {
+  /// Name of the component.
+  #[clap()]
+  name: String,
+
+  #[clap(long = "dry-run", action)]
+  dry_run: bool,
+}
+
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
+  let _span = span.enter();
+  let files: Result<Vec<File>> = span.in_scope(|| {
+    info!("Initializing wick http component: {}", opts.name);
+
+    let resource_name = "HTTP_URL";
+
+    let mut config = ComponentConfiguration::default();
+    config.set_name(opts.name.clone());
+
+    config.resources_mut().insert(
+      resource_name.to_owned(),
+      ResourceBinding::new(
+        resource_name,
+        ResourceDefinition::Url(UrlResource::new("http://localhost:8080".parse().unwrap())),
+      ),
+    );
+
+    config.set_metadata(crate::commands::new::generic_metadata("New HTTP Client wick component"));
+
+    let component = HttpClientComponentConfigBuilder::default()
+      .codec(Codec::Json)
+      .resource(resource_name)
+      .operations([HttpClientOperationDefinitionBuilder::default()
+        .name("operation_name".to_owned())
+        .method(config::components::HttpMethod::Get)
+        .path("/user/{id:string}".to_owned())
+        .inputs([Field::new("id", Type::String)])
+        .build()
+        .unwrap()])
+      .build()
+      .unwrap();
+
+    config.set_component(config::ComponentImplementation::HttpClient(component));
+
+    let config = wick_config::WickConfiguration::Component(config);
+
+    Ok(vec![File::new(
+      format!("{}.wick", opts.name),
+      config.into_v1_yaml()?.into(),
+    )])
+  });
+  Ok(crate::io::init_files(&files?, opts.dry_run).await?)
+}
diff --git a/crates/bins/wick/src/commands/new/component/sql.rs b/crates/bins/wick/src/commands/new/component/sql.rs
new file mode 100644
index 000000000..14025dad7
--- /dev/null
+++ b/crates/bins/wick/src/commands/new/component/sql.rs
@@ -0,0 +1,68 @@
+use anyhow::Result;
+use clap::Args;
+use structured_output::StructuredOutput;
+use wick_config::config::components::{SqlComponentConfigBuilder, SqlOperationDefinitionBuilder};
+use wick_config::config::{self, ComponentConfiguration};
+use wick_interface_types::{Field, Type};
+
+use crate::io::File;
+
+#[derive(Debug, Clone, Args)]
+#[clap(rename_all = "kebab-case")]
+#[group(skip)]
+pub(crate) struct Options {
+  /// Name of the component.
+  #[clap()]
+  name: String,
+
+  #[clap(long = "dry-run", action)]
+  dry_run: bool,
+}
+
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
+  let _span = span.enter();
+  let files: Result<Vec<File>> = span.in_scope(|| {
+    info!("Initializing wick sql db component: {}", opts.name);
+
+    let mut config = ComponentConfiguration::default();
+    config.set_name(opts.name.clone());
+    let resource_name = "DB_URL";
+    config.resources_mut().insert(
+      resource_name.to_owned(),
+      config::ResourceBinding::new(
+        resource_name,
+        config::ResourceDefinition::Url(config::UrlResource::new(
+          "postgres://postgres:postgres@localhost:5432/db_name".parse().unwrap(),
+        )),
+      ),
+    );
+
+    config.set_metadata(crate::commands::new::generic_metadata("New SQL wick component"));
+
+    let component = SqlComponentConfigBuilder::default()
+      .resource(resource_name)
+      .operations([SqlOperationDefinitionBuilder::default()
+        .name("operation_name".to_owned())
+        .inputs([Field::new("id", Type::String)])
+        .query("SELECT * FROM users WHERE id = $1".to_owned())
+        .arguments(["id".to_owned()])
+        .build()
+        .unwrap()])
+      .build()
+      .unwrap();
+
+    config.set_component(config::ComponentImplementation::Sql(component));
+
+    let config = wick_config::WickConfiguration::Component(config);
+
+    Ok(vec![File::new(
+      format!("{}.wick", opts.name),
+      config.into_v1_yaml()?.into(),
+    )])
+  });
+  Ok(crate::io::init_files(&files?, opts.dry_run).await?)
+}
diff --git a/crates/bins/wick/src/commands/new/component/wasmrs.rs b/crates/bins/wick/src/commands/new/component/wasmrs.rs
new file mode 100644
index 000000000..0780870db
--- /dev/null
+++ b/crates/bins/wick/src/commands/new/component/wasmrs.rs
@@ -0,0 +1,76 @@
+use anyhow::Result;
+use clap::Args;
+use structured_output::StructuredOutput;
+use wick_config::config::{
+  self,
+  ComponentConfiguration,
+  OperationSignatureBuilder,
+  WasmComponentImplementationBuilder,
+};
+use wick_interface_types::{Field, StructDefinition, Type, TypeDefinition};
+
+use crate::io::File;
+
+#[derive(Debug, Clone, Args)]
+#[clap(rename_all = "kebab-case")]
+#[group(skip)]
+pub(crate) struct Options {
+  /// Name of the component.
+  #[clap()]
+  name: String,
+
+  #[clap(long = "dry-run", action)]
+  dry_run: bool,
+}
+
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
+  let _span = span.enter();
+  let files: Result<Vec<File>> = span.in_scope(|| {
+    info!("Initializing wick http component: {}", opts.name);
+    info!("Note: WebAssembly components are often better suited by cloning a boilerplate project. See https://github.com/candlecorp/wick for directions.");
+
+    let mut config = ComponentConfiguration::default();
+    config.set_name(opts.name.clone());
+
+    let example_typedef = TypeDefinition::Struct(StructDefinition::new(
+      "user_object",
+      vec![
+        Field::new("id", Type::String),
+        Field::new("name", Type::String),
+        Field::new("email", Type::String),
+      ],
+    ));
+
+    config.types_mut().push(example_typedef);
+
+    config.set_metadata(crate::commands::new::generic_metadata("New WebAssembly wick component"));
+
+    let component = WasmComponentImplementationBuilder::default()
+      .reference(config::AssetReference::new(format!("./build/{}", opts.name)))
+      .operations([(
+        "operation_name".to_owned(),
+        OperationSignatureBuilder::default()
+          .name("operation_name".to_owned())
+          .inputs([Field::new("id", Type::String)])
+          .outputs([Field::new("output", Type::Named("user_object".to_owned()))])
+          .build()
+          .unwrap(),
+      )])
+      .build()
+      .unwrap();
+
+    config.set_component(config::ComponentImplementation::Wasm(component));
+
+    let config = wick_config::WickConfiguration::Component(config);
+
+    Ok(vec![File::new(
+      format!("{}.wick", opts.name),
+      config.into_v1_yaml()?.into(),
+    )])
+  });
+  Ok(crate::io::init_files(&files?, opts.dry_run).await?)
+}
diff --git a/crates/bins/wick/src/commands/query.rs b/crates/bins/wick/src/commands/query.rs
index e48e59805..aa3e1cc70 100644
--- a/crates/bins/wick/src/commands/query.rs
+++ b/crates/bins/wick/src/commands/query.rs
@@ -6,10 +6,13 @@ use anyhow::Result;
 use clap::Args;
 use jaq_core::{parse, Ctx, Definitions, RcIter, Val};
 use markup_converter::{Format, Transcoder};
+use serde_json::{json, Value};
+use structured_output::StructuredOutput;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct QueryCommand {
+#[group(skip)]
+pub(crate) struct Options {
   /// Option to print raw output.
   #[clap(short = 'r', long = "raw", action)]
   raw_output: bool,
@@ -47,7 +50,11 @@ impl FromStr for MarkupKind {
   }
 }
 
-pub(crate) async fn handle(opts: QueryCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
   let input = if let Some(path) = opts.path {
     match opts.kind {
       None => Transcoder::from_path(&path)?.to_json()?,
@@ -88,6 +95,10 @@ pub(crate) async fn handle(opts: QueryCommand, _settings: wick_settings::Setting
     return Err(anyhow!("Errors parsing queries"));
   }
 
+  let mut lines = Vec::new();
+  let mut json = Vec::new();
+
+  #[allow(clippy::option_if_let_else)]
   if let Some(filters) = filters {
     // start out only from core filters,
     // which do not include filters in the standard library
@@ -102,12 +113,11 @@ pub(crate) async fn handle(opts: QueryCommand, _settings: wick_settings::Setting
 
     for val in out {
       match val {
-        Ok(result) => match result {
-          Val::Str(s) if opts.raw_output => println!("{}", s),
-          _ => {
-            println!("{}", result);
-          }
-        },
+        Ok(result) => {
+          let result: Value = result.into();
+          lines.push(result.to_string());
+          json.push(result);
+        }
         Err(e) => error!("Error: {}", e),
       };
     }
@@ -115,5 +125,5 @@ pub(crate) async fn handle(opts: QueryCommand, _settings: wick_settings::Setting
     debug!("No queries successfully parsed");
   }
 
-  Ok(())
+  Ok(StructuredOutput::new(lines.join("\n"), json!({"results":json})))
 }
diff --git a/crates/bins/wick/src/commands/registry.rs b/crates/bins/wick/src/commands/registry.rs
index e81e7ff77..5724ec0a7 100644
--- a/crates/bins/wick/src/commands/registry.rs
+++ b/crates/bins/wick/src/commands/registry.rs
@@ -8,13 +8,13 @@ pub(crate) mod push;
 pub(crate) enum SubCommands {
   /// Push an artifact or bundle to an OCI registry.
   #[clap(name = "push")]
-  Push(push::RegistryPushCommand),
+  Push(push::Options),
 
   /// Pull an artifact from an OCI registry.
   #[clap(name = "pull")]
-  Pull(pull::RegistryPullCommand),
+  Pull(pull::Options),
 
   /// Save the credentials for a registry.
   #[clap(name = "login")]
-  Login(login::RegistryLoginCommand),
+  Login(login::Options),
 }
diff --git a/crates/bins/wick/src/commands/registry/login.rs b/crates/bins/wick/src/commands/registry/login.rs
index 64ea42074..aba73197f 100644
--- a/crates/bins/wick/src/commands/registry/login.rs
+++ b/crates/bins/wick/src/commands/registry/login.rs
@@ -1,10 +1,11 @@
 use anyhow::Result;
 use clap::Args;
 use dialoguer::Password;
-
+use structured_output::StructuredOutput;
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct RegistryLoginCommand {
+#[group(skip)]
+pub(crate) struct Options {
   /// Registry to save credentials for.
   #[clap(action)]
   pub(crate) registry: String,
@@ -16,10 +17,10 @@ pub(crate) struct RegistryLoginCommand {
 
 #[allow(clippy::unused_async)]
 pub(crate) async fn handle(
-  opts: RegistryLoginCommand,
+  opts: Options,
   mut settings: wick_settings::Settings,
   span: tracing::Span,
-) -> Result<()> {
+) -> Result<StructuredOutput> {
   let _enter = span.enter();
   let creds = settings.credentials.iter_mut().find(|c| c.scope == opts.registry);
   println!(
@@ -31,8 +32,12 @@ pub(crate) async fn handle(
     .with_prompt(format!("Enter the token for {}@{}: ", opts.username, opts.registry))
     .interact()?;
 
+  let mut json = serde_json::json!({});
+  let mut lines = Vec::new();
+
   if let Some(creds) = creds {
-    debug!("Updating existing credentials");
+    json.as_object_mut().unwrap().insert("updated".to_owned(), true.into());
+    lines.push("Updating existing credentials");
     match &mut creds.auth {
       wick_settings::Auth::Basic(ref mut creds) => {
         creds.username = opts.username;
@@ -40,7 +45,8 @@ pub(crate) async fn handle(
       }
     }
   } else {
-    debug!("Adding new credentials");
+    json.as_object_mut().unwrap().insert("added".to_owned(), true.into());
+    lines.push("Adding new credentials");
     settings.credentials.push(wick_settings::Credential {
       scope: opts.registry,
       auth: wick_settings::Auth::Basic(wick_settings::BasicAuth {
@@ -51,5 +57,5 @@ pub(crate) async fn handle(
   }
   settings.save()?;
 
-  Ok(())
+  Ok(StructuredOutput::new(lines.join("\n"), json))
 }
diff --git a/crates/bins/wick/src/commands/registry/pull.rs b/crates/bins/wick/src/commands/registry/pull.rs
index 4407fb5c4..b1c784cb4 100644
--- a/crates/bins/wick/src/commands/registry/pull.rs
+++ b/crates/bins/wick/src/commands/registry/pull.rs
@@ -2,13 +2,15 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use clap::Args;
+use structured_output::StructuredOutput;
 use tracing::Instrument;
 use wick_oci_utils::OciOptions;
 
 use crate::options::get_auth_for_scope;
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct RegistryPullCommand {
+#[group(skip)]
+pub(crate) struct Options {
   /// OCI reference to pull.
   #[clap(action)]
   pub(crate) reference: String,
@@ -46,10 +48,10 @@ pub(crate) async fn pull(reference: String, oci_opts: OciOptions) -> Result<wick
 
 #[allow(clippy::unused_async)]
 pub(crate) async fn handle(
-  opts: RegistryPullCommand,
+  opts: Options,
   settings: wick_settings::Settings,
   span: tracing::Span,
-) -> Result<()> {
+) -> Result<StructuredOutput> {
   let configured_creds = settings
     .credentials
     .iter()
@@ -71,10 +73,16 @@ pub(crate) async fn handle(
 
   span.in_scope(|| debug!(options=?oci_opts, reference= opts.reference, "pulling reference"));
 
-  let pull_result = pull(opts.reference, oci_opts).instrument(span.clone()).await;
+  let pull_result = pull(opts.reference, oci_opts).instrument(span.clone()).await?;
 
-  for file in pull_result?.list_files() {
-    info!("Pulled file: {}", file.path().display());
-  }
-  Ok(())
+  let files = pull_result
+    .list_files()
+    .iter()
+    .map(|f| f.path().display().to_string())
+    .collect::<Vec<_>>();
+
+  Ok(StructuredOutput::new(
+    format!("Pulled file: \n {}", files.join("\n")),
+    serde_json::json!({"files": files}),
+  ))
 }
diff --git a/crates/bins/wick/src/commands/registry/push.rs b/crates/bins/wick/src/commands/registry/push.rs
index 6774a85df..397fee743 100644
--- a/crates/bins/wick/src/commands/registry/push.rs
+++ b/crates/bins/wick/src/commands/registry/push.rs
@@ -2,13 +2,16 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use clap::Args;
+use serde_json::json;
+use structured_output::StructuredOutput;
 use tracing::Instrument;
 
 use crate::options::get_auth_for_scope;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct RegistryPushCommand {
+#[group(skip)]
+pub(crate) struct Options {
   /// OCI artifact to push.
   #[clap(action)]
   pub(crate) source: PathBuf,
@@ -22,10 +25,10 @@ pub(crate) struct RegistryPushCommand {
 
 #[allow(clippy::unused_async)]
 pub(crate) async fn handle(
-  opts: RegistryPushCommand,
+  opts: Options,
   settings: wick_settings::Settings,
   span: tracing::Span,
-) -> Result<()> {
+) -> Result<StructuredOutput> {
   span.in_scope(|| debug!("Push artifact"));
 
   let mut package = wick_package::WickPackage::from_path(&opts.source)
@@ -53,8 +56,12 @@ pub(crate) async fn handle(
 
   let reference = package.registry_reference().unwrap(); // unwrap OK because we know we have a reg from above.
 
-  span.in_scope(|| info!(reference, "pushing artifact"));
-  span.in_scope(|| debug!(options=?oci_opts, reference= &reference, "pushing reference"));
+  span.in_scope(|| {
+    info!(reference, "pushing artifact");
+    debug!(options=?oci_opts, reference= &reference, "pushing reference");
+  });
+
+  let mut lines = Vec::new();
 
   let url = if !opts.tags.is_empty() {
     for tag in &opts.tags {
@@ -62,13 +69,23 @@ pub(crate) async fn handle(
       let tagged_reference = pack.tagged_reference(tag).unwrap();
       span.in_scope(|| info!(reference = &tagged_reference, "pushing tag"));
       pack.push(&tagged_reference, &oci_opts).await?;
+      lines.push(format!("Pushed tag: {}", reference));
     }
-    package.push(&reference, &oci_opts).await?
+
+    // there must be a better way than cloning the package here, feel free to fix it.
+    let url = package.clone().push(&reference, &oci_opts).await?;
+
+    span.in_scope(|| info!(%url, "artifact pushed"));
+
+    package.push(&reference, &oci_opts).await?;
+    url
   } else {
     package.push(&reference, &oci_opts).await?
   };
+  let json = json!({"url":&url, "tags": opts.tags});
 
   span.in_scope(|| info!(%url, "artifact pushed"));
+  lines.push(format!("Pushed artifact: {}", url));
 
-  Ok(())
+  Ok(StructuredOutput::new(lines.join("\n"), json))
 }
diff --git a/crates/bins/wick/src/commands/rpc.rs b/crates/bins/wick/src/commands/rpc.rs
index ad7df7dfd..e59206b59 100644
--- a/crates/bins/wick/src/commands/rpc.rs
+++ b/crates/bins/wick/src/commands/rpc.rs
@@ -10,15 +10,15 @@ pub(crate) mod stats;
 pub(crate) enum SubCommands {
   /// Invoke a component in a collection.
   #[clap(name = "invoke")]
-  Invoke(invoke::RpcInvokeCommand),
+  Invoke(invoke::Options),
 
   /// Query a collection for a list of its components.
   #[clap(name = "list")]
-  List(list::RpcListCommand),
+  List(list::Options),
 
   /// Query a collection for its runtime statistics.
   #[clap(name = "stats")]
-  Stats(stats::RpcStatsCommand),
+  Stats(stats::Options),
 }
 
 #[derive(Debug, Clone, Args)]
diff --git a/crates/bins/wick/src/commands/rpc/invoke.rs b/crates/bins/wick/src/commands/rpc/invoke.rs
index fe67f582a..d755ce78d 100644
--- a/crates/bins/wick/src/commands/rpc/invoke.rs
+++ b/crates/bins/wick/src/commands/rpc/invoke.rs
@@ -3,6 +3,7 @@ use std::time::SystemTime;
 
 use anyhow::Result;
 use clap::Args;
+use structured_output::StructuredOutput;
 use wick_component_cli::parse_args;
 use wick_packet::{Entity, InherentData, Invocation, Observer, Packet, PacketStream};
 
@@ -10,7 +11,8 @@ use crate::utils;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct RpcInvokeCommand {
+#[group(skip)]
+pub(crate) struct Options {
   #[clap(flatten)]
   pub(crate) connection: super::ConnectOptions,
 
@@ -48,10 +50,10 @@ pub(crate) struct RpcInvokeCommand {
 }
 
 pub(crate) async fn handle(
-  opts: RpcInvokeCommand,
+  opts: Options,
   _settings: wick_settings::Settings,
   span: tracing::Span,
-) -> Result<()> {
+) -> Result<StructuredOutput> {
   let mut client = wick_rpc::make_rpc_client(
     format!("http://{}:{}", opts.connection.address, opts.connection.port),
     opts.connection.pem,
@@ -116,5 +118,5 @@ pub(crate) async fn handle(
     utils::print_stream_json(stream, &opts.filter, opts.short, opts.raw).await?;
   }
 
-  Ok(())
+  Ok(StructuredOutput::new("", serde_json::json!({})))
 }
diff --git a/crates/bins/wick/src/commands/rpc/list.rs b/crates/bins/wick/src/commands/rpc/list.rs
index 418536f97..d36d90aa2 100644
--- a/crates/bins/wick/src/commands/rpc/list.rs
+++ b/crates/bins/wick/src/commands/rpc/list.rs
@@ -1,18 +1,21 @@
 use anyhow::Result;
 use clap::Args;
+use serde_json::json;
+use structured_output::StructuredOutput;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct RpcListCommand {
+#[group(skip)]
+pub(crate) struct Options {
   #[clap(flatten)]
   pub(crate) connection: super::ConnectOptions,
 }
 
 pub(crate) async fn handle(
-  opts: RpcListCommand,
+  opts: Options,
   _settings: wick_settings::Settings,
   span: tracing::Span,
-) -> Result<()> {
+) -> Result<StructuredOutput> {
   let _span = span.enter();
   let mut client = wick_rpc::make_rpc_client(
     format!("http://{}:{}", opts.connection.address, opts.connection.port),
@@ -25,7 +28,8 @@ pub(crate) async fn handle(
 
   let list = client.list().await?;
 
-  println!("{}", serde_json::to_string(&list)?);
-
-  Ok(())
+  Ok(StructuredOutput::new(
+    serde_json::to_string(&list)?,
+    json! ({"list": list }),
+  ))
 }
diff --git a/crates/bins/wick/src/commands/rpc/stats.rs b/crates/bins/wick/src/commands/rpc/stats.rs
index fd5617d15..6e867cc23 100644
--- a/crates/bins/wick/src/commands/rpc/stats.rs
+++ b/crates/bins/wick/src/commands/rpc/stats.rs
@@ -1,20 +1,23 @@
 use anyhow::Result;
 use clap::Args;
+use serde_json::json;
+use structured_output::StructuredOutput;
 use wick_rpc::rpc::StatsRequest;
 use wick_rpc::Statistics;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct RpcStatsCommand {
+#[group(skip)]
+pub(crate) struct Options {
   #[clap(flatten)]
   pub(crate) connection: super::ConnectOptions,
 }
 
 pub(crate) async fn handle(
-  opts: RpcStatsCommand,
+  opts: Options,
   _settings: wick_settings::Settings,
   span: tracing::Span,
-) -> Result<()> {
+) -> Result<StructuredOutput> {
   let _span = span.enter();
   let mut client = wick_rpc::make_rpc_client(
     format!("http://{}:{}", opts.connection.address, opts.connection.port),
@@ -33,7 +36,8 @@ pub(crate) async fn handle(
     converted.push(item.into());
   }
 
-  println!("{}", serde_json::to_string(&converted)?);
-
-  Ok(())
+  Ok(StructuredOutput::new(
+    serde_json::to_string(&converted)?,
+    json! ({"stats": converted }),
+  ))
 }
diff --git a/crates/bins/wick/src/commands/run.rs b/crates/bins/wick/src/commands/run.rs
index 67eaec79d..0d4a43de7 100644
--- a/crates/bins/wick/src/commands/run.rs
+++ b/crates/bins/wick/src/commands/run.rs
@@ -2,6 +2,8 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use clap::Args;
+use serde_json::json;
+use structured_output::StructuredOutput;
 use tracing::Instrument;
 use wick_config::{FetchOptions, WickConfiguration};
 use wick_host::AppHostBuilder;
@@ -10,7 +12,8 @@ use crate::options::get_auth_for_scope;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct RunCommand {
+#[group(skip)]
+pub(crate) struct Options {
   #[clap(flatten)]
   pub(crate) oci: crate::oci::Options,
 
@@ -27,7 +30,11 @@ pub(crate) struct RunCommand {
   args: Vec<String>,
 }
 
-pub(crate) async fn handle(opts: RunCommand, settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+pub(crate) async fn handle(
+  opts: Options,
+  settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
   span.in_scope(|| trace!(args = ?opts.args, "rest args"));
 
   let configured_creds = settings.credentials.iter().find(|c| opts.path.starts_with(&c.scope));
@@ -67,5 +74,5 @@ pub(crate) async fn handle(opts: RunCommand, settings: wick_settings::Settings,
 
   host.wait_for_done().instrument(span.clone()).await?;
 
-  Ok(())
+  Ok(StructuredOutput::new("", json!({})))
 }
diff --git a/crates/bins/wick/src/commands/serve.rs b/crates/bins/wick/src/commands/serve.rs
index 02a55955f..29c076269 100644
--- a/crates/bins/wick/src/commands/serve.rs
+++ b/crates/bins/wick/src/commands/serve.rs
@@ -1,5 +1,7 @@
 use anyhow::Result;
 use clap::Args;
+use serde_json::json;
+use structured_output::StructuredOutput;
 use wick_component_cli::options::DefaultCliOptions;
 use wick_config::WickConfiguration;
 use wick_host::ComponentHostBuilder;
@@ -7,7 +9,8 @@ use wick_host::ComponentHostBuilder;
 use crate::utils::{merge_config, parse_config_string};
 
 #[derive(Debug, Clone, Args)]
-pub(crate) struct ServeCommand {
+#[group(skip)]
+pub(crate) struct Options {
   #[clap(flatten)]
   pub(crate) cli: DefaultCliOptions,
 
@@ -26,7 +29,11 @@ pub(crate) struct ServeCommand {
   with: Option<String>,
 }
 
-pub(crate) async fn handle(opts: ServeCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
   let fetch_options = wick_config::config::FetchOptions::new()
     .allow_latest(opts.oci.allow_latest)
     .allow_insecure(&opts.oci.insecure_registries);
@@ -59,5 +66,5 @@ pub(crate) async fn handle(opts: ServeCommand, _settings: wick_settings::Setting
   let _ = tokio::signal::ctrl_c().await;
   info!("Ctrl-C received, shutting down");
   host.stop().await;
-  Ok(())
+  Ok(StructuredOutput::new("", json!({})))
 }
diff --git a/crates/bins/wick/src/commands/wasm.rs b/crates/bins/wick/src/commands/wasm.rs
index 8c8d49edd..c71c7e0fb 100644
--- a/crates/bins/wick/src/commands/wasm.rs
+++ b/crates/bins/wick/src/commands/wasm.rs
@@ -7,9 +7,9 @@ pub(crate) mod sign;
 pub(crate) enum SubCommands {
   /// Sign a WebAssembly module.
   #[clap(name = "sign")]
-  Sign(sign::WasmSignCommand),
+  Sign(sign::Options),
 
   /// Inspect the claims of a signed WebAssembly module.
   #[clap(name = "inspect")]
-  Inspect(inspect::WasmInspectCommand),
+  Inspect(inspect::Options),
 }
diff --git a/crates/bins/wick/src/commands/wasm/inspect.rs b/crates/bins/wick/src/commands/wasm/inspect.rs
index c9b3537df..e86e6dcf8 100644
--- a/crates/bins/wick/src/commands/wasm/inspect.rs
+++ b/crates/bins/wick/src/commands/wasm/inspect.rs
@@ -3,10 +3,13 @@ use std::io::Read;
 
 use anyhow::Result;
 use clap::Args;
+use serde_json::json;
+use structured_output::StructuredOutput;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct WasmInspectCommand {
+#[group(skip)]
+pub(crate) struct Options {
   /// WebAssembly module location.
   #[clap(action)]
   pub(crate) module: String,
@@ -14,10 +17,10 @@ pub(crate) struct WasmInspectCommand {
 
 #[allow(clippy::unused_async)]
 pub(crate) async fn handle(
-  opts: WasmInspectCommand,
+  opts: Options,
   _settings: wick_settings::Settings,
   span: tracing::Span,
-) -> Result<()> {
+) -> Result<StructuredOutput> {
   let _enter = span.enter();
   let mut file = File::open(opts.module)?;
   let mut buf = Vec::new();
@@ -26,9 +29,12 @@ pub(crate) async fn handle(
   // Extract will return an error if it encounters an invalid hash in the claims
   let claims = wick_wascap::extract_claims(&buf)?;
   match claims {
-    Some(claims) => println!("{}", serde_json::to_string(&claims)?),
-    None => error!("Could not find any claims in the passed module"),
+    Some(claims) => Ok(StructuredOutput::new(
+      serde_json::to_string(&claims)?,
+      json!({"claims":claims}),
+    )),
+    None => {
+      bail!("Could not find any claims in the passed module")
+    }
   }
-
-  Ok(())
 }
diff --git a/crates/bins/wick/src/commands/wasm/sign.rs b/crates/bins/wick/src/commands/wasm/sign.rs
index 01dfbd66c..85c21b956 100644
--- a/crates/bins/wick/src/commands/wasm/sign.rs
+++ b/crates/bins/wick/src/commands/wasm/sign.rs
@@ -4,6 +4,8 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use clap::Args;
+use serde_json::json;
+use structured_output::StructuredOutput;
 use tracing::Instrument;
 use wick_config::WickConfiguration;
 use wick_wascap::{sign_buffer_with_claims, ClaimsOptions};
@@ -11,7 +13,8 @@ use wick_wascap::{sign_buffer_with_claims, ClaimsOptions};
 use crate::keys::{get_module_keys, GenerateCommon};
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct WasmSignCommand {
+#[group(skip)]
+pub(crate) struct Options {
   /// WebAssembly module location.
   #[clap(action)]
   pub(crate) source: String,
@@ -38,10 +41,10 @@ pub(crate) struct WasmSignCommand {
 
 #[allow(clippy::unused_async)]
 pub(crate) async fn handle(
-  opts: WasmSignCommand,
+  opts: Options,
   _settings: wick_settings::Settings,
   span: tracing::Span,
-) -> Result<()> {
+) -> Result<StructuredOutput> {
   span.in_scope(|| {
     debug!("Signing module");
     debug!("Reading from {}", opts.interface);
@@ -103,14 +106,19 @@ pub(crate) async fn handle(
   span.in_scope(|| debug!("Destination : {}", destination));
 
   let mut outfile = File::create(&destination).unwrap();
+
   span.in_scope(|| match outfile.write(&signed) {
     Ok(_) => {
       info!("Successfully signed {}", destination,);
+      Ok(StructuredOutput::new(
+        format!("Successfully signed: {}", destination),
+        json!({
+          "file": destination
+        }),
+      ))
     }
     Err(e) => {
-      error!("Error signing: {}", e);
+      bail!("Error signing: {}", e)
     }
-  });
-
-  Ok(())
+  })
 }
diff --git a/crates/bins/wick/src/io.rs b/crates/bins/wick/src/io.rs
index bab024a01..9f4ae4540 100644
--- a/crates/bins/wick/src/io.rs
+++ b/crates/bins/wick/src/io.rs
@@ -1,9 +1,13 @@
-use std::path::Path;
+use std::path::{Path, PathBuf};
 
+use serde_json::json;
+use structured_output::StructuredOutput;
 use thiserror::Error;
 
 #[derive(Error, Debug)]
 pub(crate) enum IoError {
+  #[error("{0} would be over-written by this operation. Use --force to over-write.")]
+  WouldOverwrite(String),
   #[error("Could not read '{0}': {1}")]
   Read(String, std::io::Error),
   #[error("Could not write '{0}': {1}")]
@@ -49,3 +53,53 @@ pub(crate) async fn mkdirp(path: impl AsRef<Path> + Send + Sync) -> Result<(), I
 fn path_to_string(path: impl AsRef<Path> + Send + Sync) -> String {
   path.as_ref().to_string_lossy().to_string()
 }
+
+pub(crate) struct File {
+  pub(crate) path: PathBuf,
+  contents: Vec<u8>,
+}
+
+impl File {
+  pub(crate) fn new(path: impl AsRef<Path>, contents: Vec<u8>) -> Self {
+    Self {
+      path: path.as_ref().to_owned(),
+      contents,
+    }
+  }
+}
+
+pub(crate) async fn init_files(files: &[File], dry_run: bool) -> Result<StructuredOutput, IoError> {
+  for file in files {
+    if file.path.exists() {
+      return Err(IoError::WouldOverwrite(path_to_string(&file.path)));
+    }
+  }
+
+  for file in files {
+    info!("Writing file: {}", file.path.display());
+
+    if dry_run {
+      info!(
+        "Dry run: not writing {} bytes to {}",
+        file.contents.len(),
+        file.path.display()
+      );
+    } else {
+      write_bytes(&file.path, &file.contents).await?;
+    }
+  }
+
+  let as_string = format!(
+    "Wrote: {}",
+    files
+      .iter()
+      .map(|f| f.path.display().to_string())
+      .collect::<Vec<_>>()
+      .join(", ")
+  );
+  let as_json = json!({
+    "files": files.iter().map(|f| f.path.display().to_string()).collect::<Vec<_>>(),
+  });
+
+  Ok(StructuredOutput::new(as_string, as_json))
+}
diff --git a/crates/bins/wick/src/main.rs b/crates/bins/wick/src/main.rs
index 92849e4ba..3e6bab893 100644
--- a/crates/bins/wick/src/main.rs
+++ b/crates/bins/wick/src/main.rs
@@ -86,6 +86,8 @@
 // !!END_LINTS
 // Add exceptions here
 
+use std::thread::sleep;
+
 use anyhow::Result;
 #[macro_use]
 extern crate tracing;
@@ -102,9 +104,10 @@ mod options;
 mod panic;
 
 pub(crate) use options::LoggingOptions;
+use structured_output::StructuredOutput;
 
 use self::commands::*;
-use self::options::apply_log_settings;
+use self::options::{apply_log_settings, GlobalOptions};
 
 static BIN_NAME: &str = "wick";
 static BIN_DESC: &str = "wick runtime executable";
@@ -120,10 +123,42 @@ fn main() {
   runtime.shutdown_background();
 
   let code = match result {
-    Ok(_) => 0,
-    Err(e) => {
-      eprintln!("\n{} exited with error: {}", BIN_NAME, e);
-      eprintln!("Run with --info, --debug, or --trace for more information.");
+    Ok((options, output)) => {
+      let (json, code) = if let Some(success) = output.json.as_object().unwrap().get("success") {
+        if success.as_bool().unwrap() {
+          (output.json.clone(), 0)
+        } else {
+          (output.json.clone(), 1)
+        }
+      } else {
+        (serde_json::json!({"success":true,"output":&output.json}), 0)
+      };
+
+      if options.json {
+        println!("{}", serde_json::to_string(&json).unwrap());
+      } else {
+        let output = output.to_string();
+        if !output.is_empty() {
+          println!("{}", output);
+        }
+      }
+      code
+    }
+    Err((options, e)) => {
+      let json = serde_json::json!({"error": e.to_string(),"success":false});
+
+      if options.json {
+        println!("{}", serde_json::to_string(&json).unwrap());
+      } else {
+        // Separate the error from the rest of the output with a few blank lines.
+        for _ in 0..8 {
+          println!();
+          sleep(std::time::Duration::from_millis(20));
+        }
+
+        println!("\n{} exited with error: {}", BIN_NAME, e);
+        println!("Run with --info, --debug, or --trace for more information.");
+      }
       1
     }
   };
@@ -131,13 +166,14 @@ fn main() {
   std::process::exit(code);
 }
 
-async fn async_start() -> Result<()> {
+async fn async_start() -> Result<(GlobalOptions, StructuredOutput), (GlobalOptions, anyhow::Error)> {
   #[cfg(debug_assertions)]
   panic::setup(human_panic::PanicStyle::Debug);
   #[cfg(not(debug_assertions))]
   panic::setup(human_panic::PanicStyle::Human);
 
   let mut cli = Cli::parse();
+  let options = cli.output.clone();
 
   // Do a preliminary logger init to catch any logs from the local settings.
   let log_options: wick_logger::LoggingOptions = cli.logging.name(BIN_NAME).into();
@@ -150,21 +186,28 @@ async fn async_start() -> Result<()> {
   // Initialize the global logger
   let logger = wick_logger::init(&logger_opts);
 
-  let res = tokio::spawn(async_main(cli, settings)).await?;
+  let res = tokio::spawn(async_main(cli, settings)).await;
   tokio::time::sleep(std::time::Duration::from_millis(100)).await;
-  match &res {
-    Ok(_) => {
-      info!("Done");
+  let res = match res {
+    Ok(Ok(output)) => {
+      debug!("Done");
+      Ok((options, output))
     }
-    Err(e) => {
+    Ok(Err(e)) => {
       error!("Error: {}", e);
+      Err((options, anyhow!("{}", e)))
+    }
+    Err(e) => {
+      error!("Internal Error: {}", e);
+      Err((options, anyhow!("{}", e)))
     }
   };
   logger.flush();
+
   res
 }
 
-async fn async_main(cli: Cli, settings: wick_settings::Settings) -> Result<()> {
+async fn async_main(cli: Cli, settings: wick_settings::Settings) -> Result<StructuredOutput> {
   let span = trace_span!(target:"cli","cli");
 
   match cli.command {
@@ -173,7 +216,6 @@ async fn async_main(cli: Cli, settings: wick_settings::Settings) -> Result<()> {
     CliCommand::Run(cmd) => commands::run::handle(cmd, settings, span).await,
     CliCommand::Invoke(cmd) => commands::invoke::handle(cmd, settings, span).await,
     CliCommand::Test(cmd) => commands::test::handle(cmd, settings, span).await,
-    CliCommand::Init(cmd) => commands::init::handle(cmd, settings, span).await,
     CliCommand::Wasm(cmd) => match cmd {
       commands::wasm::SubCommands::Sign(cmd) => commands::wasm::sign::handle(cmd, settings, span).await,
       commands::wasm::SubCommands::Inspect(cmd) => commands::wasm::inspect::handle(cmd, settings, span).await,
@@ -194,6 +236,15 @@ async fn async_main(cli: Cli, settings: wick_settings::Settings) -> Result<()> {
       commands::rpc::SubCommands::Stats(cmd) => commands::rpc::stats::handle(cmd, settings, span).await,
     },
     CliCommand::Query(cmd) => commands::query::handle(cmd, settings, span).await,
+    CliCommand::New(cmd) => match cmd {
+      new::SubCommands::Component(cmd) => match cmd {
+        new::component::SubCommands::Http(cmd) => new::component::http::handle(cmd, settings, span).await,
+        new::component::SubCommands::Composite(cmd) => new::component::composite::handle(cmd, settings, span).await,
+        new::component::SubCommands::Sql(cmd) => new::component::sql::handle(cmd, settings, span).await,
+        new::component::SubCommands::WasmRS(cmd) => new::component::wasmrs::handle(cmd, settings, span).await,
+      },
+      new::SubCommands::App(cmd) => commands::new::app::handle(cmd, settings, span).await,
+    },
   }
 }
 
diff --git a/crates/bins/wick/src/options.rs b/crates/bins/wick/src/options.rs
index 6c402883c..cc2dab0f6 100644
--- a/crates/bins/wick/src/options.rs
+++ b/crates/bins/wick/src/options.rs
@@ -1,6 +1,14 @@
 use clap::Args;
 use wick_settings::Credential;
 
+#[derive(Args, Debug, Default, Clone)]
+/// Global output options.
+pub(crate) struct GlobalOptions {
+  /// Print CLI output as JSON.
+  #[clap(long = "json", short = 'j', global = true, action)]
+  pub(crate) json: bool,
+}
+
 #[derive(Args, Debug, Default, Clone)]
 /// Logging options that can be used directly or via [Args].
 pub(crate) struct LoggingOptions {
diff --git a/crates/bins/wick/src/utils.rs b/crates/bins/wick/src/utils.rs
index 88a6fb87f..e7d9174c9 100644
--- a/crates/bins/wick/src/utils.rs
+++ b/crates/bins/wick/src/utils.rs
@@ -12,7 +12,8 @@ pub(crate) fn merge_config(
   server_cli_opts: Option<DefaultCliOptions>,
 ) -> ComponentConfiguration {
   let mut merged_manifest = def.clone();
-  let host_config = merged_manifest.host_mut();
+  let mut host_config = merged_manifest.host().cloned().unwrap_or_default();
+
   host_config.set_allow_latest(local_cli_opts.allow_latest || host_config.allow_latest());
   host_config.set_insecure_registries(
     vec![
@@ -60,6 +61,7 @@ pub(crate) fn merge_config(
       );
     };
   }
+  merged_manifest.set_host(Some(host_config));
 
   merged_manifest
 }
@@ -71,6 +73,7 @@ fn log_override<T: std::fmt::Debug>(field: &str, from: &mut T, to: T) {
 
 pub(crate) async fn print_stream_json(
   mut stream: PacketStream,
+
   filter: &[String],
   _terse: bool,
   raw: bool,
@@ -87,7 +90,8 @@ pub(crate) async fn print_stream_json(
       tracing::debug!(port = %packet.port(), "filtering out");
       continue;
     }
-    println!("{}", packet.to_json());
+    let json = packet.to_json();
+    println!("{}", json);
   }
   trace!("stream complete");
   Ok(())
diff --git a/crates/wick/flow-expression-parser/src/ast.rs b/crates/wick/flow-expression-parser/src/ast.rs
index 0f4965897..ecd27822f 100644
--- a/crates/wick/flow-expression-parser/src/ast.rs
+++ b/crates/wick/flow-expression-parser/src/ast.rs
@@ -136,6 +136,12 @@ pub struct ConnectionExpression {
   to: ConnectionTargetExpression,
 }
 
+impl std::fmt::Display for ConnectionExpression {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    write!(f, "{} -> {}", self.from, self.to)
+  }
+}
+
 impl ConnectionExpression {
   /// Create a new [ConnectionExpression] from two [ConnectionTargetExpression]s.
   pub fn new(mut from: ConnectionTargetExpression, mut to: ConnectionTargetExpression) -> Self {
@@ -325,6 +331,18 @@ pub struct ConnectionTargetExpression {
   data: Option<HashMap<String, Value>>,
 }
 
+impl std::fmt::Display for ConnectionTargetExpression {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    #[allow(clippy::option_if_let_else)]
+    if let Some(_data) = &self.data {
+      // TODO: Implement data syntax. There's no way of stringifying this yet.
+      Err(std::fmt::Error::default())
+    } else {
+      write!(f, "{}.{}", self.instance, self.port)
+    }
+  }
+}
+
 impl ConnectionTargetExpression {
   /// Create a new [ConnectionTargetExpression]
   pub fn new(instance: InstanceTarget, port: impl Into<InstancePort>) -> Self {
diff --git a/crates/wick/flow-expression-parser/src/parse/v1.rs b/crates/wick/flow-expression-parser/src/parse/v1.rs
index 56aaa5f11..2ff166a65 100644
--- a/crates/wick/flow-expression-parser/src/parse/v1.rs
+++ b/crates/wick/flow-expression-parser/src/parse/v1.rs
@@ -152,7 +152,17 @@ fn connection_expression(input: &str) -> IResult<&str, ConnectionExpression> {
       )))
     }
     // Otherwise we've got ports so let's use em.
-    (from_port, to) => (i, ((from.0, from_port.unwrap()), (to.0, to.1.unwrap()))),
+    (from_port, to) => (
+      i,
+      (
+        (from.0, from_port.unwrap()),
+        (
+          to.0,
+          to.1
+            .ok_or_else(|| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::AlphaNumeric)))?,
+        ),
+      ),
+    ),
   };
   Ok((
     i,
diff --git a/crates/wick/wick-component-wasm/src/wasm_host.rs b/crates/wick/wick-component-wasm/src/wasm_host.rs
index 1e1e802d7..be2952a0c 100644
--- a/crates/wick/wick-component-wasm/src/wasm_host.rs
+++ b/crates/wick/wick-component-wasm/src/wasm_host.rs
@@ -16,7 +16,7 @@ use wick_interface_types::ComponentSignature;
 use wick_packet::{
   from_raw_wasmrs,
   from_wasmrs,
-  into_wasmrs,
+  packetstream_to_wasmrs,
   ComponentReference,
   ContextTransport,
   Entity,
@@ -175,7 +175,7 @@ impl WasmHost {
 
     invocation.packets.set_context(config.unwrap_or_default(), inherent);
 
-    let s = into_wasmrs(index, invocation.packets);
+    let s = packetstream_to_wasmrs(index, invocation.packets);
     let out = ctx.request_channel(Box::pin(s));
     trace!(
       component = component_name,
@@ -291,7 +291,7 @@ fn make_host_callback(
         }
       }
     });
-    Ok(into_wasmrs(0, PacketStream::new(Box::new(rx))))
+    Ok(packetstream_to_wasmrs(0, PacketStream::new(Box::new(rx))))
   };
   Box::new(func)
 }
diff --git a/crates/wick/wick-component/src/lib.rs b/crates/wick/wick-component/src/lib.rs
index 00c9b78c4..4774d4713 100644
--- a/crates/wick/wick-component/src/lib.rs
+++ b/crates/wick/wick-component/src/lib.rs
@@ -96,19 +96,47 @@ pub use {flow_component, paste, wasmrs, wasmrs_codec, wasmrs_runtime as runtime,
 
 pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
 
+/// Create a stream of `Result<T, BoxError>` that yields one value and ends.
+///
+/// # Example
+///
+/// ```
+/// # use wick_component::prelude::*;
+///
+/// let mut stream = once(42);
+/// assert_eq!(stream.next().await, Some(Ok(42)));
+/// assert_eq!(stream.next().await, None);
+/// ```
+///
 pub fn once<T>(value: T) -> impl Stream<Item = Result<T, BoxError>> {
   tokio_stream::once(Ok(value))
 }
 
-pub mod prelude {
-  pub use serde_json::Value;
-
-  pub use crate::{iter, once, propagate_if_error, BoxError, Stream, StreamExt};
-}
-
+/// Create a stream of `Result<T, BoxError>` from an iterator of type T.
+///
+/// This is a convenience function for creating a Result/TryStream from an iterator of `Ok` values.
+///
+/// # Example
+///
+/// ```
+/// # use wick_component::prelude::*;
+///
+/// let mut stream = iter(vec![1, 2, 3]);
+/// assert_eq!(stream.next().await, Some(Ok(1)));
+/// assert_eq!(stream.next().await, Some(Ok(2)));
+/// assert_eq!(stream.next().await, Some(Ok(3)));
+/// assert_eq!(stream.next().await, None);
+/// ```
+///
 pub fn iter<I, O>(i: I) -> impl Stream<Item = Result<O, BoxError>>
 where
   I: IntoIterator<Item = O>,
 {
   tokio_stream::iter(i.into_iter().map(|i| Ok(i)))
 }
+
+pub mod prelude {
+  pub use serde_json::Value;
+
+  pub use crate::{iter, once, propagate_if_error, BoxError, Stream, StreamExt};
+}
diff --git a/crates/wick/wick-component/src/macros.rs b/crates/wick/wick-component/src/macros.rs
index 5e6460609..71c58732d 100644
--- a/crates/wick/wick-component/src/macros.rs
+++ b/crates/wick/wick-component/src/macros.rs
@@ -188,6 +188,27 @@ macro_rules! payload_fan_out {
     };
 }
 
+/// Unwrap a [Result] value to its [Result::Ok] value or propagate the error to the downstream inputs and
+/// short circuit the logic.
+///
+///
+/// Takes a [Result] value, a generated component's Outputs, and an action to perform on error (`break`, `continue`, or `return expr`).
+///
+/// If the [Result] is [Ok], the [Result::Ok] value is returned.
+///
+/// If the [Result] is [Err], the error is propagated to the passed outputs,
+/// and the logic is short circuited with the passed action.
+///
+/// # Example
+///
+/// ```
+/// let results: Vec<Result<i32, ()>> = vec![Ok(1),Err(()),Ok(2)];
+/// while let Some(result) = results.next() {
+///   let value = propagate_if_error!(result, outputs, continue);
+///   println!("{}", value);
+/// }
+/// ```
+///
 #[macro_export]
 macro_rules! propagate_if_error {
   ($result:expr, $outputs:ident, continue) => {
diff --git a/crates/wick/wick-config/definitions/v1/manifest.apex b/crates/wick/wick-config/definitions/v1/manifest.apex
index 690715a94..ccbfebb18 100644
--- a/crates/wick/wick-config/definitions/v1/manifest.apex
+++ b/crates/wick/wick-config/definitions/v1/manifest.apex
@@ -530,15 +530,15 @@ type CompositeOperationDefinition {
 }
 
 "A flow operation, i.e. a connection from one operation's outputs to another's inputs."
-union FlowExpression @shortform = ConnectionDefinition | BlockExpression
+union FlowExpression @shortform @untagged = ConnectionDefinition | BlockExpression
 
 "A list of FlowExpressions. Typically used only when expanding a shortform `FlowExpression` into multiple `FlowExpression`s."
-type BlockExpression {
+type BlockExpression  {
   expressions: [FlowExpression] @required
 }
 
 "A connection between Operations and their ports. This can be specified in short-form syntax."
-type ConnectionDefinition {
+type ConnectionDefinition @into_string {
   "An upstream operation's output."
   from: ConnectionTargetDefinition @required
 
@@ -583,7 +583,7 @@ type Field {
   description: string?,
 }
 
-union TypeSignature @shortform @shortformonly = I8 | I16 | I32 | I64 | U8 | U16 | U32 | U64 | F32 | F64 | Bool | StringType | Optional | Datetime | Bytes | Custom | List | Map | Object
+union TypeSignature @into_string @shortform @shortformonly = I8 | I16 | I32 | I64 | U8 | U16 | U32 | U64 | F32 | F64 | Bool | StringType | Optional | Datetime | Bytes | Custom | List | Map | Object
 
 type I8 @nameonly("i8") {}
 type I16 @nameonly("i16") {}
@@ -684,10 +684,10 @@ type TestDefinition {
   with: {string: any}?
 
   "The inputs to the test."
-  input: [PacketData]
+  inputs: [PacketData] @alias("input")
 
   "The expected outputs of the operation."
-  output: [PacketData]
+  outputs: [PacketData] @alias("output")
 }
 
 "Data inherent to all invocations."
diff --git a/crates/wick/wick-config/definitions/v1/templates/rust/partials/type-definition.hbs b/crates/wick/wick-config/definitions/v1/templates/rust/partials/type-definition.hbs
index e911d4bf9..268b26cd4 100644
--- a/crates/wick/wick-config/definitions/v1/templates/rust/partials/type-definition.hbs
+++ b/crates/wick/wick-config/definitions/v1/templates/rust/partials/type-definition.hbs
@@ -14,12 +14,15 @@ pub(crate) fn {{upperCase (snakeCase ../name.value)}}_{{upperCase name.value}}()
 {{else}}
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 {{/withAnnotation}}
+{{#withAnnotation "into_string"}}
+#[serde(into = "String")]
+{{/withAnnotation}}
 #[serde(deny_unknown_fields)]
 {{#if description}}
 /// {{{description.value}}}
 {{/if}}
 {{#withAnnotation "nameonly"}}
-pub(crate) struct {{../name.value}}();
+pub(crate) struct {{../name.value}};
 {{else}}
 pub(crate) struct {{name.value}} {
   {{#each fields}}
diff --git a/crates/wick/wick-config/definitions/v1/templates/rust/partials/union-definition.hbs b/crates/wick/wick-config/definitions/v1/templates/rust/partials/union-definition.hbs
index 5945cdec5..2b4ddd94a 100644
--- a/crates/wick/wick-config/definitions/v1/templates/rust/partials/union-definition.hbs
+++ b/crates/wick/wick-config/definitions/v1/templates/rust/partials/union-definition.hbs
@@ -6,6 +6,9 @@
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 {{/ifCond}}
 #[serde(deny_unknown_fields)]
+{{#withAnnotation "into_string"}}
+#[serde(into = "String")]
+{{/withAnnotation}}
 {{#withAnnotation "untagged"}}
 #[serde(untagged)]
 {{else}}
diff --git a/crates/wick/wick-config/docs/v1.md b/crates/wick/wick-config/docs/v1.md
index ce9cc4e3e..7764c9446 100644
--- a/crates/wick/wick-config/docs/v1.md
+++ b/crates/wick/wick-config/docs/v1.md
@@ -1368,8 +1368,8 @@ Any one of the following types:
 | `operation` | <code>`string`</code> |The operaton to test.|Yes||
 | `inherent` | <code>[`InherentData`](#inherentdata)</code> |Inherent data to use for the test.|||
 | `with` | <code>`{` `string` `: ` `any` `}`</code> |The configuration for the operation, if any.|||
-| `input` | <code>[`PacketData`](#packetdata)[]</code> |The inputs to the test.|||
-| `output` | <code>[`PacketData`](#packetdata)[]</code> |The expected outputs of the operation.|||
+| `inputs` | <code>[`PacketData`](#packetdata)[]</code> |The inputs to the test.|||
+| `outputs` | <code>[`PacketData`](#packetdata)[]</code> |The expected outputs of the operation.|||
 
 
 
diff --git a/crates/wick/wick-config/json-schema/manifest.json b/crates/wick/wick-config/json-schema/manifest.json
index 8549300b1..7ed7bd266 100644
--- a/crates/wick/wick-config/json-schema/manifest.json
+++ b/crates/wick/wick-config/json-schema/manifest.json
@@ -2261,14 +2261,14 @@
               "[a-zA-Z0-9][a-zA-Z0-9_]*": {}
             }
           },
-          "input": {
+          "inputs": {
             "description": "The inputs to the test.",
             "type": "array",
             "items": {
               "$ref": "#/$defs/v1/PacketData"
             }
           },
-          "output": {
+          "outputs": {
             "description": "The expected outputs of the operation.",
             "type": "array",
             "items": {
diff --git a/crates/wick/wick-config/json-schema/v1/manifest.json b/crates/wick/wick-config/json-schema/v1/manifest.json
index f01abdbd7..155709e52 100644
--- a/crates/wick/wick-config/json-schema/v1/manifest.json
+++ b/crates/wick/wick-config/json-schema/v1/manifest.json
@@ -1871,7 +1871,7 @@
             "[a-zA-Z0-9][a-zA-Z0-9_]*": {}
           }
         },
-        "input": {
+        "inputs": {
           "description": "The inputs to the test.",
 
           "type": "array",
@@ -1879,7 +1879,7 @@
             "$ref": "#/$defs/v1/PacketData"
           }
         },
-        "output": {
+        "outputs": {
           "description": "The expected outputs of the operation.",
 
           "type": "array",
diff --git a/crates/wick/wick-config/src/config.rs b/crates/wick/wick-config/src/config.rs
index e1cb5f5e8..181958597 100644
--- a/crates/wick/wick-config/src/config.rs
+++ b/crates/wick/wick-config/src/config.rs
@@ -115,6 +115,17 @@ impl WickConfiguration {
     resolve_configuration(src, source)
   }
 
+  pub fn into_v1_yaml(self) -> Result<String, Error> {
+    let v1_manifest = match self {
+      WickConfiguration::Component(c) => v1::WickConfig::ComponentConfiguration(c.try_into()?),
+      WickConfiguration::App(c) => v1::WickConfig::AppConfiguration(c.try_into()?),
+      WickConfiguration::Types(c) => v1::WickConfig::TypesConfiguration(c.try_into()?),
+      WickConfiguration::Tests(c) => v1::WickConfig::TestConfiguration(c.try_into()?),
+    };
+
+    Ok(serde_yaml::to_string(&v1_manifest).unwrap())
+  }
+
   #[must_use]
   pub fn name(&self) -> Option<&str> {
     match self {
@@ -265,7 +276,7 @@ fn resolve_configuration(src: &str, source: &Option<PathBuf>) -> Result<WickConf
   let version = if raw_kind.is_some() {
     1
   } else {
-    let raw_version = raw_version.ok_or(Error::NoFormat)?;
+    let raw_version = raw_version.ok_or(Error::NoFormat(source.clone()))?;
     raw_version
       .as_i64()
       .unwrap_or_else(|| -> i64 { raw_version.as_str().and_then(|s| s.parse::<i64>().ok()).unwrap_or(-1) })
@@ -286,7 +297,7 @@ fn resolve_configuration(src: &str, source: &Option<PathBuf>) -> Result<WickConf
       }
       Ok(config)
     }
-    -1 => Err(Error::NoFormat),
+    -1 => Err(Error::NoFormat(source.clone())),
     _ => Err(Error::VersionError(version.to_string())),
   }
 }
diff --git a/crates/wick/wick-config/src/config/app_config.rs b/crates/wick/wick-config/src/config/app_config.rs
index 489200913..ff067c4bb 100644
--- a/crates/wick/wick-config/src/config/app_config.rs
+++ b/crates/wick/wick-config/src/config/app_config.rs
@@ -18,7 +18,7 @@ use crate::utils::RwOption;
 use crate::{config, v1, Resolver, Result};
 
 #[derive(Debug, Clone, Default, Builder, derive_asset_container::AssetManager, property::Property)]
-#[property(get(public), set(disable), mut(disable))]
+#[property(get(public), set(public), mut(public, suffix = "_mut"))]
 #[asset(asset(AssetReference))]
 #[builder(setter(into))]
 #[must_use]
@@ -31,7 +31,6 @@ pub struct AppConfiguration {
   #[property(skip)]
   pub(crate) source: Option<PathBuf>,
   #[builder(setter(strip_option), default)]
-  #[property(skip)]
   pub(crate) metadata: Option<config::Metadata>,
   #[builder(default)]
   pub(crate) import: HashMap<String, ImportBinding>,
@@ -64,11 +63,6 @@ impl AppConfiguration {
     .await
   }
 
-  /// Set the name of the component
-  pub fn set_name(&mut self, name: String) {
-    self.name = name;
-  }
-
   /// Get the package files
   pub fn package_files(&self) -> Assets<AssetReference> {
     self.package.assets()
@@ -117,12 +111,6 @@ impl AppConfiguration {
     self.metadata.as_ref().map(|m| m.version.as_str())
   }
 
-  /// Return the metadata of the component.
-  #[must_use]
-  pub fn metadata(&self) -> Option<&config::Metadata> {
-    self.metadata.as_ref()
-  }
-
   #[must_use]
   /// Get the application's imports.
   pub fn imports(&self) -> &HashMap<String, ImportBinding> {
diff --git a/crates/wick/wick-config/src/config/app_config/triggers/cli.rs b/crates/wick/wick-config/src/config/app_config/triggers/cli.rs
index 0998db8fc..c3e71073e 100644
--- a/crates/wick/wick-config/src/config/app_config/triggers/cli.rs
+++ b/crates/wick/wick-config/src/config/app_config/triggers/cli.rs
@@ -2,7 +2,8 @@ use wick_asset_reference::AssetReference;
 
 use crate::config::ComponentOperationExpression;
 
-#[derive(Debug, Clone, PartialEq, derive_asset_container::AssetManager, property::Property)]
+#[derive(Debug, Clone, PartialEq, derive_asset_container::AssetManager, property::Property, Builder)]
+#[builder(setter(into))]
 #[asset(asset(AssetReference))]
 #[property(get(public), set(private), mut(disable))]
 
diff --git a/crates/wick/wick-config/src/config/app_config/triggers/http.rs b/crates/wick/wick-config/src/config/app_config/triggers/http.rs
index 0f8791628..f12aa2318 100644
--- a/crates/wick/wick-config/src/config/app_config/triggers/http.rs
+++ b/crates/wick/wick-config/src/config/app_config/triggers/http.rs
@@ -12,7 +12,8 @@ mod raw_router;
 mod rest_router;
 mod static_router;
 
-#[derive(Debug, Clone, derive_asset_container::AssetManager, property::Property)]
+#[derive(Debug, Clone, derive_asset_container::AssetManager, property::Property, Builder)]
+#[builder(setter(into))]
 #[property(get(public), set(private), mut(disable))]
 #[asset(asset(AssetReference))]
 #[must_use]
diff --git a/crates/wick/wick-config/src/config/app_config/triggers/time.rs b/crates/wick/wick-config/src/config/app_config/triggers/time.rs
index 9343d55d0..0e879aa55 100644
--- a/crates/wick/wick-config/src/config/app_config/triggers/time.rs
+++ b/crates/wick/wick-config/src/config/app_config/triggers/time.rs
@@ -3,7 +3,8 @@ use wick_asset_reference::AssetReference;
 use super::OperationInputConfig;
 use crate::config::ComponentOperationExpression;
 
-#[derive(Debug, Clone, PartialEq, derive_asset_container::AssetManager, property::Property)]
+#[derive(Debug, Clone, PartialEq, derive_asset_container::AssetManager, property::Property, Builder)]
+#[builder(setter(into))]
 #[property(get(public), set(private), mut(disable))]
 #[asset(asset(AssetReference))]
 /// Normalized representation of a Time trigger configuration.
diff --git a/crates/wick/wick-config/src/config/common/metadata.rs b/crates/wick/wick-config/src/config/common/metadata.rs
index 589e9dbaa..8c1a2a010 100644
--- a/crates/wick/wick-config/src/config/common/metadata.rs
+++ b/crates/wick/wick-config/src/config/common/metadata.rs
@@ -2,6 +2,7 @@ use crate::config::AssetReference;
 
 #[derive(Debug, Default, Builder, Clone, PartialEq, derive_asset_container::AssetManager, property::Property)]
 #[property(get(public), set(private), mut(disable))]
+#[builder(setter(into))]
 #[asset(asset(AssetReference))]
 /// Metadata for the component or application.
 pub struct Metadata {
diff --git a/crates/wick/wick-config/src/config/common/operation_definition.rs b/crates/wick/wick-config/src/config/common/operation_definition.rs
index 01ff54363..862f3454f 100644
--- a/crates/wick/wick-config/src/config/common/operation_definition.rs
+++ b/crates/wick/wick-config/src/config/common/operation_definition.rs
@@ -2,16 +2,20 @@ use wick_interface_types::Field;
 
 #[derive(Debug, Builder, Clone, property::Property)]
 #[property(get(public), set(private), mut(disable))]
+#[builder(setter(into))]
 pub struct OperationSignature {
   /// The name of the schematic.
   pub(crate) name: String,
 
   /// Any configuration required for the component to operate.
+  #[builder(default)]
   pub(crate) config: Vec<Field>,
 
   /// A list of the input types for the operation.
+  #[builder(default)]
   pub(crate) inputs: Vec<Field>,
 
   /// A list of the input types for the operation.
+  #[builder(default)]
   pub(crate) outputs: Vec<Field>,
 }
diff --git a/crates/wick/wick-config/src/config/component_config.rs b/crates/wick/wick-config/src/config/component_config.rs
index 5b31d45c5..63d5f4a2b 100644
--- a/crates/wick/wick-config/src/config/component_config.rs
+++ b/crates/wick/wick-config/src/config/component_config.rs
@@ -20,7 +20,7 @@ use crate::{config, v1, Error, Resolver, Result};
 
 #[derive(Debug, Default, Clone, Builder, derive_asset_container::AssetManager, property::Property)]
 #[builder(derive(Debug), setter(into))]
-#[property(get(public), set(disable), mut(disable))]
+#[property(get(public), set(public), mut(public, suffix = "_mut"))]
 #[asset(asset(AssetReference))]
 #[must_use]
 /// The internal representation of a Wick manifest.
@@ -53,7 +53,6 @@ pub struct ComponentConfiguration {
   pub(crate) tests: Vec<TestConfiguration>,
   #[asset(skip)]
   #[builder(default)]
-  #[property(skip)]
   pub(crate) metadata: Option<config::Metadata>,
   #[asset(skip)]
   #[builder(setter(skip))]
@@ -88,11 +87,6 @@ impl ComponentConfiguration {
     }
   }
 
-  /// Set the name of the component
-  pub fn set_name(&mut self, name: String) {
-    self.name = Some(name);
-  }
-
   /// Get the package files
   #[must_use]
   pub fn package_files(&self) -> Option<Assets<AssetReference>> {
@@ -136,20 +130,6 @@ impl ComponentConfiguration {
     self.import.get(name)
   }
 
-  /// Retrieve a mutable host configuration. This will create a default host configuration if one
-  /// does not exist.
-  pub fn host_mut(&mut self) -> &mut config::HostConfig {
-    if self.host.is_none() {
-      self.host = Some(config::HostConfig::default());
-    }
-    self.host.as_mut().unwrap()
-  }
-
-  /// Get the configuration related to the specific [ComponentKind].
-  pub fn component_mut(&mut self) -> &mut ComponentImplementation {
-    &mut self.component
-  }
-
   /// Get the kind of this component implementation.
   pub fn kind(&self) -> ComponentKind {
     self.component.kind()
@@ -173,12 +153,6 @@ impl ComponentConfiguration {
     self.metadata.as_ref().map(|m| m.version.as_str())
   }
 
-  /// Return the metadata of the component.
-  #[must_use]
-  pub fn metadata(&self) -> Option<&config::Metadata> {
-    self.metadata.as_ref()
-  }
-
   /// Return the underlying version of the source manifest.
   #[must_use]
   pub fn source(&self) -> Option<&Path> {
@@ -199,6 +173,10 @@ impl ComponentConfiguration {
     )
   }
 
+  pub fn types_mut(&mut self) -> &mut Vec<TypeDefinition> {
+    &mut self.types
+  }
+
   /// Fetch/cache anything critical to the first use of this configuration.
   pub(crate) async fn setup_cache(&self, options: FetchOptions) -> Result<()> {
     setup_cache(
diff --git a/crates/wick/wick-config/src/config/component_config/composite.rs b/crates/wick/wick-config/src/config/component_config/composite.rs
index 56d365907..9264dcd30 100644
--- a/crates/wick/wick-config/src/config/component_config/composite.rs
+++ b/crates/wick/wick-config/src/config/component_config/composite.rs
@@ -6,28 +6,25 @@ use wick_packet::GenericConfig;
 
 use crate::config::{self};
 
-#[derive(Debug, Default, Clone, derive_asset_container::AssetManager, property::Property)]
-#[property(get(public), set(private), mut(disable))]
+#[derive(Debug, Default, Clone, derive_asset_container::AssetManager, Builder, property::Property)]
+#[property(get(public), set(public), mut(public, suffix = "_mut"))]
+#[builder(setter(into))]
 #[asset(asset(crate::config::AssetReference))]
 #[must_use]
 /// The internal representation of a Wick manifest.
 pub struct CompositeComponentImplementation {
   /// The configuration for the component.
   #[asset(skip)]
+  #[builder(default)]
   pub(crate) config: Vec<Field>,
 
   /// The operations defined by the component.
   #[asset(skip)]
+  #[builder(default)]
   pub(crate) operations: HashMap<String, FlowOperation>,
 }
 
 impl CompositeComponentImplementation {
-  #[must_use]
-  /// Get a map of [FlowOperation]s from the [CompositeComponentConfiguration]
-  pub fn operations_mut(&mut self) -> &mut HashMap<String, FlowOperation> {
-    &mut self.operations
-  }
-
   /// Get a [FlowOperation] by name.
   #[must_use]
   pub fn flow(&self, name: &str) -> Option<&FlowOperation> {
@@ -53,6 +50,7 @@ impl From<FlowOperation> for wick_interface_types::OperationSignature {
 
 #[derive(Debug, Clone, Builder, Default, property::Property)]
 #[property(get(public), set(private), mut(public, suffix = "_mut"))]
+#[builder(setter(into))]
 /// The SchematicDefinition struct is a normalized representation of a Wick [SchematicManifest].
 /// It handles the job of translating manifest versions into a consistent data structure.
 #[must_use]
@@ -61,21 +59,27 @@ pub struct FlowOperation {
   pub(crate) name: String,
 
   /// A list of the input types for the operation.
+  #[builder(default)]
   pub(crate) inputs: Vec<Field>,
 
   /// A list of the input types for the operation.
+  #[builder(default)]
   pub(crate) outputs: Vec<Field>,
 
   /// Any configuration required for the component to operate.
+  #[builder(default)]
   pub(crate) config: Vec<Field>,
 
   /// A mapping of instance names to the components they refer to.
+  #[builder(default)]
   pub(crate) instances: HashMap<String, InstanceReference>,
 
   /// A list of connections from and to ports on instances defined in the instance map.
+  #[builder(default)]
   pub(crate) expressions: Vec<ast::FlowExpression>,
 
   /// Additional flows scoped to this operation.
+  #[builder(default)]
   pub(crate) flows: Vec<FlowOperation>,
 }
 
diff --git a/crates/wick/wick-config/src/config/component_config/wasm.rs b/crates/wick/wick-config/src/config/component_config/wasm.rs
index f3263607c..60513a05d 100644
--- a/crates/wick/wick-config/src/config/component_config/wasm.rs
+++ b/crates/wick/wick-config/src/config/component_config/wasm.rs
@@ -4,14 +4,16 @@ use wick_interface_types::Field;
 
 use crate::config::{self, OperationSignature};
 
-#[derive(Debug, Clone, derive_asset_container::AssetManager, property::Property)]
-#[property(get(public), set(private), mut(disable))]
+#[derive(Debug, Clone, derive_asset_container::AssetManager, Builder, property::Property)]
+#[property(get(public), set(public), mut(public, suffix = "_mut"))]
+#[builder(setter(into))]
 #[asset(asset(config::AssetReference))]
 #[must_use]
 /// The internal representation of a Wick manifest.
 pub struct WasmComponentImplementation {
   /// The configuration for the component.
   #[asset(skip)]
+  #[builder(default)]
   pub(crate) config: Vec<Field>,
 
   /// The location of the component.
@@ -19,6 +21,7 @@ pub struct WasmComponentImplementation {
 
   /// The operations defined by the component.
   #[asset(skip)]
+  #[builder(default)]
   pub(crate) operations: HashMap<String, OperationSignature>,
 }
 
diff --git a/crates/wick/wick-config/src/config/components/http_client.rs b/crates/wick/wick-config/src/config/components/http_client.rs
index 73a0caed6..858e661ed 100644
--- a/crates/wick/wick-config/src/config/components/http_client.rs
+++ b/crates/wick/wick-config/src/config/components/http_client.rs
@@ -3,7 +3,7 @@ use std::collections::HashMap;
 use crate::config;
 
 #[derive(Debug, Clone, Builder, PartialEq, derive_asset_container::AssetManager, property::Property)]
-#[property(get(public), set(private), mut(public, suffix = "_mut"))]
+#[property(get(public), set(public), mut(public, suffix = "_mut"))]
 #[asset(asset(config::AssetReference))]
 #[builder(setter(into))]
 #[must_use]
diff --git a/crates/wick/wick-config/src/config/components/sql.rs b/crates/wick/wick-config/src/config/components/sql.rs
index 1c4898f24..675da1fab 100644
--- a/crates/wick/wick-config/src/config/components/sql.rs
+++ b/crates/wick/wick-config/src/config/components/sql.rs
@@ -1,7 +1,7 @@
 use crate::config;
 
 #[derive(Debug, Clone, Builder, PartialEq, derive_asset_container::AssetManager, property::Property)]
-#[property(get(public), set(private), mut(public, suffix = "_mut"))]
+#[property(get(public), set(public), mut(public, suffix = "_mut"))]
 #[asset(asset(config::AssetReference))]
 #[builder(setter(into))]
 /// A component made out of other components
@@ -12,6 +12,7 @@ pub struct SqlComponentConfig {
 
   /// Whether or not to use TLS.
   #[asset(skip)]
+  #[builder(default)]
   pub(crate) tls: bool,
 
   /// A list of operations to expose on this component.
@@ -49,10 +50,12 @@ pub struct SqlOperationDefinition {
 
   /// Types of the inputs to the operation.
   #[asset(skip)]
+  #[builder(default)]
   pub(crate) inputs: Vec<wick_interface_types::Field>,
 
   /// Types of the outputs to the operation.
   #[asset(skip)]
+  #[builder(default)]
   pub(crate) outputs: Vec<wick_interface_types::Field>,
 
   /// The configuration the operation needs.
@@ -66,6 +69,7 @@ pub struct SqlOperationDefinition {
 
   /// The arguments to the query, defined as a list of input names.
   #[asset(skip)]
+  #[builder(default)]
   pub(crate) arguments: Vec<String>,
 }
 
diff --git a/crates/wick/wick-config/src/config/types_config.rs b/crates/wick/wick-config/src/config/types_config.rs
index c7b2b6671..7e70e4479 100644
--- a/crates/wick/wick-config/src/config/types_config.rs
+++ b/crates/wick/wick-config/src/config/types_config.rs
@@ -10,7 +10,7 @@ use super::OperationSignature;
 use crate::config;
 
 #[derive(Debug, Clone, Builder, derive_asset_container::AssetManager, property::Property)]
-#[property(get(public), set(private), mut(disable))]
+#[property(get(public), set(public), mut(public, suffix = "_mut"))]
 #[asset(asset(AssetReference))]
 #[must_use]
 pub struct TypesConfiguration {
@@ -22,7 +22,6 @@ pub struct TypesConfiguration {
   pub(crate) source: Option<PathBuf>,
   #[asset(skip)]
   #[builder(default)]
-  #[property(skip)]
   pub(crate) metadata: Option<config::Metadata>,
   #[asset(skip)]
   pub(crate) types: Vec<TypeDefinition>,
@@ -63,12 +62,6 @@ impl TypesConfiguration {
     self.metadata.as_ref().map(|m| m.version.as_str())
   }
 
-  /// Return the metadata of the component.
-  #[must_use]
-  pub fn metadata(&self) -> Option<&config::Metadata> {
-    self.metadata.as_ref()
-  }
-
   /// Get the package files
   #[must_use]
   pub fn package_files(&self) -> Option<Assets<AssetReference>> {
diff --git a/crates/wick/wick-config/src/error.rs b/crates/wick/wick-config/src/error.rs
index f0b66f2ba..60c4e2ece 100644
--- a/crates/wick/wick-config/src/error.rs
+++ b/crates/wick/wick-config/src/error.rs
@@ -1,3 +1,5 @@
+use std::path::PathBuf;
+
 use thiserror::Error;
 
 use crate::config::{self};
@@ -28,8 +30,8 @@ pub enum ManifestError {
   TypeNotFound(String),
 
   /// No format version or kind found in the parsed manifest.
-  #[error("Manifest needs a format version (v0) or kind (v1+)")]
-  NoFormat,
+  #[error("Manifest {} needs a format version (v0) or kind (v1+)", .0.as_ref().map_or("<raw>".to_owned(), |v|v.display().to_string()))]
+  NoFormat(Option<PathBuf>),
 
   /// Manifest not found at the specified path.
   #[error("File not found {0}")]
@@ -49,7 +51,7 @@ pub enum ManifestError {
 
   /// Error deserializing YAML manifest.
   #[error("Could not parse manifest {} as YAML: {1} at line {}, column {}", .0.as_ref().map_or("<raw>".to_owned(), |v|v.display().to_string()), .2.as_ref().map_or("unknown".to_owned(),|l|l.line().to_string()), .2.as_ref().map_or("unknown".to_owned(),|l|l.column().to_string()))]
-  YamlError(Option<std::path::PathBuf>, String, Option<serde_yaml::Location>),
+  YamlError(Option<PathBuf>, String, Option<serde_yaml::Location>),
 
   /// Error parsing or serializing Sender data.
   #[error("Error parsing or serializing Sender data: {0}")]
diff --git a/crates/wick/wick-config/src/helpers.rs b/crates/wick/wick-config/src/helpers.rs
index eec45b391..5f3b69352 100644
--- a/crates/wick/wick-config/src/helpers.rs
+++ b/crates/wick/wick-config/src/helpers.rs
@@ -202,9 +202,7 @@ where
   }
 
   // use our visitor to deserialize an `ActualValue`
-  let a = deserializer.deserialize_any(JsonStringVisitor);
-  println!("Ok did something: {:?}", a);
-  a
+  deserializer.deserialize_any(JsonStringVisitor)
 }
 
 fn expand_jsonval(value: Value) -> Result<Value, shellexpand::LookupError<std::env::VarError>> {
diff --git a/crates/wick/wick-config/src/v1.rs b/crates/wick/wick-config/src/v1.rs
index 5f059670e..d5250e130 100644
--- a/crates/wick/wick-config/src/v1.rs
+++ b/crates/wick/wick-config/src/v1.rs
@@ -1055,7 +1055,7 @@ pub(crate) struct CompositeOperationDefinition {
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-#[serde(tag = "kind")]
+#[serde(untagged)]
 /// A flow operation, i.e. a connection from one operation's outputs to another's inputs.
 pub(crate) enum FlowExpression {
   /// A variant representing a [ConnectionDefinition] type.
@@ -1074,6 +1074,7 @@ pub(crate) struct BlockExpression {
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(into = "String")]
 #[serde(deny_unknown_fields)]
 /// A connection between Operations and their ports. This can be specified in short-form syntax.
 pub(crate) struct ConnectionDefinition {
@@ -1154,6 +1155,7 @@ pub(crate) struct Field {
 
 #[derive(Debug, Clone, serde_with::DeserializeFromStr, Serialize, PartialEq)]
 #[serde(deny_unknown_fields)]
+#[serde(into = "String")]
 #[serde(tag = "kind")]
 pub(crate) enum TypeSignature {
   /// A variant representing a [I8] type.
@@ -1198,59 +1200,59 @@ pub(crate) enum TypeSignature {
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct I8();
+pub(crate) struct I8;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct I16();
+pub(crate) struct I16;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct I32();
+pub(crate) struct I32;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct I64();
+pub(crate) struct I64;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct U8();
+pub(crate) struct U8;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct U16();
+pub(crate) struct U16;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct U32();
+pub(crate) struct U32;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct U64();
+pub(crate) struct U64;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct F32();
+pub(crate) struct F32;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct F64();
+pub(crate) struct F64;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct Bool();
+pub(crate) struct Bool;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct StringType();
+pub(crate) struct StringType;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct Datetime();
+pub(crate) struct Datetime;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct Bytes();
+pub(crate) struct Bytes;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
@@ -1286,7 +1288,7 @@ pub(crate) struct Map {
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
-pub(crate) struct Object();
+pub(crate) struct Object;
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(deny_unknown_fields)]
@@ -1415,13 +1417,15 @@ pub(crate) struct TestDefinition {
   /// The inputs to the test.
 
   #[serde(default)]
+  #[serde(alias = "input")]
   #[serde(skip_serializing_if = "Vec::is_empty")]
-  pub(crate) input: Vec<PacketData>,
+  pub(crate) inputs: Vec<PacketData>,
   /// The expected outputs of the operation.
 
   #[serde(default)]
+  #[serde(alias = "output")]
   #[serde(skip_serializing_if = "Vec::is_empty")]
-  pub(crate) output: Vec<PacketData>,
+  pub(crate) outputs: Vec<PacketData>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
diff --git a/crates/wick/wick-config/src/v1/conversions.rs b/crates/wick/wick-config/src/v1/conversions.rs
index d18aabd61..1c67b79dc 100644
--- a/crates/wick/wick-config/src/v1/conversions.rs
+++ b/crates/wick/wick-config/src/v1/conversions.rs
@@ -104,6 +104,24 @@ impl TryFrom<v1::TypesConfiguration> for types_config::TypesConfiguration {
   }
 }
 
+impl TryFrom<types_config::TypesConfiguration> for v1::TypesConfiguration {
+  type Error = ManifestError;
+
+  fn try_from(value: types_config::TypesConfiguration) -> std::result::Result<Self, Self::Error> {
+    Ok(Self {
+      name: value.name,
+      metadata: value.metadata.try_map_into()?,
+      types: value.types.try_map_into()?,
+      operations: value
+        .operations
+        .into_values()
+        .map(TryInto::try_into)
+        .collect::<Result<_>>()?,
+      package: value.package.try_map_into()?,
+    })
+  }
+}
+
 impl TryFrom<v1::ComponentConfiguration> for ComponentConfiguration {
   type Error = ManifestError;
 
@@ -1340,8 +1358,8 @@ impl From<v1::TestDefinition> for test_case::TestCase {
     Self {
       name: value.name,
       operation: value.operation,
-      inputs: value.input.map_into(),
-      outputs: value.output.map_into(),
+      inputs: value.inputs.map_into(),
+      outputs: value.outputs.map_into(),
       inherent: value.inherent.map_into(),
       config: value.with.map_into(),
     }
@@ -1401,8 +1419,8 @@ impl From<test_case::TestCase> for v1::TestDefinition {
     Self {
       name: value.name,
       operation: value.operation,
-      input: value.inputs.map_into(),
-      output: value.outputs.map_into(),
+      inputs: value.inputs.map_into(),
+      outputs: value.outputs.map_into(),
       inherent: value.inherent.map_into(),
       with: value.config.map_into(),
     }
diff --git a/crates/wick/wick-config/src/v1/conversions/types.rs b/crates/wick/wick-config/src/v1/conversions/types.rs
index 5b901bb96..4a13f8183 100644
--- a/crates/wick/wick-config/src/v1/conversions/types.rs
+++ b/crates/wick/wick-config/src/v1/conversions/types.rs
@@ -177,18 +177,18 @@ impl TryFrom<wick::Type> for v1::TypeSignature {
   fn try_from(value: wick::Type) -> std::result::Result<Self, Self::Error> {
     use v1::TypeSignature as TS;
     let v = match value {
-      wick::Type::I8 => TS::I8(v1::I8()),
-      wick::Type::I16 => TS::I16(v1::I16()),
-      wick::Type::I32 => TS::I32(v1::I32()),
-      wick::Type::I64 => TS::I64(v1::I64()),
-      wick::Type::U8 => TS::U8(v1::U8()),
-      wick::Type::U16 => TS::U16(v1::U16()),
-      wick::Type::U32 => TS::U32(v1::U32()),
-      wick::Type::U64 => TS::U64(v1::U64()),
-      wick::Type::F32 => TS::F32(v1::F32()),
-      wick::Type::F64 => TS::F64(v1::F64()),
-      wick::Type::Bool => TS::Bool(v1::Bool()),
-      wick::Type::String => TS::StringType(v1::StringType()),
+      wick::Type::I8 => TS::I8(v1::I8),
+      wick::Type::I16 => TS::I16(v1::I16),
+      wick::Type::I32 => TS::I32(v1::I32),
+      wick::Type::I64 => TS::I64(v1::I64),
+      wick::Type::U8 => TS::U8(v1::U8),
+      wick::Type::U16 => TS::U16(v1::U16),
+      wick::Type::U32 => TS::U32(v1::U32),
+      wick::Type::U64 => TS::U64(v1::U64),
+      wick::Type::F32 => TS::F32(v1::F32),
+      wick::Type::F64 => TS::F64(v1::F64),
+      wick::Type::Bool => TS::Bool(v1::Bool),
+      wick::Type::String => TS::StringType(v1::StringType),
       wick::Type::Optional { ty } => TS::Optional(v1::Optional {
         ty: Box::new((*ty).try_into()?),
       }),
diff --git a/crates/wick/wick-config/src/v1/helpers.rs b/crates/wick/wick-config/src/v1/helpers.rs
index 32edc18f6..edd2dd6cf 100644
--- a/crates/wick/wick-config/src/v1/helpers.rs
+++ b/crates/wick/wick-config/src/v1/helpers.rs
@@ -8,3 +8,19 @@ pub(crate) struct LocationReference(
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 
 pub(crate) struct Glob(pub(super) String);
+
+#[allow(clippy::from_over_into)]
+impl Into<String> for super::TypeSignature {
+  fn into(self) -> String {
+    let ty: wick_interface_types::Type = self.try_into().unwrap();
+    ty.to_string()
+  }
+}
+
+#[allow(clippy::from_over_into)]
+impl Into<String> for super::ConnectionDefinition {
+  fn into(self) -> String {
+    let ty: flow_expression_parser::ast::ConnectionExpression = self.try_into().unwrap();
+    ty.to_string()
+  }
+}
diff --git a/crates/wick/wick-host/Cargo.toml b/crates/wick/wick-host/Cargo.toml
index fa2ffea6a..92c10b680 100644
--- a/crates/wick/wick-host/Cargo.toml
+++ b/crates/wick/wick-host/Cargo.toml
@@ -29,6 +29,7 @@ parking_lot = { workspace = true }
 uuid = { workspace = true }
 futures = { workspace = true }
 derive_builder = { workspace = true }
+option-utils = { workspace = true }
 
 [dev-dependencies]
 wick-logger = { workspace = true }
diff --git a/crates/wick/wick-host/src/component_host.rs b/crates/wick/wick-host/src/component_host.rs
index be68bdf06..c8ff85c6f 100644
--- a/crates/wick/wick-host/src/component_host.rs
+++ b/crates/wick/wick-host/src/component_host.rs
@@ -205,6 +205,7 @@ mod test {
   use anyhow::Result;
   use futures::StreamExt;
   use http::Uri;
+  use option_utils::OptionUtils;
   use wick_config::config::HttpConfigBuilder;
   use wick_config::WickConfiguration;
   use wick_invocation_server::connect_rpc_client;
@@ -255,12 +256,19 @@ mod test {
     let file = PathBuf::from("manifests/logger.yaml");
     let mut def = WickConfiguration::load_from_file(&file).await?.try_component_config()?;
 
-    def.host_mut().rpc_mut().replace(
-      HttpConfigBuilder::default()
-        .enabled(true)
-        .address(Ipv4Addr::from_str("127.0.0.1").unwrap())
-        .build()?,
-    );
+    if def.host().is_none() {
+      def.set_host(Some(Default::default()));
+    }
+
+    def.host_mut().inner_mut(|h| {
+      h.rpc_mut().replace(
+        HttpConfigBuilder::default()
+          .enabled(true)
+          .address(Ipv4Addr::from_str("127.0.0.1").unwrap())
+          .build()
+          .unwrap(),
+      );
+    });
 
     let mut host = ComponentHostBuilder::default().manifest(def).build()?;
     host.start(None).await?;
diff --git a/crates/wick/wick-interface-types/src/signatures/component.rs b/crates/wick/wick-interface-types/src/signatures/component.rs
index 3a8d295db..82c6688f8 100644
--- a/crates/wick/wick-interface-types/src/signatures/component.rs
+++ b/crates/wick/wick-interface-types/src/signatures/component.rs
@@ -120,7 +120,7 @@ impl From<ComponentVersion> for u32 {
 /// The Wick features this collection supports.
 pub struct ComponentMetadata {
   /// Version of the component.
-  #[serde(skip_serializing_if = "Option::is_none  ")]
+  #[serde(skip_serializing_if = "Option::is_none")]
   pub version: Option<String>,
 }
 
diff --git a/crates/wick/wick-packet/src/collection_link.rs b/crates/wick/wick-packet/src/collection_link.rs
index 49b201f77..2a2ec1e15 100644
--- a/crates/wick/wick-packet/src/collection_link.rs
+++ b/crates/wick/wick-packet/src/collection_link.rs
@@ -79,7 +79,7 @@ fn link_call(
   use wasmrs::RSocket;
   use wasmrs_guest::{FluxChannel, Observer};
 
-  let mut stream = crate::into_wasmrs(0, input);
+  let mut stream = crate::packetstream_to_wasmrs(0, input);
   let (tx, rx) = FluxChannel::new_parts();
   let first = crate::ContextTransport {
     config,
diff --git a/crates/wick/wick-packet/src/lib.rs b/crates/wick/wick-packet/src/lib.rs
index 78b55bacd..2987be735 100644
--- a/crates/wick/wick-packet/src/lib.rs
+++ b/crates/wick/wick-packet/src/lib.rs
@@ -119,7 +119,7 @@ pub use inherent::InherentData;
 pub use invocation::Invocation;
 pub use metadata::{Flags, WickMetadata, CLOSE_BRACKET, DONE_FLAG, OPEN_BRACKET};
 pub use output::Output;
-pub use packet::{from_raw_wasmrs, from_wasmrs, into_wasmrs, Packet, PacketError, PacketPayload};
+pub use packet::{from_raw_wasmrs, from_wasmrs, packetstream_to_wasmrs, Packet, PacketError, PacketPayload};
 pub use packet_stream::{into_packet, PacketSender, PacketStream};
 pub use stream_map::StreamMap;
 pub use wasmrs::Metadata;
diff --git a/crates/wick/wick-packet/src/packet.rs b/crates/wick/wick-packet/src/packet.rs
index 66f4496f0..622fe7978 100644
--- a/crates/wick/wick-packet/src/packet.rs
+++ b/crates/wick/wick-packet/src/packet.rs
@@ -339,7 +339,7 @@ impl PacketError {
 }
 
 #[must_use]
-pub fn into_wasmrs(index: u32, stream: PacketStream) -> BoxFlux<RawPayload, PayloadError> {
+pub fn packetstream_to_wasmrs(index: u32, stream: PacketStream) -> BoxFlux<RawPayload, PayloadError> {
   let s = tokio_stream::StreamExt::map(stream, move |p| {
     p.map(|p| {
       let md = wasmrs::Metadata::new_extra(index, p.extra.encode()).encode();
diff --git a/crates/wick/wick-runtime/src/triggers/time.rs b/crates/wick/wick-runtime/src/triggers/time.rs
index 7ee941f4a..4681995a1 100644
--- a/crates/wick/wick-runtime/src/triggers/time.rs
+++ b/crates/wick/wick-runtime/src/triggers/time.rs
@@ -176,17 +176,15 @@ impl Trigger for Time {
   }
 
   async fn wait_for_done(&self) {
-    info!("Scheduler started waiting for SIGINT");
     let handler = self.handler.lock().take().unwrap();
     match handler.await {
       Ok(_) => {
-        info!("Cron done");
+        info!("cron done");
       }
       Err(e) => {
-        error!("Cron error: {}", e);
+        error!("cron error: {}", e);
       }
     }
-    debug!("SIGINT received");
   }
 }
 
diff --git a/docs/content/configuration/reference/v1.md b/docs/content/configuration/reference/v1.md
index ce9cc4e3e..7764c9446 100644
--- a/docs/content/configuration/reference/v1.md
+++ b/docs/content/configuration/reference/v1.md
@@ -1368,8 +1368,8 @@ Any one of the following types:
 | `operation` | <code>`string`</code> |The operaton to test.|Yes||
 | `inherent` | <code>[`InherentData`](#inherentdata)</code> |Inherent data to use for the test.|||
 | `with` | <code>`{` `string` `: ` `any` `}`</code> |The configuration for the operation, if any.|||
-| `input` | <code>[`PacketData`](#packetdata)[]</code> |The inputs to the test.|||
-| `output` | <code>[`PacketData`](#packetdata)[]</code> |The expected outputs of the operation.|||
+| `inputs` | <code>[`PacketData`](#packetdata)[]</code> |The inputs to the test.|||
+| `outputs` | <code>[`PacketData`](#packetdata)[]</code> |The expected outputs of the operation.|||
 
 
 
diff --git a/examples/http/wasm-http-call/client.wick b/examples/http/wasm-http-call/client.wick
new file mode 100644
index 000000000..d92a29e25
--- /dev/null
+++ b/examples/http/wasm-http-call/client.wick
@@ -0,0 +1,31 @@
+kind: wick/component@v1
+name: client
+metadata:
+  version: 0.0.1
+  description: New HTTP Client wick component
+  licenses:
+    - Apache-2.0
+resources:
+  - name: HTTP_URL
+    resource:
+      kind: wick/resource/url@v1
+      url: http://$HTTPBIN/
+component:
+  kind: wick/component/http@v1
+  resource: HTTP_URL
+  codec: Json
+  operations:
+    - name: post_op
+      inputs:
+        - name: id
+          type: string
+        - name: name
+          type: string
+      method: Post
+      codec: FormData
+      path: /post
+      headers:
+        'Content-Type': ['application/x-www-form-urlencoded']
+      body:
+        id: '{{id}}'
+        name: '{{name}}'
diff --git a/examples/http/wasm-http-call/harness.wick b/examples/http/wasm-http-call/harness.wick
new file mode 100644
index 000000000..624acb901
--- /dev/null
+++ b/examples/http/wasm-http-call/harness.wick
@@ -0,0 +1,38 @@
+kind: wick/component@v1
+name: harness
+metadata:
+  version: 0.0.1
+  description: A component that wires together the wasm and client components
+  licenses:
+    - Apache-2.0
+import:
+  - name: wasm
+    component:
+      kind: wick/component/manifest@v1
+      ref: ./wasm-component/component.wick
+      provide:
+        client: client
+  - name: client
+    component:
+      kind: wick/component/manifest@v1
+      ref: ./client.wick
+component:
+  kind: wick/component/composite@v1
+  operations:
+    - name: example
+      flow:
+        - <input>.id -> wasm::request[a].id
+        - <input>.name -> a.name
+        - a.output.form -> <>.output
+tests:
+  - cases:
+      - name: basic_test
+        operation: example
+        inputs:
+          - name: id
+            value: '1'
+          - name: name
+            value: wick
+        outputs:
+          - name: output
+            value: { 'id': '1', 'name': 'wick' }
diff --git a/examples/http/wasm-http-call/wasm-component/.cargo/config b/examples/http/wasm-http-call/wasm-component/.cargo/config
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/http/wasm-http-call/wasm-component/.gitignore b/examples/http/wasm-http-call/wasm-component/.gitignore
new file mode 100644
index 000000000..82448ccd9
--- /dev/null
+++ b/examples/http/wasm-http-call/wasm-component/.gitignore
@@ -0,0 +1,2 @@
+/target
+/build
diff --git a/examples/http/wasm-http-call/wasm-component/.vscode/settings.json b/examples/http/wasm-http-call/wasm-component/.vscode/settings.json
new file mode 100644
index 000000000..1cd2bd528
--- /dev/null
+++ b/examples/http/wasm-http-call/wasm-component/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+  "rust-analyzer.cargo.target": "wasm32-unknown-unknown",
+  "extensions": [
+    "CandleCorp.wick-development-framework"
+  ]
+}
\ No newline at end of file
diff --git a/examples/http/wasm-http-call/wasm-component/Cargo.lock b/examples/http/wasm-http-call/wasm-component/Cargo.lock
new file mode 100644
index 000000000..700212373
--- /dev/null
+++ b/examples/http/wasm-http-call/wasm-component/Cargo.lock
@@ -0,0 +1,2526 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+
+[[package]]
+name = "anymap2"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
+
+[[package]]
+name = "asset-container"
+version = "0.2.0"
+dependencies = [
+ "bitflags 2.3.1",
+ "futures",
+ "thiserror",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "atomic-polyfill"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28"
+dependencies = [
+ "critical-section",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
+
+[[package]]
+name = "base64-serde"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7"
+dependencies = [
+ "base64 0.21.2",
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "check_keyword"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4d532198271d4fb82f944bd25527e760a6cfc966c7d592525e092d4c6a4f787"
+
+[[package]]
+name = "chrono"
+version = "0.4.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "winapi",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "critical-section"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52"
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "darling"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
+dependencies = [
+ "darling_core 0.14.4",
+ "darling_macro 0.14.4",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944"
+dependencies = [
+ "darling_core 0.20.1",
+ "darling_macro 0.20.1",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
+dependencies = [
+ "darling_core 0.14.4",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
+dependencies = [
+ "darling_core 0.20.1",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "dashmap"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
+dependencies = [
+ "cfg-if",
+ "hashbrown",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "derive-asset-container"
+version = "0.2.0"
+dependencies = [
+ "asset-container",
+ "proc-macro2",
+ "quote",
+ "structmeta",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
+dependencies = [
+ "darling 0.14.4",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
+dependencies = [
+ "derive_builder_core",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+dependencies = [
+ "dirs-sys 0.3.7",
+]
+
+[[package]]
+name = "dirs"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
+dependencies = [
+ "dirs-sys 0.4.1",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys",
+]
+
+[[package]]
+name = "doc-comment"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
+
+[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.2.16",
+ "windows-sys",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "flow-component"
+version = "0.5.0"
+dependencies = [
+ "anyhow",
+ "futures",
+ "seeded-random",
+ "serde",
+ "serde_json",
+ "tracing",
+ "wick-interface-types",
+ "wick-packet",
+]
+
+[[package]]
+name = "flow-expression-parser"
+version = "0.5.0"
+dependencies = [
+ "lazy_static",
+ "nom",
+ "regex",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+
+[[package]]
+name = "futures-util"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "h2"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hash32"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heapless"
+version = "0.7.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743"
+dependencies = [
+ "atomic-polyfill",
+ "hash32",
+ "rustc_version",
+ "spin 0.9.8",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "home"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "http"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-auth"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5430cacd7a1f9a02fbeb350dfc81a0e5ed42d81f3398cb0ba184017f85bdcfbc"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
+[[package]]
+name = "hyper"
+version = "0.14.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7"
+dependencies = [
+ "http",
+ "hyper",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+ "serde",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
+[[package]]
+name = "js-sys"
+version = "0.3.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "jwt"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f"
+dependencies = [
+ "base64 0.13.1",
+ "crypto-common",
+ "digest",
+ "hmac",
+ "serde",
+ "serde_json",
+ "sha2",
+]
+
+[[package]]
+name = "kstring"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
+dependencies = [
+ "serde",
+ "static_assertions",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.146"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
+
+[[package]]
+name = "liquid"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eefc214b0f57977e0036be6646a7ef9799b14456ae77a84808ddbe0c4cf326fb"
+dependencies = [
+ "doc-comment",
+ "liquid-core",
+ "liquid-derive",
+ "liquid-lib",
+ "serde",
+]
+
+[[package]]
+name = "liquid-core"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf5bf9a2ede54bdea60a387ded9bc9236d594210afb73ab55f4cbd53599711d3"
+dependencies = [
+ "anymap2",
+ "itertools",
+ "kstring",
+ "liquid-derive",
+ "num-traits",
+ "pest",
+ "pest_derive",
+ "regex",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "liquid-derive"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2438da425fd380eb2719a2a5880149697eea32a160e83b63e7848c1bee300406"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "liquid-json"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3fa84b645d05cf0eb96104b6202d0f304d3536dad406af50aa6359efd9f9e74"
+dependencies = [
+ "liquid",
+ "liquid-core",
+ "liquid-lib",
+ "once_cell",
+ "regex",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "liquid-lib"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4feb5c6363bdac5b5238725e296ed9efbe88576f07819970519f29ee29d0b11e"
+dependencies = [
+ "itertools",
+ "liquid-core",
+ "once_cell",
+ "percent-encoding",
+ "regex",
+ "time",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "normpath"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec60c60a693226186f5d6edf073232bfb6464ed97eb22cf3b01c1e8198fd97f5"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "oci-distribution"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ac5b780ce1bd6c3c2ff72a3013f4b2d56d53ae03b20d424e99d2f6556125138"
+dependencies = [
+ "futures",
+ "futures-util",
+ "http",
+ "http-auth",
+ "jwt",
+ "lazy_static",
+ "olpc-cjson",
+ "regex",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror",
+ "tokio",
+ "tokio-util",
+ "tracing",
+ "unicase",
+]
+
+[[package]]
+name = "olpc-cjson"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d637c9c15b639ccff597da8f4fa968300651ad2f1e968aefc3b4927a6fb2027a"
+dependencies = [
+ "serde",
+ "serde_json",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "option-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1800ff62dcf34d7de2c7b7c84e3107a19708578441bd998eaddadb1134a4b007"
+
+[[package]]
+name = "ordered-float"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.3.5",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "pest"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70"
+dependencies = [
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "property"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "889bff6ebe76dbef2685598944997eb7f15c9854a2e34db853a51c6ac158b9d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom",
+ "redox_syscall 0.2.16",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
+
+[[package]]
+name = "reqwest"
+version = "0.11.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
+dependencies = [
+ "base64 0.21.2",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-rustls",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls",
+ "rustls-pemfile",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-rustls",
+ "tokio-util",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+ "webpki-roots",
+ "winreg",
+]
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin 0.5.2",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustls"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki",
+ "sct",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
+dependencies = [
+ "base64 0.21.2",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.100.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "seeded-random"
+version = "0.3.0"
+dependencies = [
+ "parking_lot",
+ "rand",
+ "rand_chacha",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+
+[[package]]
+name = "serde"
+version = "1.0.163"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
+[[package]]
+name = "serde-with-expand-env"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "888d884a3be3a209308d0b66f1918ff18f60e93db837259e53ea7d8dd14e7e98"
+dependencies = [
+ "serde",
+ "shellexpand 2.1.2",
+]
+
+[[package]]
+name = "serde_bytes"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.163"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513"
+dependencies = [
+ "base64 0.21.2",
+ "chrono",
+ "hex",
+ "indexmap",
+ "serde",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070"
+dependencies = [
+ "darling 0.20.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha256"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f9f8b5de2bac3a4ae28e9b611072a8e326d9b26c8189c0972d4c321fa684f1f"
+dependencies = [
+ "hex",
+ "sha2",
+]
+
+[[package]]
+name = "shellexpand"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4"
+dependencies = [
+ "dirs 4.0.0",
+]
+
+[[package]]
+name = "shellexpand"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
+dependencies = [
+ "dirs 5.0.1",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "socket2"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "structmeta"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ad9e09554f0456d67a69c1584c9798ba733a5b50349a6c0d0948710523922d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "structmeta-derive",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "structmeta-derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "time"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc"
+dependencies = [
+ "itoa",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
+
+[[package]]
+name = "time-macros"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.28.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "url"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
+
+[[package]]
+name = "wasm-http-call"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "serde",
+ "wick-component",
+ "wick-component-codegen",
+]
+
+[[package]]
+name = "wasm-msgpack"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c566f7770882bb305d56f393e411f4a15b3b3e8404be914d183cc3cd35c3f733"
+dependencies = [
+ "byteorder",
+ "heapless",
+ "num-traits",
+ "paste",
+ "serde",
+ "serde_bytes",
+ "zerocopy",
+]
+
+[[package]]
+name = "wasm-streams"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "wasmrs"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c839558462b394d53c045fd1761a49f300d8f08e120256f64a0b1aa9e2437adf"
+dependencies = [
+ "bytes",
+ "crossbeam-channel",
+ "dashmap",
+ "futures",
+ "futures-executor",
+ "futures-util",
+ "parking_lot",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+ "wasmrs-codec",
+ "wasmrs-frames",
+ "wasmrs-runtime",
+ "wasmrs-rx",
+]
+
+[[package]]
+name = "wasmrs-codec"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8854f99ee80c9e1c6a1a46b35e4373d533ae423c623a5e18285c64b901d92ab3"
+dependencies = [
+ "heapless",
+ "serde",
+ "wasm-msgpack",
+ "wasmrs-frames",
+]
+
+[[package]]
+name = "wasmrs-frames"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469a9922dda9380880ea4273cbaca65209fb95ddb1aea6c0ac76ede6cb2778ea"
+dependencies = [
+ "bytes",
+ "serde",
+]
+
+[[package]]
+name = "wasmrs-guest"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be1025bbe96251d93a0026c50a63cccd1d74f9b1b5205b3919061cdde2114cf3"
+dependencies = [
+ "bytes",
+ "cfg-if",
+ "futures-executor",
+ "futures-util",
+ "serde",
+ "serde_json",
+ "tracing",
+ "wasmrs",
+ "wasmrs-codec",
+ "wasmrs-frames",
+ "wasmrs-runtime",
+ "wasmrs-rx",
+]
+
+[[package]]
+name = "wasmrs-runtime"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63dd95a35923a7afe0e7cc2d1a758aabf3778ce529e0f743d3a74f4641dd271c"
+dependencies = [
+ "bytes",
+ "crossbeam-channel",
+ "dashmap",
+ "futures",
+ "futures-executor",
+ "futures-util",
+ "parking_lot",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+ "wasmrs-frames",
+]
+
+[[package]]
+name = "wasmrs-rx"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1459e451fadf5ec58e0f2249f20be50c75fa155fd135bae22effebe99e8636d"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "crossbeam-channel",
+ "dashmap",
+ "futures",
+ "futures-executor",
+ "futures-util",
+ "parking_lot",
+ "pin-project-lite",
+ "tracing",
+ "wasmrs-runtime",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.22.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
+dependencies = [
+ "webpki",
+]
+
+[[package]]
+name = "wick-asset-reference"
+version = "0.3.0"
+dependencies = [
+ "asset-container",
+ "bytes",
+ "normpath",
+ "parking_lot",
+ "thiserror",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+ "wick-oci-utils",
+]
+
+[[package]]
+name = "wick-component"
+version = "0.15.0"
+dependencies = [
+ "flow-component",
+ "paste",
+ "serde_json",
+ "tokio-stream",
+ "wasm-msgpack",
+ "wasmrs",
+ "wasmrs-codec",
+ "wasmrs-guest",
+ "wasmrs-runtime",
+ "wasmrs-rx",
+ "wick-packet",
+]
+
+[[package]]
+name = "wick-component-codegen"
+version = "0.4.0"
+dependencies = [
+ "anyhow",
+ "bitflags 2.3.1",
+ "check_keyword",
+ "derive_builder",
+ "heck",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "serde_json",
+ "syn 2.0.18",
+ "tokio",
+ "wick-config",
+ "wick-interface-types",
+]
+
+[[package]]
+name = "wick-config"
+version = "0.26.0"
+dependencies = [
+ "asset-container",
+ "derive-asset-container",
+ "derive_builder",
+ "flow-component",
+ "flow-expression-parser",
+ "glob",
+ "liquid-json",
+ "num-traits",
+ "option-utils",
+ "parking_lot",
+ "property",
+ "regex",
+ "serde",
+ "serde-value",
+ "serde-with-expand-env",
+ "serde_json",
+ "serde_with",
+ "serde_yaml",
+ "shellexpand 3.1.0",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "url",
+ "wick-asset-reference",
+ "wick-interface-types",
+ "wick-packet",
+]
+
+[[package]]
+name = "wick-interface-types"
+version = "0.15.0"
+dependencies = [
+ "nom",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "serde_yaml",
+]
+
+[[package]]
+name = "wick-oci-utils"
+version = "0.3.0"
+dependencies = [
+ "bytes",
+ "flate2",
+ "oci-distribution",
+ "once_cell",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "sha256",
+ "tar",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "wick-xdg",
+]
+
+[[package]]
+name = "wick-packet"
+version = "0.15.0"
+dependencies = [
+ "base64 0.21.2",
+ "base64-serde",
+ "bytes",
+ "parking_lot",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "tokio-stream",
+ "tracing",
+ "url",
+ "wasmrs",
+ "wasmrs-codec",
+ "wasmrs-frames",
+ "wasmrs-guest",
+ "wasmrs-rx",
+ "wick-interface-types",
+]
+
+[[package]]
+name = "wick-xdg"
+version = "0.2.0"
+dependencies = [
+ "thiserror",
+ "xdg",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "winreg"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "xattr"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "xdg"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "688597db5a750e9cad4511cb94729a078e274308099a0382b5b8203bbc767fee"
+dependencies = [
+ "home",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6505e6815af7de1746a08f69c69606bb45695a17149517680f3b2149713b19a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
diff --git a/examples/http/wasm-http-call/wasm-component/Cargo.toml b/examples/http/wasm-http-call/wasm-component/Cargo.toml
new file mode 100644
index 000000000..9c24baddc
--- /dev/null
+++ b/examples/http/wasm-http-call/wasm-component/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "wasm-http-call"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[profile.release]
+strip = "symbols"
+codegen-units = 1
+debug = false
+lto = true
+opt-level = "z"
+panic = "abort"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+wick-component = { path = "../../../../crates/wick/wick-component" }
+serde = { version = "1", features = ["derive"] }
+async-trait = "0.1"
+anyhow = { version = "1" }
+
+[build-dependencies]
+wick-component-codegen = { path = "../../../../crates/wick/wick-component-codegen" }
diff --git a/examples/http/wasm-http-call/wasm-component/build.rs b/examples/http/wasm-http-call/wasm-component/build.rs
new file mode 100644
index 000000000..d450ca798
--- /dev/null
+++ b/examples/http/wasm-http-call/wasm-component/build.rs
@@ -0,0 +1,5 @@
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+  println!("cargo:rerun-if-changed=component.wick");
+  wick_component_codegen::configure().generate("component.wick")?;
+  Ok(())
+}
diff --git a/examples/http/wasm-http-call/wasm-component/component.wick b/examples/http/wasm-http-call/wasm-component/component.wick
new file mode 100644
index 000000000..cef47673d
--- /dev/null
+++ b/examples/http/wasm-http-call/wasm-component/component.wick
@@ -0,0 +1,39 @@
+# yaml-language-server: $schema=https://github.com/candlecorp/wick/releases/download/nightly/schema.json
+---
+name: 'wasm-http-call'
+kind: wick/component@v1
+metadata:
+  version: '0.0.1'
+import:
+  - name: http
+    component:
+      kind: wick/component/types@v1
+      ref: ../../../../crates/interfaces/wick-interface-http/component.yaml
+requires:
+  - name: client
+    interface:
+      operations:
+        - name: post_op
+          inputs:
+            - name: id
+              type: string
+            - name: name
+              type: string
+          outputs:
+            - name: response
+              type: http::HttpResponse
+            - name: body
+              type: object
+component:
+  kind: wick/component/wasmrs@v1
+  ref: build/wasm_http_call.signed.wasm
+  operations:
+    - name: request
+      inputs:
+        - name: id
+          type: string
+        - name: name
+          type: string
+      outputs:
+        - name: output
+          type: object
diff --git a/examples/http/wasm-http-call/wasm-component/justfile b/examples/http/wasm-http-call/wasm-component/justfile
new file mode 100644
index 000000000..2051c218a
--- /dev/null
+++ b/examples/http/wasm-http-call/wasm-component/justfile
@@ -0,0 +1,15 @@
+project_name := "wasm_http_call"
+build: setup
+    cargo build --release --target=wasm32-unknown-unknown
+    cp ./target/wasm32-unknown-unknown/release/{{project_name}}.wasm ./build/
+    cargo run --manifest-path ../../../../crates/bins/wick/Cargo.toml --  wasm sign ./build/{{project_name}}.wasm component.wick
+debug: setup
+    cargo +nightly build --target=wasm32-wasi
+    cp ./target/wasm32-wasi/debug/{{project_name}}.wasm ./build/
+    cargo run --manifest-path ../../../../crates/bins/wick/Cargo.toml --  wasm sign ./build/{{project_name}}.wasm component.wick
+setup:
+    mkdir -p build
+test: build
+    wick test component.wick
+clean:
+  cargo clean
diff --git a/examples/http/wasm-http-call/wasm-component/src/lib.rs b/examples/http/wasm-http-call/wasm-component/src/lib.rs
new file mode 100644
index 000000000..74f5f54bd
--- /dev/null
+++ b/examples/http/wasm-http-call/wasm-component/src/lib.rs
@@ -0,0 +1,45 @@
+mod wick {
+  wick_component::wick_import!();
+}
+use wick::*;
+
+#[async_trait::async_trait(?Send)]
+impl RequestOperation for Component {
+  type Error = anyhow::Error;
+  type Outputs = request::Outputs;
+  type Config = request::Config;
+
+  async fn request(
+    mut id: WickStream<String>,
+    mut name: WickStream<String>,
+    mut outputs: Self::Outputs,
+    ctx: Context<Self::Config>,
+  ) -> Result<(), Self::Error> {
+    while let (Some(id), Some(name)) = (id.next().await, name.next().await) {
+      let id = propagate_if_error!(id, outputs, continue);
+      let name = propagate_if_error!(name, outputs, continue);
+
+      // `ctx.provided()` returns the external components that have
+      // been provided to this component, like `client` which we defined
+      // in the `require` section of our component.wick.
+      let client = &ctx.provided().client;
+
+      // `post_op` is a method on the `client` component. It takes two inputs and produces
+      // two outputs. In wick, all inputs and outputs are independent streams.
+      let (mut response, mut body) = client.post_op(once(id), once(name))?;
+
+      if let Some(response) = response.next().await {
+        let _response = propagate_if_error!(response, outputs, continue);
+        // Here's where we can check response headers, status, etc.
+      }
+
+      if let Some(body) = body.next().await {
+        let body = propagate_if_error!(body, outputs, continue);
+        // Here's the
+        outputs.output.send(&body);
+      }
+    }
+    outputs.output.done();
+    Ok(())
+  }
+}
diff --git a/examples/misc/multi-trigger.wick b/examples/misc/multi-trigger.wick
new file mode 100644
index 000000000..3d2cf41a6
--- /dev/null
+++ b/examples/misc/multi-trigger.wick
@@ -0,0 +1,50 @@
+---
+kind: wick/app@v1
+name: serve_http_component
+metadata:
+  description: Serve raw HTTP with a WASM component
+  version: 0.0.1
+  authors:
+    - 'Wick Maintainers'
+  vendors:
+    - 'Candle Corporation'
+  licenses:
+    - Apache-2.0
+resources:
+  - name: http
+    resource:
+      kind: wick/resource/tcpport@v1
+      port: ${HTTP_PORT:-8999}
+      address: 0.0.0.0
+import:
+  - name: sample
+    component:
+      kind: wick/component/manifest@v1
+      ref: ../http/rest-router/component.wick
+triggers:
+  - kind: wick/trigger/time@v1
+    schedule:
+      cron: '*/1 * * * * *'
+      repeat: 5
+    payload:
+      - name: left
+        value: 2
+      - name: right
+        value: 5
+    operation:
+      name: add
+      component:
+        kind: wick/component/manifest@v1
+        ref: ../../crates/integration/test-baseline-component/component.yaml
+        with:
+          default_err: 'error from dual-trigger.wick'
+  - kind: wick/trigger/http@v1
+    resource: http
+    routers:
+      - kind: wick/router/rest@v1
+        path: /
+        routes:
+          # - uri: '/this/{first:string}/some/{second:u32}?third:string[]&fourth:bool' # TODO: fix broken test
+          - uri: '/this/{first:string}/some/{second:u32}?third:string&fourth:bool'
+            operation: sample::echo
+            description: 'Echoes inputs first, second, third, and fourth back as JSON'


Test patch:
diff --git a/crates/bins/wick/src/commands/test.rs b/crates/bins/wick/src/commands/test.rs
index 081f027f1..621d03957 100644
--- a/crates/bins/wick/src/commands/test.rs
+++ b/crates/bins/wick/src/commands/test.rs
@@ -4,6 +4,8 @@ use anyhow::Result;
 use clap::Args;
 use futures::TryFutureExt;
 use seeded_random::Seed;
+use serde_json::{json, Value};
+use structured_output::StructuredOutput;
 use wick_component_cli::options::DefaultCliOptions;
 use wick_config::WickConfiguration;
 use wick_host::ComponentHostBuilder;
@@ -13,7 +15,8 @@ use crate::utils::merge_config;
 
 #[derive(Debug, Clone, Args)]
 #[clap(rename_all = "kebab-case")]
-pub(crate) struct TestCommand {
+#[group(skip)]
+pub(crate) struct Options {
   #[clap(flatten)]
   pub(crate) oci: crate::oci::Options,
 
@@ -41,7 +44,11 @@ pub(crate) struct TestCommand {
   filter: Vec<String>,
 }
 
-pub(crate) async fn handle(opts: TestCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+pub(crate) async fn handle(
+  opts: Options,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<StructuredOutput> {
   let fetch_options = wick_config::config::FetchOptions::new()
     .allow_latest(opts.oci.allow_latest)
     .allow_insecure(&opts.oci.insecure_registries);
@@ -90,13 +97,21 @@ pub(crate) async fn handle(opts: TestCommand, _settings: wick_settings::Settings
 
   let runners = suite.run(factory).await?;
 
+  let mut lines: Vec<String> = Vec::new();
+  let mut output: Vec<Value> = Vec::new();
+  let mut num_failed = 0;
+
   for harness in runners {
-    harness.print();
-    let num_failed = harness.num_failed();
-    if num_failed > 0 {
-      return Err(anyhow!("{} tests failed", num_failed));
-    }
+    lines.extend(harness.get_tap_lines().clone().into_iter());
+    output.push(json!({"tap_output":harness.get_tap_lines()}));
+
+    num_failed += harness.num_failed();
   }
 
-  Ok(())
+  let structout = StructuredOutput::new(
+    lines.join("\n"),
+    json!({"success": num_failed ==0, "failures": num_failed, "output": output}),
+  );
+
+  Ok(structout)
 }
diff --git a/crates/bins/wick/tests/invoke.rs b/crates/bins/wick/tests/invoke.rs
index 88570c733..0604fbc44 100644
--- a/crates/bins/wick/tests/invoke.rs
+++ b/crates/bins/wick/tests/invoke.rs
@@ -3,7 +3,7 @@ static DIR: &str = "invoke";
 #[rstest::rstest]
 #[case("v1-wasmrs.toml")]
 #[case("stdin.toml")]
-fn wick_run(#[case] file: &'static str) {
+fn wick_invoke(#[case] file: &'static str) {
   let kind = "unit";
   let file = format!("tests/{}/{}/{}", kind, DIR, file);
 
diff --git a/crates/bins/wick/tests/new.rs b/crates/bins/wick/tests/new.rs
new file mode 100644
index 000000000..785c8ca56
--- /dev/null
+++ b/crates/bins/wick/tests/new.rs
@@ -0,0 +1,24 @@
+static DIR: &str = "new";
+
+#[rstest::rstest]
+#[case("app.toml")]
+#[case("component-composite.toml")]
+#[case("component-sql.toml")]
+#[case("component-http.toml")]
+#[case("component-wasm.toml")]
+fn wick_new(#[case] file: &'static str) {
+  let kind = "unit";
+  let file = format!("tests/{}/{}/{}", kind, DIR, file);
+
+  trycmd::TestCases::new().case(file);
+}
+
+// mod integration_test {
+//   use super::DIR;
+//   #[rstest::rstest]
+//   fn wick_run(#[case] file: &'static str) {
+//     let kind = "integration";
+//     let file = format!("tests/{}/{}/{}", kind, DIR, file);
+//     trycmd::TestCases::new().case(file);
+//   }
+// }
diff --git a/crates/bins/wick/tests/unit/new/app.toml b/crates/bins/wick/tests/unit/new/app.toml
new file mode 100644
index 000000000..0100b9922
--- /dev/null
+++ b/crates/bins/wick/tests/unit/new/app.toml
@@ -0,0 +1,5 @@
+bin.name = "wick"
+args = ["new", "app", "test", "--json", "--dry-run"]
+stdout = """
+{"output":{"files":["app.wick"]},"success":true}
+"""
diff --git a/crates/bins/wick/tests/unit/new/component-composite.toml b/crates/bins/wick/tests/unit/new/component-composite.toml
new file mode 100644
index 000000000..522d66849
--- /dev/null
+++ b/crates/bins/wick/tests/unit/new/component-composite.toml
@@ -0,0 +1,5 @@
+bin.name = "wick"
+args = ["new", "component", "composite", "--json", "test", "--dry-run"]
+stdout = """
+{"output":{"files":["test.wick"]},"success":true}
+"""
diff --git a/crates/bins/wick/tests/unit/new/component-http.toml b/crates/bins/wick/tests/unit/new/component-http.toml
new file mode 100644
index 000000000..faed10443
--- /dev/null
+++ b/crates/bins/wick/tests/unit/new/component-http.toml
@@ -0,0 +1,5 @@
+bin.name = "wick"
+args = ["new", "component", "http", "--json", "test", "--dry-run"]
+stdout = """
+{"output":{"files":["test.wick"]},"success":true}
+"""
diff --git a/crates/bins/wick/tests/unit/new/component-sql.toml b/crates/bins/wick/tests/unit/new/component-sql.toml
new file mode 100644
index 000000000..9653345e5
--- /dev/null
+++ b/crates/bins/wick/tests/unit/new/component-sql.toml
@@ -0,0 +1,5 @@
+bin.name = "wick"
+args = ["new", "component", "sql", "--json", "test", "--dry-run"]
+stdout = """
+{"output":{"files":["test.wick"]},"success":true}
+"""
diff --git a/crates/bins/wick/tests/unit/new/component-wasm.toml b/crates/bins/wick/tests/unit/new/component-wasm.toml
new file mode 100644
index 000000000..5f491e6d6
--- /dev/null
+++ b/crates/bins/wick/tests/unit/new/component-wasm.toml
@@ -0,0 +1,5 @@
+bin.name = "wick"
+args = ["new", "component", "wasmrs", "--json", "test", "--dry-run"]
+stdout = """
+{"output":{"files":["test.wick"]},"success":true}
+"""
diff --git a/crates/bins/wick/tests/unit/run/anonymous-component.toml b/crates/bins/wick/tests/unit/run/anonymous-component.toml
index 314131a19..cebbeba96 100644
--- a/crates/bins/wick/tests/unit/run/anonymous-component.toml
+++ b/crates/bins/wick/tests/unit/run/anonymous-component.toml
@@ -9,4 +9,6 @@ args = [
   "arg2",
   "33"
 ]
-stdout = "args: [\"mycli\", \"arg1\", \"22\", \"arg2\", \"33\"], interactive: { stdin: false, stdout: false, stderr: false }\nGot result for provided component: 8\nSTDIN is non-interactive but had no input.\n"
+stdout = """args: [\"mycli\", \"arg1\", \"22\", \"arg2\", \"33\"], interactive: { stdin: false, stdout: false, stderr: false }
+Got result for provided component: 8\nSTDIN is non-interactive but had no input.
+"""
diff --git a/crates/bins/wick/tests/unit/run/imported-component.toml b/crates/bins/wick/tests/unit/run/imported-component.toml
index 30e3ea0fd..02c0b5204 100644
--- a/crates/bins/wick/tests/unit/run/imported-component.toml
+++ b/crates/bins/wick/tests/unit/run/imported-component.toml
@@ -9,4 +9,6 @@ args = [
   "arg2",
   "33"
 ]
-stdout = "args: [\"mycli\", \"arg1\", \"22\", \"arg2\", \"33\"], interactive: { stdin: false, stdout: false, stderr: false }\nGot result for provided component: 8\nSTDIN is non-interactive but had no input.\n"
+stdout = """args: [\"mycli\", \"arg1\", \"22\", \"arg2\", \"33\"], interactive: { stdin: false, stdout: false, stderr: false }
+Got result for provided component: 8\nSTDIN is non-interactive but had no input.
+"""
diff --git a/crates/wick/wick-config/src/config/test_config.rs b/crates/wick/wick-config/src/config/test_config.rs
index ab35d4e1d..83febccb9 100644
--- a/crates/wick/wick-config/src/config/test_config.rs
+++ b/crates/wick/wick-config/src/config/test_config.rs
@@ -5,7 +5,7 @@ use asset_container::AssetManager;
 use crate::config;
 
 #[derive(Debug, Clone, Builder, derive_asset_container::AssetManager, property::Property)]
-#[property(get(public), set(private), mut(disable))]
+#[property(get(public), set(public), mut(public, suffix = "_mut"))]
 #[asset(asset(crate::config::AssetReference))]
 #[must_use]
 pub struct TestConfiguration {
diff --git a/crates/wick/wick-config/tests/v0_load_from_file.rs b/crates/wick/wick-config/tests/v0_load_from_file.rs
index 400f7cc2e..691ce8406 100644
--- a/crates/wick/wick-config/tests/v0_load_from_file.rs
+++ b/crates/wick/wick-config/tests/v0_load_from_file.rs
@@ -44,7 +44,7 @@ async fn load_minimal() -> Result<(), ManifestError> {
 async fn load_noversion_yaml() -> Result<(), ManifestError> {
   let result = load("./tests/manifests/v0/noversion.yaml").await;
   println!("result: {:?}", result);
-  assert!(matches!(result, Err(ManifestError::NoFormat)));
+  assert!(matches!(result, Err(ManifestError::NoFormat(_))));
   Ok(())
 }
 
diff --git a/tests/codegen-tests/src/import_types/mod.rs b/tests/codegen-tests/src/import_types/mod.rs
index d835f1179..30ace4438 100644
--- a/tests/codegen-tests/src/import_types/mod.rs
+++ b/tests/codegen-tests/src/import_types/mod.rs
@@ -12,8 +12,8 @@ pub use wick_component::flow_component::Context;
 extern "C" fn __wasmrs_init(guest_buffer_size: u32, host_buffer_size: u32, max_host_frame_len: u32) {
   wick_component::wasmrs_guest::init(guest_buffer_size, host_buffer_size, max_host_frame_len);
   wick_component::wasmrs_guest::register_request_response("wick", "__setup", Box::new(__setup));
-  wick_component::wasmrs_guest::register_request_channel("wick", "testop", Box::new(Component::testop_wrapper));
   wick_component::wasmrs_guest::register_request_channel("wick", "echo", Box::new(Component::echo_wrapper));
+  wick_component::wasmrs_guest::register_request_channel("wick", "testop", Box::new(Component::testop_wrapper));
 }
 #[cfg(target_family = "wasm")]
 thread_local! { static __CONFIG : std :: cell :: UnsafeCell < Option < SetupPayload >> = std :: cell :: UnsafeCell :: new (None) ; }
@@ -1520,25 +1520,14 @@ pub mod types {
     }
   }
 }
-#[doc = "Types associated with the `testop` operation"]
-pub mod testop {
+#[doc = "Types associated with the `echo` operation"]
+pub mod echo {
   use super::*;
-  #[derive(Debug, Clone, serde :: Serialize, serde :: Deserialize, PartialEq)]
-  pub struct Config {
-    pub a: String,
-    pub b: u32,
-  }
-  impl Default for Config {
-    fn default() -> Self {
-      Self {
-        a: Default::default(),
-        b: Default::default(),
-      }
-    }
-  }
+  #[derive(Debug, Clone, Default, serde :: Serialize, serde :: Deserialize, PartialEq)]
+  pub struct Config {}
   pub struct Outputs {
     #[allow(unused)]
-    pub(crate) output: wick_packet::Output<String>,
+    pub(crate) output: wick_packet::Output<types::http::HttpRequest>,
   }
   impl Outputs {
     pub fn new(channel: wasmrs_rx::FluxChannel<wasmrs::RawPayload, wasmrs::PayloadError>) -> Self {
@@ -1554,38 +1543,49 @@ pub mod testop {
 }
 # [async_trait :: async_trait (? Send)]
 #[cfg(target_family = "wasm")]
-pub trait TestopOperation {
+pub trait EchoOperation {
   type Error: std::fmt::Display;
   type Outputs;
   type Config: std::fmt::Debug;
   #[allow(unused)]
-  async fn testop(
-    message: WickStream<types::http::HttpResponse>,
+  async fn echo(
+    input: WickStream<types::http::HttpRequest>,
     outputs: Self::Outputs,
     ctx: wick_component::flow_component::Context<Self::Config>,
   ) -> std::result::Result<(), Self::Error>;
 }
 #[async_trait::async_trait]
 #[cfg(not(target_family = "wasm"))]
-pub trait TestopOperation {
+pub trait EchoOperation {
   type Error: std::fmt::Display + Send;
   type Outputs: Send;
   type Config: std::fmt::Debug + Send;
   #[allow(unused)]
-  async fn testop(
-    message: WickStream<types::http::HttpResponse>,
+  async fn echo(
+    input: WickStream<types::http::HttpRequest>,
     outputs: Self::Outputs,
     ctx: wick_component::flow_component::Context<Self::Config>,
   ) -> std::result::Result<(), Self::Error>;
 }
-#[doc = "Types associated with the `echo` operation"]
-pub mod echo {
+#[doc = "Types associated with the `testop` operation"]
+pub mod testop {
   use super::*;
-  #[derive(Debug, Clone, Default, serde :: Serialize, serde :: Deserialize, PartialEq)]
-  pub struct Config {}
+  #[derive(Debug, Clone, serde :: Serialize, serde :: Deserialize, PartialEq)]
+  pub struct Config {
+    pub a: String,
+    pub b: u32,
+  }
+  impl Default for Config {
+    fn default() -> Self {
+      Self {
+        a: Default::default(),
+        b: Default::default(),
+      }
+    }
+  }
   pub struct Outputs {
     #[allow(unused)]
-    pub(crate) output: wick_packet::Output<types::http::HttpRequest>,
+    pub(crate) output: wick_packet::Output<String>,
   }
   impl Outputs {
     pub fn new(channel: wasmrs_rx::FluxChannel<wasmrs::RawPayload, wasmrs::PayloadError>) -> Self {
@@ -1601,26 +1601,26 @@ pub mod echo {
 }
 # [async_trait :: async_trait (? Send)]
 #[cfg(target_family = "wasm")]
-pub trait EchoOperation {
+pub trait TestopOperation {
   type Error: std::fmt::Display;
   type Outputs;
   type Config: std::fmt::Debug;
   #[allow(unused)]
-  async fn echo(
-    input: WickStream<types::http::HttpRequest>,
+  async fn testop(
+    message: WickStream<types::http::HttpResponse>,
     outputs: Self::Outputs,
     ctx: wick_component::flow_component::Context<Self::Config>,
   ) -> std::result::Result<(), Self::Error>;
 }
 #[async_trait::async_trait]
 #[cfg(not(target_family = "wasm"))]
-pub trait EchoOperation {
+pub trait TestopOperation {
   type Error: std::fmt::Display + Send;
   type Outputs: Send;
   type Config: std::fmt::Debug + Send;
   #[allow(unused)]
-  async fn echo(
-    input: WickStream<types::http::HttpRequest>,
+  async fn testop(
+    message: WickStream<types::http::HttpResponse>,
     outputs: Self::Outputs,
     ctx: wick_component::flow_component::Context<Self::Config>,
   ) -> std::result::Result<(), Self::Error>;
@@ -1629,16 +1629,16 @@ pub trait EchoOperation {
 #[doc = "The struct that the component implementation hinges around"]
 pub struct Component;
 impl Component {
-  fn testop_wrapper(
+  fn echo_wrapper(
     mut input: wasmrs_rx::BoxFlux<wasmrs::Payload, wasmrs::PayloadError>,
   ) -> std::result::Result<
     wasmrs_rx::BoxFlux<wasmrs::RawPayload, wasmrs::PayloadError>,
     Box<dyn std::error::Error + Send + Sync>,
   > {
     let (channel, rx) = wasmrs_rx::FluxChannel::<wasmrs::RawPayload, wasmrs::PayloadError>::new_parts();
-    let outputs = testop::Outputs::new(channel.clone());
-    runtime::spawn("testop_wrapper", async move {
-      let (config, message) = wick_component :: payload_fan_out ! (input , raw : false , Box < dyn std :: error :: Error + Send + Sync > , testop :: Config , [("message" , types :: http :: HttpResponse) ,]);
+    let outputs = echo::Outputs::new(channel.clone());
+    runtime::spawn("echo_wrapper", async move {
+      let (config, input) = wick_component :: payload_fan_out ! (input , raw : false , Box < dyn std :: error :: Error + Send + Sync > , echo :: Config , [("input" , types :: http :: HttpRequest) ,]);
       let config = match config.await {
         Ok(Ok(config)) => config,
         _ => {
@@ -1646,22 +1646,22 @@ impl Component {
           return;
         }
       };
-      if let Err(e) = Component::testop(Box::pin(message), outputs, config).await {
+      if let Err(e) = Component::echo(Box::pin(input), outputs, config).await {
         let _ = channel.send_result(wick_packet::Packet::component_error(e.to_string()).into());
       }
     });
     Ok(Box::pin(rx))
   }
-  fn echo_wrapper(
+  fn testop_wrapper(
     mut input: wasmrs_rx::BoxFlux<wasmrs::Payload, wasmrs::PayloadError>,
   ) -> std::result::Result<
     wasmrs_rx::BoxFlux<wasmrs::RawPayload, wasmrs::PayloadError>,
     Box<dyn std::error::Error + Send + Sync>,
   > {
     let (channel, rx) = wasmrs_rx::FluxChannel::<wasmrs::RawPayload, wasmrs::PayloadError>::new_parts();
-    let outputs = echo::Outputs::new(channel.clone());
-    runtime::spawn("echo_wrapper", async move {
-      let (config, input) = wick_component :: payload_fan_out ! (input , raw : false , Box < dyn std :: error :: Error + Send + Sync > , echo :: Config , [("input" , types :: http :: HttpRequest) ,]);
+    let outputs = testop::Outputs::new(channel.clone());
+    runtime::spawn("testop_wrapper", async move {
+      let (config, message) = wick_component :: payload_fan_out ! (input , raw : false , Box < dyn std :: error :: Error + Send + Sync > , testop :: Config , [("message" , types :: http :: HttpResponse) ,]);
       let config = match config.await {
         Ok(Ok(config)) => config,
         _ => {
@@ -1669,7 +1669,7 @@ impl Component {
           return;
         }
       };
-      if let Err(e) = Component::echo(Box::pin(input), outputs, config).await {
+      if let Err(e) = Component::testop(Box::pin(message), outputs, config).await {
         let _ = channel.send_result(wick_packet::Packet::component_error(e.to_string()).into());
       }
     });