nlsh-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nlsh_cli-0.1.0/.github/workflows/ci.yml +46 -0
- nlsh_cli-0.1.0/.github/workflows/publish.yml +39 -0
- nlsh_cli-0.1.0/.gitignore +26 -0
- nlsh_cli-0.1.0/PKG-INFO +144 -0
- nlsh_cli-0.1.0/README.md +124 -0
- nlsh_cli-0.1.0/nlsh/__init__.py +8 -0
- nlsh_cli-0.1.0/nlsh/_version.py +24 -0
- nlsh_cli-0.1.0/nlsh/cli.py +349 -0
- nlsh_cli-0.1.0/nlsh/config.py +114 -0
- nlsh_cli-0.1.0/nlsh/llm.py +131 -0
- nlsh_cli-0.1.0/pyproject.toml +56 -0
- nlsh_cli-0.1.0/requirements.txt +52 -0
- nlsh_cli-0.1.0/scripts/mock_deepseek.py +41 -0
- nlsh_cli-0.1.0/tests/test_cli.py +92 -0
- nlsh_cli-0.1.0/tests/test_config.py +85 -0
- nlsh_cli-0.1.0/tests/test_llm.py +87 -0
- nlsh_cli-0.1.0/tests/test_risk.py +42 -0
- nlsh_cli-0.1.0/uv.lock +448 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ci-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python-version: ["3.9", "3.11", "3.12", "3.13"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0 # hatch-vcs derives the version from git history/tags
|
|
23
|
+
|
|
24
|
+
- name: Set up uv
|
|
25
|
+
uses: astral-sh/setup-uv@v6
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Install (locked)
|
|
30
|
+
run: uv sync --locked
|
|
31
|
+
|
|
32
|
+
- name: Lint
|
|
33
|
+
run: uv run ruff check .
|
|
34
|
+
|
|
35
|
+
- name: Test
|
|
36
|
+
run: uv run pytest -q
|
|
37
|
+
|
|
38
|
+
lock:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
with:
|
|
43
|
+
fetch-depth: 0
|
|
44
|
+
- uses: astral-sh/setup-uv@v6
|
|
45
|
+
- name: Verify lockfile is up to date
|
|
46
|
+
run: uv lock --check
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Push a tag like v0.1.0 to build and publish that version to PyPI.
|
|
4
|
+
# The version is derived from the tag by hatch-vcs — no manual version bump.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0 # full history so hatch-vcs sees the tag
|
|
16
|
+
- uses: astral-sh/setup-uv@v6
|
|
17
|
+
- name: Build sdist and wheel
|
|
18
|
+
run: uv build
|
|
19
|
+
- uses: actions/upload-artifact@v4
|
|
20
|
+
with:
|
|
21
|
+
name: dist
|
|
22
|
+
path: dist/
|
|
23
|
+
|
|
24
|
+
publish:
|
|
25
|
+
needs: build
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
# Configure a "pypi" environment on the repo and register this workflow as a
|
|
28
|
+
# PyPI Trusted Publisher — no API token needed.
|
|
29
|
+
environment: pypi
|
|
30
|
+
permissions:
|
|
31
|
+
id-token: write
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/download-artifact@v4
|
|
34
|
+
with:
|
|
35
|
+
name: dist
|
|
36
|
+
path: dist/
|
|
37
|
+
- uses: astral-sh/setup-uv@v6
|
|
38
|
+
- name: Publish to PyPI (trusted publishing)
|
|
39
|
+
run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
|
|
8
|
+
# Test / tooling caches
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
|
|
12
|
+
# Version file generated by hatch-vcs at build time
|
|
13
|
+
nlsh/_version.py
|
|
14
|
+
|
|
15
|
+
# Virtual env (recreate with `uv sync`)
|
|
16
|
+
.venv/
|
|
17
|
+
|
|
18
|
+
# uv's managed Python downloads (not committed)
|
|
19
|
+
.uv/
|
|
20
|
+
|
|
21
|
+
# Local config / secrets
|
|
22
|
+
.env
|
|
23
|
+
config.toml
|
|
24
|
+
|
|
25
|
+
# OS
|
|
26
|
+
.DS_Store
|
nlsh_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nlsh-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Turn natural language into shell commands via the DeepSeek API, with a confirm-before-run step.
|
|
5
|
+
Project-URL: Homepage, https://github.com/decajoin/nlsh
|
|
6
|
+
Project-URL: Repository, https://github.com/decajoin/nlsh
|
|
7
|
+
Author: Yiqi Yang
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: cli,deepseek,llm,natural-language,shell,terminal
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Requires-Dist: rich>=13
|
|
18
|
+
Requires-Dist: typer>=0.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# nlsh
|
|
22
|
+
|
|
23
|
+
Describe what you want to do in plain language; `nlsh` asks the DeepSeek API for
|
|
24
|
+
the matching shell command, shows it with a short explanation, and waits for your
|
|
25
|
+
confirmation before running it.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
$ nlsh find the 5 largest files under this directory
|
|
29
|
+
╭─ proposed command ───────────────────────────────────────╮
|
|
30
|
+
│ $ du -ah . | sort -rh | head -n 5 │
|
|
31
|
+
│ │
|
|
32
|
+
│ Lists files by size and shows the five largest. │
|
|
33
|
+
╰───────────────────────────────────────────────────────────╯
|
|
34
|
+
Run it? [y/n/e] (n):
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`y` runs it, `n` cancels, `e` opens the command in your `$EDITOR` first.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
Once published to PyPI, install it as a standalone tool (the package is
|
|
42
|
+
`nlsh-cli`; the command it installs is `nlsh`):
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
uv tool install nlsh-cli # or: pipx install nlsh-cli
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### From source
|
|
49
|
+
|
|
50
|
+
One command sets up the virtual environment and all dependencies from the
|
|
51
|
+
lockfile:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
uv sync
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This creates `.venv/` and installs `nlsh`. Run it with `uv run nlsh ...`, or
|
|
58
|
+
activate the env first (`source .venv/bin/activate`) and just call `nlsh`.
|
|
59
|
+
|
|
60
|
+
Plain pip (no uv) works too:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
python -m venv .venv && . .venv/bin/activate
|
|
64
|
+
pip install -r requirements.txt
|
|
65
|
+
pip install -e .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configure
|
|
69
|
+
|
|
70
|
+
Easiest — store the key interactively (written to `~/.config/nlsh/config.toml`,
|
|
71
|
+
mode 0600):
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
nlsh config set-key
|
|
75
|
+
nlsh config set-model deepseek-v4-pro # optional, change default model
|
|
76
|
+
nlsh config show # show resolved config (key masked)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or via environment variable:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
export DEEPSEEK_API_KEY=sk-...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
or edit `~/.config/nlsh/config.toml` directly:
|
|
86
|
+
|
|
87
|
+
```toml
|
|
88
|
+
api_key = "sk-..."
|
|
89
|
+
model = "deepseek-v4-flash" # optional (also: deepseek-v4-pro)
|
|
90
|
+
base_url = "https://api.deepseek.com" # optional
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Resolution order for each setting is: environment variable → config file → default.
|
|
94
|
+
|
|
95
|
+
## Usage
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
nlsh <natural language request>
|
|
99
|
+
|
|
100
|
+
-y, --yes run without confirmation (dangerous commands still prompt)
|
|
101
|
+
-n, --dry-run only print the command, never run it
|
|
102
|
+
--pro use the stronger model (deepseek-v4-pro) for this request
|
|
103
|
+
-m, --model ID override the model id for this request
|
|
104
|
+
--version show version
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
At the confirmation prompt: `y` runs, `n` cancels, `e` edits first. Commands
|
|
108
|
+
that match risky patterns (`rm -rf`, `dd of=/dev/…`, `mkfs`, fork bombs,
|
|
109
|
+
`curl … | sh`, `sudo`, …) are flagged in red and require typing the full word
|
|
110
|
+
`yes` before they run — even under `--yes`. Multi-stage commands (pipes, `&&`)
|
|
111
|
+
are shown with a per-step breakdown.
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
uv sync # installs deps + dev tools (pytest, ruff)
|
|
117
|
+
uv run pytest -q # run the test suite
|
|
118
|
+
uv run ruff check . # lint
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
CI (GitHub Actions) runs ruff + pytest across Python 3.9–3.13 and checks the
|
|
122
|
+
lockfile on every push and PR.
|
|
123
|
+
|
|
124
|
+
### Releasing
|
|
125
|
+
|
|
126
|
+
The version is derived from the git tag by `hatch-vcs`, so a release is just a
|
|
127
|
+
tag:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
git tag v0.1.0
|
|
131
|
+
git push origin v0.1.0
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The `Publish` workflow builds the sdist/wheel and uploads them to PyPI via
|
|
135
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (configure a
|
|
136
|
+
`pypi` environment on the repo and register the workflow as a trusted publisher
|
|
137
|
+
— no API token stored).
|
|
138
|
+
|
|
139
|
+
## How it works
|
|
140
|
+
|
|
141
|
+
`nlsh` sends your request plus context (OS, shell, current directory) to the
|
|
142
|
+
DeepSeek chat API constrained to JSON output, parses `{command, explanation}`,
|
|
143
|
+
and runs the chosen command through your `$SHELL` so aliases and the working
|
|
144
|
+
directory behave as expected.
|
nlsh_cli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# nlsh
|
|
2
|
+
|
|
3
|
+
Describe what you want to do in plain language; `nlsh` asks the DeepSeek API for
|
|
4
|
+
the matching shell command, shows it with a short explanation, and waits for your
|
|
5
|
+
confirmation before running it.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
$ nlsh find the 5 largest files under this directory
|
|
9
|
+
╭─ proposed command ───────────────────────────────────────╮
|
|
10
|
+
│ $ du -ah . | sort -rh | head -n 5 │
|
|
11
|
+
│ │
|
|
12
|
+
│ Lists files by size and shows the five largest. │
|
|
13
|
+
╰───────────────────────────────────────────────────────────╯
|
|
14
|
+
Run it? [y/n/e] (n):
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`y` runs it, `n` cancels, `e` opens the command in your `$EDITOR` first.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
Once published to PyPI, install it as a standalone tool (the package is
|
|
22
|
+
`nlsh-cli`; the command it installs is `nlsh`):
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
uv tool install nlsh-cli # or: pipx install nlsh-cli
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### From source
|
|
29
|
+
|
|
30
|
+
One command sets up the virtual environment and all dependencies from the
|
|
31
|
+
lockfile:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
uv sync
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This creates `.venv/` and installs `nlsh`. Run it with `uv run nlsh ...`, or
|
|
38
|
+
activate the env first (`source .venv/bin/activate`) and just call `nlsh`.
|
|
39
|
+
|
|
40
|
+
Plain pip (no uv) works too:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
python -m venv .venv && . .venv/bin/activate
|
|
44
|
+
pip install -r requirements.txt
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configure
|
|
49
|
+
|
|
50
|
+
Easiest — store the key interactively (written to `~/.config/nlsh/config.toml`,
|
|
51
|
+
mode 0600):
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
nlsh config set-key
|
|
55
|
+
nlsh config set-model deepseek-v4-pro # optional, change default model
|
|
56
|
+
nlsh config show # show resolved config (key masked)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or via environment variable:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
export DEEPSEEK_API_KEY=sk-...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
or edit `~/.config/nlsh/config.toml` directly:
|
|
66
|
+
|
|
67
|
+
```toml
|
|
68
|
+
api_key = "sk-..."
|
|
69
|
+
model = "deepseek-v4-flash" # optional (also: deepseek-v4-pro)
|
|
70
|
+
base_url = "https://api.deepseek.com" # optional
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Resolution order for each setting is: environment variable → config file → default.
|
|
74
|
+
|
|
75
|
+
## Usage
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
nlsh <natural language request>
|
|
79
|
+
|
|
80
|
+
-y, --yes run without confirmation (dangerous commands still prompt)
|
|
81
|
+
-n, --dry-run only print the command, never run it
|
|
82
|
+
--pro use the stronger model (deepseek-v4-pro) for this request
|
|
83
|
+
-m, --model ID override the model id for this request
|
|
84
|
+
--version show version
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
At the confirmation prompt: `y` runs, `n` cancels, `e` edits first. Commands
|
|
88
|
+
that match risky patterns (`rm -rf`, `dd of=/dev/…`, `mkfs`, fork bombs,
|
|
89
|
+
`curl … | sh`, `sudo`, …) are flagged in red and require typing the full word
|
|
90
|
+
`yes` before they run — even under `--yes`. Multi-stage commands (pipes, `&&`)
|
|
91
|
+
are shown with a per-step breakdown.
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
uv sync # installs deps + dev tools (pytest, ruff)
|
|
97
|
+
uv run pytest -q # run the test suite
|
|
98
|
+
uv run ruff check . # lint
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
CI (GitHub Actions) runs ruff + pytest across Python 3.9–3.13 and checks the
|
|
102
|
+
lockfile on every push and PR.
|
|
103
|
+
|
|
104
|
+
### Releasing
|
|
105
|
+
|
|
106
|
+
The version is derived from the git tag by `hatch-vcs`, so a release is just a
|
|
107
|
+
tag:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
git tag v0.1.0
|
|
111
|
+
git push origin v0.1.0
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The `Publish` workflow builds the sdist/wheel and uploads them to PyPI via
|
|
115
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (configure a
|
|
116
|
+
`pypi` environment on the repo and register the workflow as a trusted publisher
|
|
117
|
+
— no API token stored).
|
|
118
|
+
|
|
119
|
+
## How it works
|
|
120
|
+
|
|
121
|
+
`nlsh` sends your request plus context (OS, shell, current directory) to the
|
|
122
|
+
DeepSeek chat API constrained to JSON output, parses `{command, explanation}`,
|
|
123
|
+
and runs the chosen command through your `$SHELL` so aliases and the working
|
|
124
|
+
directory behave as expected.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""nlsh — natural language to shell command CLI."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("nlsh-cli") # PyPI distribution name (command is `nlsh`)
|
|
7
|
+
except PackageNotFoundError: # not installed (e.g. running from a source tree)
|
|
8
|
+
__version__ = "0+unknown"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|