Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions skills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Agent Skills

This directory holds examples for use with coding agents that interact with Nu,
either as a CLI tool or via the Nushell MCP server.

## Nushell Skill

`./skills/nushell/` - An [Agent Skills](https://agentskills.io/home) compliant
skill directory built via the Claude Code skill-writer superpower and human-tested
for validity and efficacy. Once added to the appropriate location, begin a new
session with your coding agent and it should automatically register the skill
and use it when you reference Nushell, mention the skill directly, or trigger a
use case mentioned in the description of the SKILL.md frontmatter.

### Global usage

Copy the `nushell/` directory to `~/.claude/skills/` for global use in Claude Code,
to `~/.copilot/skills` for global use with the Copilot coding agent or GitHub
Copilot CLI, or to `~/.agent/skills/` for Codex, OpenCode, and other coding agents.
See your preferred LLM provider's documentation for specific details.

### Project usage

If you only wish to load this skill for specific projects, or for use with non-
CLI based coding agents that canot access a global skills directory, create a
`.claude/skills/`, `.github/skills/`, or `.agents/skills/` directory at the root
of your project and copy the nushell skill directory into it.
71 changes: 71 additions & 0 deletions skills/nushell/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
name: nushell
description: Use when writing or running Nushell commands, scripts, or pipelines - via the Nushell MCP server (mcp__nushell__evaluate), via Bash (nu -c), or in .nu script files. Also use when working with structured data (JSON, YAML, TOML, CSV, Parquet, SQLite), doing ad-hoc data analysis or exploration, or when the user's shell is Nushell.
---

# Using Nushell

Nushell is a structured-data shell. Commands pass **tables, records, and lists** through pipelines - not text.

**Two execution paths:**
- **MCP server**: `mcp__nushell__evaluate` - persistent REPL (variables survive across calls)
- **Bash tool**: `nu -c '<code>'` - one-shot (use single quotes for outer wrapper)

## Critical Rules

**NEVER use bare `print` in MCP stdio mode.** Output will be lost (returns empty `[]`). Use `print -e "msg"` for stderr, or just return the value (implicit return).

**String interpolation uses parentheses, NOT curly braces:**
```nu
# WRONG: $"hello {$name}"
# CORRECT: $"hello ($name)"
$"($env.HOME)/docs" $"2 + 2 = (2 + 2)" $"files: (ls | length)"
```
Gotcha: `$"(some text)"` errors - parens are evaluated as code. Escape literal parens: `\(text\)`.

**No bash syntax:** `cmd1; cmd2` not `&&`, `o+e>|` not `2>&1`, `$env.VAR` not `$VAR`, `(cmd)` not `$(cmd)`.

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| `$"hello {$name}"` | `$"hello ($name)"` |
| `print "msg"` in MCP | `print -e "msg"` or return value |
| `command 2>&1` | `command o+e>\| ...` |
| `$HOME/path` | `$env.HOME` or `$"($env.HOME)/path"` |
| `export FOO=bar` | `$env.FOO = "bar"` |
| Mutating in closure | Use `reduce`, `generate`, or `each` |
| `\u001b` for ANSI | `ansi strip` to remove, `char --unicode '1b'` for ESC |
| `where ($in.a > 1) and ($in.b > 2)` | Second `$in` rebinds to bool. Use bare cols: `where a > 1 and b > 2` |
| `where not ($in.col \| cmd)` | `not` breaks `$in`. Use `where ($in.col \| cmd) == false` |
| `where col \| cmd` (no parens) | Parsed as two pipeline stages. Use `where ($in.col \| cmd)` |

## When to Use Nushell

**Always prefer Nushell for:**
- Any structured data (JSON, YAML, TOML, CSV, Parquet, SQLite) - unifies all formats
- CLI tools with `--json` flags - pipe JSON output directly into Nushell for querying (e.g. `^gh pr list --json title,state | from json`)
- Ad-hoc data analysis and exploration - faster than Python setup
- Initial data science/analytics - histograms, tabular output, basic aggregations
- Polars plugin for large datasets - DataFrames without Python overhead

**Use Bash only when:** bash-specific tooling, MCP unavailable, or bash-syntax integrations.

## Reference Files

Read the relevant file(s) based on what you need:

| File | Read when you need... |
|------|-----------------------|
| [commands.md](references/commands.md) | Command reference tables (filters, strings, conversions, filesystem, math, dates) |
| [types-and-syntax.md](references/types-and-syntax.md) | Type system, string types, operators, variables, control flow, cell-paths |
| [data-analysis.md](references/data-analysis.md) | Format conversion, HTTP, Polars, SQLite, aggregation patterns |
| [advanced.md](references/advanced.md) | Custom commands, modules, error handling, jobs, external commands, env config |
| [bash-equivalents.md](references/bash-equivalents.md) | Complete Bash-to-Nushell translation table |
| [http-transport.md](references/http-transport.md) | Differences when using HTTP transport instead of stdio |

## MCP Server Quick Notes

- `mcp__nushell__evaluate` - run code; `mcp__nushell__list_commands` - discover; `mcp__nushell__command_help` - help
- State persists between calls. `$history` stores prior results (access `$history.0`, etc.)
- Use `| to text` or `| to json` for large outputs. Use `ansi strip` for color removal.
164 changes: 164 additions & 0 deletions skills/nushell/references/advanced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Nushell Advanced Patterns

## Custom Commands with Full Signatures

```nu
def search [
pattern: string # required positional
path?: string # optional positional
--case-sensitive(-c) # boolean flag (defaults false)
--max-depth(-d): int # flag with value
...extra: string # rest params
]: nothing -> list<string> {
# implementation
}
```

### Pipeline Input/Output Types
```nu
def "str stats" []: string -> record {
let input = $in
{length: ($input | str length), words: ($input | split words | length)}
}

# Multiple input types
def process []: [string -> record, list -> table] { ... }
```

### Environment-Modifying Commands
```nu
def --env goto [dir: string] {
cd $dir
$env.LAST_DIR = (pwd)
}
```

### Wrapped External Commands
```nu
def --wrapped mygrep [...rest] {
^grep --color=always ...$rest
}
```

## Modules

```nu
module utils {
export def double [x: number] { $x * 2 }
export def triple [x: number] { $x * 3 }
export-env { $env.UTILS_LOADED = true }
}

use utils double # import specific command
use utils * # import all exports
use utils [] # import only environment
```

## Generate (Stateful Sequences)

Closure comes first, initial value second. Returns `{out: value, next: state}`. Omit `next` to stop.

```nu
# Fibonacci
generate {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]}} [0, 1] | first 10

# Counter
generate {|i| if $i <= 5 { {out: $i, next: ($i + 1)} }} 0

# With input stream (two-arg closure)
1..5 | generate {|e, sum=0| let sum = $e + $sum; {out: $sum, next: $sum}}
```

## Functional Alternatives to Mutable State

```nu
# Instead of: mut sum = 0; for x in list { $sum += $x }
[1 2 3 4 5] | reduce {|it, acc| $acc + $it}

# With initial value
[2 3 4 5] | reduce --fold 1 {|it, acc| $acc * $it}

# Instead of: mut results = []; for x in list { $results = ($results | append (f $x)) }
$list | each {|x| process $x}

# Instead of: while with mutation
generate {|state| if $state.done { null } else { {out: $state.val, next: (advance $state)} }} $initial
```

## Error Handling

```nu
# Basic try/catch
try { open nonexistent.json } catch {|e| $"Failed: ($e.msg)" }

# With finally (v0.111.0+)
try { do-work } catch {|e| log-error $e.msg } finally { cleanup }

# Custom errors
error make {msg: "Value must be non-negative"}
error make {msg: "Bad value", label: {text: "here", span: (metadata $value).span}}
```

## External Commands

```nu
^external_cmd args # explicit external invocation
ls | to text | ^grep pattern # pipe structured data to external
^cmd arg1 arg2 o+e>| str trim # capture stdout+stderr
^cmd | complete # get {exit_code, stdout, stderr} record
$env.LAST_EXIT_CODE # check last exit code

# External output to structured data
^git log --oneline -5 | lines | parse "{hash} {message}"

# Pipe structured data to external
{data: "value"} | to json | ^curl -X POST -d @- https://api.example.com
```

## Background Jobs

```nu
job spawn { long_running_cmd } # returns job ID
job spawn --tag "server" { uvicorn main:app } # with description
job list # list running jobs
job kill $id # terminate job

# Getting results back via mailbox (main thread = job 0)
job spawn { expensive_calc | job send 0 }; job recv
job spawn { cmd | job send 0 --tag 1 }; job recv --tag 1 # filtered
job recv --timeout 5sec # with timeout
```

**Key rules:** `job recv` reads from current job's mailbox only (no job ID arg). Background jobs send to main with `job send 0`. Use `--tag` for filtering.

## Environment & Configuration

```nu
$env.PATH # PATH (list with ENV_CONVERSIONS, string otherwise)
$env.Path # PATH on Windows
$env.PATH = ($env.PATH | append "/new/path")
$env.MY_VAR = "value"
$env.MY_VAR? | default "fallback" # safe access with default
hide-env MY_VAR # unset variable

# Configuration
$env.config.show_banner = false # set individual config values
$nu.default-config-dir # config directory path
$nu.home-dir # home directory
config nu # edit config in $EDITOR
```

## Using nu -c from Bash

```bash
# Simple command
nu -c 'ls | where size > 1mb | to json'

# String interpolation (outer single quotes to avoid $ conflicts)
nu -c 'let x = 42; $"answer: ($x)"'

# Multi-statement
nu -c 'let data = (open file.json); $data | get field'
```

Use single quotes for the outer wrapper since Nushell uses `$` for variables.
118 changes: 118 additions & 0 deletions skills/nushell/references/bash-equivalents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Bash to Nushell Translation

## File and Directory Operations

| Bash | Nushell |
|------|---------|
| `ls` | `ls` |
| `ls -la` | `ls --long --all` or `ls -la` |
| `ls -d */` | `ls \| where type == dir` |
| `find . -name "*.rs"` | `glob **/*.rs` or `ls **/*.rs` |
| `mkdir -p path` | `mkdir path` (creates parents automatically) |
| `touch file` | `touch file` |
| `cp src dst` | `cp src dst` |
| `cp -r src dst` | `cp -r src dst` |
| `mv old new` | `mv old new` |
| `rm file` | `rm file` |
| `rm -rf dir` | `rm -r dir` |
| `cat file` | `open --raw file` |
| `head -5 file` | `open --raw file \| lines \| first 5` |
| `tail -5 file` | `open --raw file \| lines \| last 5` |
| `wc -l file` | `open --raw file \| lines \| length` |

## Output Redirection

| Bash | Nushell |
|------|---------|
| `> file` | `o> file` or `\| save file` |
| `>> file` | `o>> file` or `\| save --append file` |
| `> /dev/null` | `\| ignore` |
| `> /dev/null 2>&1` | `o+e>\| ignore` |
| `2>&1` | `o+e>\| ...` |
| `cmd \| tee log \| cmd2` | `cmd \| tee { save log } \| cmd2` |

## Text Processing

| Bash | Nushell |
|------|---------|
| `grep pattern` | `where $it =~ pattern` or `find pattern` |
| `grep -r pattern .` | `open --raw file \| lines \| where $it =~ pattern` |
| `sed 's/old/new/'` | `str replace 'old' 'new'` |
| `sed 's/old/new/g'` | `str replace --all 'old' 'new'` |
| `awk '{print $1}'` | `split column ' ' \| get column0` |
| `sort` | `sort` |
| `sort -u` | `sort \| uniq` |
| `uniq` | `uniq` |
| `cut -d: -f1` | `split column ':' \| get column0` |
| `tr '[:upper:]' '[:lower:]'` | `str downcase` |

## Variables and Environment

| Bash | Nushell |
|------|---------|
| `VAR=value` | `let var = "value"` |
| `export VAR=value` | `$env.VAR = "value"` |
| `echo $VAR` | `$env.VAR` |
| `echo $HOME` | `$env.HOME` |
| `echo $PATH` | `$env.PATH` (list on macOS/Linux) |
| `echo $?` | `$env.LAST_EXIT_CODE` |
| `unset VAR` | `hide-env VAR` |
| `${VAR:-default}` | `$env.VAR? \| default "value"` |
| `export PATH=$PATH:/new` | `$env.PATH = ($env.PATH \| append "/new")` |
| `FOO=bar cmd` | `FOO=bar cmd` (same syntax) |

## Command Execution

| Bash | Nushell |
|------|---------|
| `cmd1 && cmd2` | `cmd1; cmd2` |
| `cmd1 \|\| cmd2` | `try { cmd1 } catch { cmd2 }` |
| `$(command)` | `(command)` |
| `` `command` `` | `(command)` |
| `command &` | `job spawn { command }` |
| `jobs` | `job list` |
| `kill %1` | `job kill $id` |
| `type cmd` | `which cmd` |
| `man cmd` | `help cmd` |
| `bash -c 'code'` | `nu -c 'code'` |
| `source script.sh` | `source script.nu` |

## Control Flow

| Bash | Nushell |
|------|---------|
| `if [ cond ]; then ... fi` | `if $cond { ... }` |
| `[ -f file ]` | `"file" \| path exists` |
| `[ -d dir ]` | `("dir" \| path exists) and ("dir" \| path type) == "dir"` |
| `for f in *.md; do echo $f; done` | `ls *.md \| each { $in.name }` |
| `for i in $(seq 1 10); do ...; done` | `for i in 1..10 { ... }` |
| `while read line; do ...; done < file` | `open file \| lines \| each {\|line\| ... }` |
| `case $x in ... esac` | `match $x { pattern => result, ... }` |

## Pipes and Subshells

| Bash | Nushell |
|------|---------|
| `cmd \| head -5` | `cmd \| first 5` |
| `cmd \| tail -5` | `cmd \| last 5` |
| `\` (line continuation) | `(` wrap in parens `)` |
| `read var` | `let var = (input)` |
| `read -s secret` | `let secret = (input -s)` |

## History

| Bash | Nushell |
|------|---------|
| `!!` | `!!` (inserts, doesn't execute) |
| `!$` | `!$` |
| `Ctrl+R` | `Ctrl+R` |

## Key Behavioral Differences

1. **Structured vs text**: Nushell pipelines pass tables/records, not text streams
2. **Immutable by default**: `let` is immutable; use `mut` when needed
3. **No word splitting**: Nushell doesn't split variables on whitespace
4. **No glob expansion in variables**: Globs only expand in command position
5. **Static parsing**: Code is parsed before execution; no `eval` equivalent
6. **Implicit return**: Last expression is the return value; no `echo` needed
7. **`>` is comparison**: Use `o>` or `save` for file redirection
Loading