librecoding 0.4.0__tar.gz → 0.5.0__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.
- {librecoding-0.4.0 → librecoding-0.5.0}/PKG-INFO +1 -1
- {librecoding-0.4.0 → librecoding-0.5.0}/pyproject.toml +1 -1
- {librecoding-0.4.0 → librecoding-0.5.0}/src/librecoding/__init__.py +35 -25
- {librecoding-0.4.0 → librecoding-0.5.0}/README.md +0 -0
- {librecoding-0.4.0 → librecoding-0.5.0}/src/librecoding/core.py +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""librecoding - вызываемый модуль: спрашивает промпт, шлёт в OpenRouter,
|
|
1
|
+
"""librecoding - вызываемый модуль: спрашивает промпт, шлёт в OpenRouter, сохраняет ответ в N.txt."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -7,13 +7,12 @@ import os
|
|
|
7
7
|
import sys
|
|
8
8
|
import urllib.error
|
|
9
9
|
import urllib.request
|
|
10
|
-
from datetime import datetime
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
from types import ModuleType
|
|
13
12
|
|
|
14
13
|
from .core import greet
|
|
15
14
|
|
|
16
|
-
__version__ = "0.
|
|
15
|
+
__version__ = "0.5.0"
|
|
17
16
|
|
|
18
17
|
API_URL = "https://openrouter.ai/api/v1/chat/completions"
|
|
19
18
|
DEFAULT_MODEL = "deepseek/deepseek-v4.1-flash"
|
|
@@ -24,6 +23,8 @@ PAUSE_TEXT = "Нажми Enter, чтобы закрыть..."
|
|
|
24
23
|
_state: dict = {"key": None, "model": DEFAULT_MODEL}
|
|
25
24
|
|
|
26
25
|
|
|
26
|
+
# --- настройки ----------------------------------------------------------
|
|
27
|
+
|
|
27
28
|
def set_api_key(key: str) -> None:
|
|
28
29
|
"""Задать ключ OpenRouter на время работы процесса."""
|
|
29
30
|
_state["key"] = key.strip()
|
|
@@ -62,6 +63,8 @@ def _api_key() -> str:
|
|
|
62
63
|
)
|
|
63
64
|
|
|
64
65
|
|
|
66
|
+
# --- сеть ---------------------------------------------------------------
|
|
67
|
+
|
|
65
68
|
def ask(prompt, model=None, temperature=0.7, timeout=180) -> str:
|
|
66
69
|
"""Отправить промпт в OpenRouter, вернуть текст ответа."""
|
|
67
70
|
payload = {
|
|
@@ -96,6 +99,8 @@ def ask(prompt, model=None, temperature=0.7, timeout=180) -> str:
|
|
|
96
99
|
raise RuntimeError(f"неожиданный ответ: {str(data)[:400]}") from None
|
|
97
100
|
|
|
98
101
|
|
|
102
|
+
# --- ввод/вывод ---------------------------------------------------------
|
|
103
|
+
|
|
99
104
|
def pause(prompt: str = PAUSE_TEXT) -> None:
|
|
100
105
|
"""Ждёт Enter, чтобы терминал не закрылся. Молчит без tty."""
|
|
101
106
|
if not sys.stdin.isatty():
|
|
@@ -116,8 +121,15 @@ def _caller_dir() -> Path:
|
|
|
116
121
|
return Path.cwd()
|
|
117
122
|
|
|
118
123
|
|
|
119
|
-
def
|
|
120
|
-
"""
|
|
124
|
+
def next_filename(directory=None) -> Path:
|
|
125
|
+
"""Следующий номер: 1.txt, 2.txt, 3.txt ... (максимальный + 1)."""
|
|
126
|
+
base = Path(directory) if directory else _caller_dir()
|
|
127
|
+
nums = [int(p.stem) for p in base.glob("*.txt") if p.stem.isdigit()]
|
|
128
|
+
return base / f"{max(nums, default=0) + 1}.txt"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def interactive(prompt=None, filename=None, model=None, directory=None):
|
|
132
|
+
"""Спросить промпт -> OpenRouter -> сохранить чистый ответ в N.txt рядом со скриптом."""
|
|
121
133
|
if not sys.stdin.isatty():
|
|
122
134
|
raise RuntimeError("нужен интерактивный терминал: запусти файл в консоли")
|
|
123
135
|
|
|
@@ -132,33 +144,31 @@ def interactive(prompt=None, filename=None, model=None):
|
|
|
132
144
|
print("Пустой промпт — выход.")
|
|
133
145
|
return None
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
answer
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
f"Промпт:\n{prompt}\n\n"
|
|
149
|
-
f"Ответ:\n{answer.strip()}\n",
|
|
150
|
-
encoding="utf-8",
|
|
151
|
-
)
|
|
147
|
+
print("думаю...")
|
|
148
|
+
answer = ask(prompt, model=model or _state["model"]).strip()
|
|
149
|
+
print(answer)
|
|
150
|
+
|
|
151
|
+
if filename:
|
|
152
|
+
path = Path(filename)
|
|
153
|
+
if not path.is_absolute():
|
|
154
|
+
path = (Path(directory) if directory else _caller_dir()) / path
|
|
155
|
+
else:
|
|
156
|
+
path = next_filename(directory)
|
|
157
|
+
|
|
158
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
path.write_text(answer + "\n", encoding="utf-8")
|
|
152
160
|
print(f"\nСохранено: {path}")
|
|
153
161
|
return path
|
|
154
162
|
|
|
155
163
|
|
|
164
|
+
# --- магия: модуль, который можно вызвать -------------------------------
|
|
165
|
+
|
|
156
166
|
class _LibreCodingModule(ModuleType):
|
|
157
167
|
def __call__(self, obj=None, *args, **kwargs):
|
|
158
168
|
if callable(obj) and not args and not kwargs:
|
|
159
|
-
return obj
|
|
169
|
+
return obj # @librecoding -> режим декоратора
|
|
160
170
|
try:
|
|
161
|
-
if isinstance(obj, str):
|
|
171
|
+
if isinstance(obj, str): # librecoding("готовый промпт")
|
|
162
172
|
kwargs["prompt"] = obj
|
|
163
173
|
interactive(*args, **kwargs)
|
|
164
174
|
except Exception as exc:
|
|
@@ -172,6 +182,6 @@ class _LibreCodingModule(ModuleType):
|
|
|
172
182
|
sys.modules[__name__].__class__ = _LibreCodingModule
|
|
173
183
|
|
|
174
184
|
__all__ = [
|
|
175
|
-
"greet", "ask", "interactive", "pause",
|
|
185
|
+
"greet", "ask", "interactive", "pause", "next_filename",
|
|
176
186
|
"set_api_key", "set_model", "get_model",
|
|
177
187
|
]
|
|
File without changes
|
|
File without changes
|