act-cli 0.7.6__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {act_cli-0.7.6 → act_cli-0.8.0}/Cargo.lock +86 -5
  2. {act_cli-0.7.6 → act_cli-0.8.0}/Cargo.toml +2 -2
  3. {act_cli-0.7.6 → act_cli-0.8.0}/PKG-INFO +1 -1
  4. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/Cargo.toml +1 -0
  5. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/main.rs +143 -26
  6. act_cli-0.8.0/act-cli/src/resolve.rs +42 -0
  7. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/rmcp_bridge.rs +23 -1
  8. act_cli-0.8.0/crates/act-store/Cargo.toml +30 -0
  9. act_cli-0.8.0/crates/act-store/README.md +125 -0
  10. act_cli-0.8.0/crates/act-store/src/fetch.rs +605 -0
  11. act_cli-0.8.0/crates/act-store/src/index.rs +302 -0
  12. act_cli-0.8.0/crates/act-store/src/layout.rs +111 -0
  13. act_cli-0.8.0/crates/act-store/src/lib.rs +61 -0
  14. act_cli-0.8.0/crates/act-store/src/lock.rs +75 -0
  15. act_cli-0.8.0/crates/act-store/src/provenance.rs +172 -0
  16. act_cli-0.8.0/crates/act-store/src/reference.rs +181 -0
  17. act_cli-0.8.0/crates/act-store/src/referrer.rs +64 -0
  18. act_cli-0.8.0/crates/act-store/src/store.rs +626 -0
  19. act_cli-0.7.6/act-cli/src/resolve.rs +0 -418
  20. {act_cli-0.7.6 → act_cli-0.8.0}/README.md +0 -0
  21. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/README.md +0 -0
  22. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/build.rs +0 -0
  23. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/config.rs +0 -0
  24. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/format.rs +0 -0
  25. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/http.rs +0 -0
  26. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/bindings/mod.rs +0 -0
  27. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/effective.rs +0 -0
  28. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/fs_matcher.rs +0 -0
  29. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/fs_policy.rs +0 -0
  30. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/http_client.rs +0 -0
  31. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/http_policy.rs +0 -0
  32. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/mod.rs +0 -0
  33. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/network.rs +0 -0
  34. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/sessions.rs +0 -0
  35. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/sockets_policy.rs +0 -0
  36. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/deps/act-core/act-core.wit +0 -0
  37. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/deps/act-tools/act-tools.wit +0 -0
  38. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/deps.lock +0 -0
  39. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/deps.toml +0 -0
  40. {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/world.wit +0 -0
  41. {act_cli-0.7.6 → act_cli-0.8.0}/pyproject.toml +0 -0
@@ -4,7 +4,7 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "act-build"
7
- version = "0.7.6"
7
+ version = "0.8.0"
8
8
  dependencies = [
9
9
  "act-types",
10
10
  "anyhow",
@@ -18,7 +18,7 @@ dependencies = [
18
18
  "json-patch",
19
19
  "minijinja",
20
20
  "oci-client",
21
- "reqwest",
21
+ "reqwest 0.13.3",
22
22
  "serde",
23
23
  "serde_json",
24
24
  "sha2 0.11.0",
@@ -35,8 +35,9 @@ dependencies = [
35
35
 
36
36
  [[package]]
37
37
  name = "act-cli"
38
- version = "0.7.6"
38
+ version = "0.8.0"
39
39
  dependencies = [
40
+ "act-store",
40
41
  "act-types",
41
42
  "anyhow",
42
43
  "axum",
@@ -57,7 +58,7 @@ dependencies = [
57
58
  "owo-colors",
58
59
  "path-clean",
59
60
  "regex",
60
- "reqwest",
61
+ "reqwest 0.13.3",
61
62
  "rmcp",
62
63
  "serde",
63
64
  "serde_json",
@@ -78,6 +79,27 @@ dependencies = [
78
79
  "wasmtime-wasi-http",
79
80
  ]
80
81
 
82
+ [[package]]
83
+ name = "act-store"
84
+ version = "0.1.0"
85
+ dependencies = [
86
+ "chrono",
87
+ "dirs",
88
+ "fs2",
89
+ "oci-client",
90
+ "oci-spec",
91
+ "regex",
92
+ "reqwest 0.12.28",
93
+ "serde",
94
+ "serde_json",
95
+ "sha2 0.11.0",
96
+ "tempfile",
97
+ "thiserror 2.0.18",
98
+ "tokio",
99
+ "tracing",
100
+ "url",
101
+ ]
102
+
81
103
  [[package]]
82
104
  name = "act-types"
83
105
  version = "0.7.1"
@@ -1203,6 +1225,16 @@ dependencies = [
1203
1225
  "windows-sys 0.59.0",
1204
1226
  ]
1205
1227
 
1228
+ [[package]]
1229
+ name = "fs2"
1230
+ version = "0.4.3"
1231
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1232
+ checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
1233
+ dependencies = [
1234
+ "libc",
1235
+ "winapi",
1236
+ ]
1237
+
1206
1238
  [[package]]
1207
1239
  name = "fs_extra"
1208
1240
  version = "1.3.0"
@@ -1577,6 +1609,7 @@ dependencies = [
1577
1609
  "tokio",
1578
1610
  "tokio-rustls",
1579
1611
  "tower-service",
1612
+ "webpki-roots",
1580
1613
  ]
1581
1614
 
1582
1615
  [[package]]
@@ -2219,7 +2252,7 @@ dependencies = [
2219
2252
  "oci-spec",
2220
2253
  "olpc-cjson",
2221
2254
  "regex",
2222
- "reqwest",
2255
+ "reqwest 0.13.3",
2223
2256
  "serde",
2224
2257
  "serde_json",
2225
2258
  "sha2 0.11.0",
@@ -2720,6 +2753,44 @@ version = "0.8.10"
2720
2753
  source = "registry+https://github.com/rust-lang/crates.io-index"
2721
2754
  checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
2722
2755
 
2756
+ [[package]]
2757
+ name = "reqwest"
2758
+ version = "0.12.28"
2759
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2760
+ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
2761
+ dependencies = [
2762
+ "base64",
2763
+ "bytes",
2764
+ "futures-core",
2765
+ "http",
2766
+ "http-body",
2767
+ "http-body-util",
2768
+ "hyper",
2769
+ "hyper-rustls",
2770
+ "hyper-util",
2771
+ "js-sys",
2772
+ "log",
2773
+ "percent-encoding",
2774
+ "pin-project-lite",
2775
+ "quinn",
2776
+ "rustls",
2777
+ "rustls-pki-types",
2778
+ "serde",
2779
+ "serde_json",
2780
+ "serde_urlencoded",
2781
+ "sync_wrapper",
2782
+ "tokio",
2783
+ "tokio-rustls",
2784
+ "tower",
2785
+ "tower-http",
2786
+ "tower-service",
2787
+ "url",
2788
+ "wasm-bindgen",
2789
+ "wasm-bindgen-futures",
2790
+ "web-sys",
2791
+ "webpki-roots",
2792
+ ]
2793
+
2723
2794
  [[package]]
2724
2795
  name = "reqwest"
2725
2796
  version = "0.13.3"
@@ -2873,6 +2944,7 @@ checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
2873
2944
  dependencies = [
2874
2945
  "aws-lc-rs",
2875
2946
  "once_cell",
2947
+ "ring",
2876
2948
  "rustls-pki-types",
2877
2949
  "rustls-webpki",
2878
2950
  "subtle",
@@ -4454,6 +4526,15 @@ dependencies = [
4454
4526
  "rustls-pki-types",
4455
4527
  ]
4456
4528
 
4529
+ [[package]]
4530
+ name = "webpki-roots"
4531
+ version = "1.0.7"
4532
+ source = "registry+https://github.com/rust-lang/crates.io-index"
4533
+ checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
4534
+ dependencies = [
4535
+ "rustls-pki-types",
4536
+ ]
4537
+
4457
4538
  [[package]]
4458
4539
  name = "winapi"
4459
4540
  version = "0.3.9"
@@ -1,9 +1,9 @@
1
1
  [workspace]
2
- members = ["act-cli"]
2
+ members = ["act-cli", "crates/act-store"]
3
3
  resolver = "3"
4
4
 
5
5
  [workspace.package]
6
- version = "0.7.6"
6
+ version = "0.8.0"
7
7
  edition = "2024"
8
8
  license = "MIT OR Apache-2.0"
9
9
  repository = "https://github.com/actcore/act-cli"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: act-cli
3
- Version: 0.7.6
3
+ Version: 0.8.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -56,6 +56,7 @@ cidr = "0.3"
56
56
  globset = "0.4"
57
57
  path-clean = "1"
58
58
  owo-colors = { version = "4.3.0", features = ["supports-colors"] }
59
+ act-store = { version = "0.1.0", path = "../crates/act-store" }
59
60
 
60
61
  [dev-dependencies]
61
62
  tempfile = "3"
@@ -185,6 +185,27 @@ enum Command {
185
185
  /// the session lives as long as the host).
186
186
  #[command(subcommand)]
187
187
  Session(SessionCommand),
188
+ /// Manage the local component store (list, update, gc).
189
+ #[command(subcommand)]
190
+ Store(StoreCommand),
191
+ }
192
+
193
+ #[derive(clap::Subcommand)]
194
+ enum StoreCommand {
195
+ /// List components in the local store.
196
+ List {
197
+ /// Output format.
198
+ #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
199
+ format: OutputFormat,
200
+ },
201
+ /// Re-resolve stored components and re-pull any whose digest moved.
202
+ Update {
203
+ /// A single ref to update (omit to update all stored components).
204
+ #[arg(name = "ref")]
205
+ reference: Option<ComponentRef>,
206
+ },
207
+ /// Delete store blobs no longer referenced by any component.
208
+ Gc,
188
209
  }
189
210
 
190
211
  #[derive(clap::Subcommand)]
@@ -220,6 +241,7 @@ async fn main() -> Result<()> {
220
241
  Command::Session(sub) => match sub {
221
242
  SessionCommand::OpenArgsSchema { opts, .. } => opts.config.as_deref(),
222
243
  },
244
+ Command::Store(_) => None,
223
245
  };
224
246
  let log_level = config::load_config(config_path)
225
247
  .ok()
@@ -269,6 +291,11 @@ async fn main() -> Result<()> {
269
291
  cmd_session_open_args_schema(component, opts).await
270
292
  }
271
293
  },
294
+ Command::Store(sub) => match sub {
295
+ StoreCommand::List { format } => cmd_list(format).await,
296
+ StoreCommand::Update { reference } => cmd_update(reference).await,
297
+ StoreCommand::Gc => cmd_gc().await,
298
+ },
272
299
  }
273
300
  }
274
301
 
@@ -804,42 +831,132 @@ async fn cmd_pull(
804
831
  output: Option<PathBuf>,
805
832
  output_from_ref: bool,
806
833
  ) -> Result<()> {
807
- // Resolve to local path (downloads to cache for remote refs)
808
- // Always download fresh — pull is explicit user action
809
- let cached_path = resolve::resolve(&reference, true).await?;
834
+ let store = resolve::open_store()?;
835
+ let reference_str = reference.to_string();
836
+ let stored = act_store::pull(&store, &reference_str)
837
+ .await
838
+ .with_context(|| format!("pulling {reference_str}"))?;
839
+
840
+ // Path to the stored wasm blob (read-through hit; no re-pull).
841
+ let stored_path = act_store::ensure(&store, &reference_str).await?;
842
+
843
+ let export = output.or_else(|| {
844
+ output_from_ref.then(|| {
845
+ let ref_str = reference.to_string();
846
+ let base = ref_str
847
+ .rsplit('/')
848
+ .next()
849
+ .unwrap_or(&ref_str)
850
+ .split(':')
851
+ .next()
852
+ .unwrap_or(&ref_str);
853
+ let filename = if base.ends_with(".wasm") {
854
+ base.to_string()
855
+ } else {
856
+ format!("{base}.wasm")
857
+ };
858
+ PathBuf::from(filename)
859
+ })
860
+ });
810
861
 
811
- if let Some(out) = output {
812
- tokio::fs::copy(&cached_path, &out)
813
- .await
814
- .with_context(|| format!("copying to {}", out.display()))?;
815
- println!("{}", out.display());
816
- } else if output_from_ref {
817
- let ref_str = reference.to_string();
818
- let base = ref_str
819
- .rsplit('/')
820
- .next()
821
- .unwrap_or(&ref_str)
822
- .split(':')
823
- .next()
824
- .unwrap_or(&ref_str);
825
- let filename = if base.ends_with(".wasm") {
826
- base.to_string()
827
- } else {
828
- format!("{base}.wasm")
829
- };
830
- let out = PathBuf::from(&filename);
831
- tokio::fs::copy(&cached_path, &out)
862
+ if let Some(out) = export {
863
+ tokio::fs::copy(&stored_path, &out)
832
864
  .await
833
865
  .with_context(|| format!("copying to {}", out.display()))?;
834
866
  println!("{}", out.display());
835
867
  } else {
836
- // No output flag — print cached path
837
- println!("{}", cached_path.display());
868
+ println!(
869
+ "{} -> {} (sha256:{})",
870
+ reference_str,
871
+ stored_path.display(),
872
+ stored.manifest_digest
873
+ );
874
+ }
875
+ Ok(())
876
+ }
877
+
878
+ // ── Store subcommands ─────────────────────────────────────────────────────────
879
+
880
+ async fn cmd_list(format: OutputFormat) -> Result<()> {
881
+ let store = resolve::open_store()?;
882
+ let mut items = store.list()?;
883
+ items.sort_by(|a, b| source_ref(&a.provenance).cmp(source_ref(&b.provenance)));
884
+ match format {
885
+ OutputFormat::Json => {
886
+ let rows: Vec<_> = items
887
+ .iter()
888
+ .map(|s| {
889
+ serde_json::json!({
890
+ "ref": source_ref(&s.provenance),
891
+ "digest": s.provenance.digest,
892
+ "name": s.provenance.name,
893
+ "version": s.provenance.version,
894
+ "fetched_at": s.provenance.fetched_at,
895
+ })
896
+ })
897
+ .collect();
898
+ println!("{}", serde_json::to_string_pretty(&rows)?);
899
+ }
900
+ OutputFormat::Text => {
901
+ if items.is_empty() {
902
+ println!("(store is empty)");
903
+ }
904
+ for s in &items {
905
+ println!(
906
+ "{}\t{}\t{}",
907
+ source_ref(&s.provenance),
908
+ s.provenance.version.as_deref().unwrap_or("-"),
909
+ s.provenance.digest
910
+ );
911
+ }
912
+ }
838
913
  }
914
+ Ok(())
915
+ }
839
916
 
917
+ async fn cmd_update(reference: Option<ComponentRef>) -> Result<()> {
918
+ let store = resolve::open_store()?;
919
+ let refs: Vec<String> = match reference {
920
+ Some(r) => vec![r.to_string()],
921
+ None => store
922
+ .list()?
923
+ .iter()
924
+ .map(|s| source_ref(&s.provenance).to_string())
925
+ .collect(),
926
+ };
927
+ if refs.is_empty() {
928
+ println!("(store is empty)");
929
+ return Ok(());
930
+ }
931
+ for r in refs {
932
+ match act_store::update(&store, &r).await {
933
+ Ok(act_store::UpdateOutcome::Unchanged) => println!("{r}\tunchanged"),
934
+ Ok(act_store::UpdateOutcome::Updated { from, to }) => {
935
+ println!("{r}\tupdated {from} -> {to}")
936
+ }
937
+ Ok(act_store::UpdateOutcome::NotStored) => println!("{r}\tnot stored"),
938
+ Err(e) => eprintln!("{r}\tERROR: {e}"),
939
+ }
940
+ }
941
+ Ok(())
942
+ }
943
+
944
+ async fn cmd_gc() -> Result<()> {
945
+ let store = resolve::open_store()?;
946
+ let removed = store.gc()?;
947
+ println!("removed {removed} unreferenced blob(s)");
840
948
  Ok(())
841
949
  }
842
950
 
951
+ /// The source ref (as typed) recorded in a provenance.
952
+ fn source_ref(p: &act_store::Provenance) -> &str {
953
+ match &p.source {
954
+ act_store::Source::Oci { reference } => reference,
955
+ act_store::Source::Http { url, .. } => url,
956
+ act_store::Source::Local { path } => path,
957
+ }
958
+ }
959
+
843
960
  #[cfg(test)]
844
961
  mod tests {
845
962
  use super::*;
@@ -0,0 +1,42 @@
1
+ //! Component reference resolution, backed by the shared `act-store`.
2
+ //!
3
+ //! `ComponentRef` is re-exported from `act-store` (the parsing source of truth).
4
+ //! Local refs run in place; remote refs (OCI/HTTP) resolve read-through the
5
+ //! store (pulled on first use, then served from disk).
6
+
7
+ use std::path::PathBuf;
8
+
9
+ use anyhow::{Context, Result};
10
+
11
+ pub use act_store::Ref as ComponentRef;
12
+
13
+ /// Open the shared component store at its platform default location.
14
+ pub fn open_store() -> Result<act_store::Store> {
15
+ let dir = act_store::store_dir().context("locating component store")?;
16
+ act_store::Store::open(&dir).context("opening component store")
17
+ }
18
+
19
+ /// Resolve a component reference to a local `.wasm` path.
20
+ ///
21
+ /// Local files are used in place (never copied into the store). Remote refs
22
+ /// (OCI/HTTP) are served read-through from the store; `fresh` forces a re-pull.
23
+ pub async fn resolve(component_ref: &ComponentRef, fresh: bool) -> Result<PathBuf> {
24
+ if let ComponentRef::Local(path) = component_ref {
25
+ anyhow::ensure!(
26
+ tokio::fs::try_exists(path).await.unwrap_or(false),
27
+ "component not found: {}",
28
+ path.display()
29
+ );
30
+ return Ok(path.clone());
31
+ }
32
+ let store = open_store()?;
33
+ let reference = component_ref.to_string();
34
+ if fresh {
35
+ act_store::pull(&store, &reference)
36
+ .await
37
+ .with_context(|| format!("pulling {reference}"))?;
38
+ }
39
+ act_store::ensure(&store, &reference)
40
+ .await
41
+ .with_context(|| format!("resolving {reference}"))
42
+ }
@@ -41,7 +41,11 @@ pub struct ActRmcpBridge {
41
41
  fn map_content_part(part: &runtime::exports::act::tools::tool_provider::ContentPart) -> Content {
42
42
  let mime = part.mime_type.as_deref().unwrap_or("");
43
43
 
44
- if mime.starts_with("text/") {
44
+ // UTF-8 text-like payloads surface as MCP text content verbatim. This
45
+ // covers `text/*` and JSON (`application/json`, `application/*+json`) —
46
+ // JSON bytes are UTF-8, not CBOR, so they must not hit the base64 path
47
+ // below. Matches the ACT-HTTP transport, which also treats JSON as text.
48
+ if mime.starts_with("text/") || mime == "application/json" || mime.ends_with("+json") {
45
49
  let text = String::from_utf8_lossy(&part.data).into_owned();
46
50
  return Content::text(text);
47
51
  }
@@ -717,6 +721,24 @@ mod tests {
717
721
  );
718
722
  }
719
723
 
724
+ #[test]
725
+ fn map_content_json_decodes_to_text() {
726
+ // application/json content (UTF-8 JSON bytes, as emitted by act-sdk's
727
+ // `Json<T>`) must surface as the literal JSON string — NOT base64.
728
+ let json = br#"{"id":1,"name":"Fixture WS"}"#;
729
+ let c = map_content_part(&part(Some("application/json"), json));
730
+ let text = content_text(&c).expect("json must become text");
731
+ assert_eq!(text, r#"{"id":1,"name":"Fixture WS"}"#);
732
+ }
733
+
734
+ #[test]
735
+ fn map_content_json_suffix_decodes_to_text() {
736
+ let body = br#"{"ok":true}"#;
737
+ let c = map_content_part(&part(Some("application/vnd.api+json"), body));
738
+ let text = content_text(&c).expect("+json must become text");
739
+ assert_eq!(text, r#"{"ok":true}"#);
740
+ }
741
+
720
742
  #[test]
721
743
  fn map_content_opaque_falls_back_to_base64() {
722
744
  let bytes = vec![0xFF, 0xD8, 0xFF, 0xE0];
@@ -0,0 +1,30 @@
1
+ [package]
2
+ name = "act-store"
3
+ description = "Local OCI-layout component store for ACT (act-cli, act-toolserver)"
4
+ version = "0.1.0"
5
+ edition.workspace = true
6
+ license.workspace = true
7
+ repository.workspace = true
8
+ homepage.workspace = true
9
+ readme = "README.md"
10
+ keywords = ["act", "wasm", "oci", "component", "store"]
11
+
12
+ [dependencies]
13
+ serde = { version = "1", features = ["derive"] }
14
+ serde_json = "1"
15
+ sha2 = "0.11"
16
+ thiserror = "2"
17
+ fs2 = "0.4"
18
+ oci-spec = "0.9.0"
19
+ dirs = "6.0.0"
20
+ tokio = { version = "1.52.3", features = ["rt", "macros", "fs", "io-util"] }
21
+ oci-client = "0.17"
22
+ reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
23
+ chrono = { version = "0.4.44", default-features = false, features = ["clock"] }
24
+ url = "2.5.8"
25
+ regex = "1.12.3"
26
+ tracing = "0.1.44"
27
+
28
+ [dev-dependencies]
29
+ tempfile = "3"
30
+ tokio = { version = "1.52.3", features = ["rt", "macros", "rt-multi-thread"] }
@@ -0,0 +1,125 @@
1
+ # ACT CLI & Build Tools
2
+
3
+ Host and build [ACT](https://actcore.dev) (Agent Component Tools) WebAssembly components.
4
+
5
+ This repo contains two tools:
6
+
7
+ - **`act`** — run, call, inspect, and serve ACT components from local files, HTTP URLs, or OCI registries
8
+ - **`act-build`** — post-process compiled WASM components: embed metadata, skills, and custom sections
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ # act (CLI host)
14
+ npm i -g @actcore/act
15
+ pip install act-cli
16
+ cargo install act-cli
17
+
18
+ # act-build (build tool)
19
+ npm i -g @actcore/act-build
20
+ pip install act-build
21
+ cargo install act-build
22
+ ```
23
+
24
+ Pre-built binaries available on [GitHub Releases](https://github.com/actcore/act-cli/releases) and Docker (`ghcr.io/actcore/act`).
25
+
26
+ ## act — Component Host
27
+
28
+ ```bash
29
+ # Discover tools in a component
30
+ act info --tools ghcr.io/actpkg/sqlite:0.1.0
31
+
32
+ # Call a tool
33
+ act call ghcr.io/actpkg/sqlite:0.1.0 query \
34
+ --args '{"sql":"SELECT sqlite_version()"}' \
35
+ --metadata '{"database_path":"/data/app.db"}' \
36
+ --allow-dir /data:./data
37
+
38
+ # Serve over HTTP
39
+ act run -l ghcr.io/actpkg/sqlite:0.1.0
40
+
41
+ # Serve over MCP stdio
42
+ act run --mcp ghcr.io/actpkg/sqlite:0.1.0
43
+ ```
44
+
45
+ Components can be referenced as:
46
+ - **OCI refs:** `ghcr.io/actpkg/sqlite:0.1.0`
47
+ - **HTTP URLs:** `https://example.com/component.wasm`
48
+ - **Local paths:** `./component.wasm`
49
+
50
+ Remote components are cached in `~/.cache/act/components/`.
51
+
52
+ ### Commands
53
+
54
+ | Command | Description |
55
+ |---------|-------------|
56
+ | `run` | Serve a component over ACT-HTTP (`-l`) or MCP stdio (`--mcp`) |
57
+ | `call` | Call a tool directly, print result to stdout |
58
+ | `info` | Show component metadata, tools, and schemas (`--tools`, `--format text\|json`) |
59
+ | `pull` | Download a component from OCI or HTTP to local file |
60
+
61
+ ### HTTP Endpoints (`run -l`)
62
+
63
+ | Method | Path | Description |
64
+ |--------|------|-------------|
65
+ | `GET` | `/info` | Component metadata |
66
+ | `POST` | `/metadata-schema` | JSON Schema for metadata |
67
+ | `POST/QUERY` | `/tools` | List tools |
68
+ | `POST/QUERY` | `/tools/{name}` | Call a tool (SSE with `Accept: text/event-stream`) |
69
+
70
+ ## act-build — Component Build Tool
71
+
72
+ ```bash
73
+ # Embed act:component metadata, act:skill, and WASM custom sections
74
+ act-build pack target/wasm32-wasip2/release/my_component.wasm
75
+
76
+ # Validate without modifying
77
+ act-build validate target/wasm32-wasip2/release/my_component.wasm
78
+
79
+ # Publish as a CNCF Wasm OCI Artifact
80
+ act-build push my_component.wasm ghcr.io/actpkg/my-component:0.1.0 \
81
+ --also-tag latest \
82
+ --source https://github.com/actpkg/my-component \
83
+ --skip-if-identical
84
+ ```
85
+
86
+ Metadata is resolved via merge-patch from project manifests:
87
+
88
+ 1. **Base** from `Cargo.toml`, `pyproject.toml`, or `package.json` (name, version, description)
89
+ 2. **Inline patch** from the same manifest (`[package.metadata.act-component]`, `[tool.act-component]`, or `actComponent`)
90
+ 3. **`act.toml`** — highest priority, applied last
91
+
92
+ `act-build push` produces artifacts conformant with the [CNCF
93
+ TAG-Runtime Wasm OCI Artifact spec](https://tag-runtime.cncf.io/wgs/wasm/deliverables/wasm-oci-artifact/):
94
+ manifest config has media type `application/vnd.wasm.config.v0+json`
95
+ (with `architecture`, `os`, `layerDigests`, and
96
+ `component.{exports,imports}` derived from the component's exports
97
+ and imports), and the layer is `application/wasm`.
98
+
99
+ Authentication is resolved in order: `OCI_USERNAME`/`OCI_PASSWORD`
100
+ env, then `GITHUB_TOKEN` for `ghcr.io`, then `~/.docker/config.json`
101
+ (or `$DOCKER_CONFIG/config.json`), then anonymous.
102
+
103
+ ## Platform Support
104
+
105
+ | Architecture | Linux (GNU) | Linux (musl) | macOS | Windows | Docker |
106
+ |-------------|:-----------:|:------------:|:-----:|:-------:|:------:|
107
+ | x86_64 | ✓ | ✓ | ✓ | ✓ | ✓ |
108
+ | aarch64 | ✓ | ✓ | ✓ | ✓ | ✓ |
109
+ | riscv64 | ✓ | ✓ | — | — | ✓ |
110
+
111
+ RISC-V (`riscv64`) is a first-class target. Regressions on RISC-V are release-blocking.
112
+
113
+ ## Building
114
+
115
+ ```bash
116
+ cargo build --release # both tools
117
+ cargo build -p act-cli # act only
118
+ cargo build -p act-build # act-build only
119
+ ```
120
+
121
+ Set `RUST_LOG=act=debug` for verbose output.
122
+
123
+ ## License
124
+
125
+ MIT OR Apache-2.0