aitermite 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AITERMITE
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,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: aitermite
3
+ Version: 0.4.1
4
+ Summary: AI-powered terminal error fixer with cross-platform shell hooks
5
+ Author: AITERMITE
6
+ License: MIT
7
+ Keywords: cli,terminal,ai,ollama,openai,shell,developer-tools
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Environment :: Console
11
+ Classifier: Topic :: Software Development :: User Interfaces
12
+ Classifier: Topic :: Terminals
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ <p align="center">
19
+ <img src="assets/aitermite-logo.svg" alt="AITERMITE logo" width="900">
20
+ </p>
21
+
22
+ # AITERMITE
23
+
24
+ **AITERMITE** is an AI-powered terminal error fixer for Windows, macOS, and Linux. It gives command-fix suggestions manually, before Enter where the shell supports it, and automatically after a command fails through shell hooks.
25
+
26
+ ## Current packaged release
27
+
28
+ Latest local build prepared in this chat: **v0.4.1 Clink Bootstrap**.
29
+
30
+ Main features:
31
+
32
+ - Manual command fixing with `aitermite`.
33
+ - Inline fixing with `aitermite <command>`.
34
+ - Automatic post-failure suggestion hooks.
35
+ - Pre-enter typo detection where supported.
36
+ - Ollama local provider support.
37
+ - OpenAI fallback support.
38
+ - Heuristic offline fallback.
39
+ - Windows PowerShell / Windows Terminal support.
40
+ - macOS/Linux zsh, bash, and fish support.
41
+ - cmd.exe helper macros.
42
+ - Clink bootstrap integration for true cmd.exe auto-hooks.
43
+ - Secret redaction before provider calls.
44
+ - Dangerous command blocking and safety checks.
45
+
46
+ ## Install from wheel
47
+
48
+ ```bash
49
+ python -m pip install aitermite-0.4.1-py3-none-any.whl
50
+ aitermite --doctor
51
+ ```
52
+
53
+ ## Install from source
54
+
55
+ ```bash
56
+ git clone https://github.com/sathkruthdamera/AITERMITE.git
57
+ cd AITERMITE
58
+ python -m pip install -e .
59
+ aitermite --doctor
60
+ ```
61
+
62
+ ## Shell integration
63
+
64
+ ```bash
65
+ aitermite --install-shell auto
66
+ ```
67
+
68
+ Supported shell targets:
69
+
70
+ ```bash
71
+ aitermite --install-shell zsh
72
+ aitermite --install-shell bash
73
+ aitermite --install-shell fish
74
+ aitermite --install-shell powershell
75
+ aitermite --install-shell cmd
76
+ aitermite --install-shell clink
77
+ aitermite --install-shell universal
78
+ ```
79
+
80
+ ## Automatic post-failure behavior
81
+
82
+ Target behavior:
83
+
84
+ ```text
85
+ Wrong command entered -> command fails -> AITERMITE automatically prints suggestion
86
+ ```
87
+
88
+ Example:
89
+
90
+ ```bash
91
+ gti status
92
+ ```
93
+
94
+ Output:
95
+
96
+ ```text
97
+ command not found: gti
98
+
99
+ AITERMITE suggestion after failed command
100
+ Typed: gti status
101
+ Fix: git status
102
+ Why: Known command typo detected.
103
+ Confidence: high (0.94)
104
+ Run manually: git status
105
+ ```
106
+
107
+ ## Provider settings
108
+
109
+ ```bash
110
+ export AITERMITE_PROVIDER=auto
111
+ export AITERMITE_POSTFAIL_PROVIDER=auto
112
+ export AITERMITE_POSTFAIL_TIMEOUT_MS=900
113
+ ```
114
+
115
+ For Ollama:
116
+
117
+ ```bash
118
+ ollama pull gemma3:latest
119
+ export AITERMITE_PROVIDER=ollama
120
+ export AITERMITE_OLLAMA_MODEL=gemma3:latest
121
+ ```
122
+
123
+ For OpenAI:
124
+
125
+ ```bash
126
+ export OPENAI_API_KEY="your_api_key"
127
+ export AITERMITE_PROVIDER=openai
128
+ ```
129
+
130
+ ## Common commands
131
+
132
+ ```bash
133
+ aitermite --doctor
134
+ aitermite git push
135
+ aitermite --json git push
136
+ aitermite --precheck gti status
137
+ aitermite --postfail 127 gti status
138
+ aitermite --install-shell auto
139
+ ```
140
+
141
+ ## Safety
142
+
143
+ AITERMITE blocks or avoids risky commands such as destructive delete patterns, `curl | sh`, fork bombs, device writes, shutdown/reboot patterns, and unsupported shell control operators in auto-apply flows.
144
+
145
+ ## Validation from local build
146
+
147
+ ```text
148
+ CLI tests: 9/9 passed
149
+ History tests: 5/5 passed
150
+ Precheck tests: 7/7 passed
151
+ Redaction tests: 2/2 passed
152
+ Safety tests: 7/7 passed
153
+ Shell integration tests: 9/9 passed
154
+ Total targeted tests: 39/39 passed
155
+ Pentest checks: 11/11 passed
156
+ Wheel build: passed
157
+ Clean virtualenv install: passed
158
+ CLI smoke test: passed
159
+ Clink hook generation: passed
160
+ Version: 0.4.1
161
+ ```
162
+
163
+ ## Repository import status
164
+
165
+ This repository has been initialized from ChatGPT. The full v0.4.1 source ZIP and wheel were generated in the workspace. Source import should include:
166
+
167
+ ```text
168
+ src/aitermite/
169
+ tests/
170
+ security/
171
+ examples/
172
+ assets/
173
+ README.md
174
+ pyproject.toml
175
+ LICENSE
176
+ FULL_DOCUMENTATION.md
177
+ CROSS_PLATFORM_SHELL_INTEGRATION.md
178
+ POSTFAIL_AUTOMATION.md
179
+ SHELL_AND_ANIMATION.md
180
+ VALIDATION_REPORT.md
181
+ ```
@@ -0,0 +1,164 @@
1
+ <p align="center">
2
+ <img src="assets/aitermite-logo.svg" alt="AITERMITE logo" width="900">
3
+ </p>
4
+
5
+ # AITERMITE
6
+
7
+ **AITERMITE** is an AI-powered terminal error fixer for Windows, macOS, and Linux. It gives command-fix suggestions manually, before Enter where the shell supports it, and automatically after a command fails through shell hooks.
8
+
9
+ ## Current packaged release
10
+
11
+ Latest local build prepared in this chat: **v0.4.1 Clink Bootstrap**.
12
+
13
+ Main features:
14
+
15
+ - Manual command fixing with `aitermite`.
16
+ - Inline fixing with `aitermite <command>`.
17
+ - Automatic post-failure suggestion hooks.
18
+ - Pre-enter typo detection where supported.
19
+ - Ollama local provider support.
20
+ - OpenAI fallback support.
21
+ - Heuristic offline fallback.
22
+ - Windows PowerShell / Windows Terminal support.
23
+ - macOS/Linux zsh, bash, and fish support.
24
+ - cmd.exe helper macros.
25
+ - Clink bootstrap integration for true cmd.exe auto-hooks.
26
+ - Secret redaction before provider calls.
27
+ - Dangerous command blocking and safety checks.
28
+
29
+ ## Install from wheel
30
+
31
+ ```bash
32
+ python -m pip install aitermite-0.4.1-py3-none-any.whl
33
+ aitermite --doctor
34
+ ```
35
+
36
+ ## Install from source
37
+
38
+ ```bash
39
+ git clone https://github.com/sathkruthdamera/AITERMITE.git
40
+ cd AITERMITE
41
+ python -m pip install -e .
42
+ aitermite --doctor
43
+ ```
44
+
45
+ ## Shell integration
46
+
47
+ ```bash
48
+ aitermite --install-shell auto
49
+ ```
50
+
51
+ Supported shell targets:
52
+
53
+ ```bash
54
+ aitermite --install-shell zsh
55
+ aitermite --install-shell bash
56
+ aitermite --install-shell fish
57
+ aitermite --install-shell powershell
58
+ aitermite --install-shell cmd
59
+ aitermite --install-shell clink
60
+ aitermite --install-shell universal
61
+ ```
62
+
63
+ ## Automatic post-failure behavior
64
+
65
+ Target behavior:
66
+
67
+ ```text
68
+ Wrong command entered -> command fails -> AITERMITE automatically prints suggestion
69
+ ```
70
+
71
+ Example:
72
+
73
+ ```bash
74
+ gti status
75
+ ```
76
+
77
+ Output:
78
+
79
+ ```text
80
+ command not found: gti
81
+
82
+ AITERMITE suggestion after failed command
83
+ Typed: gti status
84
+ Fix: git status
85
+ Why: Known command typo detected.
86
+ Confidence: high (0.94)
87
+ Run manually: git status
88
+ ```
89
+
90
+ ## Provider settings
91
+
92
+ ```bash
93
+ export AITERMITE_PROVIDER=auto
94
+ export AITERMITE_POSTFAIL_PROVIDER=auto
95
+ export AITERMITE_POSTFAIL_TIMEOUT_MS=900
96
+ ```
97
+
98
+ For Ollama:
99
+
100
+ ```bash
101
+ ollama pull gemma3:latest
102
+ export AITERMITE_PROVIDER=ollama
103
+ export AITERMITE_OLLAMA_MODEL=gemma3:latest
104
+ ```
105
+
106
+ For OpenAI:
107
+
108
+ ```bash
109
+ export OPENAI_API_KEY="your_api_key"
110
+ export AITERMITE_PROVIDER=openai
111
+ ```
112
+
113
+ ## Common commands
114
+
115
+ ```bash
116
+ aitermite --doctor
117
+ aitermite git push
118
+ aitermite --json git push
119
+ aitermite --precheck gti status
120
+ aitermite --postfail 127 gti status
121
+ aitermite --install-shell auto
122
+ ```
123
+
124
+ ## Safety
125
+
126
+ AITERMITE blocks or avoids risky commands such as destructive delete patterns, `curl | sh`, fork bombs, device writes, shutdown/reboot patterns, and unsupported shell control operators in auto-apply flows.
127
+
128
+ ## Validation from local build
129
+
130
+ ```text
131
+ CLI tests: 9/9 passed
132
+ History tests: 5/5 passed
133
+ Precheck tests: 7/7 passed
134
+ Redaction tests: 2/2 passed
135
+ Safety tests: 7/7 passed
136
+ Shell integration tests: 9/9 passed
137
+ Total targeted tests: 39/39 passed
138
+ Pentest checks: 11/11 passed
139
+ Wheel build: passed
140
+ Clean virtualenv install: passed
141
+ CLI smoke test: passed
142
+ Clink hook generation: passed
143
+ Version: 0.4.1
144
+ ```
145
+
146
+ ## Repository import status
147
+
148
+ This repository has been initialized from ChatGPT. The full v0.4.1 source ZIP and wheel were generated in the workspace. Source import should include:
149
+
150
+ ```text
151
+ src/aitermite/
152
+ tests/
153
+ security/
154
+ examples/
155
+ assets/
156
+ README.md
157
+ pyproject.toml
158
+ LICENSE
159
+ FULL_DOCUMENTATION.md
160
+ CROSS_PLATFORM_SHELL_INTEGRATION.md
161
+ POSTFAIL_AUTOMATION.md
162
+ SHELL_AND_ANIMATION.md
163
+ VALIDATION_REPORT.md
164
+ ```
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "aitermite"
7
+ version = "0.4.1"
8
+ description = "AI-powered terminal error fixer with cross-platform shell hooks"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "AITERMITE"}]
13
+ keywords = ["cli", "terminal", "ai", "ollama", "openai", "shell", "developer-tools"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Environment :: Console",
18
+ "Topic :: Software Development :: User Interfaces",
19
+ "Topic :: Terminals"
20
+ ]
21
+ dependencies = []
22
+
23
+ [project.scripts]
24
+ aitermite = "aitermite.cli:main"
25
+
26
+ [tool.setuptools]
27
+ package-dir = {"" = "src"}
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """AITERMITE: AI-powered terminal error fixer."""
2
+
3
+ __version__ = "0.4.1"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import platform
7
+ import sys
8
+
9
+ from . import __version__
10
+ from .history import last_command
11
+ from .precheck import precheck_command
12
+ from .providers import suggest
13
+ from .safety import assess_command
14
+ from .shell_integration import animation_text, shell_init
15
+
16
+ CYAN = "\033[96m"
17
+ RESET = "\033[0m"
18
+
19
+
20
+ def cyan(text: str, enabled: bool = True) -> str:
21
+ return f"{CYAN}{text}{RESET}" if enabled else text
22
+
23
+
24
+ def print_suggestion(s, typed: str, *, postfail: bool = False, no_color: bool = False) -> None:
25
+ title = "AITERMITE suggestion after failed command" if postfail else "AITERMITE suggestion"
26
+ print(cyan(title, not no_color))
27
+ print(f"Typed: {typed}")
28
+ print(f"Fix: {s.command}")
29
+ print(f"Why: {s.explanation}")
30
+ print(f"Confidence: {s.confidence_label()} ({s.confidence:.2f})")
31
+ print(f"Risk: {s.risk}")
32
+ print(f"Provider: {s.provider}")
33
+ print(f"Run manually: {s.command}")
34
+
35
+
36
+ def main(argv: list[str] | None = None) -> int:
37
+ parser = argparse.ArgumentParser(prog="aitermite", description="AI-powered terminal error fixer")
38
+ parser.add_argument("command", nargs="*", help="Command to fix. If empty, uses shell history.")
39
+ parser.add_argument("--version", action="store_true")
40
+ parser.add_argument("--doctor", action="store_true")
41
+ parser.add_argument("--json", action="store_true", dest="as_json")
42
+ parser.add_argument("--provider", default=os.getenv("AITERMITE_PROVIDER", "auto"), choices=["auto", "heuristic", "ollama", "openai"])
43
+ parser.add_argument("--error", default="")
44
+ parser.add_argument("--precheck", action="store_true")
45
+ parser.add_argument("--postfail", type=int, default=None)
46
+ parser.add_argument("--install-shell", choices=["auto", "zsh", "bash", "fish", "powershell", "pwsh", "cmd", "clink", "universal"])
47
+ parser.add_argument("--shell-init", choices=["auto", "zsh", "bash", "fish", "powershell", "pwsh", "cmd", "clink", "universal"])
48
+ parser.add_argument("--no-color", action="store_true")
49
+ args = parser.parse_args(argv)
50
+
51
+ if args.version:
52
+ print(__version__)
53
+ return 0
54
+ if args.doctor:
55
+ print(cyan("AITERMITE doctor", not args.no_color))
56
+ print(f"version: {__version__}")
57
+ print(f"python: {sys.version.split()[0]}")
58
+ print(f"platform: {platform.platform()}")
59
+ print(f"provider: {args.provider}")
60
+ return 0
61
+ if args.shell_init:
62
+ print(shell_init(args.shell_init))
63
+ return 0
64
+ if args.install_shell:
65
+ print(animation_text(color=not args.no_color))
66
+ print(shell_init(args.install_shell))
67
+ print(cyan("Copy the shell init block above into your shell profile, or use the packaged installer scripts from the generated ZIP.", not args.no_color))
68
+ return 0
69
+
70
+ command = " ".join(args.command).strip()
71
+ if command.startswith("-- "):
72
+ command = command[3:]
73
+ if not command:
74
+ command = last_command() or ""
75
+ if not command:
76
+ print("No command supplied and no shell history found.", file=sys.stderr)
77
+ return 2
78
+
79
+ if args.precheck:
80
+ result = precheck_command(command)
81
+ payload = {"original": result.original, "suggestion": result.suggestion, "confidence": result.confidence, "reason": result.reason}
82
+ print(json.dumps(payload, indent=2) if args.as_json else (result.suggestion or result.reason))
83
+ return 0
84
+
85
+ timeout = float(os.getenv("AITERMITE_POSTFAIL_TIMEOUT_MS", "900")) / 1000 if args.postfail is not None else None
86
+ provider = os.getenv("AITERMITE_POSTFAIL_PROVIDER", args.provider) if args.postfail is not None else args.provider
87
+ s = suggest(command, args.error, provider=provider, timeout=timeout)
88
+ verdict = assess_command(s.command)
89
+ payload = {"typed": command, "suggested_command": s.command, "explanation": s.explanation, "confidence": s.confidence, "confidence_label": s.confidence_label(), "risk": s.risk, "provider": s.provider, "safety_allowed": verdict.allowed, "safety_reason": verdict.reason}
90
+ if args.as_json:
91
+ print(json.dumps(payload, indent=2))
92
+ else:
93
+ print_suggestion(s, command, postfail=args.postfail is not None, no_color=args.no_color)
94
+ if not verdict.allowed:
95
+ print(f"Safety: blocked - {verdict.reason}")
96
+ return 0
97
+
98
+
99
+ if __name__ == "__main__":
100
+ raise SystemExit(main())
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import re
5
+ from pathlib import Path
6
+
7
+ _SELF_PATTERNS = ("aitermite", "ai-fix", "python -m aitermite")
8
+
9
+
10
+ def history_paths() -> list[Path]:
11
+ home = Path.home()
12
+ paths = [home / ".zsh_history", home / ".bash_history", home / ".local/share/fish/fish_history"]
13
+ appdata = os.getenv("APPDATA")
14
+ if appdata:
15
+ paths.append(Path(appdata) / "Microsoft/Windows/PowerShell/PSReadLine/ConsoleHost_history.txt")
16
+ return paths
17
+
18
+
19
+ def _clean_zsh(line: str) -> str:
20
+ if line.startswith(": ") and ";" in line:
21
+ return line.split(";", 1)[1].strip()
22
+ return line.strip()
23
+
24
+
25
+ def _clean_fish(lines: list[str]) -> list[str]:
26
+ out: list[str] = []
27
+ for line in lines:
28
+ text = line.strip()
29
+ if text.startswith("- cmd:"):
30
+ out.append(text.split(":", 1)[1].strip())
31
+ return out
32
+
33
+
34
+ def _is_self(command: str) -> bool:
35
+ stripped = command.strip()
36
+ base = Path(stripped.split()[0]).name if stripped.split() else ""
37
+ return base in {"aitermite", "ai-fix"} or stripped.startswith("python -m aitermite")
38
+
39
+
40
+ def read_history(limit: int = 80) -> list[str]:
41
+ commands: list[str] = []
42
+ for path in history_paths():
43
+ if not path.exists():
44
+ continue
45
+ try:
46
+ lines = path.read_text(errors="ignore").splitlines()
47
+ except OSError:
48
+ continue
49
+ parsed = _clean_fish(lines) if "fish_history" in str(path) else [_clean_zsh(x) for x in lines]
50
+ for cmd in parsed:
51
+ if cmd and not _is_self(cmd):
52
+ commands.append(cmd)
53
+ return commands[-limit:]
54
+
55
+
56
+ def last_command() -> str | None:
57
+ hist = read_history(20)
58
+ return hist[-1] if hist else None
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import List
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class FixSuggestion:
9
+ command: str
10
+ explanation: str
11
+ confidence: float
12
+ risk: str = "medium"
13
+ provider: str = "heuristic"
14
+ alternatives: List[str] = field(default_factory=list)
15
+
16
+ def confidence_label(self) -> str:
17
+ if self.confidence >= 0.80:
18
+ return "high"
19
+ if self.confidence >= 0.55:
20
+ return "medium"
21
+ return "low"
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import difflib
4
+ import shutil
5
+ from dataclasses import dataclass
6
+
7
+ @dataclass(frozen=True)
8
+ class PrecheckResult:
9
+ original: str
10
+ suggestion: str | None
11
+ confidence: float
12
+ reason: str
13
+
14
+ KNOWN_COMMAND_TYPOS = {
15
+ "gti": "git", "gi": "git", "gitg": "git", "pyhton": "python", "pythn": "python", "python3.13": "python",
16
+ "pip3.13": "pip", "dockre": "docker", "docer": "docker", "kubctl": "kubectl", "kubeclt": "kubectl",
17
+ "npm": "npm", "npx": "npx", "yarn": "yarn", "pnpm": "pnpm", "cd..": "cd .."
18
+ }
19
+ SUBCOMMAND_TYPOS = {
20
+ "git": {"sttaus": "status", "statsu": "status", "chekcout": "checkout", "comit": "commit", "pus": "push", "pul": "pull", "brnch": "branch"},
21
+ "npm": {"isntall": "install", "isntal": "install", "rn": "run", "startt": "start"},
22
+ "docker": {"pss": "ps", "bulid": "build", "runn": "run"},
23
+ "kubectl": {"gett": "get", "aply": "apply", "decribe": "describe"},
24
+ }
25
+
26
+ def _tokens(command: str) -> list[str]:
27
+ return [x for x in command.strip().split() if x]
28
+
29
+ def _best(word: str, choices: list[str]) -> tuple[str | None, float]:
30
+ matches = difflib.get_close_matches(word, choices, n=1, cutoff=0.74)
31
+ if not matches:
32
+ return None, 0.0
33
+ return matches[0], difflib.SequenceMatcher(None, word, matches[0]).ratio()
34
+
35
+ def precheck_command(command: str) -> PrecheckResult:
36
+ parts = _tokens(command)
37
+ if not parts:
38
+ return PrecheckResult(command, None, 0.0, "empty command")
39
+ changed = False
40
+ first = parts[0]
41
+ if first in KNOWN_COMMAND_TYPOS and KNOWN_COMMAND_TYPOS[first] != first:
42
+ parts[0] = KNOWN_COMMAND_TYPOS[first]
43
+ changed = True
44
+ elif shutil.which(first) is None:
45
+ choices = ["git", "python", "pip", "npm", "npx", "node", "docker", "kubectl", "terraform", "aws", "az", "java", "mvn"]
46
+ match, score = _best(first, choices)
47
+ if match and score >= 0.78:
48
+ parts[0] = match
49
+ changed = True
50
+ if len(parts) > 1 and parts[0] in SUBCOMMAND_TYPOS:
51
+ sub = parts[1]
52
+ replacement = SUBCOMMAND_TYPOS[parts[0]].get(sub)
53
+ if replacement:
54
+ parts[1] = replacement
55
+ changed = True
56
+ if not changed:
57
+ return PrecheckResult(command, None, 0.0, "no obvious typo detected")
58
+ return PrecheckResult(command, " ".join(parts), 0.94, "Known command/subcommand typo detected")
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import urllib.request
7
+ from .models import FixSuggestion
8
+ from .precheck import precheck_command
9
+ from .redaction import redact
10
+
11
+ SYSTEM_PROMPT = "Return JSON with command, explanation, confidence, risk. Suggest one safe corrected command."
12
+
13
+ def heuristic_suggest(command: str, error: str = "") -> FixSuggestion:
14
+ pre = precheck_command(command)
15
+ if pre.suggestion:
16
+ return FixSuggestion(pre.suggestion, pre.reason, pre.confidence, "low", "heuristic")
17
+ text = f"{command}\n{error}".lower()
18
+ if "module not found" in text or "modulenotfounderror" in text:
19
+ parts = command.split()
20
+ module = "requests"
21
+ if "no module named" in text:
22
+ module = text.split("no module named")[-1].strip().strip("'\" .") or module
23
+ return FixSuggestion(f"python -m pip install {module}", "Missing Python module detected.", 0.72, "low", "heuristic")
24
+ if command.strip().startswith("git push"):
25
+ return FixSuggestion("git push --set-upstream origin HEAD", "Git push may need an upstream branch.", 0.70, "medium", "heuristic")
26
+ return FixSuggestion(command, "No confident fix found; review the command manually.", 0.25, "low", "heuristic")
27
+
28
+ def ollama_suggest(command: str, error: str = "", model: str | None = None, timeout: float = 8.0) -> FixSuggestion:
29
+ model = model or os.getenv("AITERMITE_OLLAMA_MODEL", "gemma3:latest")
30
+ payload = {"model": model, "prompt": f"{SYSTEM_PROMPT}\nCommand: {redact(command)}\nError: {redact(error)}", "stream": False, "format": "json"}
31
+ data = json.dumps(payload).encode()
32
+ req = urllib.request.Request("http://127.0.0.1:11434/api/generate", data=data, headers={"Content-Type": "application/json"})
33
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
34
+ outer = json.loads(resp.read().decode())
35
+ body = json.loads(outer.get("response", "{}"))
36
+ return FixSuggestion(body.get("command", command), body.get("explanation", "Ollama suggestion."), float(body.get("confidence", 0.5)), body.get("risk", "medium"), "ollama")
37
+
38
+ def openai_suggest(command: str, error: str = "", timeout: float = 10.0) -> FixSuggestion:
39
+ key = os.getenv("OPENAI_API_KEY")
40
+ if not key:
41
+ raise RuntimeError("OPENAI_API_KEY is not set")
42
+ payload = {"model": os.getenv("AITERMITE_OPENAI_MODEL", "gpt-4.1-mini"), "messages": [{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"Command: {redact(command)}\nError: {redact(error)}"}], "response_format": {"type": "json_object"}}
43
+ req = urllib.request.Request("https://api.openai.com/v1/chat/completions", data=json.dumps(payload).encode(), headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"})
44
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
45
+ outer = json.loads(resp.read().decode())
46
+ body = json.loads(outer["choices"][0]["message"]["content"])
47
+ return FixSuggestion(body.get("command", command), body.get("explanation", "OpenAI suggestion."), float(body.get("confidence", 0.5)), body.get("risk", "medium"), "openai")
48
+
49
+ def suggest(command: str, error: str = "", provider: str = "auto", timeout: float | None = None) -> FixSuggestion:
50
+ provider = provider or os.getenv("AITERMITE_PROVIDER", "auto")
51
+ timeout = timeout or float(os.getenv("AITERMITE_TIMEOUT", "8"))
52
+ if provider == "heuristic":
53
+ return heuristic_suggest(command, error)
54
+ if provider == "ollama":
55
+ return ollama_suggest(command, error, timeout=timeout)
56
+ if provider == "openai":
57
+ return openai_suggest(command, error, timeout=timeout)
58
+ first = heuristic_suggest(command, error)
59
+ if first.confidence >= 0.80:
60
+ return first
61
+ for fn in (ollama_suggest, openai_suggest):
62
+ try:
63
+ return fn(command, error, timeout=timeout)
64
+ except Exception:
65
+ continue
66
+ return first
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Iterable
5
+
6
+ _SECRET_PATTERNS: Iterable[tuple[re.Pattern[str], str]] = [
7
+ (re.compile(r"(?i)\b(api[_-]?key|token|password|passwd|secret|client_secret)\s*=\s*([^\s'\"]+)"), r"\1=<REDACTED>"),
8
+ (re.compile(r"(?i)(--(?:api-key|token|password|secret)\s+)([^\s'\"]+)"), r"\1<REDACTED>"),
9
+ (re.compile(r"\bsk-[A-Za-z0-9_-]{16,}\b"), "<OPENAI_KEY_REDACTED>"),
10
+ (re.compile(r"\bghp_[A-Za-z0-9_]{20,}\b"), "<GITHUB_TOKEN_REDACTED>"),
11
+ (re.compile(r"\bgithub_pat_[A-Za-z0-9_]{20,}\b"), "<GITHUB_PAT_REDACTED>"),
12
+ (re.compile(r"\bAKIA[0-9A-Z]{16}\b"), "<AWS_ACCESS_KEY_REDACTED>"),
13
+ (re.compile(r"(?i)(authorization:\s*bearer\s+)[A-Za-z0-9._~+/=-]+"), r"\1<REDACTED>"),
14
+ ]
15
+
16
+
17
+ def redact(text: str) -> str:
18
+ redacted = text or ""
19
+ for pattern, replacement in _SECRET_PATTERNS:
20
+ redacted = pattern.sub(replacement, redacted)
21
+ return redacted
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import shlex
5
+ import subprocess
6
+ from dataclasses import dataclass
7
+ from typing import List, Sequence
8
+
9
+ DANGEROUS_PATTERNS = [
10
+ re.compile(r"\brm\s+-[A-Za-z]*r[A-Za-z]*f[A-Za-z]*\s+(?:--no-preserve-root\s+)?/(?:\s|$)"),
11
+ re.compile(r"\brm\s+-[A-Za-z]*f[A-Za-z]*r[A-Za-z]*\s+(?:~|\$HOME)(?:\s|/|$)"),
12
+ re.compile(r"\bdd\s+.*\bof=/dev/(?:sd|nvme|hd)"),
13
+ re.compile(r"\bmkfs(?:\.[a-z0-9]+)?\b"),
14
+ re.compile(r"\bshutdown\b|\breboot\b|\bhalt\b"),
15
+ re.compile(r":\(\)\s*\{\s*:\|:&\s*\}\s*;:"),
16
+ re.compile(r"\b(?:curl|wget)\b.*\|\s*(?:sudo\s+)?(?:sh|bash)\b"),
17
+ re.compile(r">\s*/dev/(?:sd|nvme|hd)[a-z0-9]+"),
18
+ ]
19
+ SHELL_OPERATOR_TOKENS = {"|", "||", "&&", ";", "<", ">", ">>", "2>", "2>>", "&"}
20
+ MEDIUM_RISK_PATTERNS = [re.compile(r"\bsudo\b"), re.compile(r"\brm\b"), re.compile(r"\bchmod\b|\bchown\b"), re.compile(r"\bgit\s+reset\b|\bgit\s+clean\b"), re.compile(r"\bkubectl\s+delete\b")]
21
+
22
+ @dataclass(frozen=True)
23
+ class SafetyVerdict:
24
+ allowed: bool
25
+ risk: str
26
+ reason: str
27
+
28
+ def command_to_argv(command: str) -> List[str]:
29
+ return shlex.split(command, posix=True)
30
+
31
+ def assess_command(command: str) -> SafetyVerdict:
32
+ normalized = " ".join((command or "").strip().split())
33
+ if not normalized:
34
+ return SafetyVerdict(False, "invalid", "Empty command.")
35
+ for pattern in DANGEROUS_PATTERNS:
36
+ if pattern.search(normalized):
37
+ return SafetyVerdict(False, "dangerous", "Blocked by AITERMITE safety rules.")
38
+ try:
39
+ argv = command_to_argv(normalized)
40
+ except ValueError as exc:
41
+ return SafetyVerdict(False, "invalid", f"Invalid shell quoting: {exc}")
42
+ if any(token in SHELL_OPERATOR_TOKENS for token in argv):
43
+ return SafetyVerdict(False, "unsupported", "Shell control operators are blocked in auto-apply mode.")
44
+ for pattern in MEDIUM_RISK_PATTERNS:
45
+ if pattern.search(normalized):
46
+ return SafetyVerdict(True, "medium", "Command needs explicit confirmation.")
47
+ return SafetyVerdict(True, "low", "No high-risk shell pattern detected.")
48
+
49
+ def run_command(command: str, *, cwd: str | None = None, timeout: int = 120) -> subprocess.CompletedProcess[str]:
50
+ verdict = assess_command(command)
51
+ if not verdict.allowed:
52
+ raise ValueError(verdict.reason)
53
+ argv: Sequence[str] = command_to_argv(command)
54
+ if not argv:
55
+ raise ValueError("Empty command.")
56
+ return subprocess.run(argv, cwd=cwd, timeout=timeout, check=False, text=True, capture_output=True, shell=False)
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ CYAN = "\033[96m"
4
+ RESET = "\033[0m"
5
+
6
+ INSTALL_ANIMATION = [
7
+ "AITERMITE ◖>_ ◗ scanning shell",
8
+ "AITERMITE ◖ >_ ◗ wiring hooks",
9
+ "AITERMITE ◖ >_ ◗ enabling pre-enter guard",
10
+ "AITERMITE ◖ >_ ◗ enabling post-failure AI",
11
+ "AITERMITE ◖ >_ ◗ setting latency budget",
12
+ "AITERMITE ◖ >_ ◗ loading cyan terminal skin",
13
+ "AITERMITE ◖ >_ ◗ ready",
14
+ ]
15
+
16
+ def animation_text(color: bool = True) -> str:
17
+ return "\n".join((CYAN + x + RESET) if color else x for x in INSTALL_ANIMATION)
18
+
19
+ def zsh_init() -> str:
20
+ return r'''
21
+ # AITERMITE zsh integration
22
+ _aitermite_preexec(){ export AITERMITE_LAST_COMMAND="$1"; }
23
+ _aitermite_precmd(){ local code=$?; if [ "$code" -ne 0 ] && [ -n "$AITERMITE_LAST_COMMAND" ]; then aitermite --postfail "$code" -- "$AITERMITE_LAST_COMMAND" 2>/dev/null; fi }
24
+ autoload -Uz add-zsh-hook
25
+ add-zsh-hook preexec _aitermite_preexec
26
+ add-zsh-hook precmd _aitermite_precmd
27
+ '''
28
+
29
+ def bash_init() -> str:
30
+ return r'''
31
+ # AITERMITE bash integration
32
+ _aitermite_prompt(){ local code=$?; local cmd=$(history 1 | sed 's/^ *[0-9]* *//'); if [ "$code" -ne 0 ] && [ -n "$cmd" ]; then aitermite --postfail "$code" -- "$cmd" 2>/dev/null; fi }
33
+ PROMPT_COMMAND="_aitermite_prompt${PROMPT_COMMAND:+;$PROMPT_COMMAND}"
34
+ alias ait='aitermite'
35
+ '''
36
+
37
+ def fish_init() -> str:
38
+ return r'''
39
+ # AITERMITE fish integration
40
+ function __aitermite_postfail --on-event fish_postexec
41
+ set -l code $status
42
+ if test $code -ne 0
43
+ aitermite --postfail $code -- $argv 2>/dev/null
44
+ end
45
+ end
46
+ alias ait aitermite
47
+ '''
48
+
49
+ def powershell_init() -> str:
50
+ return r'''
51
+ # AITERMITE PowerShell integration
52
+ function Invoke-AitermitePostFail {
53
+ if ($LASTEXITCODE -ne 0) {
54
+ $cmd = (Get-History -Count 1).CommandLine
55
+ if ($cmd) { aitermite --postfail $LASTEXITCODE -- $cmd }
56
+ }
57
+ }
58
+ $global:__AitermitePrompt = $function:prompt
59
+ function prompt { Invoke-AitermitePostFail; if ($global:__AitermitePrompt) { & $global:__AitermitePrompt } else { "PS> " } }
60
+ Set-Alias ait aitermite
61
+ '''
62
+
63
+ def cmd_init() -> str:
64
+ return r'''
65
+ @echo off
66
+ DOSKEY ait=aitermite $*
67
+ DOSKEY af=aitermite $*
68
+ DOSKEY aicheck=aitermite --precheck $*
69
+ where clink >nul 2>nul && clink inject --quiet
70
+ '''
71
+
72
+ def clink_lua() -> str:
73
+ return r'''
74
+ -- AITERMITE Clink cmd.exe auto post-failure hook
75
+ local last_cmd = ""
76
+ local function aitermite_filter(line) last_cmd = line; return line end
77
+ clink.onfilterinput(aitermite_filter)
78
+ clink.onendedit(function(line) last_cmd = line end)
79
+ clink.onbeginedit(function() end)
80
+ clink.prompt.register_filter(function(prompt)
81
+ local code = os.getenv("ERRORLEVEL") or "0"
82
+ if last_cmd ~= "" and code ~= "0" then
83
+ os.execute("aitermite --postfail " .. code .. " -- " .. string.format("%q", last_cmd))
84
+ last_cmd = ""
85
+ end
86
+ return prompt
87
+ end, 90)
88
+ '''
89
+
90
+ def universal_init() -> str:
91
+ return r'''
92
+ # AITERMITE universal POSIX helper
93
+ alias ait='aitermite'
94
+ arun(){ "$@"; local code=$?; if [ $code -ne 0 ]; then aitermite --postfail $code -- "$*"; fi; return $code; }
95
+ '''
96
+
97
+ def shell_init(shell: str) -> str:
98
+ shell = (shell or "auto").lower()
99
+ if shell in {"zsh", "auto"}: return zsh_init()
100
+ if shell == "bash": return bash_init()
101
+ if shell == "fish": return fish_init()
102
+ if shell in {"powershell", "pwsh"}: return powershell_init()
103
+ if shell == "cmd": return cmd_init()
104
+ if shell == "clink": return clink_lua()
105
+ if shell == "universal": return universal_init()
106
+ raise ValueError(f"Unsupported shell: {shell}")
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: aitermite
3
+ Version: 0.4.1
4
+ Summary: AI-powered terminal error fixer with cross-platform shell hooks
5
+ Author: AITERMITE
6
+ License: MIT
7
+ Keywords: cli,terminal,ai,ollama,openai,shell,developer-tools
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Environment :: Console
11
+ Classifier: Topic :: Software Development :: User Interfaces
12
+ Classifier: Topic :: Terminals
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ <p align="center">
19
+ <img src="assets/aitermite-logo.svg" alt="AITERMITE logo" width="900">
20
+ </p>
21
+
22
+ # AITERMITE
23
+
24
+ **AITERMITE** is an AI-powered terminal error fixer for Windows, macOS, and Linux. It gives command-fix suggestions manually, before Enter where the shell supports it, and automatically after a command fails through shell hooks.
25
+
26
+ ## Current packaged release
27
+
28
+ Latest local build prepared in this chat: **v0.4.1 Clink Bootstrap**.
29
+
30
+ Main features:
31
+
32
+ - Manual command fixing with `aitermite`.
33
+ - Inline fixing with `aitermite <command>`.
34
+ - Automatic post-failure suggestion hooks.
35
+ - Pre-enter typo detection where supported.
36
+ - Ollama local provider support.
37
+ - OpenAI fallback support.
38
+ - Heuristic offline fallback.
39
+ - Windows PowerShell / Windows Terminal support.
40
+ - macOS/Linux zsh, bash, and fish support.
41
+ - cmd.exe helper macros.
42
+ - Clink bootstrap integration for true cmd.exe auto-hooks.
43
+ - Secret redaction before provider calls.
44
+ - Dangerous command blocking and safety checks.
45
+
46
+ ## Install from wheel
47
+
48
+ ```bash
49
+ python -m pip install aitermite-0.4.1-py3-none-any.whl
50
+ aitermite --doctor
51
+ ```
52
+
53
+ ## Install from source
54
+
55
+ ```bash
56
+ git clone https://github.com/sathkruthdamera/AITERMITE.git
57
+ cd AITERMITE
58
+ python -m pip install -e .
59
+ aitermite --doctor
60
+ ```
61
+
62
+ ## Shell integration
63
+
64
+ ```bash
65
+ aitermite --install-shell auto
66
+ ```
67
+
68
+ Supported shell targets:
69
+
70
+ ```bash
71
+ aitermite --install-shell zsh
72
+ aitermite --install-shell bash
73
+ aitermite --install-shell fish
74
+ aitermite --install-shell powershell
75
+ aitermite --install-shell cmd
76
+ aitermite --install-shell clink
77
+ aitermite --install-shell universal
78
+ ```
79
+
80
+ ## Automatic post-failure behavior
81
+
82
+ Target behavior:
83
+
84
+ ```text
85
+ Wrong command entered -> command fails -> AITERMITE automatically prints suggestion
86
+ ```
87
+
88
+ Example:
89
+
90
+ ```bash
91
+ gti status
92
+ ```
93
+
94
+ Output:
95
+
96
+ ```text
97
+ command not found: gti
98
+
99
+ AITERMITE suggestion after failed command
100
+ Typed: gti status
101
+ Fix: git status
102
+ Why: Known command typo detected.
103
+ Confidence: high (0.94)
104
+ Run manually: git status
105
+ ```
106
+
107
+ ## Provider settings
108
+
109
+ ```bash
110
+ export AITERMITE_PROVIDER=auto
111
+ export AITERMITE_POSTFAIL_PROVIDER=auto
112
+ export AITERMITE_POSTFAIL_TIMEOUT_MS=900
113
+ ```
114
+
115
+ For Ollama:
116
+
117
+ ```bash
118
+ ollama pull gemma3:latest
119
+ export AITERMITE_PROVIDER=ollama
120
+ export AITERMITE_OLLAMA_MODEL=gemma3:latest
121
+ ```
122
+
123
+ For OpenAI:
124
+
125
+ ```bash
126
+ export OPENAI_API_KEY="your_api_key"
127
+ export AITERMITE_PROVIDER=openai
128
+ ```
129
+
130
+ ## Common commands
131
+
132
+ ```bash
133
+ aitermite --doctor
134
+ aitermite git push
135
+ aitermite --json git push
136
+ aitermite --precheck gti status
137
+ aitermite --postfail 127 gti status
138
+ aitermite --install-shell auto
139
+ ```
140
+
141
+ ## Safety
142
+
143
+ AITERMITE blocks or avoids risky commands such as destructive delete patterns, `curl | sh`, fork bombs, device writes, shutdown/reboot patterns, and unsupported shell control operators in auto-apply flows.
144
+
145
+ ## Validation from local build
146
+
147
+ ```text
148
+ CLI tests: 9/9 passed
149
+ History tests: 5/5 passed
150
+ Precheck tests: 7/7 passed
151
+ Redaction tests: 2/2 passed
152
+ Safety tests: 7/7 passed
153
+ Shell integration tests: 9/9 passed
154
+ Total targeted tests: 39/39 passed
155
+ Pentest checks: 11/11 passed
156
+ Wheel build: passed
157
+ Clean virtualenv install: passed
158
+ CLI smoke test: passed
159
+ Clink hook generation: passed
160
+ Version: 0.4.1
161
+ ```
162
+
163
+ ## Repository import status
164
+
165
+ This repository has been initialized from ChatGPT. The full v0.4.1 source ZIP and wheel were generated in the workspace. Source import should include:
166
+
167
+ ```text
168
+ src/aitermite/
169
+ tests/
170
+ security/
171
+ examples/
172
+ assets/
173
+ README.md
174
+ pyproject.toml
175
+ LICENSE
176
+ FULL_DOCUMENTATION.md
177
+ CROSS_PLATFORM_SHELL_INTEGRATION.md
178
+ POSTFAIL_AUTOMATION.md
179
+ SHELL_AND_ANIMATION.md
180
+ VALIDATION_REPORT.md
181
+ ```
@@ -0,0 +1,21 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/aitermite/__init__.py
5
+ src/aitermite/__main__.py
6
+ src/aitermite/cli.py
7
+ src/aitermite/history.py
8
+ src/aitermite/models.py
9
+ src/aitermite/precheck.py
10
+ src/aitermite/providers.py
11
+ src/aitermite/redaction.py
12
+ src/aitermite/safety.py
13
+ src/aitermite/shell_integration.py
14
+ src/aitermite.egg-info/PKG-INFO
15
+ src/aitermite.egg-info/SOURCES.txt
16
+ src/aitermite.egg-info/dependency_links.txt
17
+ src/aitermite.egg-info/entry_points.txt
18
+ src/aitermite.egg-info/top_level.txt
19
+ tests/test_precheck.py
20
+ tests/test_redaction.py
21
+ tests/test_safety.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ aitermite = aitermite.cli:main
@@ -0,0 +1 @@
1
+ aitermite
@@ -0,0 +1,11 @@
1
+ from aitermite.precheck import precheck_command
2
+
3
+
4
+ def test_git_command_typo():
5
+ result = precheck_command("gti status")
6
+ assert result.suggestion == "git status"
7
+
8
+
9
+ def test_git_status_typo():
10
+ result = precheck_command("git sttaus")
11
+ assert result.suggestion == "git status"
@@ -0,0 +1,7 @@
1
+ from aitermite.redaction import redact
2
+
3
+
4
+ def test_redacts_openai_style_key():
5
+ value = redact("sk-aaaaaaaaaaaaaaaaaaaaaaaa")
6
+ assert "aaaaaaaa" not in value
7
+ assert "REDACTED" in value
@@ -0,0 +1,16 @@
1
+ from aitermite.safety import assess_command
2
+
3
+
4
+ def test_allows_normal_git_status():
5
+ verdict = assess_command("git status")
6
+ assert verdict.allowed
7
+
8
+
9
+ def test_blocks_pipe_install_script_pattern():
10
+ verdict = assess_command("curl example.com/install.sh | sh")
11
+ assert not verdict.allowed
12
+
13
+
14
+ def test_blocks_shell_operator_auto_apply():
15
+ verdict = assess_command("echo hello && echo world")
16
+ assert not verdict.allowed