hey-cli-python 1.0.6__tar.gz → 1.0.8__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.6/hey_cli_python.egg-info → hey_cli_python-1.0.8}/PKG-INFO +22 -1
  2. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/README.md +21 -0
  3. hey_cli_python-1.0.8/hey_cli/cli.py +157 -0
  4. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli/llm.py +79 -39
  5. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8/hey_cli_python.egg-info}/PKG-INFO +22 -1
  6. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/pyproject.toml +1 -1
  7. hey_cli_python-1.0.6/hey_cli/cli.py +0 -109
  8. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/LICENSE +0 -0
  9. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli/__init__.py +0 -0
  10. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli/governance.py +0 -0
  11. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli/history.py +0 -0
  12. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli/models.py +0 -0
  13. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli/runner.py +0 -0
  14. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli/skills.py +0 -0
  15. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli_python.egg-info/SOURCES.txt +0 -0
  16. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli_python.egg-info/dependency_links.txt +0 -0
  17. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli_python.egg-info/entry_points.txt +0 -0
  18. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli_python.egg-info/requires.txt +0 -0
  19. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/hey_cli_python.egg-info/top_level.txt +0 -0
  20. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/setup.cfg +0 -0
  21. {hey_cli_python-1.0.6 → hey_cli_python-1.0.8}/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.6
3
+ Version: 1.0.8
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
@@ -73,6 +73,10 @@ brew tap sinsniwal/hey-cli
73
73
  brew install hey-cli
74
74
  ```
75
75
 
76
+ > [!TIP]
77
+ > **Apple Silicon (M1/M2/M3) Note:** If you see an error about `Rosetta 2` while installing via Homebrew, ensure your terminal is running natively (not under Rosetta emulation). You can force a native installation by running:
78
+ > `arch -arm64 brew install hey-cli`
79
+
76
80
  ### macOS & Linux (curl)
77
81
 
78
82
  ```bash
@@ -133,6 +137,23 @@ hey <your objective in plain English>
133
137
 
134
138
  ---
135
139
 
140
+
141
+ ---
142
+
143
+ ## Authentication & Custom Endpoints
144
+
145
+ `hey` works locally by default, but it also supports authenticated Ollama instances and custom hosts:
146
+
147
+ - **Standard Login:** Most users should run `ollama login` once to authenticate their terminal with the local or cloud instance.
148
+ - **Auth Key:** If you are in a CI/CD or server environment, you can set the `OLLAMA_API_KEY` environment variable.
149
+ - **Custom Host:** If Ollama is running on a different port or machine, set `OLLAMA_HOST` (e.g., `export OLLAMA_HOST="http://192.168.1.10:11434"`).
150
+ - **Custom Model:** You can provide a custom model via `--model`:
151
+ ```bash
152
+ hey "summarize this file" --model llama3
153
+ ```
154
+
155
+ ---
156
+
136
157
  ## Security
137
158
 
138
159
  Safety is enforced at runtime via a local governance engine (`~/.hey-rules.json`):
@@ -45,6 +45,10 @@ brew tap sinsniwal/hey-cli
45
45
  brew install hey-cli
46
46
  ```
47
47
 
48
+ > [!TIP]
49
+ > **Apple Silicon (M1/M2/M3) Note:** If you see an error about `Rosetta 2` while installing via Homebrew, ensure your terminal is running natively (not under Rosetta emulation). You can force a native installation by running:
50
+ > `arch -arm64 brew install hey-cli`
51
+
48
52
  ### macOS & Linux (curl)
49
53
 
50
54
  ```bash
@@ -105,6 +109,23 @@ hey <your objective in plain English>
105
109
 
106
110
  ---
107
111
 
112
+
113
+ ---
114
+
115
+ ## Authentication & Custom Endpoints
116
+
117
+ `hey` works locally by default, but it also supports authenticated Ollama instances and custom hosts:
118
+
119
+ - **Standard Login:** Most users should run `ollama login` once to authenticate their terminal with the local or cloud instance.
120
+ - **Auth Key:** If you are in a CI/CD or server environment, you can set the `OLLAMA_API_KEY` environment variable.
121
+ - **Custom Host:** If Ollama is running on a different port or machine, set `OLLAMA_HOST` (e.g., `export OLLAMA_HOST="http://192.168.1.10:11434"`).
122
+ - **Custom Model:** You can provide a custom model via `--model`:
123
+ ```bash
124
+ hey "summarize this file" --model llama3
125
+ ```
126
+
127
+ ---
128
+
108
129
  ## Security
109
130
 
110
131
  Safety is enforced at runtime via a local governance engine (`~/.hey-rules.json`):
@@ -0,0 +1,157 @@
1
+ import argparse
2
+ import sys
3
+ import os
4
+ import urllib.request
5
+ import urllib.error
6
+
7
+ from .governance import GovernanceEngine
8
+ from .llm import generate_command
9
+ from .history import HistoryManager
10
+ from .runner import CommandRunner
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.text import Text
14
+
15
+ console = Console()
16
+
17
+
18
+ def check_ollama():
19
+ """Check if Ollama is reachable. Exit with instructions if not."""
20
+ from .llm import OLLAMA_HOST
21
+
22
+ try:
23
+ urllib.request.urlopen(OLLAMA_HOST, timeout=2)
24
+ except Exception:
25
+ msg = Text()
26
+ msg.append("Ollama is not running or not installed.\n\n", style="bold red")
27
+ msg.append("1. Install Ollama:\n", style="bold white")
28
+ msg.append(" Linux / macOS:\n", style="dim")
29
+ msg.append(
30
+ " curl -fsSL https://ollama.com/install.sh | sh\n", style="bold cyan"
31
+ )
32
+ msg.append(" Windows:\n", style="dim")
33
+ msg.append(" https://ollama.com/download/windows\n\n", style="bold cyan")
34
+ msg.append("2. Authenticate:\n", style="bold white")
35
+ msg.append(" ollama login\n\n", style="bold cyan")
36
+ msg.append("3. Pull the default model:\n", style="bold white")
37
+ msg.append(" ollama pull gpt-oss:20b-cloud", style="bold cyan")
38
+ console.print(
39
+ Panel(
40
+ msg,
41
+ title="[bold yellow]⚠ Ollama Required[/bold yellow]",
42
+ border_style="yellow",
43
+ )
44
+ )
45
+ sys.exit(1)
46
+
47
+
48
+ def main():
49
+ parser = argparse.ArgumentParser(
50
+ description="hey-cli: a secure, zero-bloat CLI companion.",
51
+ formatter_class=argparse.RawTextHelpFormatter,
52
+ )
53
+
54
+ parser.add_argument("objective", nargs="*", help="Goal or task description")
55
+ parser.add_argument(
56
+ "--level",
57
+ type=int,
58
+ choices=[0, 1, 2, 3],
59
+ default=None,
60
+ help="0: Dry-Run\n1: Supervised (Default)\n2: Unrestricted (Danger)\n3: Troubleshooter",
61
+ )
62
+ parser.add_argument(
63
+ "--init", action="store_true", help="Initialize ~/.hey-rules.json"
64
+ )
65
+ parser.add_argument(
66
+ "--clear", action="store_true", help="Clear conversational memory history"
67
+ )
68
+ parser.add_argument(
69
+ "--check-cache", type=str, help="Check local cache for instant fix"
70
+ )
71
+
72
+ args = parser.parse_args()
73
+
74
+ gov = GovernanceEngine()
75
+ history_mgr = HistoryManager()
76
+
77
+ if args.clear:
78
+ history_mgr.clear()
79
+ console.print("[dim]Conversational history wiped clean.[/dim]")
80
+ sys.exit(0)
81
+
82
+ active_level = args.level
83
+ if active_level is None:
84
+ active_level = gov.rules.get("config", {}).get("default_level", 1)
85
+
86
+ model_name = gov.rules.get("config", {}).get("model", "gpt-oss:20b-cloud")
87
+
88
+ if args.init:
89
+ if gov.init_rules():
90
+ console.print(f"Initialized security rules at {gov.rules_path}")
91
+ else:
92
+ console.print(f"Rules already exist at {gov.rules_path}")
93
+ sys.exit(0)
94
+
95
+ if args.check_cache:
96
+ sys.exit(0)
97
+
98
+ # Only check Ollama when we're about to call the LLM
99
+ check_ollama()
100
+
101
+ piped_data = ""
102
+ if not sys.stdin.isatty():
103
+ try:
104
+ piped_data = sys.stdin.read()
105
+ sys.stdin = open("/dev/tty")
106
+ except Exception:
107
+ pass
108
+
109
+ objective = " ".join(args.objective).strip()
110
+ if not objective and not piped_data:
111
+ parser.print_help()
112
+ sys.exit(1)
113
+
114
+ # Build complete user message for saving later
115
+ user_prompt = objective
116
+ if piped_data:
117
+ user_prompt += f"\n\n[Piped Data]:\n{piped_data}"
118
+
119
+ console.print("[bold yellow]●[/bold yellow] Thinking...")
120
+ past_messages = history_mgr.load()
121
+ try:
122
+ response = generate_command(
123
+ objective, context=piped_data, model_name=model_name, history=past_messages
124
+ )
125
+ except urllib.error.HTTPError as e:
126
+ if e.code == 401:
127
+ msg = Text()
128
+ msg.append("Ollama authentication required.\n\n", style="bold red")
129
+ msg.append("Your connection to Ollama is not authenticated.\n", style="bold white")
130
+ msg.append("\nPlease run: ", style="dim")
131
+ msg.append("ollama login\n", style="bold cyan")
132
+ msg.append("\nThis will verify your identity with Ollama and allow the request to proceed.", style="dim")
133
+ console.print(Panel(msg, title="[bold yellow]🔑 Authentication Required[/bold yellow]", border_style="yellow"))
134
+ else:
135
+ console.print(f"\n[bold red]● Ollama API error:[/bold red] HTTP {e.code} — {e.reason}")
136
+ sys.exit(1)
137
+ except (urllib.error.URLError, ConnectionError, OSError):
138
+ check_ollama() # shows the panel and exits
139
+ sys.exit(1) # fallback — should never reach here
140
+ except Exception as e:
141
+ console.print(f"\n[bold red]● Error:[/bold red] {e}")
142
+ sys.exit(1)
143
+
144
+ # Save the user query to history IMMEDIATELY
145
+ history_mgr.append("user", user_prompt)
146
+
147
+ runner = CommandRunner(
148
+ governance=gov,
149
+ level=active_level,
150
+ model_name=model_name,
151
+ history_mgr=history_mgr,
152
+ )
153
+ runner.execute_flow(response, objective)
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()
@@ -6,6 +6,8 @@ import urllib.error
6
6
  from .models import CommandResponse, TroubleshootResponse
7
7
 
8
8
  DEFAULT_MODEL = "gpt-oss:20b-cloud"
9
+ OLLAMA_HOST = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
10
+ OLLAMA_API_KEY = os.environ.get("OLLAMA_API_KEY", "")
9
11
 
10
12
  SYSTEM_PROMPT = r"""You are hey-cli, an autonomous, minimalist CLI companion and terminal expert.
11
13
  Your primary goal is to turn natural language objectives and error logs into actionable shell commands.
@@ -26,33 +28,41 @@ CRITICAL JSON REQUIREMENT: If your bash command contains any backslashes (e.g. f
26
28
 
27
29
  from .skills import get_compiled_skills
28
30
 
31
+
29
32
  def get_system_context() -> str:
30
33
  os_name = platform.system()
31
34
  os_release = platform.release()
32
35
  arch = platform.machine()
33
36
  shell = os.environ.get("SHELL", "unknown")
34
-
37
+
35
38
  skills_block = f"\n\n{get_compiled_skills()}"
36
-
39
+
37
40
  return f"Operating System: {os_name} {os_release} ({arch})\nCurrent Shell: {shell}{skills_block}"
38
41
 
42
+
39
43
  TROUBLESHOOT_PROMPT = r"""You are acting as an iterative troubleshooter.
40
44
  You will be provided with an objective, the previous commands attempted, and the stdout/stderr.
41
45
  Determine the next command to run to resolve the issue, OR if the issue is resolved, indicate it.
42
46
  Keep your explanation brief and chill. If a file or tests do not exist, do not try to aggressively brute-force create configurations. Just explain the situation and set is_resolved=True to gracefully stop.
43
47
  """
44
48
 
45
- def generate_command(prompt: str, context: str = "", model_name: str = DEFAULT_MODEL, history: list = None) -> CommandResponse:
49
+
50
+ def generate_command(
51
+ prompt: str,
52
+ context: str = "",
53
+ model_name: str = DEFAULT_MODEL,
54
+ history: list = None,
55
+ ) -> CommandResponse:
46
56
  content = prompt
47
57
  if context:
48
58
  content = f"Context (e.g. error logs or piped data):\n{context}\n\nObjective:\n{prompt}"
49
-
59
+
50
60
  sys_context = f"--- ENVIRONMENT ---\n{get_system_context()}\n-------------------\n"
51
61
  msgs = [{"role": "system", "content": SYSTEM_PROMPT + "\n\n" + sys_context}]
52
62
  if history:
53
63
  msgs.extend(history)
54
64
  msgs.append({"role": "user", "content": content})
55
-
65
+
56
66
  max_retries = 3
57
67
  last_error = None
58
68
  raw_val = "None"
@@ -64,16 +74,20 @@ def generate_command(prompt: str, context: str = "", model_name: str = DEFAULT_M
64
74
  "messages": msgs,
65
75
  "format": "json",
66
76
  "stream": False,
67
- "options": {"temperature": 0.0}
77
+ "options": {"temperature": 0.0},
68
78
  }
79
+ url = f"{OLLAMA_HOST.rstrip('/')}/api/chat"
80
+ headers = {"Content-Type": "application/json"}
81
+ if OLLAMA_API_KEY:
82
+ headers["Authorization"] = f"Bearer {OLLAMA_API_KEY}"
69
83
  req = urllib.request.Request(
70
- "http://localhost:11434/api/chat",
71
- data=json.dumps(payload).encode('utf-8'),
72
- headers={"Content-Type": "application/json"}
84
+ url,
85
+ data=json.dumps(payload).encode("utf-8"),
86
+ headers=headers,
73
87
  )
74
88
  with urllib.request.urlopen(req, timeout=30) as resp:
75
- response = json.loads(resp.read().decode('utf-8'))
76
-
89
+ response = json.loads(resp.read().decode("utf-8"))
90
+
77
91
  raw_val = response["message"]["content"]
78
92
  content_str = raw_val
79
93
 
@@ -81,10 +95,10 @@ def generate_command(prompt: str, context: str = "", model_name: str = DEFAULT_M
81
95
  content_str = content_str[7:-3].strip()
82
96
  elif content_str.startswith("```"):
83
97
  content_str = content_str[3:-3].strip()
84
-
98
+
85
99
  data = json.loads(content_str)
86
100
  return CommandResponse(**data)
87
-
101
+
88
102
  except (urllib.error.URLError, ConnectionError, OSError):
89
103
  raise # Ollama not reachable — don't retry, bubble up to cli
90
104
  except Exception as e:
@@ -93,31 +107,48 @@ def generate_command(prompt: str, context: str = "", model_name: str = DEFAULT_M
93
107
  return CommandResponse(
94
108
  command="",
95
109
  explanation=f"LLM Safety Trigger: The model refused to generate this command.\n\nRaw output: {raw_val.strip()}",
96
- needs_context=False
110
+ needs_context=False,
97
111
  )
98
-
112
+
99
113
  msgs.append({"role": "assistant", "content": raw_val})
100
- msgs.append({"role": "user", "content": f"Your JSON output failed validation: {str(e)}\nPlease strictly follow the schema and output ONLY valid JSON without markdown wrapping."})
114
+ msgs.append(
115
+ {
116
+ "role": "user",
117
+ "content": f"Your JSON output failed validation: {str(e)}\nPlease strictly follow the schema and output ONLY valid JSON without markdown wrapping.",
118
+ }
119
+ )
101
120
 
102
121
  return CommandResponse(
103
- command="",
104
- explanation=f"Error generating command from LLM after {max_retries} retries: {str(last_error)}\nRaw Output:\n{raw_val}"
122
+ command="",
123
+ explanation=f"Error generating command from LLM after {max_retries} retries: {str(last_error)}\nRaw Output:\n{raw_val}",
124
+ )
125
+
126
+
127
+ def generate_troubleshoot_step(
128
+ objective: str, history: list, model_name: str = DEFAULT_MODEL
129
+ ) -> TroubleshootResponse:
130
+ history_text = "\n".join(
131
+ [
132
+ f"Cmd: {h['cmd']}\nExit: {h['exit_code']}\nOut/Err:\n{h['output']}"
133
+ for h in history
134
+ ]
105
135
  )
106
136
 
107
- def generate_troubleshoot_step(objective: str, history: list, model_name: str = DEFAULT_MODEL) -> TroubleshootResponse:
108
- history_text = "\n".join([
109
- f"Cmd: {h['cmd']}\nExit: {h['exit_code']}\nOut/Err:\n{h['output']}"
110
- for h in history
111
- ])
112
-
113
137
  content = f"Objective:\n{objective}\n\nHistory of execution:\n{history_text}\n\nAnalyze the specific error and provide the NEXT logical command to test or fix. Re-read logs carefully."
114
-
138
+
115
139
  sys_context = f"--- ENVIRONMENT ---\n{get_system_context()}\n-------------------\n"
116
140
  msgs = [
117
- {"role": "system", "content": SYSTEM_PROMPT + "\n" + TROUBLESHOOT_PROMPT + "\n\n" + sys_context},
118
- {"role": "user", "content": content}
141
+ {
142
+ "role": "system",
143
+ "content": SYSTEM_PROMPT
144
+ + "\n"
145
+ + TROUBLESHOOT_PROMPT
146
+ + "\n\n"
147
+ + sys_context,
148
+ },
149
+ {"role": "user", "content": content},
119
150
  ]
120
-
151
+
121
152
  max_retries = 3
122
153
  last_error = None
123
154
  raw_val = "None"
@@ -129,38 +160,47 @@ def generate_troubleshoot_step(objective: str, history: list, model_name: str =
129
160
  "messages": msgs,
130
161
  "format": "json",
131
162
  "stream": False,
132
- "options": {"temperature": 0.0}
163
+ "options": {"temperature": 0.0},
133
164
  }
165
+ url = f"{OLLAMA_HOST.rstrip('/')}/api/chat"
166
+ headers = {"Content-Type": "application/json"}
167
+ if OLLAMA_API_KEY:
168
+ headers["Authorization"] = f"Bearer {OLLAMA_API_KEY}"
134
169
  req = urllib.request.Request(
135
- "http://localhost:11434/api/chat",
136
- data=json.dumps(payload).encode('utf-8'),
137
- headers={"Content-Type": "application/json"}
170
+ url,
171
+ data=json.dumps(payload).encode("utf-8"),
172
+ headers=headers,
138
173
  )
139
174
  with urllib.request.urlopen(req, timeout=30) as resp:
140
- response = json.loads(resp.read().decode('utf-8'))
141
-
175
+ response = json.loads(resp.read().decode("utf-8"))
176
+
142
177
  raw_val = response["message"]["content"].strip()
143
178
  if not raw_val:
144
179
  raise ValueError("LLM returned empty JSON object.")
145
-
180
+
146
181
  content_str = raw_val
147
182
  if content_str.startswith("```json"):
148
183
  content_str = content_str[7:-3].strip()
149
184
  elif content_str.startswith("```"):
150
185
  content_str = content_str[3:-3].strip()
151
-
186
+
152
187
  data = json.loads(content_str)
153
188
  return TroubleshootResponse(**data)
154
-
189
+
155
190
  except (urllib.error.URLError, ConnectionError, OSError):
156
191
  raise # Ollama not reachable — don't retry, bubble up to cli
157
192
  except Exception as e:
158
193
  last_error = e
159
194
  msgs.append({"role": "assistant", "content": raw_val})
160
- msgs.append({"role": "user", "content": f"Your JSON output failed validation: {str(e)}\nFix the syntax and output ONLY strict JSON schema."})
195
+ msgs.append(
196
+ {
197
+ "role": "user",
198
+ "content": f"Your JSON output failed validation: {str(e)}\nFix the syntax and output ONLY strict JSON schema.",
199
+ }
200
+ )
161
201
 
162
202
  return TroubleshootResponse(
163
203
  command=None,
164
204
  explanation=f"Error analyzing execution after {max_retries} retries: {str(last_error)}\nRaw Output:\n{raw_val}",
165
- is_resolved=False
205
+ is_resolved=False,
166
206
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hey-cli-python
3
- Version: 1.0.6
3
+ Version: 1.0.8
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
@@ -73,6 +73,10 @@ brew tap sinsniwal/hey-cli
73
73
  brew install hey-cli
74
74
  ```
75
75
 
76
+ > [!TIP]
77
+ > **Apple Silicon (M1/M2/M3) Note:** If you see an error about `Rosetta 2` while installing via Homebrew, ensure your terminal is running natively (not under Rosetta emulation). You can force a native installation by running:
78
+ > `arch -arm64 brew install hey-cli`
79
+
76
80
  ### macOS & Linux (curl)
77
81
 
78
82
  ```bash
@@ -133,6 +137,23 @@ hey <your objective in plain English>
133
137
 
134
138
  ---
135
139
 
140
+
141
+ ---
142
+
143
+ ## Authentication & Custom Endpoints
144
+
145
+ `hey` works locally by default, but it also supports authenticated Ollama instances and custom hosts:
146
+
147
+ - **Standard Login:** Most users should run `ollama login` once to authenticate their terminal with the local or cloud instance.
148
+ - **Auth Key:** If you are in a CI/CD or server environment, you can set the `OLLAMA_API_KEY` environment variable.
149
+ - **Custom Host:** If Ollama is running on a different port or machine, set `OLLAMA_HOST` (e.g., `export OLLAMA_HOST="http://192.168.1.10:11434"`).
150
+ - **Custom Model:** You can provide a custom model via `--model`:
151
+ ```bash
152
+ hey "summarize this file" --model llama3
153
+ ```
154
+
155
+ ---
156
+
136
157
  ## Security
137
158
 
138
159
  Safety is enforced at runtime via a local governance engine (`~/.hey-rules.json`):
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hey-cli-python"
7
- version = "1.0.6"
7
+ version = "1.0.8"
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"
@@ -1,109 +0,0 @@
1
- import argparse
2
- import sys
3
- import os
4
- import urllib.request
5
- import urllib.error
6
-
7
- from .governance import GovernanceEngine
8
- from .llm import generate_command
9
- from .history import HistoryManager
10
- from .runner import CommandRunner
11
- from rich.console import Console
12
- from rich.panel import Panel
13
- from rich.text import Text
14
-
15
- console = Console()
16
-
17
- def check_ollama():
18
- """Check if Ollama is reachable at localhost:11434. Exit with instructions if not."""
19
- try:
20
- urllib.request.urlopen("http://localhost:11434", timeout=2)
21
- except Exception:
22
- msg = Text()
23
- msg.append("Ollama is not running or not installed.\n\n", style="bold red")
24
- msg.append("Install Ollama:\n", style="bold white")
25
- msg.append(" Linux / macOS:\n", style="dim")
26
- msg.append(" curl -fsSL https://ollama.com/install.sh | sh\n", style="bold cyan")
27
- msg.append(" Windows:\n", style="dim")
28
- msg.append(" https://ollama.com/download/windows\n\n", style="bold cyan")
29
- msg.append("Then pull the default model:\n", style="bold white")
30
- msg.append(" ollama pull gpt-oss:20b-cloud", style="bold cyan")
31
- console.print(Panel(msg, title="[bold yellow]⚠ Ollama Required[/bold yellow]", border_style="yellow"))
32
- sys.exit(1)
33
-
34
- def main():
35
- parser = argparse.ArgumentParser(
36
- description="hey-cli: a secure, zero-bloat CLI companion.",
37
- formatter_class=argparse.RawTextHelpFormatter
38
- )
39
-
40
- parser.add_argument("objective", nargs="*", help="Goal or task description")
41
- parser.add_argument("--level", type=int, choices=[0, 1, 2, 3], default=None,
42
- help="0: Dry-Run\n1: Supervised (Default)\n2: Unrestricted (Danger)\n3: Troubleshooter")
43
- parser.add_argument("--init", action="store_true", help="Initialize ~/.hey-rules.json")
44
- parser.add_argument("--clear", action="store_true", help="Clear conversational memory history")
45
- parser.add_argument("--check-cache", type=str, help="Check local cache for instant fix")
46
-
47
- args = parser.parse_args()
48
-
49
- gov = GovernanceEngine()
50
- history_mgr = HistoryManager()
51
-
52
- if args.clear:
53
- history_mgr.clear()
54
- console.print("[dim]Conversational history wiped clean.[/dim]")
55
- sys.exit(0)
56
-
57
- active_level = args.level
58
- if active_level is None:
59
- active_level = gov.rules.get("config", {}).get("default_level", 1)
60
-
61
- model_name = gov.rules.get("config", {}).get("model", "gpt-oss:20b-cloud")
62
-
63
- if args.init:
64
- if gov.init_rules():
65
- console.print(f"Initialized security rules at {gov.rules_path}")
66
- else:
67
- console.print(f"Rules already exist at {gov.rules_path}")
68
- sys.exit(0)
69
-
70
- if args.check_cache:
71
- sys.exit(0)
72
-
73
- # Only check Ollama when we're about to call the LLM
74
- check_ollama()
75
-
76
- piped_data = ""
77
- if not sys.stdin.isatty():
78
- try:
79
- piped_data = sys.stdin.read()
80
- sys.stdin = open('/dev/tty')
81
- except Exception:
82
- pass
83
-
84
- objective = " ".join(args.objective).strip()
85
- if not objective and not piped_data:
86
- parser.print_help()
87
- sys.exit(1)
88
-
89
- # Build complete user message for saving later
90
- user_prompt = objective
91
- if piped_data:
92
- user_prompt += f"\n\n[Piped Data]:\n{piped_data}"
93
-
94
- console.print("[bold yellow]●[/bold yellow] Thinking...")
95
- past_messages = history_mgr.load()
96
- try:
97
- response = generate_command(objective, context=piped_data, model_name=model_name, history=past_messages)
98
- except (urllib.error.URLError, ConnectionError, OSError):
99
- check_ollama() # shows the panel and exits
100
- sys.exit(1) # fallback — should never reach here
101
-
102
- # Save the user query to history IMMEDIATELY
103
- history_mgr.append("user", user_prompt)
104
-
105
- runner = CommandRunner(governance=gov, level=active_level, model_name=model_name, history_mgr=history_mgr)
106
- runner.execute_flow(response, objective)
107
-
108
- if __name__ == "__main__":
109
- main()
File without changes
File without changes