hey-cli-python 1.0.6__tar.gz → 1.0.7__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 (20) hide show
  1. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/PKG-INFO +1 -1
  2. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli/cli.py +57 -26
  3. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli/llm.py +79 -39
  4. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli_python.egg-info/PKG-INFO +1 -1
  5. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/pyproject.toml +1 -1
  6. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/LICENSE +0 -0
  7. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/README.md +0 -0
  8. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli/__init__.py +0 -0
  9. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli/governance.py +0 -0
  10. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli/history.py +0 -0
  11. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli/models.py +0 -0
  12. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli/runner.py +0 -0
  13. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli/skills.py +0 -0
  14. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli_python.egg-info/SOURCES.txt +0 -0
  15. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli_python.egg-info/dependency_links.txt +0 -0
  16. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli_python.egg-info/entry_points.txt +0 -0
  17. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli_python.egg-info/requires.txt +0 -0
  18. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/hey_cli_python.egg-info/top_level.txt +0 -0
  19. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/setup.cfg +0 -0
  20. {hey_cli_python-1.0.6 → hey_cli_python-1.0.7}/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.7
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
@@ -14,59 +14,82 @@ from rich.text import Text
14
14
 
15
15
  console = Console()
16
16
 
17
+
17
18
  def check_ollama():
18
- """Check if Ollama is reachable at localhost:11434. Exit with instructions if not."""
19
+ """Check if Ollama is reachable. Exit with instructions if not."""
20
+ from .llm import OLLAMA_HOST
21
+
19
22
  try:
20
- urllib.request.urlopen("http://localhost:11434", timeout=2)
23
+ urllib.request.urlopen(OLLAMA_HOST, timeout=2)
21
24
  except Exception:
22
25
  msg = Text()
23
26
  msg.append("Ollama is not running or not installed.\n\n", style="bold red")
24
27
  msg.append("Install Ollama:\n", style="bold white")
25
28
  msg.append(" Linux / macOS:\n", style="dim")
26
- msg.append(" curl -fsSL https://ollama.com/install.sh | sh\n", style="bold cyan")
29
+ msg.append(
30
+ " curl -fsSL https://ollama.com/install.sh | sh\n", style="bold cyan"
31
+ )
27
32
  msg.append(" Windows:\n", style="dim")
28
33
  msg.append(" https://ollama.com/download/windows\n\n", style="bold cyan")
29
34
  msg.append("Then pull the default model:\n", style="bold white")
30
35
  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"))
36
+ console.print(
37
+ Panel(
38
+ msg,
39
+ title="[bold yellow]⚠ Ollama Required[/bold yellow]",
40
+ border_style="yellow",
41
+ )
42
+ )
32
43
  sys.exit(1)
33
44
 
45
+
34
46
  def main():
35
47
  parser = argparse.ArgumentParser(
36
48
  description="hey-cli: a secure, zero-bloat CLI companion.",
37
- formatter_class=argparse.RawTextHelpFormatter
49
+ formatter_class=argparse.RawTextHelpFormatter,
38
50
  )
39
-
51
+
40
52
  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
-
53
+ parser.add_argument(
54
+ "--level",
55
+ type=int,
56
+ choices=[0, 1, 2, 3],
57
+ default=None,
58
+ help="0: Dry-Run\n1: Supervised (Default)\n2: Unrestricted (Danger)\n3: Troubleshooter",
59
+ )
60
+ parser.add_argument(
61
+ "--init", action="store_true", help="Initialize ~/.hey-rules.json"
62
+ )
63
+ parser.add_argument(
64
+ "--clear", action="store_true", help="Clear conversational memory history"
65
+ )
66
+ parser.add_argument(
67
+ "--check-cache", type=str, help="Check local cache for instant fix"
68
+ )
69
+
47
70
  args = parser.parse_args()
48
-
71
+
49
72
  gov = GovernanceEngine()
50
73
  history_mgr = HistoryManager()
51
-
74
+
52
75
  if args.clear:
53
76
  history_mgr.clear()
54
77
  console.print("[dim]Conversational history wiped clean.[/dim]")
55
78
  sys.exit(0)
56
-
79
+
57
80
  active_level = args.level
58
81
  if active_level is None:
59
82
  active_level = gov.rules.get("config", {}).get("default_level", 1)
60
-
83
+
61
84
  model_name = gov.rules.get("config", {}).get("model", "gpt-oss:20b-cloud")
62
-
85
+
63
86
  if args.init:
64
87
  if gov.init_rules():
65
88
  console.print(f"Initialized security rules at {gov.rules_path}")
66
89
  else:
67
90
  console.print(f"Rules already exist at {gov.rules_path}")
68
91
  sys.exit(0)
69
-
92
+
70
93
  if args.check_cache:
71
94
  sys.exit(0)
72
95
 
@@ -77,7 +100,7 @@ def main():
77
100
  if not sys.stdin.isatty():
78
101
  try:
79
102
  piped_data = sys.stdin.read()
80
- sys.stdin = open('/dev/tty')
103
+ sys.stdin = open("/dev/tty")
81
104
  except Exception:
82
105
  pass
83
106
 
@@ -85,25 +108,33 @@ def main():
85
108
  if not objective and not piped_data:
86
109
  parser.print_help()
87
110
  sys.exit(1)
88
-
111
+
89
112
  # Build complete user message for saving later
90
113
  user_prompt = objective
91
114
  if piped_data:
92
115
  user_prompt += f"\n\n[Piped Data]:\n{piped_data}"
93
-
116
+
94
117
  console.print("[bold yellow]●[/bold yellow] Thinking...")
95
118
  past_messages = history_mgr.load()
96
119
  try:
97
- response = generate_command(objective, context=piped_data, model_name=model_name, history=past_messages)
120
+ response = generate_command(
121
+ objective, context=piped_data, model_name=model_name, history=past_messages
122
+ )
98
123
  except (urllib.error.URLError, ConnectionError, OSError):
99
124
  check_ollama() # shows the panel and exits
100
- sys.exit(1) # fallback — should never reach here
101
-
125
+ sys.exit(1) # fallback — should never reach here
126
+
102
127
  # Save the user query to history IMMEDIATELY
103
128
  history_mgr.append("user", user_prompt)
104
-
105
- runner = CommandRunner(governance=gov, level=active_level, model_name=model_name, history_mgr=history_mgr)
129
+
130
+ runner = CommandRunner(
131
+ governance=gov,
132
+ level=active_level,
133
+ model_name=model_name,
134
+ history_mgr=history_mgr,
135
+ )
106
136
  runner.execute_flow(response, objective)
107
137
 
138
+
108
139
  if __name__ == "__main__":
109
140
  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.7
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
@@ -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.7"
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"
File without changes
File without changes
File without changes