1 unstable release
Uses new Rust 2024
| new 0.1.0 | Dec 6, 2025 |
|---|
#267 in Embedded development
200KB
4K
SLoC
nut-shell
Interactive CLI for microcontrollers. No heap, no bloat.
A lightweight command shell library for #![no_std] Rust environments with optional async and authentication support.
Overview
nut-shell provides essential CLI primitives for embedded systems with strict memory constraints. Built specifically for microcontrollers, it offers an interactive command-line interface over serial connections (UART/USB), with optional features including async/await support, login authentication, tab completion and command history.
Design Philosophy: Essential primitives only. No shell scripting, no dynamic allocation, no bloat.
Key Features
Core Functionality (Always Present)
- ✅ Path-based navigation - Unix-style hierarchical commands (
system/info,network/status) - ✅ Command execution - Synchronous command support with structured argument parsing
- ✅ Input parsing - Terminal I/O with line editing (backspace, double-ESC clear)
- ✅ Global commands -
ls,?,clear
Optional Features
- Async commands - Supports async/await (Embassy compatible). Zero overhead when disabled. (Default: disabled)
- Authentication - SHA-256 password hashing, login flow, session management, and access control enforcement (Default: disabled)
- Tab completion - Command and path prefix matching (Default: enabled)
- Command history - Arrow key navigation with configurable buffer (Default: enabled)
What This Library Excludes
- ❌ Shell scripting (piping, variables, conditionals, command substitution)
- ❌ Command aliases
- ❌ Job control (background jobs, fg/bg)
- ❌ Output paging
- ❌ Persistent history across reboots
See docs/PHILOSOPHY.md for rationale.
Quick Start
Bare-Metal Pattern:
// 1. Implement `CharIo` trait for your platform
impl CharIo for MyIo {
type Error = MyError;
fn get_char(&mut self) -> Result<Option<char>, Self::Error> { /* ... */ }
fn put_char(&mut self, c: char) -> Result<(), Self::Error> { /* ... */ }
}
// 2. Define command tree with metadata
const STATUS: CommandMeta<Level> = CommandMeta {
id: "status",
name: "status",
description: "Show system status",
access_level: Level::User,
kind: CommandKind::Sync,
min_args: 0,
max_args: 0,
};
const SYSTEM: Directory<Level> = Directory {
name: "system",
description: "System commands",
access_level: Level::User,
children: &[Node::Command(&STATUS)],
};
const ROOT: Directory<Level> = Directory {
name: "",
description: "Root",
access_level: Level::Guest,
children: &[Node::Directory(&SYSTEM)],
};
// 3. Implement `CommandHandler` trait
impl CommandHandler<MyConfig> for MyHandler {
fn execute_sync(&self, id: &str, args: &[&str]) -> Result<Response<MyConfig>, CliError> {
match id {
"status" => status_fn::<MyConfig>(args),
_ => Err(CliError::CommandNotFound)
}
}
}
// 4. Create shell and run main loop
let handler = MyHandler; // Your `CommandHandler` implementation
let io = MyIo::new(); // Your `CharIo` implementation
let mut shell = Shell::new(&ROOT, handler, io);
shell.activate().ok();
loop {
if let Some(c) = io.get_char()? {
shell.process_char(c)?;
}
}
Async Pattern (Embassy):
// 1. Define async command
const FETCH: CommandMeta<Level> = CommandMeta {
id: "fetch",
name: "fetch",
description: "Fetch data from network",
access_level: Level::User,
kind: CommandKind::Async, // Async command
min_args: 0,
max_args: 0,
};
// 2. Implement async handler
impl CommandHandler<MyConfig> for MyHandler {
async fn execute_async(&self, id: &str, args: &[&str]) -> Result<Response<MyConfig>, CliError> {
match id {
"fetch" => fetch_fn::<MyConfig>(args).await,
_ => Err(CliError::CommandNotFound)
}
}
}
// 3. Run shell in async task
#[embassy_executor::task]
async fn shell_task(usb: CdcAcmClass<'static, Driver<'static, USB>>) {
let handler = MyHandler;
let io = MyIo::new(usb);
let mut shell = Shell::new(&ROOT, handler, io);
shell.activate().ok();
loop {
let c = read_char().await;
shell.process_char_async(c).await?;
io.flush().await?;
}
}
See docs/EXAMPLES.md for further implementation patterns.
Interaction session (authentication disabled)
Welcome to nut-shell! Type '?' for help.
@/> ?
? - List global commands
ls - List directory contents
clear - Clear screen
ESC ESC - Clear input buffer
@/> ls
system/ - Directory
echo - Echo arguments back
@/> echo hello world!
hello world!
@/> system
@/system> ls
status - Show system status
version - Show version information
@/system> status
System Status:
CPU Usage: 23%
Uptime: 42 hours
@/system> ..
@/>
Platform Support
Built for no_std embedded systems:
- Tested on: ARM Cortex-M0 microcontrollers (RP2040, STM32F072)
- Compatible with: Any ARMv6-M or higher microcontroller
Runtime compatibility:
- Bare-metal (polling loop)
- Embassy (async runtime) and other async runtimes
I/O abstraction: Platform-agnostic CharIo trait for UART, USB-CDC, or custom adapters.
See examples/ for complete working implementations on real hardware.
Memory Footprint
Measured on ARMv6-M (Cortex-M0/M0+) with opt-level = "z" and LTO enabled:
| Feature Set | Flash (.text + .rodata) | RAM (.bss) |
|---|---|---|
| None (minimal) | ~1.5KB | 0B |
| All features | ~1.2KB | 0B |
These measurements use zero-size stubs and minimal command tree to isolate nut-shell's code overhead. Your actual footprint will also include:
Flash costs:
- Command implementations
- Directory tree metadata (command names, descriptions)
CharIo/CredentialProvider/CommandHandlertrait implementations
RAM costs (allocated in Shell instance):
- Input buffer:
MAX_INPUTbytes (default 128B) - History buffer:
HISTORY_SIZE × MAX_INPUTbytes (default 10 × 128 = 1.3KB) - Path tracking and internal state: ~100B
Typical total footprint (with default config and features):
- Flash: ~4-6KB (nut-shell + basic commands + trait implementations)
- RAM: ~2KB (buffers on stack)
For detailed analysis: See size-analysis/README.md for methodology and complete breakdown across all feature combinations.
Authentication & Security
Optional authentication feature provides:
- SHA-256 password hashing with per-user salts
- Constant-time comparison (prevents timing attacks)
- Password masking during input
- Access control enforced at every path segment
- Pluggable credential providers (build-time, flash storage, custom)
Interaction session (authentication enabled)
Welcome to nut-shell! Please login.
Login> admin:********
Logged in. Type '?' for help.
admin@/> ls
system/ - Directory
echo - Echo arguments back
admin@/> system/version
Firmware version 3.4.5
admin@/> logout
Logged out.
Login> admin:*****
Login failed. Try again.
Login> user:*******
Logged in. Type '?' for help.
user@/> ls
echo - Echo arguments back
user@/>
Documentation
| Document | Description |
|---|---|
| README.md | Quick start and overview (this file) |
| docs/EXAMPLES.md | Implementation patterns, configuration, troubleshooting |
| docs/CHAR_IO.md | CharIo trait design and platform adapters |
| docs/SECURITY.md | Authentication patterns and security considerations |
| docs/PHILOSOPHY.md | Design philosophy and feature criteria |
| docs/DESIGN.md | Architecture decisions and design patterns |
| docs/DEVELOPMENT.md | Build workflows, testing, and CI |
cargo doc --open |
Complete API reference |
Contributing
Contributions welcome! Review docs/PHILOSOPHY.md for feature criteria and docs/DESIGN.md for architectural patterns before implementing features.
Before submitting: Run ./scripts/ci-local to verify all CI checks pass.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Acknowledgments
Designed for the Rust embedded ecosystem, with inspiration from:
- Unix shell navigation and commands
- Embedded CLI best practices
no_stdRust patterns- Embassy async runtime architecture
Maintained by: Esben Dueholm Nørgaard (HybridChild)
Dependencies
~0.8–1.8MB
~37K SLoC