Turn natural language into shell commands via the DeepSeek API, with a confirm-before-run step.
Project description
nlsh
Describe what you want to do in plain language; nlsh asks the DeepSeek API for
the matching shell command, shows it with a short explanation, and waits for your
confirmation before running it.
$ nlsh find the 5 largest files under this directory
╭─ proposed command ───────────────────────────────────────╮
│ $ du -ah . | sort -rh | head -n 5 │
│ │
│ Lists files by size and shows the five largest. │
╰───────────────────────────────────────────────────────────╯
Run it? [y/n/e] (n):
y runs it, n cancels, e opens the command in your $EDITOR first.
Install
Once published to PyPI, install it as a standalone tool (the package is
nlsh-cli; the command it installs is nlsh):
uv tool install nlsh-cli # or: pipx install nlsh-cli
From source
One command sets up the virtual environment and all dependencies from the lockfile:
uv sync
This creates .venv/ and installs nlsh. Run it with uv run nlsh ..., or
activate the env first (source .venv/bin/activate) and just call nlsh.
Plain pip (no uv) works too:
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
pip install -e .
Configure
Easiest — store the key interactively (written to ~/.config/nlsh/config.toml,
mode 0600):
nlsh config set-key
nlsh config set-model deepseek-v4-pro # optional, change default model
nlsh config show # show resolved config (key masked)
Or via environment variable:
export DEEPSEEK_API_KEY=sk-...
or edit ~/.config/nlsh/config.toml directly:
api_key = "sk-..."
model = "deepseek-v4-flash" # optional (also: deepseek-v4-pro)
base_url = "https://api.deepseek.com" # optional
Resolution order for each setting is: environment variable → config file → default.
Usage
nlsh <natural language request>
-y, --yes run without confirmation (dangerous commands still prompt)
-n, --dry-run only print the command, never run it
--pro use the stronger model (deepseek-v4-pro) for this request
-m, --model ID override the model id for this request
--no-stream wait for the full response instead of streaming it
--version show version
The command streams in live as the model generates it (on a terminal); use
--no-stream to disable.
config and explain are reserved subcommand names. If your request happens to
start with one of them, put -- first to force translate mode:
nlsh -- explain why my disk keeps filling up
At the confirmation prompt: y runs, n cancels, e edits first, c copies
the command to the clipboard without running it. Commands that match risky
patterns (rm -rf, dd of=/dev/…, mkfs, fork bombs, curl … | sh, sudo,
…) are flagged in red and require typing the full word yes before they run —
even under --yes. Multi-stage commands (pipes, &&) are shown with a per-step
breakdown.
Explain a command (reverse mode)
Paste a command you don't understand and get a stage-by-stage breakdown:
nlsh explain git log --oneline --graph --decorate
Clipboard copy uses an OSC 52 terminal escape by default, so it reaches your
local clipboard even over SSH. When there is no terminal it falls back to
pbcopy/wl-copy/xclip/xsel/clip.exe.
Development
uv sync # installs deps + dev tools (pytest, ruff)
uv run pytest -q # run the test suite
uv run ruff check . # lint
CI (GitHub Actions) runs ruff + pytest across Python 3.9–3.13 and checks the lockfile on every push and PR.
Releasing
The version is derived from the git tag by hatch-vcs, so a release is just a
tag:
git tag v0.1.0
git push origin v0.1.0
The Publish workflow builds the sdist/wheel and uploads them to PyPI via
Trusted Publishing (configure a
pypi environment on the repo and register the workflow as a trusted publisher
— no API token stored).
How it works
nlsh sends your request plus context (OS, shell, current directory) to the
DeepSeek chat API constrained to JSON output, parses {command, explanation},
and runs the chosen command through your $SHELL so aliases and the working
directory behave as expected.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nlsh_cli-0.2.1.tar.gz.
File metadata
- Download URL: nlsh_cli-0.2.1.tar.gz
- Upload date:
- Size: 33.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a97cc538668eb0fd7e87da4db3a1bb3296ea7a24bb5052524f4fd0185d27f5e8
|
|
| MD5 |
7f77feb862f426ae45c7ad17fa1677bb
|
|
| BLAKE2b-256 |
e9a529e81c6e915159d5004f3afcfdf5a943bd0a7cf45ecaa26b752541671ec0
|
File details
Details for the file nlsh_cli-0.2.1-py3-none-any.whl.
File metadata
- Download URL: nlsh_cli-0.2.1-py3-none-any.whl
- Upload date:
- Size: 15.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1e2c8ad5a257b96cf338368c196e8f92c5b640d959c2550cdd61fb6b8dbbb7d
|
|
| MD5 |
c3686c1e5255433d35fdcf3723d2fa10
|
|
| BLAKE2b-256 |
ab0ef0c8f64e2dc87554dbd3cee1f2caaad1268f3d1a4fd35127c7d884d40e16
|