Your shell already saw you fail - now it can actually help
AI-powered shell assistant for zsh, bash, fish, and PowerShell.
nsh lives in your terminal. It records command history, understands your project context, reads your scrollback, and turns natural-language requests into shell commands or direct answers - all without leaving your prompt.
? why did my last command fail
? set up a python virtualenv for this repo
? fix
nsh prefills commands at your prompt for review before execution. It never runs anything blindly (unless you enable the autorun mode!)
nsh wraps your shell in a PTY, capturing scrollback and command history into a local SQLite database. When you ask a question with ?, nsh builds a rich context - your OS, shell, working directory, recent terminal output, project structure, git state, and conversation history - then streams a response from your configured LLM provider.
The assistant responds by calling tools: command to prefill a shell command, chat for text answers, or any of 16 other built-in tools for investigation, file editing, web search, and more. It can chain multiple tool calls in a single turn, investigating before acting.
you: ? install ripgrep
nsh: [searches history] → [checks brew availability] → [prefills command]
$ brew install ripgrep
↵ Enter to run · Edit first · Ctrl-C to cancel
Three aliases, each a single character:
? ...- standard query?? ...- reasoning/thinking mode (extended thinking for complex problems)?! ...- private mode (query and response are not saved to history)
Append !! to any query to auto-execute the suggested command without confirmation.
nsh writes suggested commands to your shell's editing buffer. You see the command at your prompt, can edit it, then press Enter to run it - or Ctrl-C to cancel. This is the default and safest mode.
Two alternative modes are available via configuration: confirm (approve/reject without editing) and autorun (execute immediately for safe commands).
Every query includes rich context assembled automatically:
- Terminal scrollback - recent output from your PTY session, including SSH sessions
- Command history - past commands with exit codes, durations, and AI-generated summaries
- Project detection - recognizes Rust, Node.js, Python, Go, Ruby, Java, C/C++, Nix, Docker, and Make projects
- Git state - current branch, status, and recent commits
- Cross-TTY context - optionally includes activity from other open terminal sessions
- Environment - OS, architecture, installed package managers, development tools
nsh can chain multiple tool calls per query (30 by default, configurable). It investigates before acting - searching history, reading files, running safe commands, querying the web, and asking clarifying questions when needed - then executes and verifies results.
The pending flag on command suggestions enables autonomous multi-step sequences. Safe pending=true commands now auto-execute by default and feed their output back into the same tool loop, so nsh can continue investigating, fixing, and verifying without stopping. If you prefer explicit approval for each intermediate step, set execution.confirm_intermediate_steps = true.
| Tool | Purpose |
|---|---|
command |
Prefill a shell command for review |
chat |
Final text response when work is complete or purely informational |
search_history |
FTS5 + regex search across all command history |
grep_file |
Regex search within files with context lines |
read_file |
Read file contents with line numbers |
list_directory |
List directory contents with metadata |
web_search |
Search the web via Perplexity/Sonar |
run_command |
Execute safe, allowlisted commands silently |
ask_user |
Clarify ambiguity or gather preferences while keeping the loop active |
write_file |
Create or overwrite files (with diff preview and trash backup) |
patch_file |
Surgical find-and-replace in files (with diff preview) |
man_page |
Retrieve man pages for commands |
manage_config |
Modify nsh settings (with confirmation) |
install_skill |
Create reusable custom tool templates |
install_mcp_server |
Add MCP tool servers to configuration |
nsh extracts structured entities (hostnames, IPs) from commands and stores them in a searchable index. This enables queries like:
you: ? what servers have I ssh'd into recently
nsh: Recent machine targets for `ssh` (most recent first):
- [2026-02-11T17:49:15Z] 135.181.128.145 (via ssh)
- [2026-02-11T17:47:15Z] ssh.phx.nearlyfreespeech.net (via ssh)
- Secret redaction - over 60 built-in patterns detect and redact API keys, tokens, private keys, JWTs, database URLs, and more before sending context to the LLM. Custom patterns can be added.
- Command risk assessment - every suggested command is classified as
safe,elevated, ordangerous. Dangerous commands (recursive deletion of system paths, disk formatting, fork bombs, piping remote scripts to shell) always require explicityesconfirmation. - Sensitive directory blocking - reads and writes to
~/.ssh,~/.gnupg,~/.aws,~/.kube,~/.docker, and similar directories are blocked by default. - Tool output sandboxing - tool results are delimited by random boundary tokens and treated as untrusted data. Prompt injection attempts in tool output are filtered.
- Protected settings - security-critical configuration keys (API keys, allowlists, redaction settings) cannot be modified by the AI.
- Audit logging - all tool calls are logged to
~/.nsh/audit.logwith automatic rotation.
Skills are reusable tools exposed to the model. nsh supports two flavors:
- Command-template skills (shell)
# ~/.nsh/skills/deploy.toml
name = "deploy"
description = "Deploy to production"
command = "kubectl apply -f deploy/{environment}.yaml"
timeout_seconds = 60
[parameters.environment]
type = "string"
description = "Target environment (staging, production)"Skills appear as tools in the LLM's toolkit and can be invoked naturally:
you: ? deploy to staging
nsh: [calls skill_deploy with environment=staging]
- Code-based skills (Python / Node / custom runtime)
Define a runtime and an inline script. nsh writes the script to a temp file, passes parameters as JSON via stdin and NSH_SKILL_PARAMS_JSON, and executes with the runtime:
# ~/.nsh/skills/humanize.toml
name = "humanize"
description = "Format paths or listings more readably"
runtime = "python3"
script = '''
import os, sys, json
params = json.loads(sys.stdin.read() or os.environ.get("NSH_SKILL_PARAMS_JSON","{}"))
text = params.get("text", "")
print(text.replace("/", " → "))
'''
timeout_seconds = 15
[parameters.text]
type = "string"
description = "Text to humanize"Notes:
- Project-local skills live in
./.nsh/skills/and require a one-time approval per run. - Either
commandor bothruntime+scriptmust be present. - Parameters are validated for safe characters in command-template mode. In code mode, use JSON passed on stdin.
Importing external skills:
- You can hand-write TOML as above, or use the upcoming
nsh skills import --source <path|url>to auto-detect and convert common formats (Claude Skills, MCP tools, OpenAI tool schemas, LangChain). Unknown formats will fall back to a guided conversion that outputs TOML for your review.
nsh supports the Model Context Protocol for extending its capabilities with external tool servers. Both stdio (local process) and HTTP (remote endpoint) transports are supported:
[mcp.servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
[mcp.servers.remote_api]
transport = "http"
url = "https://mcp.example.com"
headers = { Authorization = "Bearer ..." }Advanced HTTP config and tool filtering:
[mcp.servers.cloudflare]
transport = "http"
url = "https://mcp.cloudflare.com/jsonrpc"
bearer_token = "$CF_API_TOKEN" # expands from env
timeout_seconds = 30
[mcp.servers.cloudflare.headers]
X-Client = "nsh"
# Optional tool filtering
disable_tools = ["legacy_", "danger_"]
[mcp.servers.cloudflare.rename_tools]
inject_data = "cf_inject_data"You can install a skill repo directly:
nsh tools install_skill --repo https://github.com/owner/skill-repo.gitRepos are cloned into ~/.nsh/skills/<repo>/ and auto-discovered if they contain skill.toml or nsh.toml at the repo root. Project-local repos under .nsh/skills/ are also detected.
nsh works with any OpenAI-compatible API. Built-in provider support includes:
- OpenRouter (default) - access to hundreds of models
- Anthropic - Claude models with prompt caching
- OpenAI - GPT models
- Google Gemini - Gemini models
- Ollama - local models
Model chains with automatic fallback on rate limits or errors are configured via [models]:
[models]
main = ["google/gemini-2.5-flash", "anthropic/claude-sonnet-4.6"]
fast = ["google/gemini-2.5-flash-lite", "anthropic/claude-haiku-4.5"]- Interactive chat mode -
nsh chatfor a REPL-style conversation - Shell history import - automatically imports existing bash, zsh, fish, and PowerShell history on first run
- Cost tracking -
nsh costshows token usage and estimated costs by model - JSON output mode -
nsh query --jsonfor structured event stream output - Conversation export -
nsh exportin markdown or JSON format - Self-update -
nsh updatedownloads and verifies new releases via DNS TXT records and SHA256 - Shell completions -
nsh completions zsh|bash|fishgenerates completion scripts - Project-local config -
.nsh.tomlor.nsh/config.tomlfor per-project overrides (restricted tocontextanddisplaysections) - Custom instructions - global via config or per-project via
.nsh/instructions.md - Hot-reloading config - changes to
config.tomltake effect on the next query
Before every query, nsh retrieves relevant long-term memories and injects a structured XML prompt into the system prompt under a "PERSISTENT MEMORY" section. This includes:
- Core memory: user facts, agent persona, environment. Always included.
- Episodic: recent and relevant events (commands, errors, interactions).
- Semantic: facts about projects, tools, preferences.
- Procedural: step-by-step workflows.
- Resource: digests of files/docs.
- Knowledge Vault: captions of sensitive secrets (encrypted values are never shown).
Control memory behavior:
# Enable incognito mode (skip recording)
nsh config set memory.incognito true
# Re-enable recording
nsh config set memory.incognito false
# Run maintenance (decay + reflection)
nsh memory maintain
# Search memories
nsh memory search --type semantic "cargo build"
# Inspect core memory
nsh memory coreThe memory prompt is redacted for secrets before inclusion.
- Rust 1.85+ (edition 2024) - for building from source
- macOS, Linux, FreeBSD, or Windows
- zsh, bash, fish, or PowerShell
- At least one LLM provider API key (OpenRouter is the default)
curl -fsSL https://nsh.tools/install.sh | bashUse this on macOS, Linux, FreeBSD, WSL, and MSYS/Git Bash environments.
iwr -useb https://nsh.tools/install.ps1 | iexUse this on native Windows PowerShell (not WSL/MSYS).
git clone https://github.com/fluffypony/nsh.git
cd nsh
# Installs both binaries: nsh (shim) and nsh-core
cargo install --path . --lockedcargo build --release
# Binaries at target/release/: nsh (shim), nsh-core (core)nsh ships as two binaries:
nsh— the stable shim. It handlesnsh wrapdirectly so your long-lived terminal session uses frozen, stable PTY code. For all other commands, it resolves andexecs the latestnsh-coreat~/.nsh/bin/nsh-core.nsh-core— the full implementation binary. This is what updates frequently.
Installers place the shim once into your cargo bin if it’s missing, and always update ~/.nsh/bin/nsh-core. During an update, existing terminals do not need to restart. The next nsh invocation transparently runs the new core.
Daemon restarts are graceful: nsh signals SIGHUP to the daemon, which drains existing connections with a short timeout and re-execs itself using the latest core.
Create ~/.nsh/config.toml:
[provider]
default = "openrouter"
model = "google/gemini-2.5-flash"
[provider.openrouter]
api_key = "sk-or-v1-..."
# or: api_key_cmd = "op read 'op://Vault/OpenRouter/credential'"Environment variable fallback is supported: OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY.
Add to your shell rc file:
# ~/.zshrc
command -v nsh >/dev/null && [[ -z "${NSH_PTY_ACTIVE:-}" ]] && nsh wrap
eval "$(nsh init zsh)"
# ~/.bashrc
command -v nsh >/dev/null && [[ -z "${NSH_PTY_ACTIVE:-}" ]] && nsh wrap
eval "$(nsh init bash)"
# fish: ~/.config/fish/conf.d/nsh.fish
command -v nsh >/dev/null; and not set -q NSH_PTY_ACTIVE; and nsh wrap
nsh init fish | source
# PowerShell profile ($PROFILE)
Invoke-Expression (nsh init powershell)nsh wrap runs your shell inside a PTY wrapper for scrollback capture. It's optional but recommended. The PTY wrapper lives in the stable nsh shim, so it never needs to be restarted for updates.
? why did my last command fail
?? set up a python virtualenv for this repo
?! what is the safest way to clean docker images
? install ripgrep
? fix # after a failed command
? ignore # suppress hint for the last exit code| Alias | Mode | Description |
|---|---|---|
? ... |
Normal | Standard query |
?? ... |
Reasoning | Extended thinking (--think) |
?! ... |
Private | No query/response history writes |
? ignore [code] |
Suppress | Disable failure hints for an exit code |
| Command | Purpose |
|---|---|
nsh init <shell> |
Print shell integration script |
nsh wrap [--shell <path>] |
Run shell inside PTY wrapper |
nsh query [--think] [--private] [--json] <words...> |
Ask the assistant |
nsh chat |
Interactive REPL chat mode |
nsh history search <query> [--limit N] |
Full-text search command history |
nsh status |
Show runtime state (includes sidecar status and last update check) |
nsh doctor [capture] [--no-prune] [--no-vacuum] [--prune-days D] |
DB integrity check or capture-health diagnostic |
nsh config [path|show|edit] |
View or edit configuration |
nsh reset |
Clear session conversation context |
nsh cost [today|week|month|all] |
Usage and cost summary |
nsh export [--format markdown|json] [--session ID] |
Export conversation history |
nsh provider list-local |
List local Ollama models |
nsh update |
Download and verify latest release |
nsh redact-next |
Skip capture for the next command |
nsh completions <shell> |
Generate shell completion script |
| Command | Purpose |
|---|---|
nsh record ... |
Command capture hook endpoint |
nsh session start|end|label ... |
Session lifecycle management |
nsh heartbeat --session ID |
Keep session alive |
nsh daemon-send ... |
Send request to daemon |
nsh daemon-read ... |
Read daemon capture/scrollback |
Main config: ~/.nsh/config.toml
Project-local overrides: .nsh.toml or .nsh/config.toml (restricted to context and display sections).
[provider]
default = "openrouter"
model = "google/gemini-2.5-flash"
fallback_model = "anthropic/claude-sonnet-4.6"
web_search_model = "perplexity/sonar"
timeout_seconds = 120
[provider.openrouter]
# api_key = "..."
# api_key_cmd = "..."
# base_url = "https://openrouter.ai/api/v1"
[provider.anthropic]
# api_key = "..."
[provider.openai]
# api_key = "..."
[provider.ollama]
# base_url = "http://localhost:11434"
[provider.gemini]
# api_key = "..."
[context]
scrollback_lines = 1000
scrollback_pages = 10
history_summaries = 100
history_limit = 20
other_tty_summaries = 10
max_other_ttys = 20
project_files_limit = 100
git_commits = 10
retention_days = 1095
max_output_storage_bytes = 65536
scrollback_rate_limit_bps = 10485760
scrollback_pause_seconds = 2
include_other_tty = false
restore_last_cwd_per_tty = true
# custom_instructions = "..."
[hints]
suppressed_exit_codes = [130, 137, 141, 143]
[models]
main = [
"google/gemini-2.5-flash",
"google/gemini-3-flash-preview",
"anthropic/claude-sonnet-4.6",
]
fast = [
"google/gemini-2.5-flash-lite",
"anthropic/claude-haiku-4.5",
]
[tools]
run_command_allowlist = [
"uname", "which", "wc", "file", "stat", "ls", "echo",
"whoami", "hostname", "date", "env", "printenv", "id",
"df", "free", "python3 --version", "node --version",
"git status", "git branch", "git log", "git diff",
"pip list", "cargo --version",
]
sensitive_file_access = "block" # block | ask | allow
[web_search]
provider = "openrouter"
model = "perplexity/sonar"
[display]
chat_color = "\x1b[3;36m"
thinking_indicator = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
[redaction]
enabled = true
replacement = "[REDACTED]"
disable_builtin = false
patterns = [] # custom regex patterns
[capture]
mode = "vt100"
alt_screen = "drop" # drop | snapshot
[db]
busy_timeout_ms = 10000
[execution]
mode = "prefill" # prefill | confirm | autorun
allow_unsafe_autorun = false
max_tool_iterations = 30
confirm_intermediate_steps = false
[mcp]
# [mcp.servers.example]
# transport = "stdio"
# command = "npx"
# args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
# env = { EXAMPLE = "1" }
# timeout_seconds = 30These cannot be modified by the AI via manage_config:
execution.allow_unsafe_autoruntools.sensitive_file_accesstools.run_command_allowlistredaction.enabled/redaction.disable_builtin- Any
api_key,api_key_cmd, orbase_urlfield
All data is stored in ~/.nsh/:
| File | Purpose |
|---|---|
config.toml |
User configuration |
nsh.db |
SQLite database (sessions, commands, conversations, usage) |
audit.log |
JSON-line audit log of tool calls |
skills/*.toml |
Custom skill definitions |
pending_cmd_<session> |
Command prefill buffer |
pending_flag_<session> |
Multi-step continuation marker |
scrollback_<session> |
Scrollback capture buffer |
daemon_<session>.sock |
Daemon Unix socket |
update_pending |
Staged self-update metadata |
cargo fmt -- --check # format check
cargo clippy --all-targets -- -D warnings # lint
cargo test # full test suite
cargo run --bin nsh -- status # run shim
cargo run --bin nsh-core -- status # run core directlycargo-make tasks are defined in Makefile.toml:
cargo make test # lint + test
cargo make quality # format + lint + test + audit
cargo make quality-full # + unsafe code audit (geiger)
cargo make release-host # build release for current platform
cargo make release-matrix # build for all supported targets
cargo make sync-site-install # copy install scripts -> ../nsh-site/scripts/release-builds.sh --host-only
scripts/release-builds.sh --targets x86_64-apple-darwin,aarch64-apple-darwin
scripts/release-builds.sh --backend zigbuildSupported targets: macOS (x64/arm64), Linux (x64/arm64/i686/riscv64), FreeBSD (x86/x64), Windows (x64/aarch64).
- Sync the installer script to the website repo:
cargo make sync-site-install
# or: cargo make sync-site-install-watch- Build release artifacts:
# Builds per-target archives containing both binaries:
# - nsh (core binary used by auto-update)
# - nsh-shim (shim; installed once if missing)
scripts/release-builds.sh --version 1.0.0- Create a GitHub release and upload
dist/*artifacts:
gh release create v1.0.0 dist/nsh-*.tar.gz dist/nsh-*.tar.gz.sha256 \
--title "nsh v1.0.0" --notes "nsh v1.0.0"- Publish updater DNS TXT records for
update.nsh.toolsfromdist/update-records.txt:
1.0.0:x86_64-unknown-linux-gnu:<sha256-of-core-binary>
1.0.0:aarch64-unknown-linux-gnu:<sha256-of-core-binary>
...
Use one TXT record per target line.
nsh status # inspect session/provider/db state
When the global daemon is running, `nsh status` also reports the CLIProxyAPI sidecar state and update info:
$ nsh status Version: 0.9.2 Session: 7YQ2GNRQ Shell: /bin/zsh PTY active: yes Global daemon: running Sidecar: running on :8317 (6.6.80) Updates: last_check=2026-02-22T12:00:03Z (2h ago) status=up_to_date Provider: openrouter Model: google/gemini-2.5-flash DB path: /Users/alice/.nsh/nsh.db DB size: 8.4 MB
nsh doctor # integrity check + cleanup
nsh doctor capture # verify whether per-command output capture is active
nsh config show # verify config
RUST_LOG=debug nsh query ... # debug logging
API key resolution order: api_key in config → api_key_cmd → environment variable.
Contributions are welcome! Please read the Contributing Guide before submitting issues or pull requests. Human-authored PRs are prioritized, but AI-generated contributions that meet the quality bar are also accepted.
BSD 3-Clause - see LICENSE.
The local CLIProxyAPI sidecar is managed by the daemon and used to route subscription providers through a local OpenAI-compatible endpoint.
Common operations:
nsh cliproxy ensure # start the sidecar if not running
nsh cliproxy status # JSON-style status: running, port, version, pid
nsh cliproxy restart # restart the sidecar
nsh cliproxy check-updates # trigger an immediate update check
The daemon also checks for sidecar updates hourly and restarts it if an update is applied.