librecoding 1.0.0__tar.gz → 1.1.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: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Minimal prompt/response library: prompt in console, answer saved to N.txt
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 = "1.0.0"
7
+ version = "1.1.0"
8
8
  description = "Minimal prompt/response library: prompt in console, answer saved to N.txt"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,10 +1,4 @@
1
- """librecoding 1.0.0 - только запрос и ответ.
2
-
3
- Промпт -> ответ в консоль и в 1.txt, 2.txt, 3.txt рядом с запущенным скриптом.
4
- Никаких папок files/, files2/ и никаких других создаваемых файлов.
5
- """
6
-
7
- from __future__ import annotations
1
+ """librecoding 1.1.0 - тихий режим: ничего не печатает, ответ пишется в N.txt."""
8
2
 
9
3
  import json
10
4
  import os
@@ -14,25 +8,28 @@ import urllib.request
14
8
  from pathlib import Path
15
9
  from types import ModuleType
16
10
 
17
- __version__ = "1.0.0"
11
+ __version__ = "1.1.0"
18
12
 
19
13
  API_URL = "https://openrouter.ai/api/v1/chat/completions"
20
- DEFAULT_MODEL = "deepseek/deepseek-v4.1-flash"
14
+ MODEL = "deepseek/deepseek-v4.1-flash"
21
15
  KEY_FILE = Path.home() / ".config" / "librecoding" / "api_key"
22
- PROMPT = "Введи промпт: "
23
- EXIT_WORDS = {"exit", "quit", "q", "выход", "стоп"}
24
- SYSTEM_PROMPT = "Ты полезный ассистент. Отвечай по-русски, кратко и по делу."
16
+ PROMPT = ""
17
+ EXIT = {"exit", "quit", "q", "выход", "стоп"}
18
+ SYSTEM = "Ты полезный ассистент. Отвечай по-русски, кратко и по делу."
19
+
20
+ _state = {"key": None, "model": MODEL, "verbose": False}
21
+
25
22
 
26
- _state = {"key": None, "model": DEFAULT_MODEL}
23
+ def set_verbose(value=True):
24
+ """True -> снова печатать 'думаю...', ответ и 'сохранено:'."""
25
+ _state["verbose"] = bool(value)
27
26
 
28
27
 
29
28
  def set_api_key(value):
30
- """Задать ключ OpenRouter на время работы процесса."""
31
29
  _state["key"] = str(value).strip()
32
30
 
33
31
 
34
32
  def set_model(value):
35
- """Сменить модель, например deepseek/deepseek-chat."""
36
33
  _state["model"] = str(value)
37
34
 
38
35
 
@@ -40,8 +37,7 @@ def get_model():
40
37
  return _state["model"]
41
38
 
42
39
 
43
- def _api_key():
44
- """Ключ: set_api_key() -> OPENROUTER_API_KEY -> ~/.config/librecoding/api_key."""
40
+ def _key():
45
41
  if _state["key"]:
46
42
  return _state["key"]
47
43
  env = os.environ.get("OPENROUTER_API_KEY", "").strip()
@@ -52,11 +48,10 @@ def _api_key():
52
48
  line = line.strip()
53
49
  if line and not line.startswith("#"):
54
50
  return line
55
- raise RuntimeError("нет ключа OpenRouter, положи его в " + str(KEY_FILE))
51
+ raise RuntimeError("нет ключа OpenRouter: положи в " + str(KEY_FILE))
56
52
 
57
53
 
58
54
  def caller_dir():
59
- """Папка запущенного скрипта, а не библиотеки."""
60
55
  path = getattr(sys.modules.get("__main__"), "__file__", None)
61
56
  if path:
62
57
  return Path(path).resolve().parent
@@ -69,18 +64,16 @@ def caller_dir():
69
64
 
70
65
 
71
66
  def next_filename(directory=None):
72
- """Следующее имя 1.txt, 2.txt, 3.txt ..."""
73
67
  base = Path(directory) if directory else caller_dir()
74
68
  nums = [int(p.stem) for p in base.glob("*.txt") if p.stem.isdigit()]
75
69
  return base / (str(max(nums, default=0) + 1) + ".txt")
76
70
 
77
71
 
78
72
  def ask(prompt, model=None, temperature=0.7, timeout=300):
79
- """Отправить промпт и вернуть текст. Ничего не печатает и не сохраняет."""
80
73
  body = {
81
74
  "model": model or _state["model"],
82
75
  "messages": [
83
- {"role": "system", "content": SYSTEM_PROMPT},
76
+ {"role": "system", "content": SYSTEM},
84
77
  {"role": "user", "content": str(prompt)},
85
78
  ],
86
79
  "temperature": temperature,
@@ -89,7 +82,7 @@ def ask(prompt, model=None, temperature=0.7, timeout=300):
89
82
  API_URL,
90
83
  data=json.dumps(body).encode("utf-8"),
91
84
  headers={
92
- "Authorization": "Bearer " + _api_key(),
85
+ "Authorization": "Bearer " + _key(),
93
86
  "Content-Type": "application/json",
94
87
  "HTTP-Referer": "https://pypi.org/project/librecoding/",
95
88
  "X-Title": "librecoding",
@@ -111,7 +104,6 @@ def ask(prompt, model=None, temperature=0.7, timeout=300):
111
104
 
112
105
 
113
106
  def save(text, filename=None):
114
- """Сохранить текст в N.txt (или в filename) рядом со скриптом."""
115
107
  base = caller_dir()
116
108
  path = Path(filename) if filename else next_filename(base)
117
109
  if not path.is_absolute():
@@ -120,29 +112,27 @@ def save(text, filename=None):
120
112
  return path
121
113
 
122
114
 
123
- def chat(prompt, model=None, save_answer=True, filename=None):
124
- """Один промпт: напечатать ответ и сохранить его в N.txt."""
115
+ def chat(prompt, model=None, filename=None, save_answer=True):
125
116
  text = str(prompt).strip()
126
117
  if not text:
127
118
  return None
128
- print("думаю...")
119
+ verbose = bool(_state["verbose"])
120
+ if verbose:
121
+ print("думаю...")
129
122
  answer = (ask(text, model=model) or "").strip()
130
- print(answer or "(пустой ответ)")
123
+ if verbose:
124
+ print(answer or "(пустой ответ)")
131
125
  if save_answer and answer:
132
126
  path = save(answer, filename)
133
- print("сохранено: " + path.name)
127
+ if verbose:
128
+ print("сохранено: " + path.name)
134
129
  return path
135
130
  return None
136
131
 
137
132
 
138
- def interactive(prompt=None, model=None, save_answer=True, filename=None):
139
- """Без аргумента цикл промпт-ответ. С аргументом один ответ.
140
-
141
- Выход: пустой Enter, выход, exit, q, Ctrl+C.
142
- """
133
+ def interactive(prompt=None, model=None, filename=None, save_answer=True):
143
134
  if prompt is not None:
144
- return chat(prompt, model=model, save_answer=save_answer, filename=filename)
145
-
135
+ return chat(prompt, model=model, filename=filename, save_answer=save_answer)
146
136
  last = None
147
137
  while True:
148
138
  try:
@@ -151,20 +141,16 @@ def interactive(prompt=None, model=None, save_answer=True, filename=None):
151
141
  print()
152
142
  break
153
143
  line = line.strip()
154
- if not line or line.lower() in EXIT_WORDS:
144
+ if not line or line.lower() in EXIT:
155
145
  break
156
- print()
157
146
  try:
158
- last = chat(line, model=model, save_answer=save_answer, filename=filename)
147
+ last = chat(line, model=model, filename=filename, save_answer=save_answer)
159
148
  except Exception as exc:
160
149
  print("Ошибка: " + str(exc), file=sys.stderr)
161
- print()
162
150
  return last
163
151
 
164
152
 
165
- class _LibreCodingModule(ModuleType):
166
- """Модуль, который можно вызвать как функцию: librecoding()."""
167
-
153
+ class _Module(ModuleType):
168
154
  def __call__(self, obj=None, *args, **kwargs):
169
155
  if callable(obj) and not args and not kwargs:
170
156
  return obj
@@ -179,9 +165,9 @@ class _LibreCodingModule(ModuleType):
179
165
  return None
180
166
 
181
167
 
182
- sys.modules[__name__].__class__ = _LibreCodingModule
168
+ sys.modules[__name__].__class__ = _Module
183
169
 
184
170
  __all__ = [
185
171
  "ask", "chat", "save", "interactive", "caller_dir", "next_filename",
186
- "set_api_key", "set_model", "get_model",
172
+ "set_api_key", "set_model", "get_model", "set_verbose",
187
173
  ]
File without changes