Fix examples for renamed leaf endpoint surface

Update the remote shell examples to use unshell::leaves and the leaf_endpoint feature-gated endpoint module, and restore the local macro aliasing needed after removing the direct unshell dependency from unshell-leaves.
This commit is contained in:
Michael Mikovsky
2026-04-26 12:57:56 -06:00
parent d4100d0604
commit 4f8835bd25
10 changed files with 87 additions and 110 deletions
Generated
+2 -1
View File
@@ -1454,7 +1454,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"portable-pty", "portable-pty",
"rkyv", "rkyv",
"unshell", "unshell-macros",
"unshell-protocol",
] ]
[[package]] [[package]]
+9 -3
View File
@@ -44,6 +44,11 @@ description = "Pure no_std implementation of the UnShell Protocol"
default = [] default = []
log = [] log = []
log_debug = ["log", "dep:chrono"] log_debug = ["log", "dep:chrono"]
# Leaf features
leaf_endpoint = ["unshell-leaves/leaf_endpoint"]
leaf_tui = ["unshell-leaves/leaf_tui"]
# obfuscate_aes = ["ush-obfuscate/obfuscate_aes"] # obfuscate_aes = ["ush-obfuscate/obfuscate_aes"]
# obfuscate_ref = ["ush-obfuscate/obfuscate_ref"] # obfuscate_ref = ["ush-obfuscate/obfuscate_ref"]
@@ -55,9 +60,7 @@ chrono = { workspace = true, optional = true }
static_init = { workspace = true } static_init = { workspace = true }
unshell-macros = { path = "./unshell-macros" } unshell-macros = { path = "./unshell-macros" }
unshell-protocol = { workspace = true } unshell-protocol = { workspace = true }
unshell-leaves = { workspace = true }
[dev-dependencies]
unshell-leaves = { workspace = true, features = ["endpoint"] }
[[example]] [[example]]
name = "leaf_derive" name = "leaf_derive"
@@ -66,14 +69,17 @@ path = "examples/protocol/leaf_derive.rs"
[[example]] [[example]]
name = "remote_shell_endpoint" name = "remote_shell_endpoint"
path = "examples/protocol/remote_shell_endpoint.rs" path = "examples/protocol/remote_shell_endpoint.rs"
required-features = ["leaf_endpoint"]
[[example]] [[example]]
name = "remote_shell_receive" name = "remote_shell_receive"
path = "examples/protocol/remote_shell_receive.rs" path = "examples/protocol/remote_shell_receive.rs"
required-features = ["leaf_endpoint"]
[[example]] [[example]]
name = "remote_shell_single_endpoint" name = "remote_shell_single_endpoint"
path = "examples/protocol/remote_shell_single_endpoint.rs" path = "examples/protocol/remote_shell_single_endpoint.rs"
required-features = ["leaf_endpoint"]
[[example]] [[example]]
name = "bench" name = "bench"
+10 -7
View File
@@ -9,29 +9,32 @@ use std::net::TcpStream;
use std::sync::mpsc::RecvTimeoutError; use std::sync::mpsc::RecvTimeoutError;
use std::time::Duration; use std::time::Duration;
use unshell::leaves::remote_shell;
use unshell::protocol::tree::Ingress; use unshell::protocol::tree::Ingress;
use unshell_leaves::remote_shell;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let mut stream = TcpStream::connect(remote_shell::LISTEN_ADDR)?; let mut stream = TcpStream::connect(remote_shell::endpoint::LISTEN_ADDR)?;
let frame_rx = remote_shell::spawn_frame_reader(stream.try_clone()?); let frame_rx = remote_shell::endpoint::spawn_frame_reader(stream.try_clone()?);
let mut runtime = remote_shell::build_agent_runtime(); let mut runtime = remote_shell::endpoint::build_agent_runtime();
println!("connected to controller at {}", remote_shell::LISTEN_ADDR); println!(
"connected to controller at {}",
remote_shell::endpoint::LISTEN_ADDR
);
loop { loop {
match frame_rx.recv_timeout(Duration::from_millis(25)) { match frame_rx.recv_timeout(Duration::from_millis(25)) {
Ok(result) => { Ok(result) => {
let frame = result?; let frame = result?;
let outcome = runtime.receive(&Ingress::Parent, frame)?; let outcome = runtime.receive(&Ingress::Parent, frame)?;
remote_shell::write_frames(&mut stream, &outcome.frames)?; remote_shell::endpoint::write_frames(&mut stream, &outcome.frames)?;
} }
Err(RecvTimeoutError::Timeout) => {} Err(RecvTimeoutError::Timeout) => {}
Err(RecvTimeoutError::Disconnected) => break, Err(RecvTimeoutError::Disconnected) => break,
} }
let outcome = runtime.poll()?; let outcome = runtime.poll()?;
remote_shell::write_frames(&mut stream, &outcome.frames)?; remote_shell::endpoint::write_frames(&mut stream, &outcome.frames)?;
} }
Ok(()) Ok(())
+19 -13
View File
@@ -6,38 +6,40 @@
use std::error::Error; use std::error::Error;
use std::net::TcpListener; use std::net::TcpListener;
use unshell::leaves::remote_shell;
use unshell::leaves::remote_shell::OpenRequest;
use unshell::protocol::tree::encode_call_reply;
use unshell::protocol::tree::{Endpoint, EndpointOutcome, Ingress, LocalEvent}; use unshell::protocol::tree::{Endpoint, EndpointOutcome, Ingress, LocalEvent};
use unshell_leaves::remote_shell;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let listener = TcpListener::bind(remote_shell::LISTEN_ADDR)?; let listener = TcpListener::bind(remote_shell::endpoint::LISTEN_ADDR)?;
println!("listening on {}", remote_shell::LISTEN_ADDR); println!("listening on {}", remote_shell::endpoint::LISTEN_ADDR);
let (mut stream, peer_addr) = listener.accept()?; let (mut stream, peer_addr) = listener.accept()?;
println!("accepted endpoint connection from {peer_addr}"); println!("accepted endpoint connection from {peer_addr}");
let frame_rx = remote_shell::spawn_frame_reader(stream.try_clone()?); let frame_rx = remote_shell::endpoint::spawn_frame_reader(stream.try_clone()?);
let mut endpoint = remote_shell::build_controller_endpoint(); let mut endpoint = remote_shell::endpoint::build_controller_endpoint();
let hook_id = endpoint.allocate_hook_id(); let hook_id = endpoint.allocate_hook_id();
let shell_leaf_name = remote_shell::shell_leaf_name(); let shell_leaf_name = remote_shell::endpoint::RemoteShellEndpoint::protocol_leaf_name();
let open_procedure = remote_shell::shell_open_procedure(); let open_procedure = remote_shell::endpoint::ProcedureOpen::protocol_procedure_id();
remote_shell::send_forward( remote_shell::endpoint::send_forward(
&mut stream, &mut stream,
endpoint.send_call( endpoint.send_call(
remote_shell::agent_path(), agent_path(),
Some(shell_leaf_name), Some(shell_leaf_name),
open_procedure.clone(), open_procedure.clone(),
Some(hook_id), Some(hook_id),
remote_shell::shell_open_payload(), encode_call_reply(&OpenRequest).expect("remote shell open payload should encode"),
)?, )?,
)?; )?;
for (index, command) in ["pwd\n", "whoami\n", "exit\n"].iter().enumerate() { for (index, command) in ["pwd\n", "whoami\n", "exit\n"].iter().enumerate() {
remote_shell::send_forward( remote_shell::endpoint::send_forward(
&mut stream, &mut stream,
endpoint.send_data( endpoint.send_data(
remote_shell::agent_path(), agent_path(),
hook_id, hook_id,
open_procedure.clone(), open_procedure.clone(),
command.as_bytes().to_vec(), command.as_bytes().to_vec(),
@@ -48,7 +50,7 @@ fn main() -> Result<(), Box<dyn Error>> {
for result in frame_rx { for result in frame_rx {
let frame = result?; let frame = result?;
let outcome = endpoint.receive(&Ingress::Child(remote_shell::agent_path()), frame)?; let outcome = endpoint.receive(&Ingress::Child(agent_path()), frame)?;
let EndpointOutcome::Local(event) = outcome else { let EndpointOutcome::Local(event) = outcome else {
continue; continue;
}; };
@@ -71,3 +73,7 @@ fn main() -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
fn agent_path() -> Vec<String> {
vec![String::from("agent")]
}
@@ -7,25 +7,25 @@
use std::error::Error; use std::error::Error;
use unshell::leaves::remote_shell;
use unshell::protocol::tree::{EndpointOutcome, LocalEvent, ProtocolEndpoint}; use unshell::protocol::tree::{EndpointOutcome, LocalEvent, ProtocolEndpoint};
use unshell::protocol::{INTROSPECTION_PROCEDURE_ID, LeafIntrospection}; use unshell::protocol::{INTROSPECTION_PROCEDURE_ID, LeafIntrospection};
use unshell_leaves::remote_shell;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let mut endpoint = ProtocolEndpoint::new( let mut endpoint = ProtocolEndpoint::new(
remote_shell::agent_path(), agent_path(),
Some(Vec::new()), Some(Vec::new()),
Vec::new(), Vec::new(),
vec![unshell::protocol::tree::LeafSpec { vec![unshell::protocol::tree::LeafSpec {
name: remote_shell::shell_leaf_name(), name: remote_shell::endpoint::RemoteShellEndpoint::protocol_leaf_name(),
procedures: vec![remote_shell::shell_open_procedure()], procedures: vec![remote_shell::endpoint::ProcedureOpen::protocol_procedure_id()],
}], }],
); );
let hook_id = endpoint.allocate_hook_id(); let hook_id = endpoint.allocate_hook_id();
let outcome = endpoint.send_call( let outcome = endpoint.send_call(
remote_shell::agent_path(), agent_path(),
Some(remote_shell::shell_leaf_name()), Some(remote_shell::endpoint::RemoteShellEndpoint::protocol_leaf_name()),
INTROSPECTION_PROCEDURE_ID, INTROSPECTION_PROCEDURE_ID,
Some(hook_id), Some(hook_id),
Vec::new(), Vec::new(),
@@ -38,10 +38,14 @@ fn main() -> Result<(), Box<dyn Error>> {
let payload = unshell::protocol::tree::decode_call_input::<LeafIntrospection>(&message.data)?; let payload = unshell::protocol::tree::decode_call_input::<LeafIntrospection>(&message.data)?;
println!( println!(
"remote-shell examples normally listen on {}", "remote-shell examples normally listen on {}",
remote_shell::LISTEN_ADDR remote_shell::endpoint::LISTEN_ADDR
); );
println!("endpoint path: {:?}", remote_shell::agent_path()); println!("endpoint path: {:?}", agent_path());
println!("leaf: {}", payload.leaf_name); println!("leaf: {}", payload.leaf_name);
println!("procedures: {:?}", payload.procedures); println!("procedures: {:?}", payload.procedures);
Ok(()) Ok(())
} }
fn agent_path() -> Vec<String> {
vec![String::from("agent")]
}
+3
View File
@@ -23,6 +23,9 @@ pub mod logger;
/// proc-macro output and downstream code do not need a second migration. /// proc-macro output and downstream code do not need a second migration.
pub use unshell_protocol as protocol; pub use unshell_protocol as protocol;
/// Re-export the leaf library crate behind the historical `unshell::leaves` path
pub use unshell_leaves as leaves;
pub use unshell_macros::{Leaf, Procedure, procedures}; pub use unshell_macros::{Leaf, Procedure, procedures};
// pub use ush_obfuscate as obfuscate; // pub use ush_obfuscate as obfuscate;
+4 -3
View File
@@ -6,13 +6,14 @@ description = "Application-layer UnShell leaves and client surfaces"
[features] [features]
default = [] default = []
endpoint = ["dep:portable-pty"] leaf_endpoint = ["dep:portable-pty"]
tui = [] leaf_tui = []
[dependencies] [dependencies]
rkyv = { workspace = true } rkyv = { workspace = true }
portable-pty = { workspace = true, optional = true } portable-pty = { workspace = true, optional = true }
unshell = { workspace = true } unshell-macros = { path = "../unshell-macros" }
unshell-protocol = { workspace = true }
[lints.rust] [lints.rust]
elided_lifetimes_in_paths = "warn" elided_lifetimes_in_paths = "warn"
+14 -6
View File
@@ -2,10 +2,18 @@
//! protocol runtime. //! protocol runtime.
//! //!
//! Each leaf module always exports its shared protocol-facing types. Role-specific //! Each leaf module always exports its shared protocol-facing types. Role-specific
//! implementations are selected with the crate-wide `endpoint` and `tui` //! implementations are selected with the crate-wide `leaf_endpoint` and `leaf_tui`
//! features, and can optionally be re-exported behind one stable alias. //! features, and can optionally be re-exported behind one stable alias.
use unshell::protocol::DataMessage; #[allow(unused_extern_crates)]
extern crate self as unshell;
pub extern crate alloc;
use unshell_protocol::DataMessage;
pub use unshell_macros::{Leaf, Procedure, procedures};
pub use unshell_protocol as protocol;
/// Re-exports one role-specific type behind a stable public alias. /// Re-exports one role-specific type behind a stable public alias.
/// ///
@@ -20,18 +28,18 @@ macro_rules! role_leaf {
tui => $tui:path $(,)? tui => $tui:path $(,)?
} }
) => { ) => {
#[cfg(all(feature = "endpoint", feature = "tui"))] #[cfg(all(feature = "leaf_endpoint", feature = "leaf_tui"))]
compile_error!(concat!( compile_error!(concat!(
"`", "`",
stringify!($alias), stringify!($alias),
"` can only alias one concrete role at a time; enable either `endpoint` or `tui`, not both" "` can only alias one concrete role at a time; enable either `leaf_endpoint` or `leaf_tui`, not both"
)); ));
#[cfg(feature = "endpoint")] #[cfg(feature = "leaf_endpoint")]
$(#[$meta])* $(#[$meta])*
$vis type $alias = $endpoint; $vis type $alias = $endpoint;
#[cfg(all(not(feature = "endpoint"), feature = "tui"))] #[cfg(all(not(feature = "leaf_endpoint"), feature = "leaf_tui"))]
$(#[$meta])* $(#[$meta])*
$vis type $alias = $tui; $vis type $alias = $tui;
}; };
+5 -1
View File
@@ -15,7 +15,7 @@ pub use errors::ShellLeafError;
pub use session::ProcedureOpen; pub use session::ProcedureOpen;
pub use transport::{LISTEN_ADDR, send_forward, spawn_frame_reader, write_frames}; pub use transport::{LISTEN_ADDR, send_forward, spawn_frame_reader, write_frames};
use super::{OpenRequest, agent_path}; use super::OpenRequest;
/// Leaf state for the remote shell endpoint runtime. /// Leaf state for the remote shell endpoint runtime.
/// ///
@@ -94,3 +94,7 @@ pub fn build_agent_runtime() -> ProcedureRuntime<RemoteShellEndpoint, ProcedureO
); );
ProcedureRuntime::new(endpoint, RemoteShellEndpoint::default()) ProcedureRuntime::new(endpoint, RemoteShellEndpoint::default())
} }
fn agent_path() -> Vec<String> {
vec![String::from("agent")]
}
+9 -68
View File
@@ -2,26 +2,21 @@
//! //!
//! The module always exports the protocol contract for the leaf. Role-specific //! The module always exports the protocol contract for the leaf. Role-specific
//! implementations live behind crate-wide features: //! implementations live behind crate-wide features:
//! - `endpoint` builds the PTY-backed runtime leaf //! - `leaf_endpoint` builds the PTY-backed runtime leaf
//! - `tui` builds a placeholder client-side TUI surface //! - `leaf_tui` builds a placeholder client-side TUI surface
use rkyv::{Archive, Deserialize, Serialize}; use rkyv::{Archive, Deserialize, Serialize};
#[cfg(feature = "endpoint")] #[cfg(feature = "leaf_endpoint")]
mod endpoint; pub mod endpoint;
#[cfg(feature = "tui")] #[cfg(feature = "leaf_tui")]
mod tui; pub mod tui;
#[cfg(feature = "endpoint")] #[cfg(feature = "leaf_endpoint")]
pub use endpoint::{ pub use endpoint::RemoteShellEndpoint;
LISTEN_ADDR, RemoteShellEndpoint, ShellLeafError, build_agent_runtime, #[cfg(feature = "leaf_tui")]
build_controller_endpoint, send_forward, spawn_frame_reader, write_frames,
};
#[cfg(feature = "tui")]
pub use tui::RemoteShellTui; pub use tui::RemoteShellTui;
use unshell::protocol::tree::encode_call_reply;
/// Open-request payload for the remote shell leaf. /// Open-request payload for the remote shell leaf.
/// ///
/// The shell currently needs no structured arguments, but a named payload type is /// The shell currently needs no structured arguments, but a named payload type is
@@ -36,57 +31,3 @@ crate::role_leaf! {
tui => tui::RemoteShellTui, tui => tui::RemoteShellTui,
} }
} }
/// Returns the example endpoint path used by the remote shell samples.
pub fn agent_path() -> Vec<String> {
path(&["agent"])
}
/// Returns the canonical leaf id used by endpoint and TUI code.
#[cfg(feature = "endpoint")]
pub fn shell_leaf_name() -> String {
RemoteShellEndpoint::protocol_leaf_name()
}
/// Returns the canonical opening `procedure_id` for the shell leaf.
#[cfg(feature = "endpoint")]
pub fn shell_open_procedure() -> String {
endpoint::ProcedureOpen::protocol_procedure_id()
}
/// Encodes the empty open-request payload used by the shell example.
#[cfg(all(not(feature = "endpoint"), feature = "tui"))]
pub fn shell_leaf_name() -> String {
RemoteShellTui::protocol_leaf_name()
}
/// Returns the canonical opening `procedure_id` for the shell leaf.
#[cfg(all(not(feature = "endpoint"), feature = "tui"))]
pub fn shell_open_procedure() -> String {
let mut procedure_id = shell_leaf_name();
procedure_id.push_str(".open");
procedure_id
}
/// Encodes the empty open-request payload used by the shell example.
#[cfg(not(any(feature = "endpoint", feature = "tui")))]
pub fn shell_leaf_name() -> String {
String::from("remote_shell")
}
/// Returns the canonical opening `procedure_id` for the shell leaf.
#[cfg(not(any(feature = "endpoint", feature = "tui")))]
pub fn shell_open_procedure() -> String {
let mut procedure_id = shell_leaf_name();
procedure_id.push_str(".open");
procedure_id
}
/// Encodes the empty open-request payload used by the shell example.
pub fn shell_open_payload() -> Vec<u8> {
encode_call_reply(&OpenRequest).expect("remote shell open payload should encode")
}
fn path(parts: &[&str]) -> Vec<String> {
parts.iter().map(|part| (*part).to_owned()).collect()
}