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.
- {act_cli-0.7.6 → act_cli-0.8.0}/Cargo.lock +86 -5
- {act_cli-0.7.6 → act_cli-0.8.0}/Cargo.toml +2 -2
- {act_cli-0.7.6 → act_cli-0.8.0}/PKG-INFO +1 -1
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/Cargo.toml +1 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/main.rs +143 -26
- act_cli-0.8.0/act-cli/src/resolve.rs +42 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/rmcp_bridge.rs +23 -1
- act_cli-0.8.0/crates/act-store/Cargo.toml +30 -0
- act_cli-0.8.0/crates/act-store/README.md +125 -0
- act_cli-0.8.0/crates/act-store/src/fetch.rs +605 -0
- act_cli-0.8.0/crates/act-store/src/index.rs +302 -0
- act_cli-0.8.0/crates/act-store/src/layout.rs +111 -0
- act_cli-0.8.0/crates/act-store/src/lib.rs +61 -0
- act_cli-0.8.0/crates/act-store/src/lock.rs +75 -0
- act_cli-0.8.0/crates/act-store/src/provenance.rs +172 -0
- act_cli-0.8.0/crates/act-store/src/reference.rs +181 -0
- act_cli-0.8.0/crates/act-store/src/referrer.rs +64 -0
- act_cli-0.8.0/crates/act-store/src/store.rs +626 -0
- act_cli-0.7.6/act-cli/src/resolve.rs +0 -418
- {act_cli-0.7.6 → act_cli-0.8.0}/README.md +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/README.md +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/build.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/config.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/format.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/http.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/bindings/mod.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/effective.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/fs_matcher.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/fs_policy.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/http_client.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/http_policy.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/mod.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/network.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/sessions.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/src/runtime/sockets_policy.rs +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/deps/act-core/act-core.wit +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/deps/act-tools/act-tools.wit +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/deps.lock +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/deps.toml +0 -0
- {act_cli-0.7.6 → act_cli-0.8.0}/act-cli/wit/world.wit +0 -0
- {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
|
+
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.
|
|
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.
|
|
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"
|
|
@@ -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
|
-
|
|
808
|
-
|
|
809
|
-
let
|
|
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) =
|
|
812
|
-
tokio::fs::copy(&
|
|
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
|
-
|
|
837
|
-
|
|
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
|
-
|
|
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
|