gink-ai 0.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.
- gink_ai-0.1.0/PKG-INFO +31 -0
- gink_ai-0.1.0/README.md +15 -0
- gink_ai-0.1.0/gink_ai.egg-info/PKG-INFO +31 -0
- gink_ai-0.1.0/gink_ai.egg-info/SOURCES.txt +11 -0
- gink_ai-0.1.0/gink_ai.egg-info/dependency_links.txt +1 -0
- gink_ai-0.1.0/gink_ai.egg-info/entry_points.txt +2 -0
- gink_ai-0.1.0/gink_ai.egg-info/requires.txt +1 -0
- gink_ai-0.1.0/gink_ai.egg-info/top_level.txt +1 -0
- gink_ai-0.1.0/gink_cli/__init__.py +2 -0
- gink_ai-0.1.0/gink_cli/__main__.py +3 -0
- gink_ai-0.1.0/gink_cli/main.py +294 -0
- gink_ai-0.1.0/pyproject.toml +25 -0
- gink_ai-0.1.0/setup.cfg +4 -0
gink_ai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gink-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Gink in your terminal: ask, chat, search the web and make images with your Gink API key.
|
|
5
|
+
Author: Gink AI
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://amgib00.github.io/GinkAssistant/
|
|
8
|
+
Project-URL: Source, https://github.com/Amgib00/GinkAssistant
|
|
9
|
+
Keywords: gink,ai,assistant,cli,chat
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: httpx>=0.25
|
|
16
|
+
|
|
17
|
+
# Gink in your terminal
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install gink-ai
|
|
21
|
+
gink login # paste your API key (Gink > Settings > Developers)
|
|
22
|
+
gink "What's the weather in Dakar?"
|
|
23
|
+
gink # chat: /new, /model NAME, /models, /search on|off, /exit
|
|
24
|
+
cat notes.txt | gink "summarise this"
|
|
25
|
+
gink -s "latest news in Senegal" # search the web (with sources)
|
|
26
|
+
gink --image "a baobab at sunset" -o tree.png
|
|
27
|
+
gink models
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Your key can also come from `GINK_API_KEY`; another server from `GINK_BASE_URL`
|
|
31
|
+
(default `https://gink-server.onrender.com/v1`). `gink logout` forgets the saved key.
|
gink_ai-0.1.0/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Gink in your terminal
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
pip install gink-ai
|
|
5
|
+
gink login # paste your API key (Gink > Settings > Developers)
|
|
6
|
+
gink "What's the weather in Dakar?"
|
|
7
|
+
gink # chat: /new, /model NAME, /models, /search on|off, /exit
|
|
8
|
+
cat notes.txt | gink "summarise this"
|
|
9
|
+
gink -s "latest news in Senegal" # search the web (with sources)
|
|
10
|
+
gink --image "a baobab at sunset" -o tree.png
|
|
11
|
+
gink models
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Your key can also come from `GINK_API_KEY`; another server from `GINK_BASE_URL`
|
|
15
|
+
(default `https://gink-server.onrender.com/v1`). `gink logout` forgets the saved key.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gink-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Gink in your terminal: ask, chat, search the web and make images with your Gink API key.
|
|
5
|
+
Author: Gink AI
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://amgib00.github.io/GinkAssistant/
|
|
8
|
+
Project-URL: Source, https://github.com/Amgib00/GinkAssistant
|
|
9
|
+
Keywords: gink,ai,assistant,cli,chat
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: httpx>=0.25
|
|
16
|
+
|
|
17
|
+
# Gink in your terminal
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install gink-ai
|
|
21
|
+
gink login # paste your API key (Gink > Settings > Developers)
|
|
22
|
+
gink "What's the weather in Dakar?"
|
|
23
|
+
gink # chat: /new, /model NAME, /models, /search on|off, /exit
|
|
24
|
+
cat notes.txt | gink "summarise this"
|
|
25
|
+
gink -s "latest news in Senegal" # search the web (with sources)
|
|
26
|
+
gink --image "a baobab at sunset" -o tree.png
|
|
27
|
+
gink models
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Your key can also come from `GINK_API_KEY`; another server from `GINK_BASE_URL`
|
|
31
|
+
(default `https://gink-server.onrender.com/v1`). `gink logout` forgets the saved key.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
gink_ai.egg-info/PKG-INFO
|
|
4
|
+
gink_ai.egg-info/SOURCES.txt
|
|
5
|
+
gink_ai.egg-info/dependency_links.txt
|
|
6
|
+
gink_ai.egg-info/entry_points.txt
|
|
7
|
+
gink_ai.egg-info/requires.txt
|
|
8
|
+
gink_ai.egg-info/top_level.txt
|
|
9
|
+
gink_cli/__init__.py
|
|
10
|
+
gink_cli/__main__.py
|
|
11
|
+
gink_cli/main.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
httpx>=0.25
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gink_cli
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gink -- Gink in your terminal (the Gink Developer API, OpenAI-compatible).
|
|
3
|
+
|
|
4
|
+
gink "question" one answer, streamed
|
|
5
|
+
gink a chat (type /help inside)
|
|
6
|
+
cat file | gink "summarise" send a file's text with the question
|
|
7
|
+
gink -s "question" search the web (answers list their sources)
|
|
8
|
+
gink --image "prompt" -o x.png make an image
|
|
9
|
+
gink login | logout | models | version
|
|
10
|
+
"""
|
|
11
|
+
import argparse
|
|
12
|
+
import base64
|
|
13
|
+
import getpass
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
from . import __version__
|
|
20
|
+
|
|
21
|
+
DEFAULT_BASE = "https://gink-server.onrender.com/v1"
|
|
22
|
+
MAX_PIPED_CHARS = 200_000
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GinkError(Exception):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------- settings
|
|
30
|
+
def config_path() -> str:
|
|
31
|
+
if os.name == "nt":
|
|
32
|
+
root = os.environ.get("APPDATA") or os.path.expanduser("~")
|
|
33
|
+
else:
|
|
34
|
+
root = os.environ.get("XDG_CONFIG_HOME") or os.path.join(os.path.expanduser("~"), ".config")
|
|
35
|
+
return os.path.join(root, "gink", "config.json")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_config() -> dict:
|
|
39
|
+
try:
|
|
40
|
+
with open(config_path(), encoding="utf-8") as f:
|
|
41
|
+
return json.load(f)
|
|
42
|
+
except (OSError, ValueError):
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def save_config(cfg: dict) -> None:
|
|
47
|
+
path = config_path()
|
|
48
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
49
|
+
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) # only you can read your key
|
|
50
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
51
|
+
json.dump(cfg, f, indent=1)
|
|
52
|
+
try:
|
|
53
|
+
os.chmod(path, 0o600)
|
|
54
|
+
except OSError:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def settings():
|
|
59
|
+
cfg = load_config()
|
|
60
|
+
key = os.environ.get("GINK_API_KEY") or cfg.get("api_key") or ""
|
|
61
|
+
base = (os.environ.get("GINK_BASE_URL") or cfg.get("base_url") or DEFAULT_BASE).rstrip("/")
|
|
62
|
+
return key, base
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------- talking to Gink
|
|
66
|
+
def _client(key: str, timeout: float = 120):
|
|
67
|
+
import httpx
|
|
68
|
+
return httpx.Client(timeout=httpx.Timeout(timeout, connect=30),
|
|
69
|
+
headers={"Authorization": f"Bearer {key}", "User-Agent": f"gink-cli/{__version__}"})
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _explain(r) -> GinkError:
|
|
73
|
+
try:
|
|
74
|
+
body = r.json()
|
|
75
|
+
msg = (body.get("error") or {}).get("message") if isinstance(body.get("error"), dict) else body.get("detail")
|
|
76
|
+
except Exception:
|
|
77
|
+
msg = ""
|
|
78
|
+
if r.status_code == 401:
|
|
79
|
+
return GinkError("Your API key isn't valid (or was revoked). Run: gink login")
|
|
80
|
+
if r.status_code == 429:
|
|
81
|
+
wait = r.headers.get("retry-after")
|
|
82
|
+
return GinkError((msg or "Limit reached.") + (f" Try again in {wait} seconds." if wait else ""))
|
|
83
|
+
return GinkError(msg or f"The Gink server answered {r.status_code}.")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _network(exc) -> GinkError:
|
|
87
|
+
import httpx
|
|
88
|
+
if isinstance(exc, httpx.TimeoutException):
|
|
89
|
+
return GinkError("The Gink server didn't answer in time. If it was asleep it can take up to a minute "
|
|
90
|
+
"to wake up \u2014 please try again.")
|
|
91
|
+
return GinkError(f"Can't reach the Gink server ({type(exc).__name__}). Check your connection.")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def need_key(key: str):
|
|
95
|
+
if not key:
|
|
96
|
+
raise GinkError("No API key yet. Create one in Gink (Settings > Developers), then run: gink login")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def list_models(key: str, base: str) -> list:
|
|
100
|
+
need_key(key)
|
|
101
|
+
import httpx
|
|
102
|
+
try:
|
|
103
|
+
with _client(key, 90) as c:
|
|
104
|
+
r = c.get(base + "/models")
|
|
105
|
+
except httpx.HTTPError as exc:
|
|
106
|
+
raise _network(exc)
|
|
107
|
+
if r.status_code != 200:
|
|
108
|
+
raise _explain(r)
|
|
109
|
+
return [m["id"] for m in r.json().get("data", [])]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def stream_answer(key: str, base: str, messages: list, model: str = "", out=None) -> str:
|
|
113
|
+
"""Send the chat; print the answer as it arrives; return the full text."""
|
|
114
|
+
need_key(key)
|
|
115
|
+
import httpx
|
|
116
|
+
out = out or sys.stdout
|
|
117
|
+
body = {"messages": messages, "stream": True}
|
|
118
|
+
if model:
|
|
119
|
+
body["model"] = model
|
|
120
|
+
text = []
|
|
121
|
+
try:
|
|
122
|
+
with _client(key) as c, c.stream("POST", base + "/chat/completions", json=body) as r:
|
|
123
|
+
if r.status_code != 200:
|
|
124
|
+
r.read()
|
|
125
|
+
raise _explain(r)
|
|
126
|
+
for line in r.iter_lines():
|
|
127
|
+
if not line.startswith("data:"):
|
|
128
|
+
continue
|
|
129
|
+
data = line[5:].strip()
|
|
130
|
+
if data == "[DONE]":
|
|
131
|
+
break
|
|
132
|
+
try:
|
|
133
|
+
chunk = json.loads(data)
|
|
134
|
+
except ValueError:
|
|
135
|
+
continue
|
|
136
|
+
if chunk.get("error"):
|
|
137
|
+
raise GinkError(chunk["error"].get("message") or "The AI service had a problem.")
|
|
138
|
+
piece = ((chunk.get("choices") or [{}])[0].get("delta") or {}).get("content") or ""
|
|
139
|
+
if piece:
|
|
140
|
+
text.append(piece)
|
|
141
|
+
out.write(piece)
|
|
142
|
+
out.flush()
|
|
143
|
+
except httpx.HTTPError as exc:
|
|
144
|
+
raise _network(exc)
|
|
145
|
+
out.write("\n")
|
|
146
|
+
out.flush()
|
|
147
|
+
return "".join(text)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def make_image(key: str, base: str, prompt: str, path: str) -> str:
|
|
151
|
+
need_key(key)
|
|
152
|
+
import httpx
|
|
153
|
+
try:
|
|
154
|
+
with _client(key, 180) as c:
|
|
155
|
+
r = c.post(base + "/images/generations", json={"prompt": prompt})
|
|
156
|
+
except httpx.HTTPError as exc:
|
|
157
|
+
raise _network(exc)
|
|
158
|
+
if r.status_code != 200:
|
|
159
|
+
raise _explain(r)
|
|
160
|
+
data = r.json()["data"][0]["b64_json"]
|
|
161
|
+
with open(path, "wb") as f:
|
|
162
|
+
f.write(base64.b64decode(data))
|
|
163
|
+
return path
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------- the commands
|
|
167
|
+
def with_piped_text(question: str, piped: str) -> str:
|
|
168
|
+
if not piped:
|
|
169
|
+
return question
|
|
170
|
+
if len(piped) > MAX_PIPED_CHARS:
|
|
171
|
+
piped = piped[:MAX_PIPED_CHARS] + "\n[... cut: only the first 200,000 characters were sent]"
|
|
172
|
+
return (f'<attached_file name="input">\n{piped}\n</attached_file>\n\n'
|
|
173
|
+
+ (question or "Please read this and summarise the important points."))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def chat(key: str, base: str, model: str, system: str = "") -> int:
|
|
177
|
+
"""An interactive chat. /help for commands; Ctrl+C stops an answer, Ctrl+D (or /exit) leaves."""
|
|
178
|
+
need_key(key)
|
|
179
|
+
color = sys.stdout.isatty() and not os.environ.get("NO_COLOR")
|
|
180
|
+
you = "\033[1;32myou \u203a\033[0m " if color else "you > "
|
|
181
|
+
history = [{"role": "system", "content": system}] if system else []
|
|
182
|
+
print(f"Gink chat \u00b7 {model or 'default model'} \u00b7 /help for commands, /exit to leave")
|
|
183
|
+
while True:
|
|
184
|
+
try:
|
|
185
|
+
line = input(you).strip()
|
|
186
|
+
except EOFError:
|
|
187
|
+
print()
|
|
188
|
+
return 0
|
|
189
|
+
except KeyboardInterrupt:
|
|
190
|
+
print()
|
|
191
|
+
return 0
|
|
192
|
+
if not line:
|
|
193
|
+
continue
|
|
194
|
+
if line.startswith("/"):
|
|
195
|
+
cmd, _, arg = line[1:].partition(" ")
|
|
196
|
+
if cmd in ("exit", "quit", "q"):
|
|
197
|
+
return 0
|
|
198
|
+
if cmd == "new":
|
|
199
|
+
history = history[:1] if system else []
|
|
200
|
+
print("New chat.")
|
|
201
|
+
elif cmd == "model":
|
|
202
|
+
model = arg.strip()
|
|
203
|
+
print(f"Model: {model or 'default'}")
|
|
204
|
+
elif cmd == "search":
|
|
205
|
+
model = "gink-search" if arg.strip() != "off" else ""
|
|
206
|
+
print("Web search on." if model == "gink-search" else "Web search off.")
|
|
207
|
+
elif cmd == "models":
|
|
208
|
+
try:
|
|
209
|
+
print("\n".join(list_models(key, base)))
|
|
210
|
+
except GinkError as exc:
|
|
211
|
+
print(f"gink: {exc}")
|
|
212
|
+
else:
|
|
213
|
+
print("/new start over \u00b7 /model NAME \u00b7 /models \u00b7 /search on|off \u00b7 /exit")
|
|
214
|
+
continue
|
|
215
|
+
history.append({"role": "user", "content": line})
|
|
216
|
+
try:
|
|
217
|
+
answer = stream_answer(key, base, history[-30:], model)
|
|
218
|
+
history.append({"role": "assistant", "content": answer})
|
|
219
|
+
except KeyboardInterrupt:
|
|
220
|
+
print("\n(stopped)")
|
|
221
|
+
history.pop()
|
|
222
|
+
except GinkError as exc:
|
|
223
|
+
print(f"gink: {exc}")
|
|
224
|
+
history.pop()
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def login(base: str) -> int:
|
|
228
|
+
print("Paste your Gink API key (Gink > Settings > Developers > Create key). It starts with gink_live_")
|
|
229
|
+
key = getpass.getpass("API key: ").strip()
|
|
230
|
+
if not key.startswith("gink_live_"):
|
|
231
|
+
print("gink: that doesn't look like a Gink API key (it should start with gink_live_).")
|
|
232
|
+
return 1
|
|
233
|
+
models = list_models(key, base) # check it works before saving it
|
|
234
|
+
cfg = load_config()
|
|
235
|
+
cfg["api_key"] = key
|
|
236
|
+
if base != DEFAULT_BASE:
|
|
237
|
+
cfg["base_url"] = base
|
|
238
|
+
save_config(cfg)
|
|
239
|
+
print(f"Signed in. {len(models)} models available. Try: gink \"hello\"")
|
|
240
|
+
return 0
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def main(argv=None) -> int:
|
|
244
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
245
|
+
key, base = settings()
|
|
246
|
+
try:
|
|
247
|
+
if argv[:1] == ["login"]:
|
|
248
|
+
return login(base)
|
|
249
|
+
if argv[:1] == ["logout"]:
|
|
250
|
+
cfg = load_config()
|
|
251
|
+
cfg.pop("api_key", None)
|
|
252
|
+
save_config(cfg)
|
|
253
|
+
print("Signed out: your API key was removed from this computer.")
|
|
254
|
+
return 0
|
|
255
|
+
if argv[:1] == ["models"]:
|
|
256
|
+
print("\n".join(list_models(key, base)))
|
|
257
|
+
return 0
|
|
258
|
+
if argv[:1] in (["version"], ["--version"]):
|
|
259
|
+
print(f"gink {__version__}")
|
|
260
|
+
return 0
|
|
261
|
+
|
|
262
|
+
ap = argparse.ArgumentParser(prog="gink", description="Gink in your terminal.",
|
|
263
|
+
epilog="Also: gink login | logout | models | version")
|
|
264
|
+
ap.add_argument("question", nargs="*", help="what to ask (leave out for a chat)")
|
|
265
|
+
ap.add_argument("-m", "--model", default="", help="a model from 'gink models' (default: your account's)")
|
|
266
|
+
ap.add_argument("-s", "--search", action="store_true", help="search the web; answers list their sources")
|
|
267
|
+
ap.add_argument("--system", default="", help="instructions for Gink in this conversation")
|
|
268
|
+
ap.add_argument("--image", metavar="PROMPT", help="make an image instead of answering")
|
|
269
|
+
ap.add_argument("-o", "--output", default="", help="where to save the image (default gink-image-<time>.png)")
|
|
270
|
+
a = ap.parse_args(argv)
|
|
271
|
+
model = "gink-search" if a.search else a.model
|
|
272
|
+
|
|
273
|
+
if a.image:
|
|
274
|
+
path = make_image(key, base, a.image, a.output or f"gink-image-{int(time.time())}.png")
|
|
275
|
+
print(f"Saved {path}")
|
|
276
|
+
return 0
|
|
277
|
+
question = " ".join(a.question).strip()
|
|
278
|
+
piped = "" if sys.stdin is None or sys.stdin.isatty() else sys.stdin.read()
|
|
279
|
+
if not question and not piped:
|
|
280
|
+
return chat(key, base, model, a.system)
|
|
281
|
+
messages = ([{"role": "system", "content": a.system}] if a.system else []) + \
|
|
282
|
+
[{"role": "user", "content": with_piped_text(question, piped)}]
|
|
283
|
+
stream_answer(key, base, messages, model)
|
|
284
|
+
return 0
|
|
285
|
+
except GinkError as exc:
|
|
286
|
+
print(f"gink: {exc}", file=sys.stderr)
|
|
287
|
+
return 1
|
|
288
|
+
except KeyboardInterrupt:
|
|
289
|
+
print("\n(stopped)", file=sys.stderr)
|
|
290
|
+
return 130
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
if __name__ == "__main__":
|
|
294
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gink-ai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Gink in your terminal: ask, chat, search the web and make images with your Gink API key."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Gink AI" }]
|
|
13
|
+
dependencies = ["httpx>=0.25"]
|
|
14
|
+
keywords = ["gink", "ai", "assistant", "cli", "chat"]
|
|
15
|
+
classifiers = ["Environment :: Console", "Programming Language :: Python :: 3", "Operating System :: OS Independent"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://amgib00.github.io/GinkAssistant/"
|
|
19
|
+
Source = "https://github.com/Amgib00/GinkAssistant"
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
gink = "gink_cli.main:main"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools]
|
|
25
|
+
packages = ["gink_cli"]
|
gink_ai-0.1.0/setup.cfg
ADDED