aish-cli 0.1.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.
aish_cli-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kuroome
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: aish-cli
3
+ Version: 0.1.0
4
+ Summary: AI-powered shell command assistant - convert natural language to safe executable commands
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Kuroome
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/yourusername/aish
29
+ Project-URL: Repository, https://github.com/yourusername/aish.git
30
+ Project-URL: Issues, https://github.com/yourusername/aish/issues
31
+ Keywords: ai,shell,cli,command,assistant,gpt
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Intended Audience :: System Administrators
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.10
40
+ Classifier: Programming Language :: Python :: 3.11
41
+ Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Topic :: System :: Shells
44
+ Classifier: Topic :: Utilities
45
+ Requires-Python: >=3.10
46
+ Description-Content-Type: text/markdown
47
+ License-File: LICENSE
48
+ Requires-Dist: openai>=1.0.0
49
+ Requires-Dist: typer[all]>=0.9.0
50
+ Requires-Dist: rich>=13.0.0
51
+ Requires-Dist: tomli-w>=1.0.0
52
+ Dynamic: license-file
53
+
54
+ # aish (AI Shell)
55
+
56
+ Convert natural language into safe shell commands. `aish` uses an LLM to translate your intent into bash, validates it for safety, and executes it.
57
+
58
+ ## Requirements
59
+
60
+ Python 3.10 or higher.
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ git clone <repo>
66
+ cd aish
67
+ pip install -e .
68
+ ```
69
+
70
+ ## Configuration
71
+
72
+ Set up your LLM provider by running `aish init`.
73
+
74
+ **Interactive mode:**
75
+ ```bash
76
+ aish init
77
+ ```
78
+
79
+ **Flag mode:**
80
+ ```bash
81
+ aish init --base-url "https://api.openai.com/v1" --api-key "sk-..." --model "gpt-4o"
82
+ ```
83
+
84
+ ## Usage
85
+
86
+ Pass your prompt directly to `aish run`. No quotes needed.
87
+
88
+ ```bash
89
+ aish run list all python files
90
+ aish run show disk usage --dry-run
91
+ ```
92
+
93
+ ### Options
94
+
95
+ | Option | Short | Description |
96
+ |-------------|-------|------------------------------------|
97
+ | `--yes` | `-y` | Skip confirmation for safe commands |
98
+ | `--dry-run` | `-d` | Show the command without executing |
99
+
100
+ ## Safety
101
+
102
+ Built-in validation checks every command before execution:
103
+ * **ALLOW**: Low risk. Runs immediately if you use `--yes`.
104
+ * **WARN**: Medium risk. Always asks for confirmation, even with `--yes`.
105
+ * **DENY**: High risk (like disk wipes or fork bombs). Blocked completely.
106
+
107
+ ## History
108
+
109
+ Your last 1000 commands are saved to `~/.aish/history` in JSON Lines format. Old commands are automatically trimmed to save space.
@@ -0,0 +1,56 @@
1
+ # aish (AI Shell)
2
+
3
+ Convert natural language into safe shell commands. `aish` uses an LLM to translate your intent into bash, validates it for safety, and executes it.
4
+
5
+ ## Requirements
6
+
7
+ Python 3.10 or higher.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ git clone <repo>
13
+ cd aish
14
+ pip install -e .
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ Set up your LLM provider by running `aish init`.
20
+
21
+ **Interactive mode:**
22
+ ```bash
23
+ aish init
24
+ ```
25
+
26
+ **Flag mode:**
27
+ ```bash
28
+ aish init --base-url "https://api.openai.com/v1" --api-key "sk-..." --model "gpt-4o"
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ Pass your prompt directly to `aish run`. No quotes needed.
34
+
35
+ ```bash
36
+ aish run list all python files
37
+ aish run show disk usage --dry-run
38
+ ```
39
+
40
+ ### Options
41
+
42
+ | Option | Short | Description |
43
+ |-------------|-------|------------------------------------|
44
+ | `--yes` | `-y` | Skip confirmation for safe commands |
45
+ | `--dry-run` | `-d` | Show the command without executing |
46
+
47
+ ## Safety
48
+
49
+ Built-in validation checks every command before execution:
50
+ * **ALLOW**: Low risk. Runs immediately if you use `--yes`.
51
+ * **WARN**: Medium risk. Always asks for confirmation, even with `--yes`.
52
+ * **DENY**: High risk (like disk wipes or fork bombs). Blocked completely.
53
+
54
+ ## History
55
+
56
+ Your last 1000 commands are saved to `~/.aish/history` in JSON Lines format. Old commands are automatically trimmed to save space.
File without changes
@@ -0,0 +1,3 @@
1
+ from aish.cli import app
2
+
3
+ app()
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import Optional
5
+
6
+ import typer
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+
10
+ from aish.config import AishConfig, ConfigNotFoundError, read_config, write_config
11
+ from aish.executor import run_command
12
+ from aish.history import append_history
13
+ from aish.llm import generate_command
14
+ from aish.safety import RiskLevel, check_command
15
+
16
+ app = typer.Typer(name="aish", help="AI-powered shell assistant")
17
+ console = Console()
18
+ err_console = Console(stderr=True)
19
+
20
+
21
+ @app.command()
22
+ def init(
23
+ base_url: Optional[str] = typer.Option(None, "--base-url", help="LLM API base URL"),
24
+ api_key: Optional[str] = typer.Option(None, "--api-key", help="LLM API key"),
25
+ model: Optional[str] = typer.Option(None, "--model", help="Model name"),
26
+ ) -> None:
27
+ """Configure aish with LLM API credentials."""
28
+ resolved_base_url: str = (
29
+ base_url if base_url is not None else typer.prompt("Base URL")
30
+ )
31
+ resolved_api_key: str = (
32
+ api_key if api_key is not None else typer.prompt("API key", hide_input=True)
33
+ )
34
+ resolved_model: str = (
35
+ model if model is not None else typer.prompt("Model", default="gpt-4o")
36
+ )
37
+
38
+ try:
39
+ write_config(
40
+ AishConfig(
41
+ base_url=resolved_base_url,
42
+ api_key=resolved_api_key,
43
+ model=resolved_model,
44
+ )
45
+ )
46
+ console.print(
47
+ "[bold green]✓[/bold green] Configuration saved to [cyan]~/.aish/config[/cyan]"
48
+ )
49
+ except Exception as e:
50
+ err_console.print(f"[bold red]Error:[/bold red] Failed to save config: {e}")
51
+ raise typer.Exit(1)
52
+
53
+
54
+ @app.command()
55
+ def run(
56
+ prompt: list[str] = typer.Argument(..., help="Natural language request"),
57
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
58
+ dry_run: bool = typer.Option(
59
+ False, "--dry-run", "-d", help="Print command but do not execute"
60
+ ),
61
+ ) -> None:
62
+ """Generate and execute a shell command from natural language."""
63
+ user_prompt = " ".join(prompt)
64
+
65
+ try:
66
+ config = read_config()
67
+ except ConfigNotFoundError:
68
+ err_console.print(
69
+ "[bold red]Error:[/bold red] No configuration found. Run [cyan]aish init[/cyan] first."
70
+ )
71
+ raise typer.Exit(1)
72
+
73
+ with console.status("[bold cyan]Generating command…[/bold cyan]"):
74
+ try:
75
+ output = generate_command(
76
+ user_prompt, config.base_url, config.api_key, config.model
77
+ )
78
+ except ValueError as e:
79
+ err_console.print(f"[bold red]Error:[/bold red] {e}")
80
+ raise typer.Exit(1)
81
+
82
+ risk_color = {"low": "green", "medium": "yellow", "high": "red"}.get(
83
+ output.risk_level, "white"
84
+ )
85
+ console.print(
86
+ Panel(
87
+ f"[bold]{output.command}[/bold]",
88
+ title="[bold]Command[/bold]",
89
+ border_style=risk_color,
90
+ )
91
+ )
92
+ console.print(f"[dim]Explanation:[/dim] {output.explanation}")
93
+ console.print(f"[dim]Risk:[/dim] [{risk_color}]{output.risk_level}[/{risk_color}]")
94
+
95
+ safety_level, _ = check_command(output.command)
96
+
97
+ if safety_level == RiskLevel.DENY:
98
+ err_console.print(
99
+ "[bold red]✗ Denied:[/bold red] This command matches a dangerous pattern and cannot be executed."
100
+ )
101
+ raise typer.Exit(1)
102
+
103
+ confirmed = False
104
+
105
+ if safety_level == RiskLevel.WARN:
106
+ if output.risk_tip:
107
+ console.print(f"[bold yellow]⚠ Warning:[/bold yellow] {output.risk_tip}")
108
+ confirmed = typer.confirm(
109
+ "This command is potentially dangerous. Execute anyway?"
110
+ )
111
+ if not confirmed:
112
+ console.print("[yellow]Aborted.[/yellow]")
113
+ raise typer.Exit(0)
114
+
115
+ timestamp = datetime.now(timezone.utc).isoformat()
116
+
117
+ if dry_run:
118
+ console.print("[bold yellow]Dry run — not executing.[/bold yellow]")
119
+ append_history(
120
+ {
121
+ "timestamp": timestamp,
122
+ "prompt": user_prompt,
123
+ "command": output.command,
124
+ "exit_code": None,
125
+ "executed": False,
126
+ }
127
+ )
128
+ raise typer.Exit(0)
129
+
130
+ if not yes and not confirmed:
131
+ confirmed = typer.confirm("Execute this command?")
132
+ if not confirmed:
133
+ console.print("[yellow]Aborted.[/yellow]")
134
+ raise typer.Exit(0)
135
+
136
+ result = run_command(output.command)
137
+
138
+ if result.stdout:
139
+ console.print(Panel(result.stdout.rstrip(), title="stdout", border_style="dim"))
140
+ if result.stderr:
141
+ console.print(
142
+ Panel(result.stderr.rstrip(), title="stderr", border_style="red dim")
143
+ )
144
+
145
+ exit_label = "[green]✓[/green]" if result.success else "[red]✗[/red]"
146
+ console.print(f"{exit_label} Exit code: [bold]{result.exit_code}[/bold]")
147
+
148
+ append_history(
149
+ {
150
+ "timestamp": timestamp,
151
+ "prompt": user_prompt,
152
+ "command": output.command,
153
+ "exit_code": result.exit_code,
154
+ "executed": True,
155
+ }
156
+ )
157
+
158
+
159
+ if __name__ == "__main__":
160
+ app()
@@ -0,0 +1,70 @@
1
+ from __future__ import annotations
2
+
3
+ import tomllib
4
+ from pathlib import Path
5
+ from dataclasses import dataclass
6
+
7
+ import tomli_w
8
+
9
+ CONFIG_DIR = Path.home() / ".aish"
10
+ CONFIG_PATH = CONFIG_DIR / "config"
11
+
12
+
13
+ class ConfigNotFoundError(Exception):
14
+ """Raised when ~/.aish/config does not exist."""
15
+
16
+ pass
17
+
18
+
19
+ class ConfigInvalidError(Exception):
20
+ """Raised when config exists but is missing required fields."""
21
+
22
+ pass
23
+
24
+
25
+ @dataclass
26
+ class AishConfig:
27
+ base_url: str
28
+ api_key: str
29
+ model: str
30
+
31
+
32
+ def read_config() -> AishConfig:
33
+ """Read config from ~/.aish/config. Raises ConfigNotFoundError if not found."""
34
+ if not CONFIG_PATH.exists():
35
+ raise ConfigNotFoundError("Run 'aish init' first")
36
+ with open(CONFIG_PATH, "rb") as f:
37
+ data = tomllib.load(f)
38
+ missing = [k for k in ("base_url", "api_key", "model") if k not in data]
39
+ if missing:
40
+ raise ConfigInvalidError(
41
+ f"Config missing required fields: {', '.join(missing)}"
42
+ )
43
+ return AishConfig(
44
+ base_url=data["base_url"],
45
+ api_key=data["api_key"],
46
+ model=data["model"],
47
+ )
48
+
49
+
50
+ def write_config(config: AishConfig) -> None:
51
+ """Write config to ~/.aish/config as TOML."""
52
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
53
+ data = {
54
+ "base_url": config.base_url,
55
+ "api_key": config.api_key,
56
+ "model": config.model,
57
+ }
58
+ with open(CONFIG_PATH, "wb") as f:
59
+ tomli_w.dump(data, f)
60
+
61
+
62
+ def config_exists() -> bool:
63
+ """Return True if config file exists and has required fields."""
64
+ if not CONFIG_PATH.exists():
65
+ return False
66
+ try:
67
+ read_config()
68
+ return True
69
+ except Exception:
70
+ return False
@@ -0,0 +1,52 @@
1
+ """Command execution wrapper for aish."""
2
+
3
+ from __future__ import annotations
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass
9
+ class ExecResult:
10
+ command: str
11
+ exit_code: int
12
+ stdout: str
13
+ stderr: str
14
+
15
+ @property
16
+ def success(self) -> bool:
17
+ return self.exit_code == 0
18
+
19
+
20
+ def run_command(command: str, timeout: int = 30) -> ExecResult:
21
+ """
22
+ Execute a shell command using shell=True (supports pipes, redirects).
23
+ Returns ExecResult with exit code, stdout, stderr.
24
+ """
25
+ try:
26
+ result = subprocess.run(
27
+ command,
28
+ shell=True,
29
+ capture_output=True,
30
+ text=True,
31
+ timeout=timeout,
32
+ )
33
+ return ExecResult(
34
+ command=command,
35
+ exit_code=result.returncode,
36
+ stdout=result.stdout,
37
+ stderr=result.stderr,
38
+ )
39
+ except subprocess.TimeoutExpired:
40
+ return ExecResult(
41
+ command=command,
42
+ exit_code=124, # Standard timeout exit code
43
+ stdout="",
44
+ stderr=f"Command timed out after {timeout} seconds",
45
+ )
46
+ except Exception as e:
47
+ return ExecResult(
48
+ command=command,
49
+ exit_code=1,
50
+ stdout="",
51
+ stderr=str(e),
52
+ )
@@ -0,0 +1,67 @@
1
+ """Command history management for aish — JSON Lines format."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+
9
+ HISTORY_DIR = Path.home() / ".aish"
10
+ HISTORY_PATH = HISTORY_DIR / "history"
11
+ MAX_ENTRIES = 1000
12
+
13
+
14
+ def append_history(entry: dict) -> None:
15
+ """
16
+ Append one history entry to ~/.aish/history (JSON Lines format).
17
+ Auto-trims to MAX_ENTRIES by removing oldest entries.
18
+
19
+ Expected entry fields:
20
+ timestamp: str (ISO 8601) — auto-added if missing
21
+ prompt: str
22
+ command: str
23
+ exit_code: int | None
24
+ executed: bool
25
+ """
26
+ if "timestamp" not in entry:
27
+ entry = {**entry, "timestamp": datetime.now(timezone.utc).isoformat()}
28
+
29
+ HISTORY_DIR.mkdir(parents=True, exist_ok=True)
30
+
31
+ existing: list[dict] = []
32
+ if HISTORY_PATH.exists():
33
+ with open(HISTORY_PATH, "r", encoding="utf-8") as f:
34
+ for line in f:
35
+ line = line.strip()
36
+ if line:
37
+ try:
38
+ existing.append(json.loads(line))
39
+ except json.JSONDecodeError:
40
+ pass
41
+
42
+ existing.append(entry)
43
+
44
+ if len(existing) > MAX_ENTRIES:
45
+ existing = existing[-MAX_ENTRIES:]
46
+
47
+ with open(HISTORY_PATH, "w", encoding="utf-8") as f:
48
+ for e in existing:
49
+ f.write(json.dumps(e, ensure_ascii=False) + "\n")
50
+
51
+
52
+ def read_history() -> list[dict]:
53
+ """Return all history entries, newest first."""
54
+ if not HISTORY_PATH.exists():
55
+ return []
56
+
57
+ entries: list[dict] = []
58
+ with open(HISTORY_PATH, "r", encoding="utf-8") as f:
59
+ for line in f:
60
+ line = line.strip()
61
+ if line:
62
+ try:
63
+ entries.append(json.loads(line))
64
+ except json.JSONDecodeError:
65
+ pass
66
+
67
+ return list(reversed(entries))
@@ -0,0 +1,88 @@
1
+ """LLM integration for aish — OpenAI-compatible API client."""
2
+
3
+ from __future__ import annotations
4
+ import json
5
+ import platform
6
+ from dataclasses import dataclass
7
+
8
+ from openai import OpenAI
9
+
10
+
11
+ @dataclass
12
+ class CommandOutput:
13
+ command: str
14
+ explanation: str
15
+ risk_level: str # "low", "medium", "high"
16
+ risk_tip: str
17
+
18
+
19
+ SYSTEM_PROMPT = """You are a shell command generator. Your job is to convert natural language requests into executable shell commands.
20
+
21
+ Rules:
22
+ 1. Return ONLY valid JSON — no markdown, no code blocks, no extra text
23
+ 2. Return EXACTLY ONE command
24
+ 3. The command must be executable on {os_name} ({shell})
25
+ 4. JSON schema: {{"command": "...", "explanation": "...", "risk_level": "low|medium|high", "risk_tip": "..."}}
26
+ 5. risk_level: "low" for safe commands, "medium" for potentially impactful, "high" for destructive/irreversible
27
+ 6. risk_tip: brief warning if risk_level is medium/high, otherwise empty string
28
+ 7. If the request is ambiguous or dangerous, still return the most reasonable command but set risk_level to "high"
29
+
30
+ Example response:
31
+ {{"command": "ls -la", "explanation": "Lists all files in current directory with details", "risk_level": "low", "risk_tip": ""}}"""
32
+
33
+
34
+ def generate_command(
35
+ prompt: str,
36
+ base_url: str,
37
+ api_key: str,
38
+ model: str,
39
+ ) -> CommandOutput:
40
+ """
41
+ Call the LLM API to generate a shell command from a natural language prompt.
42
+ Raises ValueError if the response cannot be parsed.
43
+ """
44
+ client = OpenAI(api_key=api_key, base_url=base_url)
45
+
46
+ os_name = platform.system()
47
+ shell = "bash" # default shell assumption
48
+
49
+ system_msg = SYSTEM_PROMPT.format(os_name=os_name, shell=shell)
50
+
51
+ response = client.chat.completions.create(
52
+ model=model,
53
+ messages=[
54
+ {"role": "system", "content": system_msg},
55
+ {"role": "user", "content": prompt},
56
+ ],
57
+ temperature=0,
58
+ timeout=60,
59
+ )
60
+
61
+ content = response.choices[0].message.content
62
+ if not content:
63
+ raise ValueError("LLM returned empty response")
64
+
65
+ # Strip markdown code blocks if present
66
+ content = content.strip()
67
+ if content.startswith("```"):
68
+ lines = content.split("\n")
69
+ # Remove first and last lines (``` markers)
70
+ content = "\n".join(lines[1:-1] if lines[-1] == "```" else lines[1:])
71
+ content = content.strip()
72
+
73
+ try:
74
+ data = json.loads(content)
75
+ except json.JSONDecodeError as e:
76
+ raise ValueError(f"LLM returned invalid JSON: {e}\nContent: {content}") from e
77
+
78
+ required_fields = ("command", "explanation", "risk_level", "risk_tip")
79
+ missing = [f for f in required_fields if f not in data]
80
+ if missing:
81
+ raise ValueError(f"LLM response missing fields: {missing}")
82
+
83
+ return CommandOutput(
84
+ command=data["command"],
85
+ explanation=data["explanation"],
86
+ risk_level=data.get("risk_level", "low"),
87
+ risk_tip=data.get("risk_tip", ""),
88
+ )
@@ -0,0 +1,70 @@
1
+ """Dangerous command detection for aish."""
2
+
3
+ from __future__ import annotations
4
+ import re
5
+ from enum import Enum
6
+
7
+
8
+ class RiskLevel(Enum):
9
+ ALLOW = "allow"
10
+ WARN = "warn"
11
+ DENY = "deny"
12
+
13
+
14
+ # Patterns that DENY execution outright
15
+ DENY_PATTERNS = [
16
+ r":\s*\(\s*\)\s*\{", # fork bomb: :(){
17
+ r"\bmkfs\b", # format filesystem
18
+ r"\bfdisk\b.*--wipe", # disk wipe
19
+ r"dd\s+if=.*of=/dev/(sd|hd|nvme|disk)", # dd to raw disk
20
+ r">\s*/dev/(sd[a-z]|hd[a-z]|nvme\d)", # redirect to raw disk
21
+ r"shred\s+.*(/dev/|/boot/)", # shred critical paths
22
+ ]
23
+
24
+ # Patterns that WARN and require explicit confirmation
25
+ WARN_PATTERNS = [
26
+ r"\brm\s+(-[rfRF]+\s+|-[rfRF]+$)", # rm with -r or -f flags
27
+ r"\brm\s+-[a-zA-Z]*[rf][a-zA-Z]*", # rm with r or f anywhere in flags
28
+ r"\bsudo\b", # sudo usage
29
+ r"chmod\s+(777|[0-7]{3})\s+", # chmod
30
+ r"\|\s*(sh|bash|zsh|fish)\b", # pipe to shell
31
+ r"\bwget\b.*\|\s*(sh|bash)", # wget | bash
32
+ r"\bcurl\b.*\|\s*(sh|bash)", # curl | bash
33
+ r"\bdd\b.*\bof=", # dd with output file
34
+ r">\s*/etc/", # redirect to /etc/
35
+ r">\s*/usr/", # redirect to /usr/
36
+ r">\s*/boot/", # redirect to /boot/
37
+ r"\bchown\b.*-[Rr]", # recursive chown
38
+ r"\bkillall\b", # killall
39
+ r"\bpkill\b", # pkill
40
+ r"\bsystemctl\b.*(stop|disable|mask)", # stop system services
41
+ r"\buseradd\b|\buserdel\b|\busermod\b", # user management
42
+ ]
43
+
44
+
45
+ def check_command(command: str) -> tuple[RiskLevel, list[str]]:
46
+ """
47
+ Check a command for dangerous patterns.
48
+ Returns (RiskLevel, list_of_matched_patterns).
49
+ """
50
+ matches: list[str] = []
51
+
52
+ for pattern in DENY_PATTERNS:
53
+ if re.search(pattern, command, re.IGNORECASE):
54
+ matches.append(pattern)
55
+ return RiskLevel.DENY, matches
56
+
57
+ for pattern in WARN_PATTERNS:
58
+ if re.search(pattern, command, re.IGNORECASE):
59
+ matches.append(pattern)
60
+
61
+ if matches:
62
+ return RiskLevel.WARN, matches
63
+
64
+ return RiskLevel.ALLOW, []
65
+
66
+
67
+ def is_dangerous(command: str) -> bool:
68
+ """Returns True if the command is WARN or DENY level."""
69
+ level, _ = check_command(command)
70
+ return level != RiskLevel.ALLOW
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: aish-cli
3
+ Version: 0.1.0
4
+ Summary: AI-powered shell command assistant - convert natural language to safe executable commands
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Kuroome
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/yourusername/aish
29
+ Project-URL: Repository, https://github.com/yourusername/aish.git
30
+ Project-URL: Issues, https://github.com/yourusername/aish/issues
31
+ Keywords: ai,shell,cli,command,assistant,gpt
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Intended Audience :: System Administrators
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.10
40
+ Classifier: Programming Language :: Python :: 3.11
41
+ Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Topic :: System :: Shells
44
+ Classifier: Topic :: Utilities
45
+ Requires-Python: >=3.10
46
+ Description-Content-Type: text/markdown
47
+ License-File: LICENSE
48
+ Requires-Dist: openai>=1.0.0
49
+ Requires-Dist: typer[all]>=0.9.0
50
+ Requires-Dist: rich>=13.0.0
51
+ Requires-Dist: tomli-w>=1.0.0
52
+ Dynamic: license-file
53
+
54
+ # aish (AI Shell)
55
+
56
+ Convert natural language into safe shell commands. `aish` uses an LLM to translate your intent into bash, validates it for safety, and executes it.
57
+
58
+ ## Requirements
59
+
60
+ Python 3.10 or higher.
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ git clone <repo>
66
+ cd aish
67
+ pip install -e .
68
+ ```
69
+
70
+ ## Configuration
71
+
72
+ Set up your LLM provider by running `aish init`.
73
+
74
+ **Interactive mode:**
75
+ ```bash
76
+ aish init
77
+ ```
78
+
79
+ **Flag mode:**
80
+ ```bash
81
+ aish init --base-url "https://api.openai.com/v1" --api-key "sk-..." --model "gpt-4o"
82
+ ```
83
+
84
+ ## Usage
85
+
86
+ Pass your prompt directly to `aish run`. No quotes needed.
87
+
88
+ ```bash
89
+ aish run list all python files
90
+ aish run show disk usage --dry-run
91
+ ```
92
+
93
+ ### Options
94
+
95
+ | Option | Short | Description |
96
+ |-------------|-------|------------------------------------|
97
+ | `--yes` | `-y` | Skip confirmation for safe commands |
98
+ | `--dry-run` | `-d` | Show the command without executing |
99
+
100
+ ## Safety
101
+
102
+ Built-in validation checks every command before execution:
103
+ * **ALLOW**: Low risk. Runs immediately if you use `--yes`.
104
+ * **WARN**: Medium risk. Always asks for confirmation, even with `--yes`.
105
+ * **DENY**: High risk (like disk wipes or fork bombs). Blocked completely.
106
+
107
+ ## History
108
+
109
+ Your last 1000 commands are saved to `~/.aish/history` in JSON Lines format. Old commands are automatically trimmed to save space.
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ aish/__init__.py
5
+ aish/__main__.py
6
+ aish/cli.py
7
+ aish/config.py
8
+ aish/executor.py
9
+ aish/history.py
10
+ aish/llm.py
11
+ aish/safety.py
12
+ aish_cli.egg-info/PKG-INFO
13
+ aish_cli.egg-info/SOURCES.txt
14
+ aish_cli.egg-info/dependency_links.txt
15
+ aish_cli.egg-info/entry_points.txt
16
+ aish_cli.egg-info/requires.txt
17
+ aish_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ aish = aish.cli:app
@@ -0,0 +1,4 @@
1
+ openai>=1.0.0
2
+ typer[all]>=0.9.0
3
+ rich>=13.0.0
4
+ tomli-w>=1.0.0
@@ -0,0 +1 @@
1
+ aish
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "aish-cli"
7
+ version = "0.1.0"
8
+ description = "AI-powered shell command assistant - convert natural language to safe executable commands"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Your Name", email = "your.email@example.com" }
12
+ ]
13
+ license = { file = "LICENSE" }
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: System Administrators",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: System :: Shells",
27
+ "Topic :: Utilities",
28
+ ]
29
+ keywords = ["ai", "shell", "cli", "command", "assistant", "gpt"]
30
+ requires-python = ">=3.10"
31
+ dependencies = [
32
+ "openai>=1.0.0",
33
+ "typer[all]>=0.9.0",
34
+ "rich>=13.0.0",
35
+ "tomli-w>=1.0.0",
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/yourusername/aish"
40
+ Repository = "https://github.com/yourusername/aish.git"
41
+ Issues = "https://github.com/yourusername/aish/issues"
42
+
43
+ [project.scripts]
44
+ aish = "aish.cli:app"
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["."]
48
+ include = ["aish*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+