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.
Files changed (21) hide show
  1. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/PKG-INFO +44 -29
  2. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/README.md +43 -28
  3. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/cli.py +33 -0
  4. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/governance.py +1 -1
  5. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/runner.py +34 -4
  6. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/PKG-INFO +44 -29
  7. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/SOURCES.txt +2 -1
  8. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/pyproject.toml +1 -1
  9. hey_cli_python-1.1.1/tests/test_runner.py +87 -0
  10. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/LICENSE +0 -0
  11. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/__init__.py +0 -0
  12. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/history.py +0 -0
  13. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/llm.py +0 -0
  14. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/models.py +0 -0
  15. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli/skills.py +0 -0
  16. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/dependency_links.txt +0 -0
  17. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/entry_points.txt +0 -0
  18. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/requires.txt +0 -0
  19. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/hey_cli_python.egg-info/top_level.txt +0 -0
  20. {hey_cli_python-1.0.17 → hey_cli_python-1.1.1}/setup.cfg +0 -0
  21. {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.0.17
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
- <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>
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
- $ hey find all python files modified in the last 24 hours
45
- ● Thinking...
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 | What happens |
133
- |---------|-------------|
134
- | `hey list all running docker containers` | Generates and runs `docker ps` |
135
- | `hey is port 8080 in use?` | Silently runs `lsof -i :8080`, reads output, answers in English |
136
- | `hey forcefully delete all .pyc files` | Generates `find . -name "*.pyc" -delete`, pauses for confirmation |
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?` | Reads piped stderr and explains the error |
139
- | `hey --clear` | Wipes conversational memory |
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
- ### Execution Levels
139
+ ---
142
140
 
143
- | Level | Flag | Behavior |
144
- |-------|------|----------|
145
- | 0 | `--level 0` | Dry-run shows the command but never executes |
146
- | 1 | *(default)* | Supervised — safe commands auto-run, risky ones ask for confirmation |
147
- | 2 | `--level 2` | Unrestricted — executes everything without confirmation |
148
- | 3 | `--level 3` | Troubleshooter — iteratively debugs until the objective is resolved |
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
- <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>
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
- $ hey find all python files modified in the last 24 hours
17
- ● Thinking...
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 | What happens |
105
- |---------|-------------|
106
- | `hey list all running docker containers` | Generates and runs `docker ps` |
107
- | `hey is port 8080 in use?` | Silently runs `lsof -i :8080`, reads output, answers in English |
108
- | `hey forcefully delete all .pyc files` | Generates `find . -name "*.pyc" -delete`, pauses for confirmation |
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?` | Reads piped stderr and explains the error |
111
- | `hey --clear` | Wipes conversational memory |
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
- ### Execution Levels
111
+ ---
114
112
 
115
- | Level | Flag | Behavior |
116
- |-------|------|----------|
117
- | 0 | `--level 0` | Dry-run shows the command but never executes |
118
- | 1 | *(default)* | Supervised — safe commands auto-run, risky ones ask for confirmation |
119
- | 2 | `--level 2` | Unrestricted — executes everything without confirmation |
120
- | 3 | `--level 3` | Troubleshooter — iteratively debugs until the objective is resolved |
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
- cmd,
37
+ full_cmd,
25
38
  shell=True,
26
39
  stdout=subprocess.PIPE,
27
40
  stderr=subprocess.STDOUT,
28
41
  text=True
29
42
  )
30
- return result.returncode, result.stdout
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.0.17
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
- <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>
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
- $ hey find all python files modified in the last 24 hours
45
- ● Thinking...
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 | What happens |
133
- |---------|-------------|
134
- | `hey list all running docker containers` | Generates and runs `docker ps` |
135
- | `hey is port 8080 in use?` | Silently runs `lsof -i :8080`, reads output, answers in English |
136
- | `hey forcefully delete all .pyc files` | Generates `find . -name "*.pyc" -delete`, pauses for confirmation |
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?` | Reads piped stderr and explains the error |
139
- | `hey --clear` | Wipes conversational memory |
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
- ### Execution Levels
139
+ ---
142
140
 
143
- | Level | Flag | Behavior |
144
- |-------|------|----------|
145
- | 0 | `--level 0` | Dry-run shows the command but never executes |
146
- | 1 | *(default)* | Supervised — safe commands auto-run, risky ones ask for confirmation |
147
- | 2 | `--level 2` | Unrestricted — executes everything without confirmation |
148
- | 3 | `--level 3` | Troubleshooter — iteratively debugs until the objective is resolved |
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
 
@@ -15,4 +15,5 @@ hey_cli_python.egg-info/dependency_links.txt
15
15
  hey_cli_python.egg-info/entry_points.txt
16
16
  hey_cli_python.egg-info/requires.txt
17
17
  hey_cli_python.egg-info/top_level.txt
18
- tests/test_cli.py
18
+ tests/test_cli.py
19
+ tests/test_runner.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hey-cli-python"
7
- version = "1.0.17"
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