Metadata-Version: 2.5
Name: lightning-nl2sh
Version: 0.1.0
Summary: Natural language to a shell command, generated for the shell you are actually using.
Project-URL: Homepage, https://github.com/haris-a11/lightning-NL2SH
Project-URL: Issues, https://github.com/haris-a11/lightning-NL2SH/issues
Author: Haris
License-Expression: MIT
License-File: LICENSE
Keywords: cli,llm,natural-language,shell
Requires-Python: >=3.9
Requires-Dist: python-dotenv
Requires-Dist: requests
Description-Content-Type: text/markdown

# lightning-nl2sh

## 1. What it does

`lightning-nl2sh` turns a plain-English task into a shell command, using any OpenAI-compatible chat completions API (OpenRouter by default), and generates it in the syntax of **the shell you are actually using**.

```console
$ lightning-nl2sh "find all python files modified today"
find . -name '*.py' -mtime -1
```

The command is written to stdout and nothing else is. Errors go to stderr with exit code `1`, so a shell wrapper can stop cleanly when generation fails.

### Why shell-specific generation matters

| Situation               | What a generic answer gets wrong                                                                    |
| ----------------------- | --------------------------------------------------------------------------------------------------- |
| **fish**                | fish is not POSIX. `$(cmd)` must be `(cmd)`, and `export VAR=value` must be `set -x VAR value`.     |
| **PowerShell 5.1 vs 7** | Windows PowerShell 5.1 has no `&&`; commands must be chained with `;`. PowerShell 7+ supports `&&`. |
| **macOS**               | BSD userland: `sed -i` requires an argument (`sed -i '' ...`) and `stat` uses `-f`, not `-c`.       |
| **WSL**                 | Windows drives live at `/mnt/c/...`.                                                                |
| **Git Bash**            | Windows drives live at `/c/...`.                                                                    |

Supported shell IDs: `bash`, `zsh`, `fish`, `sh`, `dash`, `ksh`, `tcsh`, `csh`,
`wsl`, `gitbash`, `pwsh`, `powershell`, `cmd`. (`dash`, `ksh`, `tcsh` and `csh` are treated as `sh`.)

## 2. Install

```bash
pip install lightning-nl2sh
```

Or with [pipx](https://pipx.pypa.io/), which keeps it out of your project environments:

```bash
pipx install lightning-nl2sh
```

### From this repo

```bash
git clone https://github.com/haris-a11/lightning-NL2SH.git
cd lightning-NL2SH
pip install .
```

For development, install it editable so your edits take effect without reinstalling:

```bash
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e .
python tests/test_nl2sh.py      # 7 checks, no network, no pytest needed
```

`pip install -e .` puts `lightning-nl2sh` on your PATH only while that venv is
active. If you want the shell integration in section 3 to work from any
directory, use `pipx install .` from the checkout instead.

Then store your API key:

```bash
lightning-nl2sh set-key
```

The default provider is [OpenRouter](https://openrouter.ai/); see
[Using another provider](#using-another-provider) to point it somewhere else.

## 3. Shell setup

The shell integration exports **`NL2SH_SHELL`**. That is the whole shell-detection mechanism: the shell that runs the wrapper tells the tool what it is.

The fallback, when `NL2SH_SHELL` is unset, is the basename of `$SHELL`. Note that **`$SHELL` is only your _login_ shell** — if you are in `fish` launched from a `bash` login shell, `$SHELL` still says `bash`. Set `NL2SH_SHELL` (the integrations below do it for you) or pass `--shell`.

Every integration below stops when `lightning-nl2sh` exits non-zero.

### zsh

Add to `~/.zshrc`:

```zsh
export NL2SH_SHELL=zsh

ai() {
  local cmd
  cmd=$(lightning-nl2sh "$@") || return $?
  [[ -n "$cmd" ]] || return 1
  print -z -- "$cmd"
}
```

`print -z` pushes the command onto the editable command buffer: it appears at your prompt, ready to read, edit, or discard. Nothing runs until you press Enter.

### bash

Add to `~/.bashrc` on Linux, or `~/.bash_profile` on macOS:

```bash
export NL2SH_SHELL=bash

ai() {
  local cmd
  cmd=$(lightning-nl2sh "$@") || return $?
  [[ -n "$cmd" ]] || return 1
  read -e -i "$cmd" -p "> " cmd || return 1
  [[ -n "$cmd" ]] || return 1
  history -s "$cmd"
  eval "$cmd"
}
```

bash has no `print -z`, so `read -e -i` is the equivalent: the generated command is pre-loaded into an editable readline prompt. It is your confirmation step. `history -s` puts it in your history so
Up-arrow works afterwards.

**WSL and Git Bash** use exactly the same function; only the shell ID changes, so the model knows how Windows drives are spelled:

```bash
export NL2SH_SHELL=wsl        # inside WSL: drives are /mnt/c/...
export NL2SH_SHELL=gitbash    # in Git Bash: drives are /c/...
```

### fish

Add to `~/.config/fish/config.fish`:

```fish
set -gx NL2SH_SHELL fish

function ai
    set -l cmd (lightning-nl2sh $argv) ; or return $status
    test -n "$cmd" ; or return 1
    echo $cmd
    read -l -P "run? [y/N] " reply
    test "$reply" = y -o "$reply" = Y ; or return 1
    history append -- $cmd
    eval $cmd
end
```

fish's `commandline` builtin — the only way to preload the command buffer — works **only from a key binding**, not from a normal function. So the function above prints the command and asks for an explicit `y/N` confirmation instead, and runs it only after you confirm. `history append` needs **fish 3.2+**; drop that line on older versions.

If you would rather have the zsh-style buffer behaviour, use a key binding
(Alt-A here) instead of the function:

```fish
bind \ea 'commandline -r (lightning-nl2sh (commandline) | string collect)'
```

### PowerShell

Add to `$PROFILE`:

```powershell
$env:NL2SH_SHELL = "pwsh"        # PowerShell 7+
# $env:NL2SH_SHELL = "powershell"  # Windows PowerShell 5.1

function ai {
    $cmd = lightning-nl2sh @args
    if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($cmd)) { return }
    Write-Host $cmd
    [Microsoft.PowerShell.PSConsoleReadLine]::AddToHistory($cmd)
}
```

`AddToHistory` puts the generated command in PSReadLine's history, so pressing **Up-arrow** recalls it into your editable prompt. Nothing is executed for you.

If the profile file does not exist yet:

```powershell
New-Item -Path $PROFILE -Force
```

Pick the shell ID that matches your version — `powershell` (5.1) makes the model chain with `;`, `pwsh` (7+) lets it use `&&`.

### cmd.exe

cmd.exe cannot reliably replace the current command-line buffer, so there is no wrapper. Call the tool directly and copy the result:

```text
lightning-nl2sh "find all Python files modified today"
```

Set `NL2SH_SHELL` once with `setx NL2SH_SHELL cmd`.

## 4. Configuration

```bash
lightning-nl2sh set-key           # prompts, never echoes, never hits your history
lightning-nl2sh set-key sk-or-... # inline; warns that this lands in shell history
lightning-nl2sh config            # show config path, masked key, shell, base url, model
```

`config` exits `1` when no API key is configured.

The config file is `<config dir>/.env`, where the config directory is the first of:

1. `NL2SH_CONFIG_DIR`
2. `$XDG_CONFIG_HOME/lightning-nl2sh`
3. `~/.config/lightning-nl2sh` (works on Windows too — `Path.home()`)

It is written atomically and created with mode `0600` where the OS supports it.
`set-key` rewrites only the `NL2SH_API_KEY` line; comments and every other variable in the file are preserved.

Environment variables:

| Variable           | Meaning                                                                                |
| ------------------ | -------------------------------------------------------------------------------------- |
| `NL2SH_API_KEY`    | API key. A real environment variable always beats the config file.                     |
| `NL2SH_BASE_URL`   | API base URL. Default `https://openrouter.ai/api/v1`. `/chat/completions` is appended. |
| `NL2SH_MODEL`      | Model slug. Default `qwen/qwen3-14b`.                                                  |
| `NL2SH_SHELL`      | Shell ID, exported by the shell integration.                                           |
| `NL2SH_CONFIG_DIR` | Override the config directory.                                                         |

> **Upgrading from 0.1.0:** the key variable was renamed from `OPENROUTER_API_KEY`
> to `NL2SH_API_KEY`. Re-run `lightning-nl2sh set-key`, or rename the line in your
> config file.

### Using another provider

The whole config `.env` is loaded, not just the key line — so a provider is three
variables in that one file. There is no separate CLI command for it; run
`lightning-nl2sh config` to find the file and edit it.

```ini
# ~/.config/lightning-nl2sh/.env
NL2SH_BASE_URL=https://api.openai.com/v1
NL2SH_API_KEY=sk-...
NL2SH_MODEL=gpt-4o-mini
```

| Provider         | `NL2SH_BASE_URL`                                |
| ---------------- | ----------------------------------------------- |
| OpenRouter       | `https://openrouter.ai/api/v1` (default)        |
| OpenAI           | `https://api.openai.com/v1`                     |
| Groq             | `https://api.groq.com/openai/v1`                |
| DeepSeek         | `https://api.deepseek.com/v1`                   |
| Together         | `https://api.together.xyz/v1`                   |
| Ollama           | `http://localhost:11434/v1` (any non-empty key) |
| vLLM / LM Studio | your server's `/v1`                             |

You can also test one without touching the config file:

```bash
NL2SH_BASE_URL=https://api.groq.com/openai/v1 \
NL2SH_MODEL=llama-3.3-70b-versatile \
NL2SH_API_KEY=gsk_... \
  lightning-nl2sh "find python files modified today"
```

**Limits.** The endpoint must accept an OpenAI-shaped `POST /chat/completions`
with an `Authorization: Bearer` token. Anthropic and Gemini work through their
OpenAI-compatibility base URLs, not their native APIs; Azure OpenAI, which
authenticates with an `api-key` header, is not supported. Two provider-specific
request tweaks are applied automatically and only where they belong: OpenRouter's
`reasoning` field, and Qwen's `/no_think` token.

## 5. Direct use

```bash
lightning-nl2sh "compress this folder into a tarball"
lightning-nl2sh --shell fish "list every file larger than 100MB"
lightning-nl2sh -- "-n flag examples for grep"   # -- when the task starts with -
```

`--shell` overrides detection, which is handy for generating a command for a machine you are about to SSH into.

## 6. Safety

**Commands are model-generated and unverified. Read every command before you run it.** The model can hallucinate flags, misread your intent, or produce something destructive that looks reasonable.

Be especially careful with anything involving:

- `rm`, `rmdir`, `dd`, `mkfs`, `shred`
- output redirection (`>`, `>>`) that overwrites files
- permission and ownership changes (`chmod`, `chown`, `icacls`)
- anything recursive, anything with a wildcard, anything run with `sudo`
- filesystem-wide operations, `find ... -delete`, `find ... -exec`

The `ai` shell function must **never** execute the generated command automatically, unless the integration for that shell explicitly asks for confirmation first. The zsh and PowerShell integrations above never execute anything — they hand you an editable line. The bash integration stops at an editable readline prompt, and the fish integration stops at a `y/N` prompt. If you write your own wrapper, keep that property.
