how-cli 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. how_cli-0.3.0/PKG-INFO +209 -0
  2. how_cli-0.3.0/README.md +176 -0
  3. how_cli-0.3.0/how/__init__.py +3 -0
  4. how_cli-0.3.0/how/actions.py +182 -0
  5. how_cli-0.3.0/how/core/__init__.py +6 -0
  6. how_cli-0.3.0/how/core/chains.py +18 -0
  7. how_cli-0.3.0/how/core/config.py +86 -0
  8. how_cli-0.3.0/how/core/context.py +85 -0
  9. how_cli-0.3.0/how/core/exceptions.py +6 -0
  10. how_cli-0.3.0/how/core/history.py +93 -0
  11. how_cli-0.3.0/how/core/llm.py +67 -0
  12. {how_cli-0.2.0 → how_cli-0.3.0}/how/core/parser.py +3 -2
  13. {how_cli-0.2.0 → how_cli-0.3.0}/how/core/prompts.py +15 -6
  14. how_cli-0.3.0/how/core/providers.py +110 -0
  15. how_cli-0.3.0/how/core/safety.py +88 -0
  16. how_cli-0.3.0/how/core/schema.py +11 -0
  17. how_cli-0.3.0/how/core/template.py +23 -0
  18. how_cli-0.3.0/how/formatting.py +71 -0
  19. how_cli-0.3.0/how/how.py +301 -0
  20. how_cli-0.3.0/how/infer.py +55 -0
  21. how_cli-0.3.0/how_cli.egg-info/PKG-INFO +209 -0
  22. {how_cli-0.2.0 → how_cli-0.3.0}/how_cli.egg-info/SOURCES.txt +12 -1
  23. {how_cli-0.2.0 → how_cli-0.3.0}/how_cli.egg-info/requires.txt +2 -0
  24. {how_cli-0.2.0 → how_cli-0.3.0}/requirements.txt +3 -1
  25. {how_cli-0.2.0 → how_cli-0.3.0}/setup.py +5 -4
  26. how_cli-0.3.0/tests/test_actions.py +66 -0
  27. how_cli-0.3.0/tests/test_context.py +64 -0
  28. how_cli-0.3.0/tests/test_core.py +77 -0
  29. how_cli-0.3.0/tests/test_fix.py +45 -0
  30. how_cli-0.3.0/tests/test_ollama.py +68 -0
  31. how_cli-0.3.0/tests/test_safety.py +67 -0
  32. how_cli-0.2.0/PKG-INFO +0 -116
  33. how_cli-0.2.0/README.md +0 -96
  34. how_cli-0.2.0/how/__init__.py +0 -1
  35. how_cli-0.2.0/how/core/__init__.py +0 -6
  36. how_cli-0.2.0/how/core/chains.py +0 -4
  37. how_cli-0.2.0/how/core/config.py +0 -50
  38. how_cli-0.2.0/how/core/llm.py +0 -9
  39. how_cli-0.2.0/how/core/providers.py +0 -17
  40. how_cli-0.2.0/how/core/schema.py +0 -6
  41. how_cli-0.2.0/how/core/template.py +0 -19
  42. how_cli-0.2.0/how/formatting.py +0 -36
  43. how_cli-0.2.0/how/how.py +0 -82
  44. how_cli-0.2.0/how/infer.py +0 -28
  45. how_cli-0.2.0/how_cli.egg-info/PKG-INFO +0 -116
  46. {how_cli-0.2.0 → how_cli-0.3.0}/LICENSE +0 -0
  47. {how_cli-0.2.0 → how_cli-0.3.0}/MANIFEST.in +0 -0
  48. {how_cli-0.2.0 → how_cli-0.3.0}/how/__main__.py +0 -0
  49. {how_cli-0.2.0 → how_cli-0.3.0}/how_cli.egg-info/dependency_links.txt +0 -0
  50. {how_cli-0.2.0 → how_cli-0.3.0}/how_cli.egg-info/entry_points.txt +0 -0
  51. {how_cli-0.2.0 → how_cli-0.3.0}/how_cli.egg-info/top_level.txt +0 -0
  52. {how_cli-0.2.0 → how_cli-0.3.0}/setup.cfg +0 -0
how_cli-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: how-cli
3
+ Version: 0.3.0
4
+ Summary: An AI-based CLI assistant to help you with command line & shell.
5
+ Home-page: https://github.com/FireHead90544/how-cli
6
+ Author: Rudransh Joshi (FireHead90544)
7
+ Author-email: rudranshjoshi1806@gmail.com
8
+ License: MIT
9
+ Project-URL: Issue Tracker, https://github.com/FireHead90544/how-cli/issues
10
+ Platform: any
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: typer
14
+ Requires-Dist: langchain
15
+ Requires-Dist: langchain-google-genai
16
+ Requires-Dist: langchain-google-vertexai
17
+ Requires-Dist: langchain-groq
18
+ Requires-Dist: langchain-openai
19
+ Requires-Dist: langchain-anthropic
20
+ Requires-Dist: langchain-ollama
21
+ Requires-Dist: pyperclip
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: license
28
+ Dynamic: license-file
29
+ Dynamic: platform
30
+ Dynamic: project-url
31
+ Dynamic: requires-dist
32
+ Dynamic: summary
33
+
34
+ # how-cli
35
+ An AI-based CLI assistant to help you with command line & shell.
36
+
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
+
39
+ ## Demo
40
+ https://github.com/user-attachments/assets/effefe1a-c0ed-4b60-838c-98f992f6c25f
41
+
42
+ ## Features
43
+
44
+ - ⚡ **Deferred & Lazy LLM Initialization**: Zero heavy imports during `--help` or startup, preventing startup crashes.
45
+ - 🦙 **Local LLM Support (Ollama)**: Run 100% offline with Ollama models (`qwen2.5-coder`, `llama3.2`, etc.) with no API keys required.
46
+ - 🎯 **Intelligent Environment & Project Context**: Automatically detects your active shell, available package managers (`apt`, `brew`, `dnf`, `pacman`, etc.), and project root markers (`package.json`, `Cargo.toml`, `pyproject.toml`, `go.mod`, `Dockerfile`).
47
+ - 🛡️ **Safety Guardrails**: Heuristic pattern detection warns against destructive commands (`rm -rf`, `dd if=`, `mkfs`, `> /dev/sd`, `chmod -R 777`, `git reset --hard`, `git push --force`, fork bombs, curl-pipe-to-shell) and gates execution with an explicit confirmation phrase.
48
+ - 📋 **Interactive Action Menu & Clipboard**:
49
+ - `[E]xecute`: Safely runs generated commands step-by-step, halting on non-zero exits.
50
+ - `[C]opy`: Copies commands directly to your system clipboard via `pyperclip`.
51
+ - `[M]odify`: Lets you edit commands inline before executing.
52
+ - `[A]bort`: Cleanly exits with status 0.
53
+ - 🔧 **`how fix` Diagnostic Command**: Inspects the last failed shell command from history or stderr and diagnoses fixes.
54
+
55
+ ---
56
+
57
+ ## Installation
58
+
59
+ **1. Using `pip`**
60
+ ```bash
61
+ pip install -U how-cli
62
+ ```
63
+
64
+ **2. Manual Installation**
65
+ ```bash
66
+ git clone https://github.com/FireHead90544/how-cli.git && cd how-cli
67
+ python3 -m venv .venv
68
+ source .venv/bin/activate
69
+ pip install -e .
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Usage
75
+
76
+ ```console
77
+ $ how [OPTIONS] COMMAND [ARGS]...
78
+ ```
79
+
80
+ **Options**:
81
+ * `-v, --version`: Shows the version of the application.
82
+ * `--help`: Show help message and exit.
83
+
84
+ **Commands**:
85
+ * `setup`: Sets up the LLM provider, API key, custom model, or local endpoint.
86
+ * `to`: Sends a task description to the LLM and returns the commands.
87
+ * `fix`: Diagnoses the last failed command from shell history or stderr.
88
+
89
+ ---
90
+
91
+ ## `how setup`
92
+
93
+ Configure your preferred LLM provider. Supports both cloud providers (OpenAI, Anthropic, Google, Groq) and local models via Ollama.
94
+
95
+ **Interactive Mode**:
96
+ ```bash
97
+ how setup
98
+ ```
99
+
100
+ **Non-Interactive Mode**:
101
+ ```bash
102
+ # Using local Ollama (no API key needed!)
103
+ how setup --no-interactive --provider Ollama --model qwen2.5-coder:latest --endpoint http://localhost:11434
104
+
105
+ # Using OpenAI
106
+ how setup --no-interactive --provider OpenAI --api-key "sk-..." --model gpt-4o
107
+
108
+ # Using Groq
109
+ how setup --no-interactive --provider GroqLLaMa --api-key "gsk_..."
110
+ ```
111
+
112
+ ---
113
+
114
+ ## `how to`
115
+
116
+ Translates a natural language query into concrete shell commands tailored to your system and project.
117
+
118
+ ```bash
119
+ how to "find all files larger than 100MB"
120
+ how to "install dependencies"
121
+ ```
122
+
123
+ After commands are generated, the interactive action menu appears:
124
+ ```text
125
+ Options: [E]xecute | [C]opy | [M]odify | [A]bort
126
+ Select an action [e/c/m/a/E/C/M/A] (a):
127
+ ```
128
+
129
+ ### Safety Guardrails
130
+ When a destructive command is detected (e.g. `rm -rf`, `git push --force`), `how` warns you with a prominent alert panel:
131
+ ```text
132
+ ╭─────────────────────────── SAFETY GUARDRAIL ALERT ───────────────────────────╮
133
+ │ WARNING: Destructive or High-Risk Command(s) Detected! │
134
+ │ • Recursive Force Delete: rm -rf node_modules │
135
+ │ Permanently deletes files and directories recursively without │
136
+ │ confirmation. │
137
+ │ │
138
+ │ To prevent accidental damage, type the exact confirmation phrase: │
139
+ │ I UNDERSTAND THE RISKS │
140
+ ╰──────────────────────────────────────────────────────────────────────────────╯
141
+ ```
142
+ Typing `y` is rejected; you must explicitly enter `I UNDERSTAND THE RISKS` before execution is permitted.
143
+
144
+ ---
145
+
146
+ ## `how fix`
147
+
148
+ Diagnoses why your last command failed and suggests the fix.
149
+
150
+ ```bash
151
+ # Automatically read the last failed command from ~/.bash_history or ~/.zsh_history:
152
+ how fix
153
+
154
+ # Provide the command and stderr explicitly:
155
+ how fix -c "git push origin main" -e "error: failed to push some refs" -x 1
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Providers
161
+
162
+ | Provider | Default Model | Package | Requires Key | Tested |
163
+ |:--------:|:-------------:|:-------:|:------------:|:------:|
164
+ | **Ollama** (Local) | `qwen2.5-coder:latest` | `langchain-ollama` | ❌ No | ✅ |
165
+ | GoogleGenAI | `gemma-4-31b-it` | `langchain-google-genai` | ✅ Yes | ✅ |
166
+ | GoogleVertexAI | `gemma-4-31b-it` | `langchain-google-vertexai` | ✅ Yes | ❌ |
167
+ | GroqMistralAI | `mixtral-8x7b-32768` | `langchain-groq` | ✅ Yes | ✅ |
168
+ | GroqLLaMa | `qwen/qwen3.8-27b` | `langchain-groq` | ✅ Yes | ✅ |
169
+ | OpenAI | `gpt-4o` | `langchain-openai` | ✅ Yes | ✅ |
170
+ | Anthropic | `claude-3-5-sonnet-20240620` | `langchain-anthropic` | ✅ Yes | ✅ |
171
+
172
+ ---
173
+
174
+ ## Shell Integration
175
+
176
+ You can bind `how to` directly to a keyboard shortcut (e.g. `Ctrl+G`) in your shell to immediately convert your typed command buffer into an AI prompt:
177
+
178
+ ### Zsh (`~/.zshrc`)
179
+ ```bash
180
+ how-widget() {
181
+ BUFFER="how to \"$BUFFER\""
182
+ zle accept-line
183
+ }
184
+ zle -N how-widget
185
+ bindkey '^G' how-widget
186
+ ```
187
+
188
+ ### Bash (`~/.bashrc`)
189
+ ```bash
190
+ how-widget() {
191
+ local cmd="how to \"$READLINE_LINE\""
192
+ READLINE_LINE=""
193
+ eval "$cmd"
194
+ }
195
+ bind -x '"\C-g": how-widget'
196
+ ```
197
+
198
+ ---
199
+
200
+ ## License
201
+ `how-cli` is licensed under the MIT License. See [LICENSE](LICENSE) for details.
202
+
203
+ ## Honourable Mentions
204
+ This project is inspired by [kynnyhsap's](https://github.com/kynnyhsap) [how](https://github.com/kynnyhsap/how).
205
+
206
+ ## Maintainers
207
+
208
+ - [@FireHead90544](https://github.com/FireHead90544) — Creator & Primary Maintainer
209
+ - [@ashishsinghbora](https://github.com/ashishsinghbora) — Contributor
@@ -0,0 +1,176 @@
1
+ # how-cli
2
+ An AI-based CLI assistant to help you with command line & shell.
3
+
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ ## Demo
7
+ https://github.com/user-attachments/assets/effefe1a-c0ed-4b60-838c-98f992f6c25f
8
+
9
+ ## Features
10
+
11
+ - ⚡ **Deferred & Lazy LLM Initialization**: Zero heavy imports during `--help` or startup, preventing startup crashes.
12
+ - 🦙 **Local LLM Support (Ollama)**: Run 100% offline with Ollama models (`qwen2.5-coder`, `llama3.2`, etc.) with no API keys required.
13
+ - 🎯 **Intelligent Environment & Project Context**: Automatically detects your active shell, available package managers (`apt`, `brew`, `dnf`, `pacman`, etc.), and project root markers (`package.json`, `Cargo.toml`, `pyproject.toml`, `go.mod`, `Dockerfile`).
14
+ - 🛡️ **Safety Guardrails**: Heuristic pattern detection warns against destructive commands (`rm -rf`, `dd if=`, `mkfs`, `> /dev/sd`, `chmod -R 777`, `git reset --hard`, `git push --force`, fork bombs, curl-pipe-to-shell) and gates execution with an explicit confirmation phrase.
15
+ - 📋 **Interactive Action Menu & Clipboard**:
16
+ - `[E]xecute`: Safely runs generated commands step-by-step, halting on non-zero exits.
17
+ - `[C]opy`: Copies commands directly to your system clipboard via `pyperclip`.
18
+ - `[M]odify`: Lets you edit commands inline before executing.
19
+ - `[A]bort`: Cleanly exits with status 0.
20
+ - 🔧 **`how fix` Diagnostic Command**: Inspects the last failed shell command from history or stderr and diagnoses fixes.
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ **1. Using `pip`**
27
+ ```bash
28
+ pip install -U how-cli
29
+ ```
30
+
31
+ **2. Manual Installation**
32
+ ```bash
33
+ git clone https://github.com/FireHead90544/how-cli.git && cd how-cli
34
+ python3 -m venv .venv
35
+ source .venv/bin/activate
36
+ pip install -e .
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Usage
42
+
43
+ ```console
44
+ $ how [OPTIONS] COMMAND [ARGS]...
45
+ ```
46
+
47
+ **Options**:
48
+ * `-v, --version`: Shows the version of the application.
49
+ * `--help`: Show help message and exit.
50
+
51
+ **Commands**:
52
+ * `setup`: Sets up the LLM provider, API key, custom model, or local endpoint.
53
+ * `to`: Sends a task description to the LLM and returns the commands.
54
+ * `fix`: Diagnoses the last failed command from shell history or stderr.
55
+
56
+ ---
57
+
58
+ ## `how setup`
59
+
60
+ Configure your preferred LLM provider. Supports both cloud providers (OpenAI, Anthropic, Google, Groq) and local models via Ollama.
61
+
62
+ **Interactive Mode**:
63
+ ```bash
64
+ how setup
65
+ ```
66
+
67
+ **Non-Interactive Mode**:
68
+ ```bash
69
+ # Using local Ollama (no API key needed!)
70
+ how setup --no-interactive --provider Ollama --model qwen2.5-coder:latest --endpoint http://localhost:11434
71
+
72
+ # Using OpenAI
73
+ how setup --no-interactive --provider OpenAI --api-key "sk-..." --model gpt-4o
74
+
75
+ # Using Groq
76
+ how setup --no-interactive --provider GroqLLaMa --api-key "gsk_..."
77
+ ```
78
+
79
+ ---
80
+
81
+ ## `how to`
82
+
83
+ Translates a natural language query into concrete shell commands tailored to your system and project.
84
+
85
+ ```bash
86
+ how to "find all files larger than 100MB"
87
+ how to "install dependencies"
88
+ ```
89
+
90
+ After commands are generated, the interactive action menu appears:
91
+ ```text
92
+ Options: [E]xecute | [C]opy | [M]odify | [A]bort
93
+ Select an action [e/c/m/a/E/C/M/A] (a):
94
+ ```
95
+
96
+ ### Safety Guardrails
97
+ When a destructive command is detected (e.g. `rm -rf`, `git push --force`), `how` warns you with a prominent alert panel:
98
+ ```text
99
+ ╭─────────────────────────── SAFETY GUARDRAIL ALERT ───────────────────────────╮
100
+ │ WARNING: Destructive or High-Risk Command(s) Detected! │
101
+ │ • Recursive Force Delete: rm -rf node_modules │
102
+ │ Permanently deletes files and directories recursively without │
103
+ │ confirmation. │
104
+ │ │
105
+ │ To prevent accidental damage, type the exact confirmation phrase: │
106
+ │ I UNDERSTAND THE RISKS │
107
+ ╰──────────────────────────────────────────────────────────────────────────────╯
108
+ ```
109
+ Typing `y` is rejected; you must explicitly enter `I UNDERSTAND THE RISKS` before execution is permitted.
110
+
111
+ ---
112
+
113
+ ## `how fix`
114
+
115
+ Diagnoses why your last command failed and suggests the fix.
116
+
117
+ ```bash
118
+ # Automatically read the last failed command from ~/.bash_history or ~/.zsh_history:
119
+ how fix
120
+
121
+ # Provide the command and stderr explicitly:
122
+ how fix -c "git push origin main" -e "error: failed to push some refs" -x 1
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Providers
128
+
129
+ | Provider | Default Model | Package | Requires Key | Tested |
130
+ |:--------:|:-------------:|:-------:|:------------:|:------:|
131
+ | **Ollama** (Local) | `qwen2.5-coder:latest` | `langchain-ollama` | ❌ No | ✅ |
132
+ | GoogleGenAI | `gemma-4-31b-it` | `langchain-google-genai` | ✅ Yes | ✅ |
133
+ | GoogleVertexAI | `gemma-4-31b-it` | `langchain-google-vertexai` | ✅ Yes | ❌ |
134
+ | GroqMistralAI | `mixtral-8x7b-32768` | `langchain-groq` | ✅ Yes | ✅ |
135
+ | GroqLLaMa | `qwen/qwen3.8-27b` | `langchain-groq` | ✅ Yes | ✅ |
136
+ | OpenAI | `gpt-4o` | `langchain-openai` | ✅ Yes | ✅ |
137
+ | Anthropic | `claude-3-5-sonnet-20240620` | `langchain-anthropic` | ✅ Yes | ✅ |
138
+
139
+ ---
140
+
141
+ ## Shell Integration
142
+
143
+ You can bind `how to` directly to a keyboard shortcut (e.g. `Ctrl+G`) in your shell to immediately convert your typed command buffer into an AI prompt:
144
+
145
+ ### Zsh (`~/.zshrc`)
146
+ ```bash
147
+ how-widget() {
148
+ BUFFER="how to \"$BUFFER\""
149
+ zle accept-line
150
+ }
151
+ zle -N how-widget
152
+ bindkey '^G' how-widget
153
+ ```
154
+
155
+ ### Bash (`~/.bashrc`)
156
+ ```bash
157
+ how-widget() {
158
+ local cmd="how to \"$READLINE_LINE\""
159
+ READLINE_LINE=""
160
+ eval "$cmd"
161
+ }
162
+ bind -x '"\C-g": how-widget'
163
+ ```
164
+
165
+ ---
166
+
167
+ ## License
168
+ `how-cli` is licensed under the MIT License. See [LICENSE](LICENSE) for details.
169
+
170
+ ## Honourable Mentions
171
+ This project is inspired by [kynnyhsap's](https://github.com/kynnyhsap) [how](https://github.com/kynnyhsap/how).
172
+
173
+ ## Maintainers
174
+
175
+ - [@FireHead90544](https://github.com/FireHead90544) — Creator & Primary Maintainer
176
+ - [@ashishsinghbora](https://github.com/ashishsinghbora) — Contributor
@@ -0,0 +1,3 @@
1
+ from how.core import __author__, __version__
2
+
3
+ __all__ = ["__author__", "__version__"]
@@ -0,0 +1,182 @@
1
+ import contextlib
2
+ import subprocess
3
+
4
+ import typer
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.prompt import Prompt
8
+
9
+ from how.core.safety import assess_risk
10
+
11
+ console = Console()
12
+
13
+ CONFIRMATION_PHRASE = "I UNDERSTAND THE RISKS"
14
+
15
+
16
+ def check_safety_gate(commands: list[str]) -> bool:
17
+ """
18
+ Check if commands contain destructive patterns. If so, display a bold
19
+ warning panel and require an exact confirmation phrase.
20
+ """
21
+ flags = assess_risk(commands)
22
+ if not flags:
23
+ return True
24
+
25
+ warning_text = "[bold red]WARNING: Destructive or High-Risk Command(s) Detected![/bold red]\n\n"
26
+ for flag in flags:
27
+ warning_text += (
28
+ f"• [bold yellow]{flag.rule_name}[/bold yellow]: [bold white]{flag.command}[/bold white]\n"
29
+ f" {flag.description}\n"
30
+ )
31
+ warning_text += (
32
+ f"\nTo prevent accidental damage, type the exact confirmation phrase:\n"
33
+ f"[bold white]{CONFIRMATION_PHRASE}[/bold white]"
34
+ )
35
+
36
+ console.print(
37
+ Panel(
38
+ warning_text,
39
+ title="[bold red]SAFETY GUARDRAIL ALERT[/bold red]",
40
+ border_style="red",
41
+ )
42
+ )
43
+
44
+ try:
45
+ user_input = Prompt.ask("Confirmation phrase")
46
+ if user_input.strip() != CONFIRMATION_PHRASE:
47
+ console.print(
48
+ "[yellow]Confirmation phrase mismatch. Execution blocked.[/yellow]"
49
+ )
50
+ return False
51
+ return True
52
+ except KeyboardInterrupt:
53
+ console.print("\n[yellow]Execution blocked.[/yellow]")
54
+ return False
55
+
56
+
57
+ def copy_to_clipboard(commands: list[str]) -> bool:
58
+ """Copy commands to clipboard via pyperclip."""
59
+ joined = "\n".join(commands)
60
+ try:
61
+ import pyperclip
62
+
63
+ pyperclip.copy(joined)
64
+ console.print("[bold green]✓ Commands copied to clipboard![/bold green]")
65
+ return True
66
+ except Exception as e: # noqa: BLE001
67
+ console.print(
68
+ f"[yellow]! Clipboard copy failed: {e}. "
69
+ "Please ensure xclip, xsel, or wl-clipboard is installed.[/yellow]"
70
+ )
71
+ return False
72
+
73
+
74
+ def execute_commands(commands: list[str]) -> int:
75
+ """
76
+ Run commands sequentially via subprocess.run(cmd, shell=True)
77
+ after confirmation and safety verification.
78
+ """
79
+ if not commands:
80
+ return 0
81
+
82
+ if not check_safety_gate(commands):
83
+ return 1
84
+
85
+ if not typer.confirm("Are you sure you want to execute these commands?"):
86
+ console.print("[yellow]Execution cancelled.[/yellow]")
87
+ return 0
88
+
89
+ for idx, cmd in enumerate(commands, start=1):
90
+ console.print(f"\n[bold cyan]({idx}/{len(commands)}) $ {cmd}[/bold cyan]")
91
+ try:
92
+ proc = subprocess.run(cmd, shell=True, check=False)
93
+ if proc.returncode != 0:
94
+ console.print(
95
+ f"[bold red]Command failed with exit code {proc.returncode}[/bold red]"
96
+ )
97
+ if idx < len(commands):
98
+ continue_run = typer.confirm(
99
+ "Do you want to continue running the remaining commands?",
100
+ default=False,
101
+ )
102
+ if not continue_run:
103
+ console.print("[yellow]Execution stopped.[/yellow]")
104
+ return proc.returncode
105
+ else:
106
+ return proc.returncode
107
+ else:
108
+ console.print("[green]✓ Success (exit code 0)[/green]")
109
+ except KeyboardInterrupt:
110
+ console.print("\n[yellow]Execution interrupted by user.[/yellow]")
111
+ return 130
112
+ except OSError as e:
113
+ console.print(f"[bold red]Error executing command: {e}[/bold red]")
114
+ return 1
115
+
116
+ return 0
117
+
118
+
119
+ def modify_commands(commands: list[str]) -> list[str]:
120
+ """Allow inline editing of the commands."""
121
+ joined = "\n".join(commands)
122
+ console.print("[cyan]Modify the command below:[/cyan]")
123
+ try:
124
+ import readline
125
+
126
+ def hook() -> None:
127
+ readline.insert_text(joined)
128
+ readline.set_startup_hook()
129
+
130
+ readline.set_startup_hook(hook)
131
+ edited = input("> ")
132
+ except Exception: # noqa: BLE001
133
+ edited = Prompt.ask("Edit command", default=joined)
134
+ finally:
135
+ with contextlib.suppress(Exception):
136
+ import readline
137
+
138
+ readline.set_startup_hook(None)
139
+
140
+ new_cmds = [line.strip() for line in edited.splitlines() if line.strip()]
141
+ if new_cmds:
142
+ return new_cmds
143
+ return commands
144
+
145
+
146
+ def interactive_action_menu(commands: list[str]) -> None:
147
+ """Interactive action menu offering Execute, Copy, Modify, and Abort."""
148
+ current_commands = list(commands)
149
+ if not current_commands:
150
+ return
151
+
152
+ while True:
153
+ try:
154
+ console.print(
155
+ "\n[bold]Options:[/bold] "
156
+ "[bold green][E]xecute[/bold green] | "
157
+ "[bold cyan][C]opy[/bold cyan] | "
158
+ "[bold yellow][M]odify[/bold yellow] | "
159
+ "[bold red][A]bort[/bold red]"
160
+ )
161
+ choice = Prompt.ask(
162
+ "Select an action",
163
+ choices=["e", "c", "m", "a", "E", "C", "M", "A"],
164
+ default="a",
165
+ ).lower()
166
+
167
+ if choice == "e":
168
+ execute_commands(current_commands)
169
+ break
170
+ elif choice == "c":
171
+ copy_to_clipboard(current_commands)
172
+ elif choice == "m":
173
+ current_commands = modify_commands(current_commands)
174
+ console.print(
175
+ f"[green]Updated command(s):[/green] {', '.join(current_commands)}"
176
+ )
177
+ elif choice == "a":
178
+ console.print("[yellow]Aborted.[/yellow]")
179
+ break
180
+ except KeyboardInterrupt:
181
+ console.print("\n[yellow]Aborted.[/yellow]")
182
+ break
@@ -0,0 +1,6 @@
1
+ """
2
+ Package containing the core modules of the project.
3
+ """
4
+
5
+ __version__ = "0.3.0"
6
+ __author__ = "Rudransh Joshi (FireHead90544)"
@@ -0,0 +1,18 @@
1
+ from typing import Any
2
+
3
+
4
+ def get_chain(llm: Any | None = None) -> Any:
5
+ """Build and return the LangChain runnable chain using an initialized LLM."""
6
+ from how.core.llm import get_llm
7
+ from how.core.template import PROMPT_TEMPLATE
8
+
9
+ if llm is None:
10
+ llm = get_llm()
11
+
12
+ return PROMPT_TEMPLATE | llm
13
+
14
+
15
+ def __getattr__(name: str) -> Any:
16
+ if name == "HOW_CLI_CHAIN":
17
+ return get_chain()
18
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -0,0 +1,86 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+
6
+ class Config:
7
+ """
8
+ Manages the configuration of the application.
9
+ Config file is stored in ~/.how/config.json by default.
10
+ """
11
+
12
+ def __init__(self, config_dir: Path | None = None) -> None:
13
+ self.config_dir = config_dir or (Path.home() / ".how")
14
+ self.config_file = self.config_dir / "config.json"
15
+ self.__init_config()
16
+
17
+ def __init_config(self) -> None:
18
+ """
19
+ Initialize the configuration directory & file if they don't exist.
20
+ """
21
+ if not self.config_dir.exists():
22
+ self.config_dir.mkdir(parents=True, exist_ok=True)
23
+
24
+ if not self.config_file.exists():
25
+ with open(self.config_file, "w", encoding="utf-8") as f:
26
+ json.dump(
27
+ {
28
+ "provider": "",
29
+ "api_key": "",
30
+ "model": "",
31
+ "endpoint": "",
32
+ },
33
+ f,
34
+ indent=4,
35
+ )
36
+
37
+ def setup(
38
+ self,
39
+ provider: str,
40
+ api_key: str = "",
41
+ model: str | None = None,
42
+ endpoint: str | None = None,
43
+ ) -> None:
44
+ """
45
+ Save the LLM Provider, API Key, and optional custom model/endpoint.
46
+ """
47
+ data: dict[str, Any] = {
48
+ "provider": provider,
49
+ "api_key": api_key,
50
+ "model": model or "",
51
+ "endpoint": endpoint or "",
52
+ }
53
+ with open(self.config_file, "w", encoding="utf-8") as f:
54
+ json.dump(data, f, indent=4)
55
+
56
+ @property
57
+ def values(self) -> dict[str, Any]:
58
+ """
59
+ Get the configuration values.
60
+ """
61
+ try:
62
+ with open(self.config_file, "r", encoding="utf-8") as f:
63
+ return json.load(f)
64
+ except (OSError, json.JSONDecodeError):
65
+ return {}
66
+
67
+ def is_ready(self) -> bool:
68
+ """
69
+ Check if the configuration file is ready to use.
70
+ """
71
+ try:
72
+ vals = self.values
73
+ provider = vals.get("provider")
74
+ if not provider:
75
+ return False
76
+
77
+ from how.core.providers import LLM_PROVIDERS
78
+
79
+ provider_info = LLM_PROVIDERS.get(provider)
80
+ if not provider_info:
81
+ return False
82
+
83
+ requires_key = provider_info.get("requires_key", True)
84
+ return not (requires_key and not vals.get("api_key"))
85
+ except (KeyError, TypeError, ValueError, ImportError):
86
+ return False