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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: librecoding
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Free and open-source coding library
5
5
  Author: ar
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "librecoding"
7
- version = "0.4.0"
7
+ version = "0.5.0"
8
8
  description = "Free and open-source coding library"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,4 +1,4 @@
1
- """librecoding - вызываемый модуль: спрашивает промпт, шлёт в OpenRouter, пишет .txt."""
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.4.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 interactive(prompt=None, filename=None, model=None):
120
- """Спросить промпт -> запросить OpenRouter -> сохранить .txt рядом со скриптом."""
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
- mdl = model or _state["model"]
136
- print(f"[{mdl}] думаю...")
137
- answer = ask(prompt, model=mdl)
138
-
139
- print("\n--- ответ ---")
140
- print(answer.strip())
141
- print("--- конец ---")
142
-
143
- name = filename or f"response_{datetime.now():%Y-%m-%d_%H-%M-%S}.txt"
144
- path = _caller_dir() / name
145
- path.write_text(
146
- f"Модель: {mdl}\n"
147
- f"Дата: {datetime.now():%Y-%m-%d %H:%M:%S}\n\n"
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