act-cli 0.7.1__tar.gz → 0.7.3__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 (28) hide show
  1. {act_cli-0.7.1 → act_cli-0.7.3}/Cargo.lock +7 -2
  2. {act_cli-0.7.1 → act_cli-0.7.3}/Cargo.toml +1 -1
  3. {act_cli-0.7.1 → act_cli-0.7.3}/PKG-INFO +18 -1
  4. {act_cli-0.7.1/act-cli → act_cli-0.7.3}/README.md +17 -0
  5. {act_cli-0.7.1 → act_cli-0.7.3/act-cli}/README.md +17 -0
  6. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/main.rs +146 -44
  7. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/resolve.rs +11 -0
  8. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/Cargo.toml +0 -0
  9. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/build.rs +0 -0
  10. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/config.rs +0 -0
  11. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/format.rs +0 -0
  12. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/http.rs +0 -0
  13. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/rmcp_bridge.rs +0 -0
  14. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/runtime/bindings/mod.rs +0 -0
  15. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/runtime/effective.rs +0 -0
  16. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/runtime/fs_matcher.rs +0 -0
  17. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/runtime/fs_policy.rs +0 -0
  18. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/runtime/http_client.rs +0 -0
  19. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/runtime/http_policy.rs +0 -0
  20. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/runtime/mod.rs +0 -0
  21. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/runtime/network.rs +0 -0
  22. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/src/runtime/sessions.rs +0 -0
  23. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/wit/deps/act-core/act-core.wit +0 -0
  24. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/wit/deps/act-tools/act-tools.wit +0 -0
  25. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/wit/deps.lock +0 -0
  26. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/wit/deps.toml +0 -0
  27. {act_cli-0.7.1 → act_cli-0.7.3}/act-cli/wit/world.wit +0 -0
  28. {act_cli-0.7.1 → act_cli-0.7.3}/pyproject.toml +0 -0
@@ -4,18 +4,23 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "act-build"
7
- version = "0.7.1"
7
+ version = "0.7.3"
8
8
  dependencies = [
9
9
  "act-types",
10
10
  "anyhow",
11
+ "base64",
11
12
  "ciborium",
12
13
  "clap",
14
+ "dirs",
13
15
  "globset",
14
16
  "json-patch",
17
+ "oci-client",
15
18
  "serde",
16
19
  "serde_json",
20
+ "sha2 0.11.0",
17
21
  "tar",
18
22
  "tempfile",
23
+ "tokio",
19
24
  "toml 1.1.2+spec-1.1.0",
20
25
  "tracing",
21
26
  "tracing-subscriber",
@@ -25,7 +30,7 @@ dependencies = [
25
30
 
26
31
  [[package]]
27
32
  name = "act-cli"
28
- version = "0.7.1"
33
+ version = "0.7.3"
29
34
  dependencies = [
30
35
  "act-types",
31
36
  "anyhow",
@@ -3,7 +3,7 @@ members = ["act-cli"]
3
3
  resolver = "3"
4
4
 
5
5
  [workspace.package]
6
- version = "0.7.1"
6
+ version = "0.7.3"
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.1
3
+ Version: 0.7.3
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -92,6 +92,12 @@ act-build pack target/wasm32-wasip2/release/my_component.wasm
92
92
 
93
93
  # Validate without modifying
94
94
  act-build validate target/wasm32-wasip2/release/my_component.wasm
95
+
96
+ # Publish as a CNCF Wasm OCI Artifact
97
+ act-build push my_component.wasm ghcr.io/actpkg/my-component:0.1.0 \
98
+ --also-tag latest \
99
+ --source https://github.com/actpkg/my-component \
100
+ --skip-if-identical
95
101
  ```
96
102
 
97
103
  Metadata is resolved via merge-patch from project manifests:
@@ -100,6 +106,17 @@ Metadata is resolved via merge-patch from project manifests:
100
106
  2. **Inline patch** from the same manifest (`[package.metadata.act-component]`, `[tool.act-component]`, or `actComponent`)
101
107
  3. **`act.toml`** — highest priority, applied last
102
108
 
109
+ `act-build push` produces artifacts conformant with the [CNCF
110
+ TAG-Runtime Wasm OCI Artifact spec](https://tag-runtime.cncf.io/wgs/wasm/deliverables/wasm-oci-artifact/):
111
+ manifest config has media type `application/vnd.wasm.config.v0+json`
112
+ (with `architecture`, `os`, `layerDigests`, and
113
+ `component.{exports,imports}` derived from the component's exports
114
+ and imports), and the layer is `application/wasm`.
115
+
116
+ Authentication is resolved in order: `OCI_USERNAME`/`OCI_PASSWORD`
117
+ env, then `GITHUB_TOKEN` for `ghcr.io`, then `~/.docker/config.json`
118
+ (or `$DOCKER_CONFIG/config.json`), then anonymous.
119
+
103
120
  ## Platform Support
104
121
 
105
122
  | Architecture | Linux (GNU) | Linux (musl) | macOS | Windows | Docker |
@@ -75,6 +75,12 @@ act-build pack target/wasm32-wasip2/release/my_component.wasm
75
75
 
76
76
  # Validate without modifying
77
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
78
84
  ```
79
85
 
80
86
  Metadata is resolved via merge-patch from project manifests:
@@ -83,6 +89,17 @@ Metadata is resolved via merge-patch from project manifests:
83
89
  2. **Inline patch** from the same manifest (`[package.metadata.act-component]`, `[tool.act-component]`, or `actComponent`)
84
90
  3. **`act.toml`** — highest priority, applied last
85
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
+
86
103
  ## Platform Support
87
104
 
88
105
  | Architecture | Linux (GNU) | Linux (musl) | macOS | Windows | Docker |
@@ -75,6 +75,12 @@ act-build pack target/wasm32-wasip2/release/my_component.wasm
75
75
 
76
76
  # Validate without modifying
77
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
78
84
  ```
79
85
 
80
86
  Metadata is resolved via merge-patch from project manifests:
@@ -83,6 +89,17 @@ Metadata is resolved via merge-patch from project manifests:
83
89
  2. **Inline patch** from the same manifest (`[package.metadata.act-component]`, `[tool.act-component]`, or `actComponent`)
84
90
  3. **`act.toml`** — highest priority, applied last
85
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
+
86
103
  ## Platform Support
87
104
 
88
105
  | Architecture | Linux (GNU) | Linux (musl) | macOS | Windows | Docker |
@@ -104,6 +104,16 @@ enum Command {
104
104
  #[arg(long, default_value = "{}")]
105
105
  args: String,
106
106
 
107
+ /// Session args as a JSON object. When set, the host opens a
108
+ /// session before the call (`open-session(args, metadata)`),
109
+ /// injects the returned id as `std:session-id` metadata for
110
+ /// the tool call, and closes the session before exit. Use
111
+ /// this when the component requires a session — bridges,
112
+ /// stateful components — and you want the whole open/call/
113
+ /// close cycle in one process.
114
+ #[arg(long)]
115
+ session_args: Option<String>,
116
+
107
117
  #[command(flatten)]
108
118
  opts: CommonOpts,
109
119
  },
@@ -217,8 +227,9 @@ async fn main() -> Result<()> {
217
227
  component,
218
228
  tool,
219
229
  args,
230
+ session_args,
220
231
  opts,
221
- } => cmd_call(component, tool, args, opts).await,
232
+ } => cmd_call(component, tool, args, session_args, opts).await,
222
233
  Command::Info {
223
234
  component,
224
235
  tools,
@@ -419,6 +430,7 @@ async fn cmd_call(
419
430
  component: ComponentRef,
420
431
  tool: String,
421
432
  args: String,
433
+ session_args: Option<String>,
422
434
  opts: CommonOpts,
423
435
  ) -> Result<()> {
424
436
  let pc = prepare_component(&component, &opts).await?;
@@ -427,64 +439,154 @@ async fn cmd_call(
427
439
  serde_json::from_str(&args).context("invalid --args JSON")?;
428
440
  let cbor_args = cbor::json_to_cbor(&arguments).context("encoding args as CBOR")?;
429
441
 
442
+ // If --session-args is set, open a session before the call and
443
+ // close it on the way out. session-id is injected into the call's
444
+ // metadata under `std:session-id`.
445
+ let session_id = match session_args {
446
+ Some(json) => {
447
+ if !pc.has_sessions {
448
+ anyhow::bail!(
449
+ "--session-args was set, but the component does not export \
450
+ act:sessions/session-provider"
451
+ );
452
+ }
453
+ Some(open_session_for_call(&pc, &json).await?)
454
+ }
455
+ None => None,
456
+ };
457
+
458
+ let mut metadata = pc.metadata.clone();
459
+ if let Some(ref id) = session_id {
460
+ metadata.insert(
461
+ act_types::constants::META_SESSION_ID,
462
+ serde_json::Value::String(id.clone()),
463
+ );
464
+ }
465
+
430
466
  let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
431
467
  let request = runtime::ComponentRequest::CallTool {
432
468
  name: tool,
433
469
  arguments: cbor_args,
434
- metadata: pc.metadata.clone().into(),
470
+ metadata: metadata.into(),
435
471
  reply: reply_tx,
436
472
  };
437
473
 
438
- pc.handle
439
- .send(request)
440
- .await
441
- .map_err(|_| anyhow::anyhow!("component actor unavailable"))?;
474
+ let send_result = pc.handle.send(request).await;
475
+ let call_result = match send_result {
476
+ Err(_) => Err(anyhow::anyhow!("component actor unavailable")),
477
+ Ok(()) => match reply_rx.await {
478
+ Err(_) => Err(anyhow::anyhow!("component actor dropped reply")),
479
+ Ok(r) => Ok(r),
480
+ },
481
+ };
442
482
 
443
- match reply_rx.await? {
444
- Ok(result) => {
445
- for event in &result.events {
446
- match event {
447
- runtime::exports::act::tools::tool_provider::ToolEvent::Content(part) => {
448
- let mime = part.mime_type.as_deref().unwrap_or("application/cbor");
449
- if mime.starts_with("text/")
450
- || mime == "application/json"
451
- || mime == "application/xml"
452
- {
453
- let text = String::from_utf8_lossy(&part.data);
454
- println!("{text}");
455
- } else if mime == "application/cbor" {
456
- let json_val = act_types::cbor::cbor_to_json(&part.data)
457
- .unwrap_or_else(|_| {
458
- serde_json::Value::String(format!(
459
- "[binary: {}, {} bytes]",
460
- mime,
461
- part.data.len()
462
- ))
463
- });
464
- match json_val {
465
- serde_json::Value::String(s) => println!("{s}"),
466
- other => println!("{}", serde_json::to_string_pretty(&other)?),
467
- }
468
- } else if std::io::IsTerminal::is_terminal(&std::io::stdout()) {
469
- println!("[binary: {}, {} bytes]", mime, part.data.len());
470
- } else {
471
- use std::io::Write;
472
- std::io::stdout().write_all(&part.data)?;
473
- }
474
- }
475
- runtime::exports::act::tools::tool_provider::ToolEvent::Error(err) => {
476
- let ls = act_types::types::LocalizedString::from(&err.message);
477
- anyhow::bail!("{}: {}", err.kind, ls.any_text());
483
+ // Best-effort close before returning the call result, so the
484
+ // session is closed even if the call errored.
485
+ if let Some(id) = session_id {
486
+ close_session_best_effort(&pc, id).await;
487
+ }
488
+
489
+ let result = call_result?.map_err(|e| match e {
490
+ runtime::ComponentError::Tool(te) => {
491
+ let ls = act_types::types::LocalizedString::from(&te.message);
492
+ anyhow::anyhow!("{}: {}", te.kind, ls.any_text())
493
+ }
494
+ runtime::ComponentError::Internal(e) => e,
495
+ })?;
496
+
497
+ for event in &result.events {
498
+ match event {
499
+ runtime::exports::act::tools::tool_provider::ToolEvent::Content(part) => {
500
+ let mime = part.mime_type.as_deref().unwrap_or("application/cbor");
501
+ if mime.starts_with("text/")
502
+ || mime == "application/json"
503
+ || mime == "application/xml"
504
+ {
505
+ let text = String::from_utf8_lossy(&part.data);
506
+ println!("{text}");
507
+ } else if mime == "application/cbor" {
508
+ let json_val = act_types::cbor::cbor_to_json(&part.data).unwrap_or_else(|_| {
509
+ serde_json::Value::String(format!(
510
+ "[binary: {}, {} bytes]",
511
+ mime,
512
+ part.data.len()
513
+ ))
514
+ });
515
+ match json_val {
516
+ serde_json::Value::String(s) => println!("{s}"),
517
+ other => println!("{}", serde_json::to_string_pretty(&other)?),
478
518
  }
519
+ } else if std::io::IsTerminal::is_terminal(&std::io::stdout()) {
520
+ println!("[binary: {}, {} bytes]", mime, part.data.len());
521
+ } else {
522
+ use std::io::Write;
523
+ std::io::stdout().write_all(&part.data)?;
479
524
  }
480
525
  }
481
- Ok(())
526
+ runtime::exports::act::tools::tool_provider::ToolEvent::Error(err) => {
527
+ let ls = act_types::types::LocalizedString::from(&err.message);
528
+ anyhow::bail!("{}: {}", err.kind, ls.any_text());
529
+ }
482
530
  }
531
+ }
532
+ Ok(())
533
+ }
534
+
535
+ /// Marshal a JSON object of session args into the WIT shape and call
536
+ /// `open-session` against the prepared component. Returns the
537
+ /// allocated session-id.
538
+ async fn open_session_for_call(pc: &PreparedComponent, json: &str) -> Result<String> {
539
+ let value: serde_json::Value =
540
+ serde_json::from_str(json).context("invalid --session-args JSON")?;
541
+ let serde_json::Value::Object(args_obj) = value else {
542
+ anyhow::bail!("--session-args must be a JSON object");
543
+ };
544
+ let mut wit_args: Vec<(String, Vec<u8>)> = Vec::with_capacity(args_obj.len());
545
+ for (key, value) in args_obj {
546
+ let bytes =
547
+ act_types::cbor::json_to_cbor(&value).context("encoding session arg as CBOR")?;
548
+ wit_args.push((key, bytes));
549
+ }
550
+
551
+ let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
552
+ pc.handle
553
+ .send(runtime::ComponentRequest::OpenSession {
554
+ args: wit_args,
555
+ metadata: pc.metadata.clone().into(),
556
+ reply: reply_tx,
557
+ })
558
+ .await
559
+ .map_err(|_| anyhow::anyhow!("component actor unavailable"))?;
560
+
561
+ match reply_rx.await? {
562
+ Ok(session) => Ok(session.id),
483
563
  Err(runtime::ComponentError::Tool(te)) => {
484
564
  let ls = act_types::types::LocalizedString::from(&te.message);
485
- anyhow::bail!("{}: {}", te.kind, ls.any_text());
565
+ anyhow::bail!("open-session failed: {}: {}", te.kind, ls.any_text());
486
566
  }
487
- Err(runtime::ComponentError::Internal(e)) => Err(e),
567
+ Err(runtime::ComponentError::Internal(e)) => Err(e.context("open-session failed")),
568
+ }
569
+ }
570
+
571
+ /// Best-effort close. Logs failures at debug; never propagates errors,
572
+ /// because the call result is what the user asked for and a failed
573
+ /// close should not surface as the command's exit code.
574
+ async fn close_session_best_effort(pc: &PreparedComponent, session_id: String) {
575
+ let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
576
+ if pc
577
+ .handle
578
+ .send(runtime::ComponentRequest::CloseSession {
579
+ session_id: session_id.clone(),
580
+ reply: reply_tx,
581
+ })
582
+ .await
583
+ .is_err()
584
+ {
585
+ tracing::debug!(%session_id, "actor unavailable for close-session");
586
+ return;
587
+ }
588
+ if let Err(e) = reply_rx.await {
589
+ tracing::debug!(%session_id, error = %e, "close-session reply dropped");
488
590
  }
489
591
  }
490
592
 
@@ -224,6 +224,17 @@ async fn resolve_oci(reference: &str, fresh: bool) -> Result<PathBuf> {
224
224
  .first()
225
225
  .context("no layers in OCI manifest")?;
226
226
 
227
+ // Per the CNCF Wasm OCI Artifact spec, the layer media type must be
228
+ // `application/wasm`. We tolerate the empty/legacy case for backwards
229
+ // compatibility but log a warning — anything else is rejected.
230
+ match layer.media_type.as_str() {
231
+ "application/wasm" => {}
232
+ "" => tracing::warn!(%reference, "OCI layer has no media type (legacy artifact)"),
233
+ other => anyhow::bail!(
234
+ "unexpected layer media type for {reference}: '{other}' (expected 'application/wasm')"
235
+ ),
236
+ }
237
+
227
238
  let total = if layer.size > 0 {
228
239
  Some(layer.size as u64)
229
240
  } else {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes