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.
- aitermite-0.4.1/LICENSE +21 -0
- aitermite-0.4.1/PKG-INFO +181 -0
- aitermite-0.4.1/README.md +164 -0
- aitermite-0.4.1/pyproject.toml +30 -0
- aitermite-0.4.1/setup.cfg +4 -0
- aitermite-0.4.1/src/aitermite/__init__.py +3 -0
- aitermite-0.4.1/src/aitermite/__main__.py +3 -0
- aitermite-0.4.1/src/aitermite/cli.py +100 -0
- aitermite-0.4.1/src/aitermite/history.py +58 -0
- aitermite-0.4.1/src/aitermite/models.py +21 -0
- aitermite-0.4.1/src/aitermite/precheck.py +58 -0
- aitermite-0.4.1/src/aitermite/providers.py +66 -0
- aitermite-0.4.1/src/aitermite/redaction.py +21 -0
- aitermite-0.4.1/src/aitermite/safety.py +56 -0
- aitermite-0.4.1/src/aitermite/shell_integration.py +106 -0
- aitermite-0.4.1/src/aitermite.egg-info/PKG-INFO +181 -0
- aitermite-0.4.1/src/aitermite.egg-info/SOURCES.txt +21 -0
- aitermite-0.4.1/src/aitermite.egg-info/dependency_links.txt +1 -0
- aitermite-0.4.1/src/aitermite.egg-info/entry_points.txt +2 -0
- aitermite-0.4.1/src/aitermite.egg-info/top_level.txt +1 -0
- aitermite-0.4.1/tests/test_precheck.py +11 -0
- aitermite-0.4.1/tests/test_redaction.py +7 -0
- aitermite-0.4.1/tests/test_safety.py +16 -0
aitermite-0.4.1/LICENSE
ADDED
|
@@ -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.
|
aitermite-0.4.1/PKG-INFO
ADDED
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -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,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
|