hey-cli-python 1.0.17__tar.gz → 1.1.1__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.
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/PKG-INFO +44 -29
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/README.md +43 -28
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/cli.py +33 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/governance.py +1 -1
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/runner.py +34 -4
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/PKG-INFO +44 -29
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/SOURCES.txt +2 -1
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/pyproject.toml +1 -1
- hey_cli_python-1.1.1/tests/test_runner.py +87 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/LICENSE +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/__init__.py +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/history.py +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/llm.py +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/models.py +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/skills.py +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/dependency_links.txt +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/entry_points.txt +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/requires.txt +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/top_level.txt +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/setup.cfg +0 -0
- {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/tests/test_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hey-cli-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: A secure, zero-bloat CLI companion that turns natural language and error logs into executable commands.
|
|
5
5
|
Author: Mohit Singh Sinsniwal
|
|
6
6
|
Project-URL: Homepage, https://github.com/sinsniwal/hey-cli
|
|
@@ -30,26 +30,22 @@ Dynamic: license-file
|
|
|
30
30
|
<h1>hey-cli</h1>
|
|
31
31
|
<p><strong>Your terminal buddy that turns plain English into shell scripts — and runs them for you.</strong></p>
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
<a href="https://pypi.org/project/hey-cli-python/"><img src="https://img.shields.io/pypi/v/hey-cli-python?label=PyPI&color=blue" alt="PyPI" /></a>
|
|
34
|
+
<img src="https://img.shields.io/pypi/pyversions/hey-cli-python?color=blue" alt="Python" />
|
|
35
|
+
<a href="https://github.com/sinsniwal/hey-cli/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License" /></a>
|
|
36
|
+
<a href="https://github.com/sinsniwal/hey-cli/releases/latest"><img src="https://img.shields.io/github/v/release/sinsniwal/hey-cli?label=Release&color=orange" alt="Release" /></a>
|
|
37
|
+
|
|
37
38
|
</div>
|
|
38
39
|
|
|
39
40
|
<br>
|
|
40
41
|
|
|
41
42
|
`hey` is a terminal-native AI assistant that translates plain English into executable shell commands using a locally-hosted LLM via [Ollama](https://ollama.com). Your data never leaves your machine.
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
▶ find . -name "*.py" -mtime -1 -type f
|
|
48
|
-
|
|
49
|
-
Run this command? [Y/n]:
|
|
50
|
-
```
|
|
44
|
+
<div align="center">
|
|
45
|
+
<img src="https://github.com/sinsniwal/hey-cli/raw/main/assets/demo.gif" width="800" alt="Hey CLI Demo">
|
|
46
|
+
</div>
|
|
51
47
|
|
|
52
|
-
|
|
48
|
+
<br>
|
|
53
49
|
|
|
54
50
|
## Features
|
|
55
51
|
|
|
@@ -110,11 +106,13 @@ uv tool install hey-cli-python
|
|
|
110
106
|
### Uninstallation
|
|
111
107
|
|
|
112
108
|
**macOS & Linux:**
|
|
109
|
+
|
|
113
110
|
```bash
|
|
114
111
|
curl -sL https://raw.githubusercontent.com/sinsniwal/hey-cli/main/uninstall.sh | bash
|
|
115
112
|
```
|
|
116
113
|
|
|
117
114
|
**Windows (PowerShell):**
|
|
115
|
+
|
|
118
116
|
```powershell
|
|
119
117
|
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/sinsniwal/hey-cli/main/uninstall.ps1" -OutFile "$env:TEMP\hey_uninstall.ps1"; & "$env:TEMP\hey_uninstall.ps1"
|
|
120
118
|
```
|
|
@@ -129,26 +127,43 @@ hey <your objective in plain English>
|
|
|
129
127
|
|
|
130
128
|
### Examples
|
|
131
129
|
|
|
132
|
-
| Command
|
|
133
|
-
|
|
134
|
-
| `hey list all running docker containers` | Generates and runs `docker ps`
|
|
135
|
-
| `hey is port 8080 in use?`
|
|
136
|
-
| `hey forcefully delete all .pyc files`
|
|
137
|
-
| `hey compress this folder into a tar.gz` | Generates the correct `tar` command for your OS
|
|
138
|
-
| `npm run build 2>&1 \| hey what broke?`
|
|
139
|
-
| `hey --clear`
|
|
130
|
+
| Command | What happens |
|
|
131
|
+
| ---------------------------------------- | ----------------------------------------------------------------- |
|
|
132
|
+
| `hey list all running docker containers` | Generates and runs `docker ps` |
|
|
133
|
+
| `hey is port 8080 in use?` | Silently runs `lsof -i :8080`, reads output, answers in English |
|
|
134
|
+
| `hey forcefully delete all .pyc files` | Generates `find . -name "*.pyc" -delete`, pauses for confirmation |
|
|
135
|
+
| `hey compress this folder into a tar.gz` | Generates the correct `tar` command for your OS |
|
|
136
|
+
| `npm run build 2>&1 \| hey what broke?` | Reads piped stderr and explains the error |
|
|
137
|
+
| `hey --clear` | Wipes conversational memory |
|
|
140
138
|
|
|
141
|
-
|
|
139
|
+
---
|
|
142
140
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
## Shell Integration (Recommended)
|
|
142
|
+
|
|
143
|
+
By default, CLI tools cannot change your terminal's directory because they run in a subshell. To enable `hey` to change your directory (e.g., `hey go to desktop`), add the following to your shell configuration (`~/.zshrc` or `~/.bashrc`):
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
eval "$(hey --shell-init)"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**For Windows (PowerShell):**
|
|
150
|
+
|
|
151
|
+
```powershell
|
|
152
|
+
hey --shell-init | Out-String | iex
|
|
153
|
+
```
|
|
149
154
|
|
|
150
155
|
---
|
|
151
156
|
|
|
157
|
+
## Execution Levels
|
|
158
|
+
|
|
159
|
+
| Level | Flag | Behavior |
|
|
160
|
+
| ----- | ----------- | -------------------------------------------------------------------- |
|
|
161
|
+
| 0 | `--level 0` | Dry-run — shows the command but never executes |
|
|
162
|
+
| 1 | _(default)_ | Supervised — safe commands auto-run, risky ones ask for confirmation |
|
|
163
|
+
| 2 | `--level 2` | Unrestricted — executes everything without confirmation |
|
|
164
|
+
| 3 | `--level 3` | Troubleshooter — iteratively debugs until the objective is resolved |
|
|
165
|
+
|
|
166
|
+
---
|
|
152
167
|
|
|
153
168
|
---
|
|
154
169
|
|
|
@@ -2,26 +2,22 @@
|
|
|
2
2
|
<h1>hey-cli</h1>
|
|
3
3
|
<p><strong>Your terminal buddy that turns plain English into shell scripts — and runs them for you.</strong></p>
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
<a href="https://pypi.org/project/hey-cli-python/"><img src="https://img.shields.io/pypi/v/hey-cli-python?label=PyPI&color=blue" alt="PyPI" /></a>
|
|
6
|
+
<img src="https://img.shields.io/pypi/pyversions/hey-cli-python?color=blue" alt="Python" />
|
|
7
|
+
<a href="https://github.com/sinsniwal/hey-cli/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License" /></a>
|
|
8
|
+
<a href="https://github.com/sinsniwal/hey-cli/releases/latest"><img src="https://img.shields.io/github/v/release/sinsniwal/hey-cli?label=Release&color=orange" alt="Release" /></a>
|
|
9
|
+
|
|
9
10
|
</div>
|
|
10
11
|
|
|
11
12
|
<br>
|
|
12
13
|
|
|
13
14
|
`hey` is a terminal-native AI assistant that translates plain English into executable shell commands using a locally-hosted LLM via [Ollama](https://ollama.com). Your data never leaves your machine.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
▶ find . -name "*.py" -mtime -1 -type f
|
|
20
|
-
|
|
21
|
-
Run this command? [Y/n]:
|
|
22
|
-
```
|
|
16
|
+
<div align="center">
|
|
17
|
+
<img src="https://github.com/sinsniwal/hey-cli/raw/main/assets/demo.gif" width="800" alt="Hey CLI Demo">
|
|
18
|
+
</div>
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
<br>
|
|
25
21
|
|
|
26
22
|
## Features
|
|
27
23
|
|
|
@@ -82,11 +78,13 @@ uv tool install hey-cli-python
|
|
|
82
78
|
### Uninstallation
|
|
83
79
|
|
|
84
80
|
**macOS & Linux:**
|
|
81
|
+
|
|
85
82
|
```bash
|
|
86
83
|
curl -sL https://raw.githubusercontent.com/sinsniwal/hey-cli/main/uninstall.sh | bash
|
|
87
84
|
```
|
|
88
85
|
|
|
89
86
|
**Windows (PowerShell):**
|
|
87
|
+
|
|
90
88
|
```powershell
|
|
91
89
|
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/sinsniwal/hey-cli/main/uninstall.ps1" -OutFile "$env:TEMP\hey_uninstall.ps1"; & "$env:TEMP\hey_uninstall.ps1"
|
|
92
90
|
```
|
|
@@ -101,26 +99,43 @@ hey <your objective in plain English>
|
|
|
101
99
|
|
|
102
100
|
### Examples
|
|
103
101
|
|
|
104
|
-
| Command
|
|
105
|
-
|
|
106
|
-
| `hey list all running docker containers` | Generates and runs `docker ps`
|
|
107
|
-
| `hey is port 8080 in use?`
|
|
108
|
-
| `hey forcefully delete all .pyc files`
|
|
109
|
-
| `hey compress this folder into a tar.gz` | Generates the correct `tar` command for your OS
|
|
110
|
-
| `npm run build 2>&1 \| hey what broke?`
|
|
111
|
-
| `hey --clear`
|
|
102
|
+
| Command | What happens |
|
|
103
|
+
| ---------------------------------------- | ----------------------------------------------------------------- |
|
|
104
|
+
| `hey list all running docker containers` | Generates and runs `docker ps` |
|
|
105
|
+
| `hey is port 8080 in use?` | Silently runs `lsof -i :8080`, reads output, answers in English |
|
|
106
|
+
| `hey forcefully delete all .pyc files` | Generates `find . -name "*.pyc" -delete`, pauses for confirmation |
|
|
107
|
+
| `hey compress this folder into a tar.gz` | Generates the correct `tar` command for your OS |
|
|
108
|
+
| `npm run build 2>&1 \| hey what broke?` | Reads piped stderr and explains the error |
|
|
109
|
+
| `hey --clear` | Wipes conversational memory |
|
|
112
110
|
|
|
113
|
-
|
|
111
|
+
---
|
|
114
112
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
113
|
+
## Shell Integration (Recommended)
|
|
114
|
+
|
|
115
|
+
By default, CLI tools cannot change your terminal's directory because they run in a subshell. To enable `hey` to change your directory (e.g., `hey go to desktop`), add the following to your shell configuration (`~/.zshrc` or `~/.bashrc`):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
eval "$(hey --shell-init)"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**For Windows (PowerShell):**
|
|
122
|
+
|
|
123
|
+
```powershell
|
|
124
|
+
hey --shell-init | Out-String | iex
|
|
125
|
+
```
|
|
121
126
|
|
|
122
127
|
---
|
|
123
128
|
|
|
129
|
+
## Execution Levels
|
|
130
|
+
|
|
131
|
+
| Level | Flag | Behavior |
|
|
132
|
+
| ----- | ----------- | -------------------------------------------------------------------- |
|
|
133
|
+
| 0 | `--level 0` | Dry-run — shows the command but never executes |
|
|
134
|
+
| 1 | _(default)_ | Supervised — safe commands auto-run, risky ones ask for confirmation |
|
|
135
|
+
| 2 | `--level 2` | Unrestricted — executes everything without confirmation |
|
|
136
|
+
| 3 | `--level 3` | Troubleshooter — iteratively debugs until the objective is resolved |
|
|
137
|
+
|
|
138
|
+
---
|
|
124
139
|
|
|
125
140
|
---
|
|
126
141
|
|
|
@@ -68,6 +68,9 @@ def main():
|
|
|
68
68
|
parser.add_argument(
|
|
69
69
|
"--check-cache", type=str, help="Check local cache for instant fix"
|
|
70
70
|
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"--shell-init", action="store_true", help="Output shell function for directory persistence"
|
|
73
|
+
)
|
|
71
74
|
|
|
72
75
|
args = parser.parse_args()
|
|
73
76
|
|
|
@@ -95,6 +98,36 @@ def main():
|
|
|
95
98
|
if args.check_cache:
|
|
96
99
|
sys.exit(0)
|
|
97
100
|
|
|
101
|
+
if args.shell_init:
|
|
102
|
+
is_windows = os.name == "nt"
|
|
103
|
+
if is_windows:
|
|
104
|
+
shell_func = r"""
|
|
105
|
+
function hey {
|
|
106
|
+
& hey.exe @args
|
|
107
|
+
$handoff = Join-Path $HOME ".hey_cwd_handoff"
|
|
108
|
+
if (Test-Path $handoff) {
|
|
109
|
+
$target = Get-Content $handoff -Raw
|
|
110
|
+
Remove-Item $handoff
|
|
111
|
+
if (Test-Path $target.Trim()) {
|
|
112
|
+
Set-Location $target.Trim()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
"""
|
|
117
|
+
else:
|
|
118
|
+
shell_func = r"""
|
|
119
|
+
hey() {
|
|
120
|
+
command hey "$@"
|
|
121
|
+
if [ -f "$HOME/.hey_cwd_handoff" ]; then
|
|
122
|
+
local target=$(cat "$HOME/.hey_cwd_handoff")
|
|
123
|
+
rm -f "$HOME/.hey_cwd_handoff"
|
|
124
|
+
[ -d "$target" ] && cd "$target"
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
"""
|
|
128
|
+
print(shell_func.strip())
|
|
129
|
+
sys.exit(0)
|
|
130
|
+
|
|
98
131
|
# Only check Ollama when we're about to call the LLM
|
|
99
132
|
check_ollama()
|
|
100
133
|
|
|
@@ -21,7 +21,7 @@ DEFAULT_RULES = {
|
|
|
21
21
|
"kubectl delete"
|
|
22
22
|
],
|
|
23
23
|
"allowed": [
|
|
24
|
-
"ls", "cat", "pwd", "grep", "find", "echo", "tail"
|
|
24
|
+
"ls", "cat", "pwd", "grep", "find", "echo", "tail", "cd"
|
|
25
25
|
],
|
|
26
26
|
"high_risk_keywords": [
|
|
27
27
|
"reset", "delete", "drop", "truncate", "prune", "rm", "-exec", ">"
|
|
@@ -2,6 +2,9 @@ import subprocess
|
|
|
2
2
|
import sys
|
|
3
3
|
import json
|
|
4
4
|
import dataclasses
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import re
|
|
5
8
|
from typing import Optional
|
|
6
9
|
|
|
7
10
|
from .governance import GovernanceEngine, Action
|
|
@@ -17,17 +20,44 @@ class CommandRunner:
|
|
|
17
20
|
self.history_mgr = history_mgr
|
|
18
21
|
self.console = Console()
|
|
19
22
|
|
|
20
|
-
def run_command(self, cmd: str) -> tuple[int, str]:
|
|
23
|
+
def run_command(self, cmd: str, capture_pwd: bool = False) -> tuple[int, str]:
|
|
21
24
|
"""Executes a command and returns exit code and combined output."""
|
|
25
|
+
is_windows = platform.system() == "Windows"
|
|
22
26
|
try:
|
|
27
|
+
full_cmd = cmd
|
|
28
|
+
if capture_pwd:
|
|
29
|
+
if is_windows:
|
|
30
|
+
# Windows CMD syntax for capturing PWD
|
|
31
|
+
full_cmd = f'("{cmd}") & echo. & echo HEY_CWD_HANDOFF:%CD%'
|
|
32
|
+
else:
|
|
33
|
+
# Unix shell syntax
|
|
34
|
+
full_cmd = f'{{ {cmd} ; }} ; printf "\\nHEY_CWD_HANDOFF:%s\\n" "$(pwd)"'
|
|
35
|
+
|
|
23
36
|
result = subprocess.run(
|
|
24
|
-
|
|
37
|
+
full_cmd,
|
|
25
38
|
shell=True,
|
|
26
39
|
stdout=subprocess.PIPE,
|
|
27
40
|
stderr=subprocess.STDOUT,
|
|
28
41
|
text=True
|
|
29
42
|
)
|
|
30
|
-
|
|
43
|
+
|
|
44
|
+
out = result.stdout
|
|
45
|
+
if capture_pwd and "HEY_CWD_HANDOFF:" in out:
|
|
46
|
+
match = re.search(r"HEY_CWD_HANDOFF:(.*)", out)
|
|
47
|
+
if match:
|
|
48
|
+
cwd = match.group(1).strip()
|
|
49
|
+
# Clean up output to hide the marker and the extra newline
|
|
50
|
+
out = re.sub(r"\n?HEY_CWD_HANDOFF:.*", "", out, flags=re.DOTALL).strip()
|
|
51
|
+
|
|
52
|
+
# Normalize paths for comparison (especially on Windows)
|
|
53
|
+
norm_cwd = os.path.normpath(cwd).lower() if is_windows else os.path.normpath(cwd)
|
|
54
|
+
norm_actual = os.path.normpath(os.getcwd()).lower() if is_windows else os.path.normpath(os.getcwd())
|
|
55
|
+
|
|
56
|
+
if norm_cwd != norm_actual:
|
|
57
|
+
with open(os.path.expanduser("~/.hey_cwd_handoff"), "w") as f:
|
|
58
|
+
f.write(cwd)
|
|
59
|
+
|
|
60
|
+
return result.returncode, out
|
|
31
61
|
except Exception as e:
|
|
32
62
|
return -1, str(e)
|
|
33
63
|
|
|
@@ -146,7 +176,7 @@ class CommandRunner:
|
|
|
146
176
|
if self.level in (1, 2):
|
|
147
177
|
if self._check_governance(cmd):
|
|
148
178
|
self.console.print(f"[bold green]● Running:[/bold green] {cmd}")
|
|
149
|
-
code, out = self.run_command(cmd)
|
|
179
|
+
code, out = self.run_command(cmd, capture_pwd=True)
|
|
150
180
|
if out.strip():
|
|
151
181
|
print(out.strip())
|
|
152
182
|
sys.exit(code)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hey-cli-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: A secure, zero-bloat CLI companion that turns natural language and error logs into executable commands.
|
|
5
5
|
Author: Mohit Singh Sinsniwal
|
|
6
6
|
Project-URL: Homepage, https://github.com/sinsniwal/hey-cli
|
|
@@ -30,26 +30,22 @@ Dynamic: license-file
|
|
|
30
30
|
<h1>hey-cli</h1>
|
|
31
31
|
<p><strong>Your terminal buddy that turns plain English into shell scripts — and runs them for you.</strong></p>
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
<a href="https://pypi.org/project/hey-cli-python/"><img src="https://img.shields.io/pypi/v/hey-cli-python?label=PyPI&color=blue" alt="PyPI" /></a>
|
|
34
|
+
<img src="https://img.shields.io/pypi/pyversions/hey-cli-python?color=blue" alt="Python" />
|
|
35
|
+
<a href="https://github.com/sinsniwal/hey-cli/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License" /></a>
|
|
36
|
+
<a href="https://github.com/sinsniwal/hey-cli/releases/latest"><img src="https://img.shields.io/github/v/release/sinsniwal/hey-cli?label=Release&color=orange" alt="Release" /></a>
|
|
37
|
+
|
|
37
38
|
</div>
|
|
38
39
|
|
|
39
40
|
<br>
|
|
40
41
|
|
|
41
42
|
`hey` is a terminal-native AI assistant that translates plain English into executable shell commands using a locally-hosted LLM via [Ollama](https://ollama.com). Your data never leaves your machine.
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
▶ find . -name "*.py" -mtime -1 -type f
|
|
48
|
-
|
|
49
|
-
Run this command? [Y/n]:
|
|
50
|
-
```
|
|
44
|
+
<div align="center">
|
|
45
|
+
<img src="https://github.com/sinsniwal/hey-cli/raw/main/assets/demo.gif" width="800" alt="Hey CLI Demo">
|
|
46
|
+
</div>
|
|
51
47
|
|
|
52
|
-
|
|
48
|
+
<br>
|
|
53
49
|
|
|
54
50
|
## Features
|
|
55
51
|
|
|
@@ -110,11 +106,13 @@ uv tool install hey-cli-python
|
|
|
110
106
|
### Uninstallation
|
|
111
107
|
|
|
112
108
|
**macOS & Linux:**
|
|
109
|
+
|
|
113
110
|
```bash
|
|
114
111
|
curl -sL https://raw.githubusercontent.com/sinsniwal/hey-cli/main/uninstall.sh | bash
|
|
115
112
|
```
|
|
116
113
|
|
|
117
114
|
**Windows (PowerShell):**
|
|
115
|
+
|
|
118
116
|
```powershell
|
|
119
117
|
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/sinsniwal/hey-cli/main/uninstall.ps1" -OutFile "$env:TEMP\hey_uninstall.ps1"; & "$env:TEMP\hey_uninstall.ps1"
|
|
120
118
|
```
|
|
@@ -129,26 +127,43 @@ hey <your objective in plain English>
|
|
|
129
127
|
|
|
130
128
|
### Examples
|
|
131
129
|
|
|
132
|
-
| Command
|
|
133
|
-
|
|
134
|
-
| `hey list all running docker containers` | Generates and runs `docker ps`
|
|
135
|
-
| `hey is port 8080 in use?`
|
|
136
|
-
| `hey forcefully delete all .pyc files`
|
|
137
|
-
| `hey compress this folder into a tar.gz` | Generates the correct `tar` command for your OS
|
|
138
|
-
| `npm run build 2>&1 \| hey what broke?`
|
|
139
|
-
| `hey --clear`
|
|
130
|
+
| Command | What happens |
|
|
131
|
+
| ---------------------------------------- | ----------------------------------------------------------------- |
|
|
132
|
+
| `hey list all running docker containers` | Generates and runs `docker ps` |
|
|
133
|
+
| `hey is port 8080 in use?` | Silently runs `lsof -i :8080`, reads output, answers in English |
|
|
134
|
+
| `hey forcefully delete all .pyc files` | Generates `find . -name "*.pyc" -delete`, pauses for confirmation |
|
|
135
|
+
| `hey compress this folder into a tar.gz` | Generates the correct `tar` command for your OS |
|
|
136
|
+
| `npm run build 2>&1 \| hey what broke?` | Reads piped stderr and explains the error |
|
|
137
|
+
| `hey --clear` | Wipes conversational memory |
|
|
140
138
|
|
|
141
|
-
|
|
139
|
+
---
|
|
142
140
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
## Shell Integration (Recommended)
|
|
142
|
+
|
|
143
|
+
By default, CLI tools cannot change your terminal's directory because they run in a subshell. To enable `hey` to change your directory (e.g., `hey go to desktop`), add the following to your shell configuration (`~/.zshrc` or `~/.bashrc`):
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
eval "$(hey --shell-init)"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**For Windows (PowerShell):**
|
|
150
|
+
|
|
151
|
+
```powershell
|
|
152
|
+
hey --shell-init | Out-String | iex
|
|
153
|
+
```
|
|
149
154
|
|
|
150
155
|
---
|
|
151
156
|
|
|
157
|
+
## Execution Levels
|
|
158
|
+
|
|
159
|
+
| Level | Flag | Behavior |
|
|
160
|
+
| ----- | ----------- | -------------------------------------------------------------------- |
|
|
161
|
+
| 0 | `--level 0` | Dry-run — shows the command but never executes |
|
|
162
|
+
| 1 | _(default)_ | Supervised — safe commands auto-run, risky ones ask for confirmation |
|
|
163
|
+
| 2 | `--level 2` | Unrestricted — executes everything without confirmation |
|
|
164
|
+
| 3 | `--level 3` | Troubleshooter — iteratively debugs until the objective is resolved |
|
|
165
|
+
|
|
166
|
+
---
|
|
152
167
|
|
|
153
168
|
---
|
|
154
169
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hey-cli-python"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.1.1"
|
|
8
8
|
description = "A secure, zero-bloat CLI companion that turns natural language and error logs into executable commands."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import patch, MagicMock
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
from hey_cli.runner import CommandRunner
|
|
6
|
+
from hey_cli.governance import GovernanceEngine
|
|
7
|
+
|
|
8
|
+
class TestCommandRunnerHandoff(unittest.TestCase):
|
|
9
|
+
def setUp(self):
|
|
10
|
+
self.gov = MagicMock(spec=GovernanceEngine)
|
|
11
|
+
self.runner = CommandRunner(governance=self.gov)
|
|
12
|
+
self.handoff_path = os.path.expanduser("~/.hey_cwd_handoff")
|
|
13
|
+
if os.path.exists(self.handoff_path):
|
|
14
|
+
os.remove(self.handoff_path)
|
|
15
|
+
|
|
16
|
+
def tearDown(self):
|
|
17
|
+
if os.path.exists(self.handoff_path):
|
|
18
|
+
os.remove(self.handoff_path)
|
|
19
|
+
|
|
20
|
+
@patch("subprocess.run")
|
|
21
|
+
def test_run_command_captures_pwd(self, mock_run):
|
|
22
|
+
# Simulate a command that includes the HEY_CWD_HANDOFF marker
|
|
23
|
+
mock_result = MagicMock()
|
|
24
|
+
mock_result.returncode = 0
|
|
25
|
+
# Mocking the output of '(cd /tmp) ; printf "\nHEY_CWD_HANDOFF:%s\n" "$(pwd)"'
|
|
26
|
+
mock_result.stdout = "some output\nHEY_CWD_HANDOFF:/tmp\n"
|
|
27
|
+
mock_run.return_value = mock_result
|
|
28
|
+
|
|
29
|
+
code, out = self.runner.run_command("cd /tmp", capture_pwd=True)
|
|
30
|
+
|
|
31
|
+
# Verify the handoff file was created with the correct path
|
|
32
|
+
self.assertTrue(os.path.exists(self.handoff_path))
|
|
33
|
+
with open(self.handoff_path, "r") as f:
|
|
34
|
+
self.assertEqual(f.read().strip(), "/tmp")
|
|
35
|
+
|
|
36
|
+
# Verify the marker was stripped from the output
|
|
37
|
+
self.assertEqual(out.strip(), "some output")
|
|
38
|
+
|
|
39
|
+
# Verify the command was wrapped correctly
|
|
40
|
+
mock_run.assert_called_once()
|
|
41
|
+
called_cmd = mock_run.call_args[0][0]
|
|
42
|
+
self.assertIn("HEY_CWD_HANDOFF", called_cmd)
|
|
43
|
+
|
|
44
|
+
@patch("subprocess.run")
|
|
45
|
+
def test_run_command_no_capture_pwd(self, mock_run):
|
|
46
|
+
mock_result = MagicMock()
|
|
47
|
+
mock_result.returncode = 0
|
|
48
|
+
mock_result.stdout = "normal output\n"
|
|
49
|
+
mock_run.return_value = mock_result
|
|
50
|
+
|
|
51
|
+
code, out = self.runner.run_command("ls", capture_pwd=False)
|
|
52
|
+
|
|
53
|
+
self.assertFalse(os.path.exists(self.handoff_path))
|
|
54
|
+
self.assertEqual(out.strip(), "normal output")
|
|
55
|
+
|
|
56
|
+
# Verify the command was NOT wrapped
|
|
57
|
+
mock_run.assert_called_once_with(
|
|
58
|
+
"ls", shell=True, stdout=-1, stderr=-2, text=True
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@patch("platform.system")
|
|
62
|
+
@patch("subprocess.run")
|
|
63
|
+
def test_run_command_windows_handoff(self, mock_run, mock_platform):
|
|
64
|
+
mock_platform.return_value = "Windows"
|
|
65
|
+
|
|
66
|
+
mock_result = MagicMock()
|
|
67
|
+
mock_result.returncode = 0
|
|
68
|
+
mock_result.stdout = "some output\nHEY_CWD_HANDOFF:C:\\Temp\n"
|
|
69
|
+
mock_run.return_value = mock_result
|
|
70
|
+
|
|
71
|
+
# We need to mock os.getcwd and os.path.normpath to match Windows style in this test
|
|
72
|
+
with patch("os.getcwd", return_value="C:\\Users\\Test"), \
|
|
73
|
+
patch("os.path.expanduser", return_value=self.handoff_path):
|
|
74
|
+
|
|
75
|
+
code, out = self.runner.run_command("cd C:\\Temp", capture_pwd=True)
|
|
76
|
+
|
|
77
|
+
# Verify the command was wrapped using Windows CMD syntax
|
|
78
|
+
called_cmd = mock_run.call_args[0][0]
|
|
79
|
+
self.assertEqual(called_cmd, '("cd C:\\Temp") & echo. & echo HEY_CWD_HANDOFF:%CD%')
|
|
80
|
+
|
|
81
|
+
# Verify the handoff file was created
|
|
82
|
+
self.assertTrue(os.path.exists(self.handoff_path))
|
|
83
|
+
with open(self.handoff_path, "r") as f:
|
|
84
|
+
self.assertEqual(f.read().strip(), "C:\\Temp")
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|