ai-ebash 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl
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.
- {ai_ebash-0.1.5.dist-info → ai_ebash-0.1.6.dist-info}/METADATA +1 -1
- ai_ebash-0.1.6.dist-info/RECORD +14 -0
- aiebash/__main__.py +87 -68
- aiebash/llm_factory.py +15 -0
- aiebash/llm_interface.py +6 -0
- aiebash/openai_client.py +32 -0
- aiebash/settings.py +27 -53
- ai_ebash-0.1.5.dist-info/RECORD +0 -11
- {ai_ebash-0.1.5.dist-info → ai_ebash-0.1.6.dist-info}/WHEEL +0 -0
- {ai_ebash-0.1.5.dist-info → ai_ebash-0.1.6.dist-info}/entry_points.txt +0 -0
- {ai_ebash-0.1.5.dist-info → ai_ebash-0.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
aiebash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
aiebash/__main__.py,sha256=MNnFYCUxiS-z47d75k7o5c5_rzWuxwCTGeWdksluolc,3802
|
3
|
+
aiebash/api_client.py,sha256=-hcgY3CP0lsV9Jct_-HQ6WpDosDChwalJjy4aFQ4CIM,2004
|
4
|
+
aiebash/block_runner.py,sha256=JNcdBklJmrlO7ZX7xXiO0fUGqrcNJiRqdwM-yr3OP3k,1599
|
5
|
+
aiebash/formatter_text.py,sha256=NRz_pCpakZ9k8477_4Gx1scDXrEbbOaOi-U7WRp6ZWo,847
|
6
|
+
aiebash/llm_factory.py,sha256=SDG4mSStKtairW2FaExbX49vDXITt5LBvHIKJ72nJfk,718
|
7
|
+
aiebash/llm_interface.py,sha256=LufTw-QvXkcQJMRON8RElmalKntk0YRUDHBEcjCDfaM,222
|
8
|
+
aiebash/openai_client.py,sha256=94v86FCrAd1v9SgDRtrCu87iSCnq_o2Zz0iXQdnlWbQ,1264
|
9
|
+
aiebash/settings.py,sha256=lD8qCEczEe5Fc4gFcqHqnEEmDko0lD6QEldOE68bOBE,1868
|
10
|
+
ai_ebash-0.1.6.dist-info/METADATA,sha256=gwM4t6wafhhH9bB3hcqaVYk4SUJIUvl8tdBhEfrWTlk,2384
|
11
|
+
ai_ebash-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
12
|
+
ai_ebash-0.1.6.dist-info/entry_points.txt,sha256=bstvFHHX76KHqYpjM8FPoRVvNbQprST74CI30Mr5TIM,45
|
13
|
+
ai_ebash-0.1.6.dist-info/top_level.txt,sha256=JJ-bWdhWlFCfCQGLhmqzN4_bNO0pfVPRsdbx8o9A9AA,8
|
14
|
+
ai_ebash-0.1.6.dist-info/RECORD,,
|
aiebash/__main__.py
CHANGED
@@ -1,100 +1,119 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
import sys
|
3
|
+
import threading
|
4
|
+
import time
|
3
5
|
from pathlib import Path
|
6
|
+
from rich.console import Console
|
7
|
+
from rich.markdown import Markdown
|
8
|
+
from rich.rule import Rule
|
4
9
|
|
5
|
-
#
|
6
|
-
# Это нужно, если вы запускаете файл напрямую: python src/aibash/__main__.py
|
10
|
+
# sys.path, как раньше
|
7
11
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
8
12
|
|
13
|
+
from aiebash.llm_factory import create_llm_client
|
14
|
+
from aiebash.formatter_text import annotate_bash_blocks
|
15
|
+
from aiebash.block_runner import run_code_selection
|
16
|
+
from aiebash.settings import settings
|
9
17
|
|
10
|
-
|
11
|
-
|
12
|
-
from rich.rule import Rule
|
13
|
-
import threading
|
14
|
-
import time
|
18
|
+
# Получаем настройки для конкретного бэкенда, например 'openai_over_proxy'
|
19
|
+
BACKEND = "openai_over_proxy"
|
15
20
|
|
21
|
+
MODEL = settings.get(BACKEND, "MODEL")
|
22
|
+
API_URL = settings.get(BACKEND, "API_URL")
|
23
|
+
API_KEY = settings.get(BACKEND, "API_KEY")
|
16
24
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
from aiebash.settings import settings as user_settings
|
22
|
-
|
23
|
-
# нормализуем DEBUG (возможные варианты: True/False, "true"/"False", "1"/"0")
|
24
|
-
_raw_debug = user_settings.get("DEBUG")
|
25
|
-
if isinstance(_raw_debug, bool):
|
26
|
-
DEBUG = _raw_debug
|
27
|
-
else:
|
28
|
-
DEBUG = str(_raw_debug).strip().lower() in ("1", "true", "yes", "on")
|
29
|
-
CONTEXT = user_settings.get("CONTEXT")
|
30
|
-
MODEL = user_settings.get("MODEL")
|
31
|
-
API_URL = user_settings.get("API_URL")
|
32
|
-
API_KEY = user_settings.get("API_KEY")
|
33
|
-
|
34
|
-
# Флаг для остановки потока (заменён на Event)
|
35
|
-
import threading
|
36
|
-
stop_event = threading.Event()
|
25
|
+
# Глобальные настройки
|
26
|
+
DEBUG = settings.get_bool("global", "DEBUG")
|
27
|
+
CONTEXT = settings.get("global", "CONTEXT")
|
37
28
|
|
29
|
+
# Клиент
|
30
|
+
llm_client = create_llm_client(
|
31
|
+
backend=BACKEND,
|
32
|
+
model=MODEL,
|
33
|
+
api_url=API_URL,
|
34
|
+
api_key=API_KEY,
|
35
|
+
)
|
36
|
+
|
37
|
+
# Управление прогрессом
|
38
|
+
stop_event = threading.Event()
|
38
39
|
def run_progress():
|
39
40
|
console = Console()
|
40
|
-
with console.status("[bold green]Ai печатает...[/bold green]", spinner="dots")
|
41
|
-
|
41
|
+
with console.status("[bold green]Ai печатает...[/bold green]", spinner="dots"):
|
42
|
+
while not stop_event.is_set():
|
42
43
|
time.sleep(0.1)
|
43
44
|
|
45
|
+
|
44
46
|
def main():
|
45
|
-
# Если нет аргументов, выводим подсказку по использованию
|
46
47
|
if len(sys.argv) < 2:
|
47
|
-
print("Использование: ai [-run] ваш запрос к ИИ без кавычек")
|
48
|
+
print("Использование: ai [-run] [-chat] ваш запрос к ИИ без кавычек")
|
48
49
|
sys.exit(0)
|
49
50
|
|
50
51
|
console = Console()
|
51
52
|
|
52
|
-
#
|
53
|
-
run_mode =
|
54
|
-
|
55
|
-
if "-run"
|
56
|
-
run_mode = True
|
57
|
-
args.remove("-run")
|
53
|
+
# Режимы
|
54
|
+
run_mode = "-run" in sys.argv
|
55
|
+
chat_mode = "-chat" in sys.argv
|
56
|
+
args = [a for a in sys.argv[1:] if a not in ("-run", "-chat")]
|
58
57
|
|
59
|
-
# Собираем текст запроса из оставшихся аргументов
|
60
58
|
prompt = " ".join(args)
|
61
59
|
|
62
60
|
try:
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
61
|
+
if chat_mode:
|
62
|
+
# Диалоговый режим
|
63
|
+
console.print(Rule(" Вход в чатовый режим ", style="cyan"))
|
64
|
+
messages = []
|
65
|
+
if CONTEXT:
|
66
|
+
messages.append({"role": "system", "content": CONTEXT})
|
67
|
+
|
68
|
+
while True:
|
69
|
+
user_input = console.input("[bold green]Вы:[/bold green] ")
|
70
|
+
if user_input.strip().lower() in ("exit", "quit", "выход"):
|
71
|
+
break
|
72
|
+
|
73
|
+
messages.append({"role": "user", "content": user_input})
|
74
|
+
|
75
|
+
stop_event.clear()
|
76
|
+
progress_thread = threading.Thread(target=run_progress)
|
77
|
+
progress_thread.start()
|
78
|
+
|
79
|
+
answer = llm_client.send_chat(messages)
|
80
|
+
|
81
|
+
stop_event.set()
|
82
|
+
progress_thread.join()
|
83
|
+
|
84
|
+
messages.append({"role": "assistant", "content": answer})
|
85
|
+
console.print(Markdown(answer))
|
86
|
+
console.print(Rule("", style="green"))
|
87
|
+
|
88
88
|
else:
|
89
|
-
|
90
|
-
|
91
|
-
|
89
|
+
# Обычный режим (один вопрос → один ответ)
|
90
|
+
stop_event.clear()
|
91
|
+
progress_thread = threading.Thread(target=run_progress)
|
92
|
+
progress_thread.start()
|
93
|
+
|
94
|
+
answer = llm_client.send_prompt(prompt, system_context=CONTEXT)
|
95
|
+
|
96
|
+
stop_event.set()
|
97
|
+
progress_thread.join()
|
98
|
+
|
99
|
+
if DEBUG:
|
100
|
+
print("=== RAW RESPONSE ===")
|
101
|
+
print(answer)
|
102
|
+
print("=== /RAW RESPONSE ===")
|
103
|
+
|
104
|
+
annotated_answer, code_blocks = annotate_bash_blocks(answer)
|
105
|
+
|
106
|
+
if run_mode and code_blocks:
|
107
|
+
console.print(Markdown(annotated_answer))
|
108
|
+
run_code_selection(console, code_blocks)
|
109
|
+
else:
|
110
|
+
console.print(Markdown(answer))
|
111
|
+
|
112
|
+
console.print(Rule("", style="green"))
|
92
113
|
|
93
114
|
except Exception as e:
|
94
|
-
# Прочие ошибки (сеть, JSON, и т.д.)
|
95
115
|
print("Ошибка:", e)
|
96
116
|
|
97
117
|
|
98
|
-
|
99
118
|
if __name__ == "__main__":
|
100
119
|
main()
|
aiebash/llm_factory.py
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# llm_factory.py
|
2
|
+
from .llm_interface import LLMClient
|
3
|
+
from .openai_client import OpenAIClient
|
4
|
+
# сюда в будущем можно добавить HuggingFaceClient, OllamaClient и др.
|
5
|
+
|
6
|
+
def create_llm_client(backend: str, model: str, api_url: str, api_key: str = None, timeout: int = 60) -> LLMClient:
|
7
|
+
backend = backend.lower().strip()
|
8
|
+
if backend == "openai_over_proxy":
|
9
|
+
return OpenAIClient(model=model, api_url=api_url, api_key=api_key, timeout=timeout)
|
10
|
+
# elif backend == "huggingface":
|
11
|
+
# return HuggingFaceClient(...)
|
12
|
+
# elif backend == "ollama":
|
13
|
+
# return OllamaClient(...)
|
14
|
+
else:
|
15
|
+
raise ValueError(f"Неизвестный backend: {backend}")
|
aiebash/llm_interface.py
ADDED
aiebash/openai_client.py
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# openai_client.py
|
2
|
+
import json, requests
|
3
|
+
from typing import List
|
4
|
+
from .llm_interface import LLMClient
|
5
|
+
|
6
|
+
class OpenAIClient(LLMClient):
|
7
|
+
def __init__(self, model: str, api_url: str, api_key: str = None, timeout: int = 60):
|
8
|
+
self.model = model
|
9
|
+
self.api_url = api_url
|
10
|
+
self.api_key = api_key
|
11
|
+
self.timeout = timeout
|
12
|
+
|
13
|
+
def send_chat(self, messages: List[dict]) -> str:
|
14
|
+
payload = {"model": self.model, "messages": messages}
|
15
|
+
headers = {"Content-Type": "application/json"}
|
16
|
+
if self.api_key:
|
17
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
18
|
+
|
19
|
+
resp = requests.post(self.api_url, headers=headers, json=payload, timeout=self.timeout)
|
20
|
+
resp.raise_for_status()
|
21
|
+
data = resp.json()
|
22
|
+
|
23
|
+
if "choices" in data and data["choices"]:
|
24
|
+
return data["choices"][0]["message"]["content"]
|
25
|
+
raise RuntimeError("Unexpected API response format")
|
26
|
+
|
27
|
+
def send_prompt(self, prompt: str, system_context: str = "") -> str:
|
28
|
+
messages = []
|
29
|
+
if system_context:
|
30
|
+
messages.append({"role": "system", "content": system_context})
|
31
|
+
messages.append({"role": "user", "content": prompt})
|
32
|
+
return self.send_chat(messages)
|
aiebash/settings.py
CHANGED
@@ -1,65 +1,39 @@
|
|
1
|
-
|
2
|
-
from pathlib import Path
|
3
1
|
import configparser
|
2
|
+
import shutil
|
3
|
+
from pathlib import Path
|
4
4
|
from platformdirs import user_config_dir
|
5
|
+
from typing import Optional # <--- ДОБАВЛЕНО
|
5
6
|
|
6
|
-
|
7
|
+
# --- Пути к конфигурации ---
|
8
|
+
APP_NAME = "ai-ebash"
|
9
|
+
# Путь к конфигу пользователя (e.g., %APPDATA%\ai-ebash\config.ini)
|
10
|
+
USER_CONFIG_PATH = Path(user_config_dir(APP_NAME)) / "config.ini"
|
11
|
+
# Путь к дефолтному конфигу, который лежит рядом с этим файлом
|
12
|
+
DEFAULT_CONFIG_PATH = Path(__file__).parent / "default_config.ini"
|
7
13
|
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
"CONTEXT": ( # Контекст по умолчанию
|
14
|
-
"Ты профессиональный системный администратор, помощник по Linux. "
|
15
|
-
"Пользователь - новичок в Linux, помоги ему. "
|
16
|
-
"Система Ubuntu, оболочка $SHELL. "
|
17
|
-
"При ответе учитывай, что пользователь работает в терминале Bash."
|
18
|
-
"Отвечай всегда на русском языке. "
|
19
|
-
"Ты разговариваешь с пользователем в терминале. "
|
20
|
-
),
|
21
|
-
"DEBUG": False # Включить отладочную информацию
|
22
|
-
}
|
14
|
+
# --- Логика инициализации ---
|
15
|
+
# Если у пользователя нет config.ini, копируем ему дефолтный
|
16
|
+
if not USER_CONFIG_PATH.exists():
|
17
|
+
USER_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
18
|
+
shutil.copy(DEFAULT_CONFIG_PATH, USER_CONFIG_PATH)
|
23
19
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
class Settings:
|
29
|
-
def __init__(self):
|
30
|
-
# Кроссплатформенная директория конфигов
|
31
|
-
self.config_dir = Path(user_config_dir(APP_NAME))
|
32
|
-
self.config_dir.mkdir(parents=True, exist_ok=True)
|
20
|
+
# --- Чтение и предоставление доступа к настройкам ---
|
21
|
+
_config = configparser.ConfigParser()
|
22
|
+
_config.read(USER_CONFIG_PATH, encoding="utf-8")
|
33
23
|
|
34
|
-
self.config_file = self.config_dir / "config.ini"
|
35
|
-
self.config = configparser.ConfigParser()
|
36
24
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
self._save()
|
43
|
-
else:
|
44
|
-
self.config["settings"] = DEFAULTS
|
45
|
-
self._save()
|
46
|
-
print(f"[INFO] Создан новый конфиг: {self.config_file}")
|
47
|
-
|
48
|
-
def _save(self):
|
49
|
-
with open(self.config_file, "w") as f:
|
50
|
-
self.config.write(f)
|
51
|
-
|
52
|
-
# Получение значений
|
53
|
-
def get(self, key):
|
54
|
-
return self.config["settings"].get(key, DEFAULTS.get(key))
|
55
|
-
|
56
|
-
# Изменение значений
|
57
|
-
def set(self, key, value):
|
58
|
-
self.config["settings"][key] = str(value)
|
59
|
-
self._save()
|
25
|
+
class Settings:
|
26
|
+
"""Простая обертка для доступа к настройкам из config.ini."""
|
27
|
+
def get(self, section: str, key: str, fallback: Optional[str] = None) -> Optional[str]: # <--- ИЗМЕНЕНО
|
28
|
+
"""Получить значение из [section] по ключу key."""
|
29
|
+
return _config.get(section, key, fallback=fallback)
|
60
30
|
|
31
|
+
def get_bool(self, section: str, key: str, fallback: bool = False) -> bool:
|
32
|
+
"""Получить булево значение."""
|
33
|
+
return _config.getboolean(section, key, fallback=fallback)
|
61
34
|
|
62
|
-
#
|
35
|
+
# --- Экземпляр для всего проекта ---
|
36
|
+
# В других файлах импортируйте его: from aiebash.settings import settings
|
63
37
|
settings = Settings()
|
64
38
|
|
65
39
|
|
ai_ebash-0.1.5.dist-info/RECORD
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
aiebash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
aiebash/__main__.py,sha256=x7e3HI9_0UUyhohmEvTi8Yf2mTQ9YgyicJ6xSPgOHcE,3725
|
3
|
-
aiebash/api_client.py,sha256=-hcgY3CP0lsV9Jct_-HQ6WpDosDChwalJjy4aFQ4CIM,2004
|
4
|
-
aiebash/block_runner.py,sha256=JNcdBklJmrlO7ZX7xXiO0fUGqrcNJiRqdwM-yr3OP3k,1599
|
5
|
-
aiebash/formatter_text.py,sha256=NRz_pCpakZ9k8477_4Gx1scDXrEbbOaOi-U7WRp6ZWo,847
|
6
|
-
aiebash/settings.py,sha256=Ml9_ixXl-DVpFYfCygMAhGSH-1luTxN22YYz1mHlsfw,2671
|
7
|
-
ai_ebash-0.1.5.dist-info/METADATA,sha256=ITMnYGPQdqAVbuVZATgQ9d-QCcGO7FSHUeiiG9kFW9s,2384
|
8
|
-
ai_ebash-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
9
|
-
ai_ebash-0.1.5.dist-info/entry_points.txt,sha256=bstvFHHX76KHqYpjM8FPoRVvNbQprST74CI30Mr5TIM,45
|
10
|
-
ai_ebash-0.1.5.dist-info/top_level.txt,sha256=JJ-bWdhWlFCfCQGLhmqzN4_bNO0pfVPRsdbx8o9A9AA,8
|
11
|
-
ai_ebash-0.1.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|