nushell-plugin-builder
Skill30 starsChanged 10 months agoMITView on GitHub
Guide for creating Nushell plugins in Rust using nu_plugin and nu_protocol crates. Use when users want to build custom Nushell commands, extend Nushell with new functionality, create data transformations, or integrate external tools/APIs into Nushell. Covers project setup, command implementation, streaming data, custom values, and testing.
- Installs packages
In this repository13 other files it gives its agents.
- AGENTS.mdAGENTS.md
- cursor-agent-supervisorSkill
- github-pr-workflowSkill
- jj-todo-workflowSkill
- nix-profile-managerSkill
- nushell-usageSkill
- package-npm-nixSkill
- read-bin-docsSkill
- research-assistantSkill
- searxng-searchSkill
- textual-builderSkill
- typst-writerSkill
- working-with-jjSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 4 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 100
Use for: transformations, simple filters, single value operations
- Distinctive: line 106
Use for: streaming transformations, lazy processing, large datasets
- Very specific: line 147
Always return LabeledError with span information:
- Distinctive: line 159
Use for production plugins
- Very specific: line 291
Check engine.is_using_stdio() before attempting stdio access
- Very specific: line 294
Always use paths relative to engine.get_current_dir()
- Distinctive: line 295
Never assume current working directory
- Very specific: line 318
Use scripts/init_plugin.py to scaffold a new plugin with proper structure:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Nushell Plugin Builder
Overview
This skill helps create Nushell plugins in Rust. Plugins are standalone executables that extend Nushell with custom commands, data transformations, and integrations.
Quick Start
1. Create Plugin Project
cargo new nu_plugin_<name>
cd nu_plugin_<name>
cargo add nu-plugin nu-protocol2. Basic Plugin Structure
use nu_plugin::{EvaluatedCall, MsgPackSerializer, serve_plugin};
use nu_plugin::{EngineInterface, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{LabeledError, Signature, Type, Value};
struct MyPlugin;
impl Plugin for MyPlugin {
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
vec![Box::new(MyCommand)]
}
}
struct MyCommand;
impl SimplePluginCommand for MyCommand {
type Plugin = MyPlugin;
fn name(&self) -> &str {
"my-command"
}
fn signature(&self) -> Signature {
Signature::build("my-command")
.input_output_type(Type::String, Type::Int)
}
fn run(
&self,
_plugin: &MyPlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,
) -> Result<Value, LabeledError> {
match input {
Value::String { val, .. } => {
Ok(Value::int(val.len() as i64, call.head))
}
_ => Err(LabeledError::new("Expected string input")
.with_label("requires string", call.head))
}
}
}
fn main() {
serve_plugin(&MyPlugin, MsgPackSerializer)
}3. Build and Install
# Build
cargo build --release
# Install to cargo bin
cargo install --path . --locked
# Register with nushell
plugin add ~/.cargo/bin/nu_plugin_<name> # Add .exe on Windows
plugin use <name>
# Test
"hello" | my-commandCommand Types
SimplePluginCommand
For commands that operate on single values:
- Input:
&Value - Output:
Result<Value, LabeledError> - Use for: transformations, simple filters, single value operations
PluginCommand
For commands that handle streams:
- Input:
PipelineData - Output:
Result<PipelineData, LabeledError> - Use for: streaming transformations, lazy processing, large datasets
See references/advanced-features.md for streaming examples.
Defining Command Signatures
Input-Output Types
use nu_protocol::{Signature, Type};
Signature::build("my-command")
.input_output_type(Type::String, Type::Int)Common types: String, Int, Float, Bool, List(Box<Type>), Record(...), Any
Parameters
Signature::build("my-command")
// Named flags
.named("output", SyntaxShape::Filepath, "output file", Some('o'))
.switch("verbose", "enable verbose output", Some('v'))
// Positional arguments
.required("input", SyntaxShape::String, "input value")
.optional("count", SyntaxShape::Int, "repeat count")
.rest("files", SyntaxShape::Filepath, "files to process")Accessing Arguments
fn run(&self, call: &EvaluatedCall, ...) -> Result<Value, LabeledError> {
let output: Option<String> = call.get_flag("output")?;
let verbose: bool = call.has_flag("verbose")?;
let input: String = call.req(0)?; // First positional
let count: Option<i64> = call.opt(1)?; // Second positional
let files: Vec<String> = call.rest(2)?; // Remaining args
}Error Handling
Always return LabeledError with span information:
Err(LabeledError::new("Error message")
.with_label("specific issue", call.head))This shows users exactly where the error occurred in their command.
Serialization
MsgPackSerializer (Recommended)
- Binary format, much faster
- Use for production plugins
JsonSerializer
- Text-based, human-readable
- Useful for debugging
Choose in main():
serve_plugin(&MyPlugin, MsgPackSerializer) // Production
// serve_plugin(&MyPlugin, JsonSerializer) // DebugCommon Patterns
String Transformation
Value::String { val, .. } => {
Ok(Value::string(val.to_uppercase(), call.head))
}List Generation
let items = vec![
Value::string("a", call.head),
Value::string("b", call.head),
];
Ok(Value::list(items, call.head))Record (Table Row)
use nu_protocol::record;
Ok(Value::record(
record! {
"name" => Value::string("example", call.head),
"size" => Value::int(42, call.head),
},
call.head,
))Table (List of Records)
let records = vec![
Value::record(record! { "name" => Value::string("a", span) }, span),
Value::record(record! { "name" => Value::string("b", span) }, span),
];
Ok(Value::list(records, call.head))See references/examples.md for complete working examples including:
- Filtering streams
- HTTP API calls
- File system operations
- Multi-command plugins
Development Workflow
Iterative Development
# Build
cargo build
# Test (debug build)
plugin add target/debug/nu_plugin_<name>
plugin use <name>
"test" | my-command
# After changes, reload
plugin rm <name>
plugin add target/debug/nu_plugin_<name>
plugin use <name>Automated Testing
[dev-dependencies]
nu-plugin-test-support = "0.109.1"#[cfg(test)]
mod tests {
use nu_plugin_test_support::PluginTest;
#[test]
fn test_command() -> Result<(), nu_protocol::ShellError> {
PluginTest::new("myplugin", MyPlugin.into())?
.test_examples(&MyCommand)
}
}See references/testing-debugging.md for debugging techniques and troubleshooting.
Advanced Features
Streaming Data
For lazy processing of large datasets, use PipelineData:
impl PluginCommand for MyCommand {
fn run(&self, input: PipelineData, ...) -> Result<PipelineData, LabeledError> {
let filtered = input.into_iter().filter(|v| /* condition */);
Ok(PipelineData::ListStream(ListStream::new(filtered, span, None), None))
}
}Engine Interaction
// Get environment variables
let home = engine.get_env_var("HOME")?;
// Set environment variables (before response)
engine.add_env_var("MY_VAR", Value::string("value", span))?;
// Get plugin config from $env.config.plugins.<name>
let config = engine.get_plugin_config()?;
// Get current directory for path resolution
let cwd = engine.get_current_dir()?;Custom Values
Define custom data types that extend beyond Nushell's built-in types. See references/advanced-features.md for complete guide.
Important Constraints
Stdio Restrictions
- Plugins cannot use stdin/stdout (reserved for protocol)
- Check
engine.is_using_stdio()before attempting stdio access
Path Handling
- Always use paths relative to
engine.get_current_dir() - Never assume current working directory
Version Compatibility
- Match
nu-pluginandnu-protocolversions - Both should match target Nushell version
Reference Documentation
references/plugin-protocol.md- Protocol details, serialization, lifecyclereferences/advanced-features.md- Streaming, EngineInterface, custom valuesreferences/examples.md- Complete working examples and patternsreferences/testing-debugging.md- Development workflow, debugging, troubleshooting
External Resources
- Official Plugin Guide
- nu-plugin API Docs
- Plugin Examples Repository
- Awesome Nu - Community plugins
- nushellWith - Nix flake for building reproducible Nushell environments with plugins. Includes 100+ pre-packaged plugins from crates.io with binary caching via Garnix. Useful for testing plugins in isolated environments or building standalone Nu scripts.
Template Script
Use scripts/init_plugin.py to scaffold a new plugin with proper structure:
python3 scripts/init_plugin.py <plugin-name> [--output-dir <path>]This creates a complete working plugin template ready to customize.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

