AgentSkillsCN

nushell-tooling

当用户提出“对 Nushell 代码进行 lint 检查”、“使用 nu-lint”、“设置 Nushell LSP”、“配置 nu --lsp”、“使用 nu --mcp”、“测试 Nushell 脚本”、“生成文档”、“格式化 Nushell 代码”、“调试 Nushell”,或在 Nushell 中提及 LSP、MCP 服务器、Lint 检查、测试、调试,或 IDE 集成时,应使用此技能。

SKILL.md
--- frontmatter
name: nushell-tooling
description: This skill should be used when the user asks to "lint Nushell code", "use nu-lint", "set up Nushell LSP", "configure nu --lsp", "use nu --mcp", "test Nushell scripts", "generate documentation", "format Nushell code", "debug Nushell", or mentions LSP, MCP server, linting, testing, debugging, or IDE integration for Nushell.
version: 1.0.0

Nushell Tooling & Developer Experience

Guide for Nushell development tooling including LSP, MCP server, linting, testing, and documentation generation. Covers IDE integration, debugging, and workflow automation.

Language Server Protocol (LSP)

Starting the LSP

nushell
# Start LSP server
nu --lsp

# The LSP provides:
# - Code completion
# - Hover information
# - Go to definition
# - Diagnostics (errors/warnings)
# - Document symbols

IDE Configuration

VS Code

Install "Nushell" extension, then configure:

json
// settings.json
{
    "nushell.server.path": "nu",
    "nushell.server.args": ["--lsp"],
    "nushell.config.path": "~/.config/nushell/config.nu",
    "nushell.env.path": "~/.config/nushell/env.nu"
}

Neovim (nvim-lspconfig)

lua
-- init.lua
require('lspconfig').nushell.setup{
    cmd = { "nu", "--lsp" },
    filetypes = { "nu" },
    settings = {}
}

Helix

toml
# languages.toml
[[language]]
name = "nu"
language-servers = ["nu"]

[language-server.nu]
command = "nu"
args = ["--lsp"]

Zed

json
// settings.json
{
    "languages": {
        "Nushell": {
            "language_servers": ["nu-lsp"]
        }
    },
    "lsp": {
        "nu-lsp": {
            "binary": { "path": "nu", "arguments": ["--lsp"] }
        }
    }
}

LSP Features

FeatureDescription
CompletionCommand names, flags, variables
HoverType info, documentation
DiagnosticsSyntax errors, type mismatches
Go to DefinitionJump to command/variable definition
Document SymbolsOutline of definitions in file
Signature HelpParameter hints while typing

MCP Server (Model Context Protocol)

Starting MCP Server

nushell
# Start MCP server for Claude Code integration
nu --mcp

# Available tools:
# - list_commands: List all Nushell commands
# - command_help: Get detailed help for a command
# - evaluate: Execute Nushell code and return results

MCP Configuration

For Claude Code, add to settings:

json
{
    "mcpServers": {
        "nushell": {
            "command": "nu",
            "args": ["--mcp"]
        }
    }
}

Using MCP Tools

The MCP server provides structured access to Nushell:

nushell
# Through MCP tools:

# list_commands - discover available commands
# Returns structured list of all commands with signatures

# command_help [command_name] - get detailed help
# Returns documentation, examples, flags

# evaluate [code] - run Nushell code
# Returns structured output (tables, records, etc.)

Linting

nu-lint

bash
# Install nu-lint (separate tool)
cargo install nu-lint

# Run linter
nu-lint check script.nu
nu-lint check --recursive ./src/

Built-in Checks

nushell
# Syntax validation
nu --commands "source script.nu"

# Parse without execution
nu-check script.nu
nu-check --as-module module.nu

# Check specific constructs
nu-check --debug script.nu  # Verbose output

Common Lint Rules

RuleDescriptionFix
Unused variableVariable defined but not usedRemove or use with _ prefix
Shadow variableVariable redefined in same scopeUse different name
Type mismatchIncompatible types in operationAdd type conversion
Missing parameterRequired parameter not providedAdd parameter or default
Deprecated commandUsing old command nameUse new command name

Custom Lint Script

nushell
# lint.nu - Custom linting wrapper
def lint [path: path, --fix] {
    let files = if ($path | path type) == "dir" {
        glob $"($path)/**/*.nu"
    } else {
        [$path]
    }

    let results = $files | each { |file|
        let check = do { nu-check $file } | complete
        {
            file: $file
            ok: ($check.exit_code == 0)
            errors: ($check.stderr | lines)
        }
    }

    let errors = $results | where not ok

    if ($errors | is-empty) {
        print "✅ All files passed"
    } else {
        $errors | each { |r|
            print $"❌ ($r.file)"
            $r.errors | each { |e| print $"   ($e)" }
        }
        error make { msg: $"($errors | length) files with errors" }
    }
}

Testing

Test Framework Pattern

nushell
# tests/test_utils.nu
use ../src/utils.nu

# Test definitions
def "test add" [] {
    let result = utils add 2 3
    assert-eq 5 $result "add should return sum"
}

def "test subtract" [] {
    let result = utils subtract 5 3
    assert-eq 2 $result "subtract should return difference"
}

# Test runner
def "test all" [] {
    let tests = (scope commands | where name =~ "^test " | get name)

    let results = $tests | each { |test_name|
        try {
            do (scope commands | where name == $test_name | get 0.closure).0
            {name: $test_name, passed: true, error: null}
        } catch { |err|
            {name: $test_name, passed: false, error: $err.msg}
        }
    }

    let passed = $results | where passed | length
    let failed = $results | where not passed

    print $"Passed: ($passed)/($results | length)"

    if ($failed | is-not-empty) {
        print "\nFailures:"
        $failed | each { |f|
            print $"  ❌ ($f.name): ($f.error)"
        }
        exit 1
    }
}

Assertion Helpers

nushell
# assertions.nu

# Assert equality
def assert-eq [expected: any, actual: any, message?: string] {
    if $expected != $actual {
        error make {
            msg: ($message | default $"Expected ($expected), got ($actual)")
        }
    }
}

# Assert truthy
def assert [condition: bool, message?: string] {
    if not $condition {
        error make { msg: ($message | default "Assertion failed") }
    }
}

# Assert contains
def assert-contains [haystack: any, needle: any, message?: string] {
    let contains = match ($haystack | describe) {
        "string" => { $haystack | str contains $needle }
        "list" => { $needle in $haystack }
        _ => false
    }
    if not $contains {
        error make {
            msg: ($message | default $"Expected ($haystack) to contain ($needle)")
        }
    }
}

# Assert throws
def assert-throws [closure: closure, message?: string] {
    try {
        do $closure
        error make { msg: ($message | default "Expected exception, but none thrown") }
    } catch {
        # Expected behavior
    }
}

Running Tests

nushell
# Run all tests
nu tests/test_all.nu

# Run specific test file
nu tests/test_utils.nu

# With verbose output
nu --commands "use tests/test_utils.nu; test all"

Documentation Generation

Built-in Documentation

nushell
# Generate help for custom commands
def documented-command [
    arg1: string  # First argument description
    --flag: int   # Flag description
] {
    # Implementation
}

# Access documentation
help documented-command

DocGen Script

nushell
# docgen.nu - Generate markdown documentation from .nu files

def generate-docs [source_dir: path, output_dir: path] {
    mkdir $output_dir

    glob $"($source_dir)/**/*.nu" | each { |file|
        let commands = extract-commands $file
        let doc = format-markdown $file $commands
        let output = [
            $output_dir,
            ($file | path basename | str replace ".nu" ".md")
        ] | path join
        $doc | save --force $output
    }
}

def extract-commands [file: path] {
    # Parse file and extract exported commands
    open $file
    | lines
    | window 2
    | where { |w| $w.1 | str starts-with "export def" }
    | each { |w|
        {
            comment: $w.0
            signature: $w.1
        }
    }
}

def format-markdown [file: path, commands: list] {
    let header = $"# ($file | path basename)\n\n"
    let body = $commands | each { |cmd|
        $"## `($cmd.signature | str replace 'export def ' '' | str trim)`\n\n($cmd.comment | str replace '# ' '')\n"
    } | str join "\n"
    $header + $body
}

Debugging

Print Debugging

nushell
# Debug helper
def dbg [label: string, value: any] {
    print -e $"[DEBUG] ($label): ($value | to nuon)"
    $value  # Pass through
}

# Usage in pipeline
$data
| dbg "after load" $in
| transform
| dbg "after transform" $in

Describe Types

nushell
# Inspect type
$value | describe

# Detailed type info
$value | describe --detailed

# Check structure
$data | columns
$data | schema

Error Investigation

nushell
# Capture errors
try {
    risky-operation
} catch { |err|
    print $"Error type: ($err | describe)"
    print $"Message: ($err.msg)"
    print $"Debug: ($err | to nuon)"
}

REPL Exploration

nushell
# Interactive exploration
let data = open complex.json

# Check structure
$data | describe
$data | columns
$data | get some_field | first 5

# Test transformations incrementally
$data | step1
$data | step1 | step2

Help System Integration

Using Built-in Help

nushell
# Command categories
help commands      # All commands
help modules       # Available modules
help operators     # Operator reference
help escapes       # Escape sequences
help pipe-and-redirect  # Pipeline syntax

# Specific help
help str           # String commands
help str join      # Specific subcommand
help http get      # HTTP commands

Creating Discoverable Commands

nushell
# Well-documented command
# Processes input data according to specified format
#
# The format parameter determines output structure.
# Supported formats: json, csv, table
#
# Examples:
#   process-data input.json --format csv
#   open data.csv | process-data --format table
export def process-data [
    input?: path      # Input file path (or pipe data)
    --format: string  # Output format: json, csv, table
    --verbose (-v)    # Enable verbose logging
] {
    # Implementation with rich help
}

Workflow Automation

Pre-commit Hook

nushell
# .git/hooks/pre-commit (make executable)
#!/usr/bin/env nu

# Lint all changed .nu files
let files = ^git diff --cached --name-only --diff-filter=d
| lines
| where ($it | str ends-with ".nu")

if ($files | is-not-empty) {
    $files | each { |f|
        print $"Checking: ($f)"
        let result = do { nu-check $f } | complete
        if $result.exit_code != 0 {
            print $"Error in ($f):"
            print $result.stderr
            exit 1
        }
    }
}

print "✅ All Nushell files pass"

CI/CD Integration

yaml
# .github/workflows/nushell.yml
name: Nushell CI

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Nushell
        run: |
          curl -sL https://github.com/nushell/nushell/releases/latest/download/nu-*-x86_64-unknown-linux-gnu.tar.gz | tar xz
          sudo mv nu-*/nu /usr/local/bin/
      - name: Lint
        run: |
          nu -c "glob **/*.nu | each { |f| nu-check $f }"

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Nushell
        run: |
          curl -sL https://github.com/nushell/nushell/releases/latest/download/nu-*-x86_64-unknown-linux-gnu.tar.gz | tar xz
          sudo mv nu-*/nu /usr/local/bin/
      - name: Test
        run: nu tests/run_all.nu

Additional Resources

Reference Files

For detailed configurations:

  • references/ide-configs.md - Complete IDE setup guides
  • references/ci-templates.md - CI/CD configuration templates

Example Files

Working examples in examples/:

  • test-framework.nu - Complete test framework
  • docgen.nu - Documentation generator
  • lint-wrapper.nu - Enhanced linting script

Scripts

Utility scripts in scripts/:

  • setup-hooks.nu - Install git hooks
  • run-tests.nu - Test runner