ai-ebash 0.1.5a0__tar.gz → 0.1.7__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.
- {ai_ebash-0.1.5a0/src/ai_ebash.egg-info → ai_ebash-0.1.7}/PKG-INFO +1 -1
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/setup.cfg +1 -1
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7/src/ai_ebash.egg-info}/PKG-INFO +1 -1
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/src/ai_ebash.egg-info/SOURCES.txt +3 -0
- ai_ebash-0.1.7/src/aiebash/__main__.py +119 -0
- ai_ebash-0.1.7/src/aiebash/llm_factory.py +15 -0
- ai_ebash-0.1.7/src/aiebash/llm_interface.py +6 -0
- ai_ebash-0.1.7/src/aiebash/openai_client.py +32 -0
- ai_ebash-0.1.7/src/aiebash/settings.py +39 -0
- ai_ebash-0.1.5a0/src/aiebash/__main__.py +0 -100
- ai_ebash-0.1.5a0/src/aiebash/settings.py +0 -65
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/MANIFEST.in +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/README.md +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/pyproject.toml +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/src/ai_ebash.egg-info/dependency_links.txt +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/src/ai_ebash.egg-info/entry_points.txt +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/src/ai_ebash.egg-info/requires.txt +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/src/ai_ebash.egg-info/top_level.txt +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/src/aiebash/__init__.py +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/src/aiebash/api_client.py +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/src/aiebash/block_runner.py +0 -0
- {ai_ebash-0.1.5a0 → ai_ebash-0.1.7}/src/aiebash/formatter_text.py +0 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
import sys
|
3
|
+
import threading
|
4
|
+
import time
|
5
|
+
from pathlib import Path
|
6
|
+
from rich.console import Console
|
7
|
+
from rich.markdown import Markdown
|
8
|
+
from rich.rule import Rule
|
9
|
+
|
10
|
+
# sys.path, как раньше
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
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
|
17
|
+
|
18
|
+
# Получаем настройки для конкретного бэкенда, например 'openai_over_proxy'
|
19
|
+
BACKEND = "openai_over_proxy"
|
20
|
+
|
21
|
+
MODEL = settings.get(BACKEND, "MODEL")
|
22
|
+
API_URL = settings.get(BACKEND, "API_URL")
|
23
|
+
API_KEY = settings.get(BACKEND, "API_KEY")
|
24
|
+
|
25
|
+
# Глобальные настройки
|
26
|
+
DEBUG = settings.get_bool("global", "DEBUG")
|
27
|
+
CONTEXT = settings.get("global", "CONTEXT")
|
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()
|
39
|
+
def run_progress():
|
40
|
+
console = Console()
|
41
|
+
with console.status("[bold green]Ai печатает...[/bold green]", spinner="dots"):
|
42
|
+
while not stop_event.is_set():
|
43
|
+
time.sleep(0.1)
|
44
|
+
|
45
|
+
|
46
|
+
def main():
|
47
|
+
if len(sys.argv) < 2:
|
48
|
+
print("Использование: ai [-run] [-chat] ваш запрос к ИИ без кавычек")
|
49
|
+
sys.exit(0)
|
50
|
+
|
51
|
+
console = Console()
|
52
|
+
|
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")]
|
57
|
+
|
58
|
+
prompt = " ".join(args)
|
59
|
+
|
60
|
+
try:
|
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
|
+
else:
|
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"))
|
113
|
+
|
114
|
+
except Exception as e:
|
115
|
+
print("Ошибка:", e)
|
116
|
+
|
117
|
+
|
118
|
+
if __name__ == "__main__":
|
119
|
+
main()
|
@@ -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}")
|
@@ -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)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import configparser
|
2
|
+
import shutil
|
3
|
+
from pathlib import Path
|
4
|
+
from platformdirs import user_config_dir
|
5
|
+
from typing import Optional # <--- ДОБАВЛЕНО
|
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"
|
13
|
+
|
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)
|
19
|
+
|
20
|
+
# --- Чтение и предоставление доступа к настройкам ---
|
21
|
+
_config = configparser.ConfigParser()
|
22
|
+
_config.read(USER_CONFIG_PATH, encoding="utf-8")
|
23
|
+
|
24
|
+
|
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)
|
30
|
+
|
31
|
+
def get_bool(self, section: str, key: str, fallback: bool = False) -> bool:
|
32
|
+
"""Получить булево значение."""
|
33
|
+
return _config.getboolean(section, key, fallback=fallback)
|
34
|
+
|
35
|
+
# --- Экземпляр для всего проекта ---
|
36
|
+
# В других файлах импортируйте его: from aiebash.settings import settings
|
37
|
+
settings = Settings()
|
38
|
+
|
39
|
+
|
@@ -1,100 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
import sys
|
3
|
-
from pathlib import Path
|
4
|
-
|
5
|
-
# Убедимся, что parent (src) в sys.path, чтобы можно было import aibash.*
|
6
|
-
# Это нужно, если вы запускаете файл напрямую: python src/aibash/__main__.py
|
7
|
-
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
8
|
-
|
9
|
-
|
10
|
-
from rich.markdown import Markdown
|
11
|
-
from rich.console import Console
|
12
|
-
from rich.rule import Rule
|
13
|
-
import threading
|
14
|
-
import time
|
15
|
-
|
16
|
-
|
17
|
-
from aiebash.api_client import send_prompt
|
18
|
-
from aiebash.formatter_text import annotate_bash_blocks
|
19
|
-
from aiebash.block_runner import run_code_selection # добавлен импорт
|
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()
|
37
|
-
|
38
|
-
def run_progress():
|
39
|
-
console = Console()
|
40
|
-
with console.status("[bold green]Ai печатает...[/bold green]", spinner="dots") as status:
|
41
|
-
while not stop_event.is_set():
|
42
|
-
time.sleep(0.1)
|
43
|
-
|
44
|
-
def main():
|
45
|
-
# Если нет аргументов, выводим подсказку по использованию
|
46
|
-
if len(sys.argv) < 2:
|
47
|
-
print("Использование: ai [-run] ваш запрос к ИИ без кавычек")
|
48
|
-
sys.exit(0)
|
49
|
-
|
50
|
-
console = Console()
|
51
|
-
|
52
|
-
# Проверяем ключ -run
|
53
|
-
run_mode = False
|
54
|
-
args = sys.argv[1:]
|
55
|
-
if "-run" in args:
|
56
|
-
run_mode = True
|
57
|
-
args.remove("-run")
|
58
|
-
|
59
|
-
# Собираем текст запроса из оставшихся аргументов
|
60
|
-
prompt = " ".join(args)
|
61
|
-
|
62
|
-
try:
|
63
|
-
# Запуск прогресс-бара в отдельном потоке
|
64
|
-
progress_thread = threading.Thread(target=run_progress)
|
65
|
-
progress_thread.start()
|
66
|
-
|
67
|
-
# Получаем ответ от API через новый интерфейс
|
68
|
-
answer = send_prompt(prompt, MODEL, API_URL, API_KEY, CONTEXT)
|
69
|
-
|
70
|
-
# Сигнализируем потоку прогресса остановиться
|
71
|
-
stop_event.set()
|
72
|
-
progress_thread.join() # Ждём завершения потока
|
73
|
-
|
74
|
-
# В режиме DEBUG выводим исходную (неформатированную) версию ответа
|
75
|
-
if not DEBUG:
|
76
|
-
print("=== RAW RESPONSE (from send_prompt) ===")
|
77
|
-
print(answer)
|
78
|
-
print("=== /RAW RESPONSE === \n")
|
79
|
-
|
80
|
-
# Размечаем bash-блоки и получаем список кодов
|
81
|
-
annotated_answer, code_blocks = annotate_bash_blocks(answer)
|
82
|
-
|
83
|
-
|
84
|
-
# Если включён режим выполнения и есть блоки кода — предлагаем выбрать
|
85
|
-
if run_mode and code_blocks:
|
86
|
-
console.print(Markdown(annotated_answer))
|
87
|
-
run_code_selection(console, code_blocks)
|
88
|
-
else:
|
89
|
-
console.print(Markdown(answer))
|
90
|
-
|
91
|
-
console.print(Rule("", style="green"))
|
92
|
-
|
93
|
-
except Exception as e:
|
94
|
-
# Прочие ошибки (сеть, JSON, и т.д.)
|
95
|
-
print("Ошибка:", e)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
if __name__ == "__main__":
|
100
|
-
main()
|
@@ -1,65 +0,0 @@
|
|
1
|
-
|
2
|
-
from pathlib import Path
|
3
|
-
import configparser
|
4
|
-
from platformdirs import user_config_dir
|
5
|
-
|
6
|
-
APP_NAME = "ai-bash"
|
7
|
-
|
8
|
-
# Дефолтные значения хранятся в словаре
|
9
|
-
DEFAULTS = {
|
10
|
-
"API_URL": "https://openai-proxy.andrey-bch-1976.workers.dev/v1/chat/completions", # Прокси для OpenAI API
|
11
|
-
"API_KEY": "", # Ключ API (если нужен)
|
12
|
-
"MODEL": "gpt-4o-mini", # Модель по умолчанию
|
13
|
-
"CONTEXT": ( # Контекст по умолчанию
|
14
|
-
"Ты профессиональный системный администратор, помощник по Linux. "
|
15
|
-
"Пользователь - новичок в Linux, помоги ему. "
|
16
|
-
"Система Ubuntu, оболочка $SHELL. "
|
17
|
-
"При ответе учитывай, что пользователь работает в терминале Bash."
|
18
|
-
"Отвечай всегда на русском языке. "
|
19
|
-
"Ты разговариваешь с пользователем в терминале. "
|
20
|
-
),
|
21
|
-
"DEBUG": False # Включить отладочную информацию
|
22
|
-
}
|
23
|
-
|
24
|
-
# Где хранится INI-файл
|
25
|
-
# Ubuntu → ~/.config/ai-bash/config.ini
|
26
|
-
# Windows → C:\Users\<user>\AppData\Local\ai-bash\ai-bash\config.ini
|
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)
|
33
|
-
|
34
|
-
self.config_file = self.config_dir / "config.ini"
|
35
|
-
self.config = configparser.ConfigParser()
|
36
|
-
|
37
|
-
# Если файл есть — читаем, иначе создаём с дефолтами
|
38
|
-
if self.config_file.exists():
|
39
|
-
self.config.read(self.config_file)
|
40
|
-
if "settings" not in self.config:
|
41
|
-
self.config["settings"] = DEFAULTS
|
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()
|
60
|
-
|
61
|
-
|
62
|
-
# Глобальный объект настроек
|
63
|
-
settings = Settings()
|
64
|
-
|
65
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|