ai-ebash 0.1.5a0__py3-none-any.whl → 0.1.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-ebash
3
- Version: 0.1.5a0
3
+ Version: 0.1.7
4
4
  Summary: Console utility for integrating artificial intelligence into a Linux terminal.
5
5
  Author: Andrey Bochkarev
6
6
  Author-email: andrey.bch.1976@gmail.com
@@ -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.7.dist-info/METADATA,sha256=c8etL64A7X9Hlpw-xnNn072dSswHBN5-2Iid7o1-PF8,2384
11
+ ai_ebash-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ ai_ebash-0.1.7.dist-info/entry_points.txt,sha256=bstvFHHX76KHqYpjM8FPoRVvNbQprST74CI30Mr5TIM,45
13
+ ai_ebash-0.1.7.dist-info/top_level.txt,sha256=JJ-bWdhWlFCfCQGLhmqzN4_bNO0pfVPRsdbx8o9A9AA,8
14
+ ai_ebash-0.1.7.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
- # Убедимся, что parent (src) в sys.path, чтобы можно было import aibash.*
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
- from rich.markdown import Markdown
11
- from rich.console import Console
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
- 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()
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") as status:
41
- while not stop_event.is_set():
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
- # Проверяем ключ -run
53
- run_mode = False
54
- args = sys.argv[1:]
55
- if "-run" in args:
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
- 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)
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
- console.print(Markdown(answer))
90
-
91
- console.print(Rule("", style="green"))
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}")
@@ -0,0 +1,6 @@
1
+ # llm_interface.py
2
+ from typing import List, Protocol
3
+
4
+ class LLMClient(Protocol):
5
+ def send_chat(self, messages: List[dict]) -> str: ...
6
+ def send_prompt(self, prompt: str, system_context: str = "") -> str: ...
@@ -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
- APP_NAME = "ai-bash"
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
- 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
- }
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
- # Где хранится 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)
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
- 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()
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
 
@@ -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.5a0.dist-info/METADATA,sha256=fyxLpQiImea0Cn-4r2Q1v1e80wRrYRxz9aImHz7T7x8,2386
8
- ai_ebash-0.1.5a0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- ai_ebash-0.1.5a0.dist-info/entry_points.txt,sha256=bstvFHHX76KHqYpjM8FPoRVvNbQprST74CI30Mr5TIM,45
10
- ai_ebash-0.1.5a0.dist-info/top_level.txt,sha256=JJ-bWdhWlFCfCQGLhmqzN4_bNO0pfVPRsdbx8o9A9AA,8
11
- ai_ebash-0.1.5a0.dist-info/RECORD,,