polyllmkit 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.
- llmkit/__init__.py +20 -0
- llmkit/bridge/__init__.py +52 -0
- llmkit/bridge/__main__.py +11 -0
- llmkit/bridge/adapters/__init__.py +14 -0
- llmkit/bridge/adapters/anthropic.py +154 -0
- llmkit/bridge/adapters/claude_code.py +166 -0
- llmkit/bridge/adapters/google.py +106 -0
- llmkit/bridge/adapters/openai.py +60 -0
- llmkit/bridge/bridge.py +374 -0
- llmkit/bridge/cli.py +174 -0
- llmkit/bridge/client.py +88 -0
- llmkit/bridge/config.py +277 -0
- llmkit/bridge/sinks.py +56 -0
- llmkit/bridge/stream.py +161 -0
- llmkit/md/__init__.py +13 -0
- llmkit/md/render/__init__.py +1 -0
- llmkit/md/render/__main__.py +8 -0
- llmkit/md/render/cli.py +231 -0
- llmkit/md/render/stream.py +244 -0
- llmkit/md/view/__init__.py +1 -0
- llmkit/md/view/__main__.py +8 -0
- llmkit/md/view/app.py +258 -0
- llmkit/md/view/cli.py +74 -0
- llmkit/py.typed +0 -0
- polyllmkit-0.1.0.dist-info/METADATA +17 -0
- polyllmkit-0.1.0.dist-info/RECORD +27 -0
- polyllmkit-0.1.0.dist-info/WHEEL +4 -0
llmkit/md/view/app.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Textual-based modal viewer for streamed markdown output.
|
|
2
|
+
|
|
3
|
+
Input sources: a regular file, a fifo, or stdin (path "-"). The viewer
|
|
4
|
+
auto-detects the kind and picks sensible defaults for ``follow`` and
|
|
5
|
+
``exit_on_eof``:
|
|
6
|
+
|
|
7
|
+
regular file : follow=off, exit_on_eof=off → static view, q to quit
|
|
8
|
+
fifo : follow=on, exit_on_eof=on → live stream, exits on
|
|
9
|
+
writer close
|
|
10
|
+
stdin ("-") : follow=on, exit_on_eof=on → pipeline shape;
|
|
11
|
+
keyboard via /dev/tty
|
|
12
|
+
|
|
13
|
+
The defaults can be overridden via CLI flags (``--follow``,
|
|
14
|
+
``--exit-on-eof`` and their ``--no-...`` counterparts).
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import stat
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Optional, TypedDict
|
|
25
|
+
|
|
26
|
+
from textual.app import App, ComposeResult
|
|
27
|
+
from textual.binding import Binding
|
|
28
|
+
from textual.containers import VerticalScroll
|
|
29
|
+
from textual.widgets import Footer, Markdown
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ViewArgs(TypedDict):
|
|
33
|
+
"""Resolved CLI args for the viewer. Lives here (not in cli.py)
|
|
34
|
+
so ViewerApp can type its constructor against it without circular
|
|
35
|
+
imports. Mirror of the argparse Namespace."""
|
|
36
|
+
path: str
|
|
37
|
+
title: str
|
|
38
|
+
subtitle: str
|
|
39
|
+
follow: Optional[bool] # None = auto-detect
|
|
40
|
+
exit_on_eof: Optional[bool] # None = auto-detect
|
|
41
|
+
inline: bool
|
|
42
|
+
height: int
|
|
43
|
+
width: int
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ViewerApp(App[int]):
|
|
47
|
+
BINDINGS = [
|
|
48
|
+
Binding("q,escape", "quit", "Quit"),
|
|
49
|
+
Binding("ctrl+c", "quit", "Quit", show=False),
|
|
50
|
+
Binding("f", "toggle_follow", "Toggle follow"),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
CSS = """
|
|
54
|
+
#scroller {
|
|
55
|
+
/* Rounded chrome around the scrollable area. The title /
|
|
56
|
+
subtitle are placed in the border (top-left / top-right)
|
|
57
|
+
via border_title / border_subtitle set in on_mount. */
|
|
58
|
+
border: round $primary;
|
|
59
|
+
padding: 0 1;
|
|
60
|
+
/* 1fr = "consume the remaining vertical space after the
|
|
61
|
+
fixed-height Footer sibling". Required because the inner
|
|
62
|
+
Markdown widget defaults to height:auto, which is 0 when
|
|
63
|
+
content is empty at mount; without 1fr on the scroller
|
|
64
|
+
the layout collapses regardless of --height. */
|
|
65
|
+
height: 1fr;
|
|
66
|
+
}
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self, config: ViewArgs) -> None:
|
|
70
|
+
super().__init__()
|
|
71
|
+
# Use ANSI default colors instead of the theme's RGB palette
|
|
72
|
+
# so the background is the terminal's own bg, not textual's
|
|
73
|
+
# dark fill. Set on the instance (not as a class attr) to
|
|
74
|
+
# play nice with pyright's view of the Reactive descriptor.
|
|
75
|
+
self.ansi_color = True
|
|
76
|
+
self.config: ViewArgs = config
|
|
77
|
+
# Classify the input source and pick defaults.
|
|
78
|
+
self._source = _classify(config["path"])
|
|
79
|
+
self._following = (
|
|
80
|
+
config["follow"]
|
|
81
|
+
if config["follow"] is not None
|
|
82
|
+
else self._source != "file"
|
|
83
|
+
)
|
|
84
|
+
self._exit_on_eof = (
|
|
85
|
+
config["exit_on_eof"]
|
|
86
|
+
if config["exit_on_eof"] is not None
|
|
87
|
+
else self._source != "file"
|
|
88
|
+
)
|
|
89
|
+
# File-mode state.
|
|
90
|
+
self._file_offset = 0
|
|
91
|
+
# Stdin/fifo state.
|
|
92
|
+
self._read_fd: Optional[int] = None
|
|
93
|
+
if config["title"]:
|
|
94
|
+
self.title = config["title"]
|
|
95
|
+
if config["subtitle"]:
|
|
96
|
+
self.sub_title = config["subtitle"]
|
|
97
|
+
|
|
98
|
+
# ── compose ────────────────────────────────────────────────────────
|
|
99
|
+
def compose(self) -> ComposeResult:
|
|
100
|
+
with VerticalScroll(id="scroller"):
|
|
101
|
+
yield Markdown(id="md")
|
|
102
|
+
yield Footer()
|
|
103
|
+
|
|
104
|
+
# ── lifecycle ──────────────────────────────────────────────────────
|
|
105
|
+
def on_mount(self) -> None:
|
|
106
|
+
scroller = self.query_one("#scroller", VerticalScroll)
|
|
107
|
+
if self.config["title"]:
|
|
108
|
+
scroller.border_title = self.config["title"]
|
|
109
|
+
if self.config["subtitle"]:
|
|
110
|
+
scroller.border_subtitle = self.config["subtitle"]
|
|
111
|
+
if self.config["inline"]:
|
|
112
|
+
# In inline mode, Screen.styles.height drives the rendered
|
|
113
|
+
# area; the `size=(W, H)` arg only caps that value.
|
|
114
|
+
self.screen.styles.height = self.config["height"]
|
|
115
|
+
if self._following:
|
|
116
|
+
scroller.anchor()
|
|
117
|
+
# Open the read fd for stdin / fifo here so we can attach it
|
|
118
|
+
# to the running event loop in the worker.
|
|
119
|
+
if self._source != "file":
|
|
120
|
+
self._read_fd = self._open_read_fd()
|
|
121
|
+
self.run_worker(self._stream_markdown(), exclusive=True)
|
|
122
|
+
|
|
123
|
+
# ── streaming worker ───────────────────────────────────────────────
|
|
124
|
+
async def _stream_markdown(self) -> None:
|
|
125
|
+
md = self.query_one("#md", Markdown)
|
|
126
|
+
scroller = self.query_one("#scroller", VerticalScroll)
|
|
127
|
+
stream = Markdown.get_stream(md)
|
|
128
|
+
# Status indicator: subtitle starts as "waiting…" so a slow
|
|
129
|
+
# TTFT doesn't leave the user staring at empty chrome with no
|
|
130
|
+
# feedback. Flips to "streaming" on first content, "complete"
|
|
131
|
+
# on EOF when we're not auto-exiting.
|
|
132
|
+
base_sub = self.config["subtitle"]
|
|
133
|
+
scroller.border_subtitle = self._subtitle("waiting…", base_sub)
|
|
134
|
+
seen_first_chunk = False
|
|
135
|
+
try:
|
|
136
|
+
while True:
|
|
137
|
+
chunk = await self._next_chunk()
|
|
138
|
+
if chunk is None:
|
|
139
|
+
# EOF.
|
|
140
|
+
if self._exit_on_eof:
|
|
141
|
+
self.exit()
|
|
142
|
+
return
|
|
143
|
+
scroller.border_subtitle = self._subtitle("complete", base_sub)
|
|
144
|
+
return
|
|
145
|
+
if not seen_first_chunk:
|
|
146
|
+
scroller.border_subtitle = self._subtitle("streaming", base_sub)
|
|
147
|
+
seen_first_chunk = True
|
|
148
|
+
await stream.write(chunk)
|
|
149
|
+
finally:
|
|
150
|
+
await stream.stop()
|
|
151
|
+
if self._read_fd is not None and self._read_fd != 0:
|
|
152
|
+
# Don't close fd 0 (stdin) — Python owns it.
|
|
153
|
+
try:
|
|
154
|
+
os.close(self._read_fd)
|
|
155
|
+
except OSError:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _subtitle(status: str, base: str) -> str:
|
|
160
|
+
"""Compose the border subtitle: "<base> · <status>" or just <status>."""
|
|
161
|
+
return f"{base} · {status}" if base else status
|
|
162
|
+
|
|
163
|
+
async def _next_chunk(self) -> Optional[str]:
|
|
164
|
+
"""Block until new bytes are available, or EOF. Returns the
|
|
165
|
+
chunk, or None to signal the worker should terminate.
|
|
166
|
+
|
|
167
|
+
Cooperative: when there's no data, we `await asyncio.sleep`
|
|
168
|
+
instead of blocking the thread. select.select() would freeze
|
|
169
|
+
textual's event loop and starve input handling — a 50ms select
|
|
170
|
+
per iteration is enough to make keystrokes feel sluggish."""
|
|
171
|
+
while True:
|
|
172
|
+
if self._source == "file":
|
|
173
|
+
chunk = self._read_new_bytes_from_file()
|
|
174
|
+
if chunk:
|
|
175
|
+
return chunk
|
|
176
|
+
if not self._following:
|
|
177
|
+
# Static view: read what's there, then idle until q.
|
|
178
|
+
return None
|
|
179
|
+
await asyncio.sleep(0.05)
|
|
180
|
+
else:
|
|
181
|
+
# stdin or fifo: non-blocking read; EAGAIN/EWOULDBLOCK
|
|
182
|
+
# → yield to the event loop, try again. Never blocks
|
|
183
|
+
# the thread synchronously.
|
|
184
|
+
assert self._read_fd is not None
|
|
185
|
+
try:
|
|
186
|
+
data = os.read(self._read_fd, 4096)
|
|
187
|
+
except BlockingIOError:
|
|
188
|
+
await asyncio.sleep(0.05)
|
|
189
|
+
continue
|
|
190
|
+
except OSError:
|
|
191
|
+
return None
|
|
192
|
+
if not data:
|
|
193
|
+
# EOF — writer closed.
|
|
194
|
+
return None
|
|
195
|
+
return data.decode("utf-8", errors="replace")
|
|
196
|
+
|
|
197
|
+
def _read_new_bytes_from_file(self) -> str:
|
|
198
|
+
try:
|
|
199
|
+
size = os.path.getsize(self.config["path"])
|
|
200
|
+
except OSError:
|
|
201
|
+
return ""
|
|
202
|
+
if size <= self._file_offset:
|
|
203
|
+
return ""
|
|
204
|
+
try:
|
|
205
|
+
with open(self.config["path"], "rb") as f:
|
|
206
|
+
f.seek(self._file_offset)
|
|
207
|
+
chunk = f.read(size - self._file_offset)
|
|
208
|
+
except OSError:
|
|
209
|
+
return ""
|
|
210
|
+
if not chunk:
|
|
211
|
+
return ""
|
|
212
|
+
self._file_offset += len(chunk)
|
|
213
|
+
return chunk.decode("utf-8", errors="replace")
|
|
214
|
+
|
|
215
|
+
def _open_read_fd(self) -> int:
|
|
216
|
+
"""Open the input fd for stdin or fifo sources."""
|
|
217
|
+
if self._source == "stdin":
|
|
218
|
+
return sys.stdin.fileno()
|
|
219
|
+
# fifo: open RW so we don't block waiting for a writer; once a
|
|
220
|
+
# writer connects and closes, our reads will see EOF.
|
|
221
|
+
return os.open(self.config["path"], os.O_RDWR | os.O_NONBLOCK)
|
|
222
|
+
|
|
223
|
+
# ── actions ────────────────────────────────────────────────────────
|
|
224
|
+
def action_toggle_follow(self) -> None:
|
|
225
|
+
self._following = not self._following
|
|
226
|
+
if self._following:
|
|
227
|
+
self.query_one("#scroller", VerticalScroll).anchor()
|
|
228
|
+
|
|
229
|
+
# ── entry point ────────────────────────────────────────────────────
|
|
230
|
+
def run_viewer(self) -> int:
|
|
231
|
+
if self._source == "file" and not Path(self.config["path"]).is_file():
|
|
232
|
+
print(
|
|
233
|
+
f"mdview: '{self.config['path']}' not found",
|
|
234
|
+
file=sys.stderr,
|
|
235
|
+
)
|
|
236
|
+
return 2
|
|
237
|
+
try:
|
|
238
|
+
if self.config["inline"]:
|
|
239
|
+
width = self.config["width"] or shutil.get_terminal_size().columns
|
|
240
|
+
self.run(inline=True, size=(width, self.config["height"]))
|
|
241
|
+
else:
|
|
242
|
+
self.run()
|
|
243
|
+
except KeyboardInterrupt:
|
|
244
|
+
return 130
|
|
245
|
+
return 0
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _classify(path: str) -> str:
|
|
249
|
+
"""Return 'stdin', 'fifo', or 'file' for the input source."""
|
|
250
|
+
if path == "-":
|
|
251
|
+
return "stdin"
|
|
252
|
+
try:
|
|
253
|
+
st = os.stat(path)
|
|
254
|
+
except OSError:
|
|
255
|
+
return "file" # may not exist yet; caller validates
|
|
256
|
+
if stat.S_ISFIFO(st.st_mode):
|
|
257
|
+
return "fifo"
|
|
258
|
+
return "file"
|
llmkit/md/view/cli.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""argparse + dispatch for ``mdview``."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
from typing import List, Optional, cast
|
|
6
|
+
|
|
7
|
+
from .app import ViewArgs, ViewerApp
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
11
|
+
p = argparse.ArgumentParser(
|
|
12
|
+
prog="mdview",
|
|
13
|
+
description="Modal viewer for streamed markdown (file, fifo, or stdin).",
|
|
14
|
+
)
|
|
15
|
+
p.add_argument(
|
|
16
|
+
"path",
|
|
17
|
+
help="File to display. Use '-' for stdin (e.g. `bridge | view -`). "
|
|
18
|
+
"If the path is a fifo or '-', the viewer auto-follows and "
|
|
19
|
+
"exits on EOF unless overridden.",
|
|
20
|
+
)
|
|
21
|
+
p.add_argument(
|
|
22
|
+
"--title", default="thinking",
|
|
23
|
+
help="Panel title (default: 'thinking').",
|
|
24
|
+
)
|
|
25
|
+
p.add_argument(
|
|
26
|
+
"--subtitle", default="",
|
|
27
|
+
help="Panel subtitle (shown in the border on the top-right).",
|
|
28
|
+
)
|
|
29
|
+
follow = p.add_mutually_exclusive_group()
|
|
30
|
+
follow.add_argument(
|
|
31
|
+
"--follow", action="store_true", dest="follow", default=None,
|
|
32
|
+
help="Tail-follow the input as it grows. Default: on for stdin "
|
|
33
|
+
"and fifos, off for regular files.",
|
|
34
|
+
)
|
|
35
|
+
follow.add_argument(
|
|
36
|
+
"--no-follow", action="store_false", dest="follow",
|
|
37
|
+
help="Open the file as-is, no follow.",
|
|
38
|
+
)
|
|
39
|
+
eof = p.add_mutually_exclusive_group()
|
|
40
|
+
eof.add_argument(
|
|
41
|
+
"--exit-on-eof", action="store_true", dest="exit_on_eof", default=None,
|
|
42
|
+
help="Exit when the input source EOFs. Default: on for stdin and "
|
|
43
|
+
"fifos (writer closing = done); off for regular files (no "
|
|
44
|
+
"natural EOF signal — user quits with q).",
|
|
45
|
+
)
|
|
46
|
+
eof.add_argument(
|
|
47
|
+
"--no-exit-on-eof", action="store_false", dest="exit_on_eof",
|
|
48
|
+
help="Stay open after EOF; user quits with q.",
|
|
49
|
+
)
|
|
50
|
+
# ── inline-mode sizing ─────────────────────────────────────────────
|
|
51
|
+
p.add_argument(
|
|
52
|
+
"--inline", action="store_true",
|
|
53
|
+
help="Render inline below the cursor instead of taking over the "
|
|
54
|
+
"screen with alt-screen. Default: alt-screen.",
|
|
55
|
+
)
|
|
56
|
+
p.add_argument(
|
|
57
|
+
"--height", type=int, default=20,
|
|
58
|
+
help="Rows for the inline area (default 20). Ignored unless "
|
|
59
|
+
"--inline is set.",
|
|
60
|
+
)
|
|
61
|
+
p.add_argument(
|
|
62
|
+
"--width", type=int, default=0,
|
|
63
|
+
help="Columns for the inline area (default 0 = full terminal "
|
|
64
|
+
"width). Ignored unless --inline is set.",
|
|
65
|
+
)
|
|
66
|
+
return p
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
70
|
+
args = build_parser().parse_args(argv)
|
|
71
|
+
# Defaults that depend on the input source kind happen in the App
|
|
72
|
+
# (it stats the path); leaving --follow / --exit-on-eof as None
|
|
73
|
+
# here signals "auto" to the App.
|
|
74
|
+
return ViewerApp(cast(ViewArgs, vars(args))).run_viewer()
|
llmkit/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: polyllmkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Streaming, multi-provider LLM bridge + markdown rendering toolkit for terminal apps.
|
|
5
|
+
Requires-Dist: anthropic>=0.40 ; extra == 'anthropic'
|
|
6
|
+
Requires-Dist: openai>=1.0 ; extra == 'bridge'
|
|
7
|
+
Requires-Dist: claude-agent-sdk>=0.2 ; extra == 'claude'
|
|
8
|
+
Requires-Dist: google-genai>=1.0 ; extra == 'google'
|
|
9
|
+
Requires-Dist: rich>=13.0 ; extra == 'md'
|
|
10
|
+
Requires-Dist: textual>=0.50 ; extra == 'md'
|
|
11
|
+
Requires-Dist: markdown-it-py>=3.0 ; extra == 'md'
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Provides-Extra: anthropic
|
|
14
|
+
Provides-Extra: bridge
|
|
15
|
+
Provides-Extra: claude
|
|
16
|
+
Provides-Extra: google
|
|
17
|
+
Provides-Extra: md
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
llmkit/__init__.py,sha256=bLAzfdtbVtvjY1RGqjdpLPNCyG55ezc3PL5T7vS4OV0,940
|
|
2
|
+
llmkit/bridge/__init__.py,sha256=M72Zi6rfCenBnAWHIBQKgwe0UNjybGpnFnODa7T9G3Y,1542
|
|
3
|
+
llmkit/bridge/__main__.py,sha256=xPFtVm4Yw1AvRYODXxu6wektJ0dEQyAiQ35Fdu_5dkY,202
|
|
4
|
+
llmkit/bridge/adapters/__init__.py,sha256=QicJlecW2fCFp1-8K9dyf5sistv4vrvfAPSI2wZ6cKE,779
|
|
5
|
+
llmkit/bridge/adapters/anthropic.py,sha256=Z9FUnGO1poPxFSWwxomQkEb4lZ5ZZUhWCTZkqN_jNqE,6883
|
|
6
|
+
llmkit/bridge/adapters/claude_code.py,sha256=6VXo8EKZRZvSvWjsrmXMyvpO2Oyv4OEGqksRDA0jJlQ,7053
|
|
7
|
+
llmkit/bridge/adapters/google.py,sha256=cFjDxxadPQ2MxG2CzmlDuFDKUlWE-jLkumU0VeqTQnw,4413
|
|
8
|
+
llmkit/bridge/adapters/openai.py,sha256=SeQAoVrhP9sGKNRBUYWEh8nk-TqkOuk62JoEtswMxiQ,2444
|
|
9
|
+
llmkit/bridge/bridge.py,sha256=IUxDxjLRAc7v-jtBbtTOtCENoEyfoI5X3p2XPWchd-8,13670
|
|
10
|
+
llmkit/bridge/cli.py,sha256=qqO_YbeGAwGOtAhUTEMjRvp99ELLlwlRiR99hcWZUHA,6135
|
|
11
|
+
llmkit/bridge/client.py,sha256=rmBd9h5eeoJ7tNRZ4RYf1VpUndtqPDglXkk1PNqaPEM,3254
|
|
12
|
+
llmkit/bridge/config.py,sha256=mo-fyfry6oY_O-XtAsT_mksJXxqJECKCWqGuoC50ewU,10929
|
|
13
|
+
llmkit/bridge/sinks.py,sha256=xVavomgi7SHElpziCBhXSxNHgBzdh9kuiQI0FadREqg,1944
|
|
14
|
+
llmkit/bridge/stream.py,sha256=DRXVQskHRi4EgWLVzZlkg-1_7-S4LlAtyENNHZLhQzE,6570
|
|
15
|
+
llmkit/md/__init__.py,sha256=2tSVeD5QLy4doE1hNbrq9sjvzd3lUN8ghvWB1APEIlI,561
|
|
16
|
+
llmkit/md/render/__init__.py,sha256=d02nHe71mZ7fphJKLKZXWHZ89XYl4LmeadMgL_dBjdo,60
|
|
17
|
+
llmkit/md/render/__main__.py,sha256=ayAHlfGR7GlcqXIBSlgaEb7WpwyCqHWP_0pLCxNgTo4,144
|
|
18
|
+
llmkit/md/render/cli.py,sha256=oAOyoh5AdwYOLVsHWaWms-4DbuKBRvLL-QmW0uuMAZ4,8016
|
|
19
|
+
llmkit/md/render/stream.py,sha256=YcDW0UUZzOMtyA9wiAYgKbUzSdYCN_DuVg52ifseSMo,9358
|
|
20
|
+
llmkit/md/view/__init__.py,sha256=EpokGpMl9Qj0CHu-BbvLRBTLJ8IFAdRyV_7Z7lygWV4,50
|
|
21
|
+
llmkit/md/view/__main__.py,sha256=gsO_RhoJLxPMNuYtoPtnUGNvcUzgRpqX8YXGo5Cdnpk,142
|
|
22
|
+
llmkit/md/view/app.py,sha256=NRZOzf1N6YMBEzNJGe3YuGWAFwiXc3BM7p-E-dhrfNk,10564
|
|
23
|
+
llmkit/md/view/cli.py,sha256=Vuz8-EgdV0MqOHAoVB0tl8XL2-_-LCzlpGwsIMM-mWQ,2855
|
|
24
|
+
llmkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
polyllmkit-0.1.0.dist-info/WHEEL,sha256=CoDSoyhtC_eO_tlxRYzsTraPv1fPJRXFx91k6ISeAvA,81
|
|
26
|
+
polyllmkit-0.1.0.dist-info/METADATA,sha256=wApTjsq9kHo9lK_t_RQo3eIMb20k_59hvcUU_1wN2hI,635
|
|
27
|
+
polyllmkit-0.1.0.dist-info/RECORD,,
|