magic-answer 0.1.0__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.
@@ -0,0 +1,13 @@
1
+ __all__ = [
2
+ "ask",
3
+ "ask_clipboard",
4
+ "open_key_manager"
5
+ ]
6
+
7
+ from magic_answer.api import ask_clipboard
8
+ from magic_answer.api import ask
9
+
10
+ from magic_answer.supabase_manager import open_key_manager
11
+
12
+ import config
13
+ import models
magic_answer/_ask.py ADDED
@@ -0,0 +1,84 @@
1
+ import openai
2
+ from openai import OpenAI
3
+ import requests
4
+ import pyperclip
5
+
6
+ from magic_answer.config import SUPABASE_URL, SUPABASE_KEY, CUSTOM_INSTRUCTION
7
+ from magic_answer.models import models
8
+
9
+
10
+ def v(k):
11
+ url = f"{SUPABASE_URL}/rest/v1/kv"
12
+
13
+ params = {
14
+ "key": f"eq.{k}"
15
+ }
16
+
17
+ headers = {
18
+ "apikey": SUPABASE_KEY,
19
+ "Authorization": f"Bearer {SUPABASE_KEY}"
20
+ }
21
+
22
+ res = requests.get(url, params=params, headers=headers)
23
+ data = res.json()
24
+
25
+ if len(data) == 0:
26
+ return None
27
+
28
+ return data[0]["value"]
29
+
30
+
31
+ def clip_input(_):
32
+ return pyperclip.paste()
33
+
34
+
35
+ def clip_print(text):
36
+ pyperclip.copy(text)
37
+
38
+
39
+ def _ask(key, use_clipboard=False):
40
+ read = clip_input if use_clipboard else input
41
+ write = clip_print if use_clipboard else print
42
+ try:
43
+ client = OpenAI(
44
+ api_key=v(key),
45
+ base_url="https://openrouter.ai/api/v1"
46
+ )
47
+ except openai.OpenAIError:
48
+ print(
49
+ "ОШИБКА: API-ключ по вашему ключу не найден. Проверьте правильность введённого ключа при вызове функции `answer`.")
50
+ quit()
51
+
52
+ content = read("Enter request: ")
53
+ content += CUSTOM_INSTRUCTION
54
+
55
+ # noinspection PyTypeChecker
56
+ def response(model):
57
+ try:
58
+ r = client.chat.completions.create(
59
+ model=model,
60
+ messages=[
61
+ {"role": "user", "content": content}
62
+ ]
63
+ )
64
+
65
+ # check if response is valid
66
+ if not r or not hasattr(r, "choices") or len(r.choices) == 0:
67
+ return None
68
+
69
+ return r
70
+
71
+ except Exception as e:
72
+ print(f"[ERROR] model {model} failed:", e)
73
+ return None
74
+
75
+ result = None
76
+
77
+ for m in models:
78
+ res = response(m)
79
+ if res is not None:
80
+ # noinspection PyTypeChecker
81
+ result = res.choices[0].message.content
82
+ break
83
+
84
+ write(result if result else "All models failed")
magic_answer/api.py ADDED
@@ -0,0 +1,9 @@
1
+ from magic_answer._ask import _ask
2
+
3
+
4
+ def ask(key):
5
+ _ask(key)
6
+
7
+
8
+ def ask_clipboard(key):
9
+ _ask(key, True)
@@ -0,0 +1,179 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>KV Store</title>
6
+
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ font-family: Arial, sans-serif;
11
+ background: #0f172a;
12
+ color: #e2e8f0;
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: center;
16
+ height: 100vh;
17
+ }
18
+
19
+ .card {
20
+ background: #1e293b;
21
+ padding: 25px;
22
+ border-radius: 12px;
23
+ width: 320px;
24
+ box-shadow: 0 10px 30px rgba(0,0,0,0.4);
25
+ }
26
+
27
+ h2 {
28
+ margin-top: 0;
29
+ text-align: center;
30
+ }
31
+
32
+ input {
33
+ width: calc(100% - 20px);
34
+ padding: 10px;
35
+ margin: 8px 0;
36
+ border: none;
37
+ border-radius: 6px;
38
+ background: #334155;
39
+ color: white;
40
+ outline: none;
41
+ }
42
+
43
+ button {
44
+ width: 100%;
45
+ padding: 10px;
46
+ margin-top: 10px;
47
+ border: none;
48
+ border-radius: 6px;
49
+ background: #3b82f6;
50
+ color: white;
51
+ font-weight: bold;
52
+ cursor: pointer;
53
+ }
54
+
55
+ button:hover {
56
+ background: #2563eb;
57
+ }
58
+
59
+ .msg {
60
+ margin-top: 12px;
61
+ text-align: center;
62
+ font-size: 14px;
63
+ min-height: 20px;
64
+ }
65
+
66
+ .ok { color: #22c55e; }
67
+ .err { color: #ef4444; }
68
+ </style>
69
+ </head>
70
+
71
+ <body>
72
+ <div class="card">
73
+ <h2>KV Store</h2>
74
+
75
+ <!-- WRITE -->
76
+ <label for="key"></label><input id="key" placeholder="key (e.g. Lastname Name)">
77
+ <label for="value"></label><input id="value" placeholder="value (e.g. API_KEY)">
78
+ <button id="btnSave">Save</button>
79
+
80
+ <hr style="margin: 15px 0; border-color:#334155">
81
+
82
+ <!-- READ -->
83
+ <label for="lookupKey"></label><input id="lookupKey" placeholder="check key exists...">
84
+ <button id="btnCheck">Check</button>
85
+
86
+ <div id="msg" class="msg"></div>
87
+ </div>
88
+
89
+ <script>
90
+ const SUPABASE_URL = "https://gxralozqwlifabqykgle.supabase.co";
91
+ const SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imd4cmFsb3pxd2xpZmFicXlrZ2xlIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODI3MzY1OTQsImV4cCI6MjA5ODMxMjU5NH0.cxrJxRoDiRCjCLrFmzIUy9lL4fPO-XZ0JqjeHHoJC8c";
92
+
93
+ const msg = document.getElementById("msg");
94
+
95
+ function setMsg(text, ok=true) {
96
+ msg.textContent = text;
97
+ msg.className = "msg " + (ok ? "ok" : "err");
98
+ }
99
+
100
+ // =========================
101
+ // SAVE (INSERT / UPDATE)
102
+ // =========================
103
+ async function send() {
104
+ const key = document.getElementById("key").value.trim();
105
+ const value = document.getElementById("value").value.trim();
106
+
107
+ if (!key || !value) {
108
+ setMsg("Please fill both fields", false);
109
+ return;
110
+ }
111
+
112
+ setMsg("Saving...");
113
+
114
+ try {
115
+ const res = await fetch(`${SUPABASE_URL}/rest/v1/kv?on_conflict=key`, {
116
+ method: "POST",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ "apikey": SUPABASE_KEY,
120
+ "Authorization": "Bearer " + SUPABASE_KEY,
121
+ "Prefer": "resolution=merge-duplicates"
122
+ },
123
+ body: JSON.stringify({ key, value })
124
+ });
125
+
126
+ if (res.ok) {
127
+ setMsg("Saved successfully ✔");
128
+ } else {
129
+ setMsg("Error: " + await res.text(), false);
130
+ }
131
+
132
+ } catch (e) {
133
+ setMsg("Network error", false);
134
+ }
135
+ }
136
+
137
+ // =========================
138
+ // LOOKUP (GET VALUE)
139
+ // =========================
140
+ async function check() {
141
+ const key = document.getElementById("lookupKey").value.trim();
142
+
143
+ if (!key) {
144
+ setMsg("Enter a key to check", false);
145
+ return;
146
+ }
147
+
148
+ setMsg("Checking...");
149
+
150
+ try {
151
+ const res = await fetch(
152
+ `${SUPABASE_URL}/rest/v1/kv?key=eq.${encodeURIComponent(key)}`,
153
+ {
154
+ method: "GET",
155
+ headers: {
156
+ "apikey": SUPABASE_KEY,
157
+ "Authorization": "Bearer " + SUPABASE_KEY
158
+ }
159
+ }
160
+ );
161
+
162
+ const data = await res.json();
163
+
164
+ if (data.length > 0) {
165
+ setMsg(`Found ✔ Value: ${data[0].value}`);
166
+ } else {
167
+ setMsg("Not found ❌", false);
168
+ }
169
+
170
+ } catch (e) {
171
+ setMsg("Network error", false);
172
+ }
173
+ }
174
+
175
+ document.getElementById("btnSave").addEventListener("click", send);
176
+ document.getElementById("btnCheck").addEventListener("click", check);
177
+ </script>
178
+ </body>
179
+ </html>
magic_answer/config.py ADDED
@@ -0,0 +1,3 @@
1
+ SUPABASE_URL = "https://gxralozqwlifabqykgle.supabase.co"
2
+ SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imd4cmFsb3pxd2xpZmFicXlrZ2xlIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODI3MzY1OTQsImV4cCI6MjA5ODMxMjU5NH0.cxrJxRoDiRCjCLrFmzIUy9lL4fPO-XZ0JqjeHHoJC8c"
3
+ CUSTOM_INSTRUCTION = ". Отвечай по делу, не используй MD форматирование. Отвечай только на заданные вопросы или выполняй заданные инструкции, если это практическое задание, не преподноси лишнюю информацию. Формулируй ответ, как будто это ответ на экзаменационный билет"
magic_answer/models.py ADDED
@@ -0,0 +1,5 @@
1
+ models = [
2
+ "nvidia/nemotron-3-nano-30b-a3b:free",
3
+ "qwen/qwen3-235b-a22b:free",
4
+ "deepseek/deepseek-r1:free"
5
+ ]
@@ -0,0 +1,8 @@
1
+ import webbrowser
2
+ from importlib.resources import files
3
+
4
+
5
+ def open_key_manager():
6
+ html = files("magic_answer.assets").joinpath("supabase.html")
7
+ # noinspection PyUnresolvedReferences
8
+ webbrowser.open(html.as_uri())
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: magic-answer
3
+ Version: 0.1.0
4
+ Summary: A lightweight OpenRouter client with Supabase-based remote API key storage.
5
+ Author: Melkii_Mel
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: openai
11
+ Requires-Dist: requests
12
+ Requires-Dist: pyperclip
13
+ Dynamic: license-file
14
+
15
+ # Magic Answer
16
+
17
+ A lightweight Python library for querying OpenRouter LLMs using API keys stored in a Supabase key-value database.
18
+
19
+ Instead of embedding an OpenRouter API key into your application, **Magic Answer** retrieves it from a Supabase key-value store at runtime using a lookup key. This allows API keys to be updated or rotated without modifying or redistributing client applications.
20
+
21
+ ## Features
22
+
23
+ * Query any model available through OpenRouter
24
+ * Automatic fallback between multiple models
25
+ * Retrieve OpenRouter API keys from a Supabase key-value store
26
+ * Exceptionally simple Python API
27
+ * Optional clipboard integration
28
+ * Built-in key manager for maintaining the Supabase database
29
+ * Easily customizable through configuration
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install magic-answer
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ Query using console input:
40
+
41
+ ```python
42
+ import magic_answer
43
+
44
+ magic_answer.ask("your_lookup_key")
45
+ ```
46
+
47
+ Or use the clipboard:
48
+
49
+ ```python
50
+ import magic_answer
51
+
52
+ magic_answer.ask_clipboard("your_lookup_key")
53
+ ```
54
+
55
+ ## Managing API Keys
56
+
57
+ Magic Answer includes a simple graphical key manager for maintaining your Supabase key-value store.
58
+
59
+ ```python
60
+ import magic_answer
61
+
62
+ magic_answer.open_key_manager()
63
+ ```
64
+
65
+ The key manager allows you to:
66
+
67
+ * Add or update API keys
68
+ * Check whether a lookup key exists
69
+ * View the stored value associated with a lookup key
70
+
71
+ ## Configuration
72
+
73
+ Override the default Supabase configuration:
74
+
75
+ ```python
76
+ from magic_answer import config
77
+
78
+ config.SUPABASE_URL = "https://your-project.supabase.co"
79
+ config.SUPABASE_KEY = "your_supabase_anon_key"
80
+ config.CUSTOM_INSTRUCTION = "Your custom instruction"
81
+ ```
82
+
83
+ Configure fallback models:
84
+
85
+ ```python
86
+ from magic_answer import models
87
+
88
+ models.models = [
89
+ "openai/gpt-oss-20b:free",
90
+ "qwen/qwen3-235b-a22b:free",
91
+ "deepseek/deepseek-r1:free",
92
+ ]
93
+ ```
94
+
95
+ The library will attempt each model in order until one successfully returns a response.
96
+
97
+ ## API
98
+
99
+ ### `ask(lookup_key)`
100
+
101
+ Prompts the user for a question, retrieves the corresponding OpenRouter API key from Supabase using `lookup_key`, queries OpenRouter, and prints the model's response.
102
+
103
+ ### `ask_clipboard(lookup_key)`
104
+
105
+ Reads the prompt from the clipboard, retrieves the corresponding OpenRouter API key from Supabase, queries OpenRouter, and copies the model's response back to the clipboard.
106
+
107
+ ### `open_key_manager()`
108
+
109
+ Opens the built-in HTML key manager in the default web browser, allowing you to add, update, and inspect entries in your Supabase key-value store.
110
+
111
+ ## Requirements
112
+
113
+ * Python 3.10+
114
+ * Internet connection
115
+ * A Supabase project containing the key-value table
116
+ * An OpenRouter API key stored in that table
117
+
118
+ ## License
119
+
120
+ MIT
@@ -0,0 +1,12 @@
1
+ magic_answer/__init__.py,sha256=qRqPiVMd-CRmtLloSrf1tkieKqLzO1YNmgNdZf6FqYk,251
2
+ magic_answer/_ask.py,sha256=sx4mX7izvKRPE82Vf3jaBDRxcJo9auSO3_CWeAHXX10,2147
3
+ magic_answer/api.py,sha256=6WYhbAQX7Ud4FsTyS3Zs40ZHqtW9DkdlctSDySW8gaQ,123
4
+ magic_answer/config.py,sha256=fGiLAwJUQrKw7Kh7Mu9qpABurUKcyGXtPJoJ-XaBmLs,762
5
+ magic_answer/models.py,sha256=F3tsOzeQl8Hlvl5xJ5XNlSxPc6cCFkGVq7e9dyJfgLQ,129
6
+ magic_answer/supabase_manager.py,sha256=XfrCkVjBV79H31GgkdgoDxhMuduyaEyE-z88UAroRQA,236
7
+ magic_answer/assets/supabase.html,sha256=8vrfGLg-DbRknKvegcgtjlLrJIwS-J_7Krs7EbpUK8w,4511
8
+ magic_answer-0.1.0.dist-info/licenses/LICENSE,sha256=dffn-bw-FwKJ4O4cfMqB32LETA9j96F3l-sRE71IR2w,1091
9
+ magic_answer-0.1.0.dist-info/METADATA,sha256=bCGe-76a-U6qW_CCF8PA-MPapoydX1goTawqcHWXjJ8,3126
10
+ magic_answer-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
+ magic_answer-0.1.0.dist-info/top_level.txt,sha256=osk9HpwAt_bhbr3KM2Tn3g0Y7QmqF03mAtVj8JL5r7M,13
12
+ magic_answer-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [year] [fullname]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ magic_answer