diff --git a/embassy/demos/std/Cargo.lock b/embassy/demos/std/Cargo.lock
index 8f17a036c15d4ff7a54be84ae14daf81bc3983a0..1dca071627664968c16cbc94fd63f339c2793f51 100644
--- a/embassy/demos/std/Cargo.lock
+++ b/embassy/demos/std/Cargo.lock
@@ -838,6 +838,12 @@ version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 
+[[package]]
+name = "menu"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b03d7f798bfe97329ad6df937951142eec93886b37d87010502dd25e8cc75fd5"
+
 [[package]]
 name = "nb"
 version = "0.1.3"
@@ -1277,6 +1283,7 @@ dependencies = [
  "heapless",
  "libc",
  "log",
+ "menu",
  "rand",
  "sha2 0.10.6",
  "static_cell",
diff --git a/embassy/demos/std/Cargo.toml b/embassy/demos/std/Cargo.toml
index c1ec25b5dd21dc6a352e671d176c0c6d34e74e9d..c7a133999d0b520d4355c292c43ea976c33d8f6e 100644
--- a/embassy/demos/std/Cargo.toml
+++ b/embassy/demos/std/Cargo.toml
@@ -28,6 +28,8 @@ heapless = "0.7.15"
 libc = "0.2.101"
 async-io = "1.6.0"
 
+menu = "0.3"
+
 sunset-embassy = { path = "../../" }
 sunset = { path = "../../.." }
 
diff --git a/embassy/demos/std/src/demo_menu.rs b/embassy/demos/std/src/demo_menu.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a5443efd8b7795cf48c51e57493d35e1502f50ab
--- /dev/null
+++ b/embassy/demos/std/src/demo_menu.rs
@@ -0,0 +1,180 @@
+use sunset_embassy::SSHServer;
+use sunset::Result;
+
+use menu::*;
+use core::fmt::Write;
+
+#[derive(Default)]
+pub struct Output {
+    s: heapless::String<1024>,
+}
+
+impl Output {
+    pub async fn flush(&mut self, serv: &SSHServer<'_>, chan: u32) -> Result<()> {
+
+        let mut b = self.s.as_str().as_bytes();
+        while b.len() > 0 {
+            let l = serv.write_channel(chan, None, b).await?;
+            b = &b[l..];
+        }
+        self.s.clear();
+        Ok(())
+    }
+}
+
+impl core::fmt::Write for Output {
+    fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
+        for c in s.chars() {
+            if c == '\n' {
+                self.s.push('\r').map_err(|_| core::fmt::Error)?;
+            }
+            self.s.push(c).map_err(|_| core::fmt::Error)?;
+        }
+        Ok(())
+    }
+}
+
+// from menu crate examples/simple.rs
+
+pub const ROOT_MENU: Menu<Output> = Menu {
+    label: "root",
+    items: &[
+        &Item {
+            item_type: ItemType::Callback {
+                function: select_foo,
+                parameters: &[
+                    Parameter::Mandatory {
+                        parameter_name: "a",
+                        help: Some("This is the help text for 'a'"),
+                    },
+                    Parameter::Optional {
+                        parameter_name: "b",
+                        help: None,
+                    },
+                    Parameter::Named {
+                        parameter_name: "verbose",
+                        help: None,
+                    },
+                    Parameter::NamedValue {
+                        parameter_name: "level",
+                        argument_name: "INT",
+                        help: Some("Set the level of the dangle"),
+                    },
+                ],
+            },
+            command: "foo",
+            help: Some(
+                "Makes a foo appear.
+
+This is some extensive help text.
+
+It contains multiple paragraphs and should be preceeded by the parameter list.
+",
+            ),
+        },
+        &Item {
+            item_type: ItemType::Callback {
+                function: select_bar,
+                parameters: &[],
+            },
+            command: "bar",
+            help: Some("fandoggles a bar"),
+        },
+        &Item {
+            item_type: ItemType::Menu(&Menu {
+                label: "sub",
+                items: &[
+                    &Item {
+                        item_type: ItemType::Callback {
+                            function: select_baz,
+                            parameters: &[],
+                        },
+                        command: "baz",
+                        help: Some("thingamobob a baz"),
+                    },
+                    &Item {
+                        item_type: ItemType::Callback {
+                            function: select_quux,
+                            parameters: &[],
+                        },
+                        command: "quux",
+                        help: Some("maximum quux"),
+                    },
+                ],
+                entry: Some(enter_sub),
+                exit: Some(exit_sub),
+            }),
+            command: "sub",
+            help: Some("enter sub-menu"),
+        },
+    ],
+    entry: Some(enter_root),
+    exit: Some(exit_root),
+};
+
+fn enter_root(_menu: &Menu<Output>, context: &mut Output) {
+    writeln!(context, "In enter_root").unwrap();
+}
+
+fn exit_root(_menu: &Menu<Output>, context: &mut Output) {
+    writeln!(context, "In exit_root").unwrap();
+}
+
+fn select_foo<'a>(_menu: &Menu<Output>, item: &Item<Output>, args: &[&str], context: &mut Output) {
+    writeln!(context, "In select_foo. Args = {:?}", args).unwrap();
+    writeln!(
+        context,
+        "a = {:?}",
+        ::menu::argument_finder(item, args, "a")
+    )
+    .unwrap();
+    writeln!(
+        context,
+        "b = {:?}",
+        ::menu::argument_finder(item, args, "b")
+    )
+    .unwrap();
+    writeln!(
+        context,
+        "verbose = {:?}",
+        ::menu::argument_finder(item, args, "verbose")
+    )
+    .unwrap();
+    writeln!(
+        context,
+        "level = {:?}",
+        ::menu::argument_finder(item, args, "level")
+    )
+    .unwrap();
+    writeln!(
+        context,
+        "no_such_arg = {:?}",
+        ::menu::argument_finder(item, args, "no_such_arg")
+    )
+    .unwrap();
+}
+
+fn select_bar<'a>(_menu: &Menu<Output>, _item: &Item<Output>, args: &[&str], context: &mut Output) {
+    writeln!(context, "In select_bar. Args = {:?}", args).unwrap();
+}
+
+fn enter_sub(_menu: &Menu<Output>, context: &mut Output) {
+    writeln!(context, "In enter_sub").unwrap();
+}
+
+fn exit_sub(_menu: &Menu<Output>, context: &mut Output) {
+    writeln!(context, "In exit_sub").unwrap();
+}
+
+fn select_baz<'a>(_menu: &Menu<Output>, _item: &Item<Output>, args: &[&str], context: &mut Output) {
+    writeln!(context, "In select_baz: Args = {:?}", args).unwrap();
+}
+
+fn select_quux<'a>(
+    _menu: &Menu<Output>,
+    _item: &Item<Output>,
+    args: &[&str],
+    context: &mut Output,
+) {
+    writeln!(context, "In select_quux: Args = {:?}", args).unwrap();
+}
diff --git a/embassy/demos/std/src/main.rs b/embassy/demos/std/src/main.rs
index 83a6ca6242a606e744f975513ed96fe38b7b2746..deac6e983dd2dfb49317bce2e55bc294d3de6f63 100644
--- a/embassy/demos/std/src/main.rs
+++ b/embassy/demos/std/src/main.rs
@@ -27,6 +27,9 @@ use core::task::{Context,Poll,Waker,RawWaker,RawWakerVTable};
 use rand::rngs::OsRng;
 use rand::RngCore;
 
+use menu::Runner as MenuRunner;
+use menu::Menu;
+
 use sunset::*;
 use sunset::error::TrapBug;
 use sunset_embassy::SSHServer;
@@ -34,6 +37,7 @@ use sunset_embassy::SSHServer;
 use crate::tuntap::TunTapDevice;
 
 mod tuntap;
+mod demo_menu;
 
 const NUM_LISTENERS: usize = 4;
 
@@ -87,7 +91,6 @@ async fn listener(stack: &'static Stack<TunTapDevice>) -> ! {
 
     loop {
         let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
-        socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10)));
 
         info!("Listening on TCP:22...");
         if let Err(e) = socket.accept(22).await {
@@ -158,6 +161,7 @@ impl<'a> ServBehaviour for DemoServer<'a> {
     }
 }
 
+
 #[derive(Default)]
 struct DemoShell {
     notify: Signal<CriticalSectionRawMutex, u32>,
@@ -167,28 +171,31 @@ impl DemoShell {
     async fn run<'f>(&self, serv: &SSHServer<'f>) -> Result<()>
     {
         let session = async {
+            // wait for a shell to start
             let chan = self.notify.wait().await;
 
+            let mut menu_buf = [0u8; 64];
+            let mut menu_out = demo_menu::Output::default();
+
+            let mut menu = MenuRunner::new(&demo_menu::ROOT_MENU, &mut menu_buf, menu_out);
+
             loop {
-                let mut b = [0u8; 100];
+                let mut b = [0u8; 20];
                 let lr = serv.read_channel_stdin(chan, &mut b).await?;
                 let b = &mut b[..lr];
-                for c in b.iter_mut() {
-                    if *c >= b'1' && *c <= b'9' {
-                        *c = b'1' + (b'9' - *c)
-                    }
-                }
-                let lw = serv.write_channel(chan, None, b).await?;
-                if lr != lw {
-                    trace!("read/write mismatch {} {}", lr, lw);
+                for c in b.iter() {
+                    menu.input_byte(*c);
+                    menu.context.flush(serv, chan).await?;
                 }
             }
             Ok(())
         };
+
         session.await
     }
 }
 
+
 async fn session(socket: &mut TcpSocket<'_>) -> sunset::Result<()> {
     let shell = DemoShell::default();
     let app = DemoServer::new(&shell)?;