lcode-cli 0.21.0__py3-none-any.whl → 0.22.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.
- lcode/__init__.py +1 -1
- lcode/mcp/catalog.toml +21 -0
- lcode/servers/xai.py +289 -0
- {lcode_cli-0.21.0.dist-info → lcode_cli-0.22.0.dist-info}/METADATA +3 -2
- {lcode_cli-0.21.0.dist-info → lcode_cli-0.22.0.dist-info}/RECORD +8 -7
- {lcode_cli-0.21.0.dist-info → lcode_cli-0.22.0.dist-info}/entry_points.txt +1 -0
- {lcode_cli-0.21.0.dist-info → lcode_cli-0.22.0.dist-info}/WHEEL +0 -0
- {lcode_cli-0.21.0.dist-info → lcode_cli-0.22.0.dist-info}/licenses/LICENSE +0 -0
lcode/__init__.py
CHANGED
lcode/mcp/catalog.toml
CHANGED
|
@@ -281,3 +281,24 @@ secret = true
|
|
|
281
281
|
var = "VALOHAI_HOST"
|
|
282
282
|
prompt = "Valohai address (Enter for app.valohai.com; for a self-hosted installation, its URL)"
|
|
283
283
|
optional = true
|
|
284
|
+
|
|
285
|
+
[xai]
|
|
286
|
+
name = "xAI Grok Imagine"
|
|
287
|
+
description = "Generate and edit images, and generate videos and speech, with xAI's Grok Imagine and voice API (paid, in the cloud)"
|
|
288
|
+
homepage = "https://nasser1941.github.io/lcode/mcp/#xai"
|
|
289
|
+
requires = ["uvx"]
|
|
290
|
+
setup = """Uses xAI's API: your prompts, and images you edit or animate, go to xAI, and every call is billed to your xAI
|
|
291
|
+
account (an image $0.02-0.05, video $0.02-0.08 a second, speech per character). Create an API key at https://console.x.ai.
|
|
292
|
+
Files are saved in generated/ in the project. lcode asks before each call; to stop asking for some tools, list them
|
|
293
|
+
under "allow" for this server in mcp.json. xAI has no music generation."""
|
|
294
|
+
|
|
295
|
+
[xai.server]
|
|
296
|
+
command = "uvx"
|
|
297
|
+
args = ["--isolated", "--python", "3.12", "--from", "lcode-cli", "lcode-mcp-xai"]
|
|
298
|
+
env = { XAI_API_KEY = "${XAI_API_KEY}" }
|
|
299
|
+
timeout = 900 # a video takes minutes
|
|
300
|
+
|
|
301
|
+
[[xai.inputs]]
|
|
302
|
+
var = "XAI_API_KEY"
|
|
303
|
+
prompt = "xAI API key (create one at https://console.x.ai)"
|
|
304
|
+
secret = true
|
lcode/servers/xai.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""An MCP server for xAI's Grok Imagine and voice APIs: images, image edits, videos and speech.
|
|
2
|
+
|
|
3
|
+
xAI has an HTTP API but no MCP server, so lcode ships this one. It needs an API key (XAI_API_KEY,
|
|
4
|
+
from https://console.x.ai) and saves what it makes in the project, in `generated/` unless a path is
|
|
5
|
+
given. The prompts, and any image it edits or animates, go to xAI, which bills per image, per
|
|
6
|
+
second of video and per character of speech, and applies its own usage policies.
|
|
7
|
+
|
|
8
|
+
Run: lcode-mcp-xai (lcode mcp add xai sets it up)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import base64
|
|
15
|
+
import mimetypes
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
import requests
|
|
22
|
+
|
|
23
|
+
from lcode import __version__
|
|
24
|
+
from lcode.mcp.server import Server, ToolFailure
|
|
25
|
+
|
|
26
|
+
API = "https://api.x.ai/v1"
|
|
27
|
+
TIMEOUT = 120
|
|
28
|
+
IMAGE_MODELS = ["grok-imagine-image-2.0", "grok-imagine-image-quality", "grok-imagine-image"]
|
|
29
|
+
VIDEO_MODELS = ["grok-imagine-video-1.5", "grok-imagine-video-1.5-lite", "grok-imagine-video"]
|
|
30
|
+
ASPECT_RATIOS = ["auto", "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "2:1", "1:2", "21:9"]
|
|
31
|
+
VOICES = ["eve", "ara", "leo", "rex", "sal", "luna", "iris", "orion", "helix", "zagan", "carina", "altair", "zenith",
|
|
32
|
+
"perseus", "helios", "lux", "kepler", "rigel", "cosmo", "celeste", "ursa", "sirius", "lumen", "castor",
|
|
33
|
+
"naksh", "atlas"] # fmt: skip
|
|
34
|
+
AUDIO_FORMATS = {"mp3": ".mp3", "wav": ".wav"}
|
|
35
|
+
VIDEO_WAIT = 600 # seconds generate_video waits before handing back the request id
|
|
36
|
+
POLL = 5
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class XAI:
|
|
40
|
+
def __init__(self, key: str, base: str = API, output: Path | None = None):
|
|
41
|
+
self.base = base.rstrip("/")
|
|
42
|
+
self.output = output or Path("generated")
|
|
43
|
+
self.session = requests.Session()
|
|
44
|
+
self.session.headers.update({"Authorization": f"Bearer {key}", "Content-Type": "application/json"})
|
|
45
|
+
|
|
46
|
+
def request(self, method: str, path: str, **kwargs) -> requests.Response:
|
|
47
|
+
try:
|
|
48
|
+
r = self.session.request(method, f"{self.base}{path}", timeout=TIMEOUT, **kwargs)
|
|
49
|
+
except requests.RequestException as e:
|
|
50
|
+
raise ToolFailure(f"can't reach xAI: {e}") from e
|
|
51
|
+
if r.status_code in (401, 403):
|
|
52
|
+
raise ToolFailure(
|
|
53
|
+
f"xAI refused the request ({r.status_code}): {_error(r)}. Check XAI_API_KEY (console.x.ai)."
|
|
54
|
+
)
|
|
55
|
+
if r.status_code == 429:
|
|
56
|
+
raise ToolFailure(f"xAI's rate limit or credit limit was reached: {_error(r)}")
|
|
57
|
+
if r.status_code >= 400:
|
|
58
|
+
raise ToolFailure(f"xAI error {r.status_code}: {_error(r)}")
|
|
59
|
+
return r
|
|
60
|
+
|
|
61
|
+
def post(self, path: str, body: dict) -> dict:
|
|
62
|
+
return self.request("POST", path, json=body).json()
|
|
63
|
+
|
|
64
|
+
def download(self, url: str) -> bytes:
|
|
65
|
+
try:
|
|
66
|
+
r = requests.get(url, timeout=TIMEOUT)
|
|
67
|
+
r.raise_for_status()
|
|
68
|
+
except requests.RequestException as e:
|
|
69
|
+
raise ToolFailure(f"couldn't download the result from xAI: {e}") from e
|
|
70
|
+
return r.content
|
|
71
|
+
|
|
72
|
+
def target(self, path: str, stem: str, suffix: str) -> Path:
|
|
73
|
+
"""Where to save: the given path, or a new file in the output folder."""
|
|
74
|
+
if path:
|
|
75
|
+
target = Path(path).expanduser()
|
|
76
|
+
if not target.suffix:
|
|
77
|
+
target = target.with_suffix(suffix)
|
|
78
|
+
else:
|
|
79
|
+
name = re.sub(r"[^a-z0-9]+", "-", stem.lower()).strip("-")[:40] or "xai"
|
|
80
|
+
target = self.output / f"{name}-{time.strftime('%Y%m%d-%H%M%S')}{suffix}"
|
|
81
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
n, unique = 2, target
|
|
83
|
+
while unique.exists():
|
|
84
|
+
unique = target.with_name(f"{target.stem}-{n}{target.suffix}")
|
|
85
|
+
n += 1
|
|
86
|
+
return unique
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _error(r: requests.Response) -> str:
|
|
90
|
+
try:
|
|
91
|
+
data = r.json()
|
|
92
|
+
except ValueError:
|
|
93
|
+
return r.text[:300]
|
|
94
|
+
error = data.get("error") if isinstance(data, dict) else None
|
|
95
|
+
if isinstance(error, dict):
|
|
96
|
+
return str(error.get("message") or error)[:300]
|
|
97
|
+
return str(error or data)[:300]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def data_url(path: str) -> str:
|
|
101
|
+
"""A local image as a data: URL, for edits and image-to-video."""
|
|
102
|
+
file = Path(path).expanduser()
|
|
103
|
+
if not file.is_file():
|
|
104
|
+
raise ToolFailure(f"{path} doesn't exist")
|
|
105
|
+
mime = mimetypes.guess_type(file.name)[0] or "image/png"
|
|
106
|
+
if mime not in ("image/png", "image/jpeg", "image/webp"):
|
|
107
|
+
raise ToolFailure(f"{path} isn't a PNG, JPEG or WebP image")
|
|
108
|
+
return f"data:{mime};base64,{base64.b64encode(file.read_bytes()).decode()}"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def image_suffix(data: bytes) -> str:
|
|
112
|
+
if data[:3] == b"\xff\xd8\xff":
|
|
113
|
+
return ".jpg"
|
|
114
|
+
if data[:4] == b"RIFF" and data[8:12] == b"WEBP":
|
|
115
|
+
return ".webp"
|
|
116
|
+
return ".png"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def save_images(api: XAI, reply: dict, prompt: str, path: str) -> list[Path]:
|
|
120
|
+
saved = []
|
|
121
|
+
items = reply.get("data") or []
|
|
122
|
+
if not items:
|
|
123
|
+
raise ToolFailure(f"xAI returned no image: {str(reply)[:300]}")
|
|
124
|
+
for i, item in enumerate(items):
|
|
125
|
+
data = base64.b64decode(item["b64_json"]) if item.get("b64_json") else api.download(item["url"])
|
|
126
|
+
many = path and len(items) > 1
|
|
127
|
+
target = api.target(f"{Path(path).with_suffix('')}-{i + 1}" if many else path, prompt, image_suffix(data))
|
|
128
|
+
target.write_bytes(data)
|
|
129
|
+
saved.append(target)
|
|
130
|
+
return saved
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def build(api: XAI) -> Server:
|
|
134
|
+
server = Server(
|
|
135
|
+
"xai",
|
|
136
|
+
__version__,
|
|
137
|
+
"xAI's Grok Imagine and voice: generate and edit images, generate videos (from text or a start image) "
|
|
138
|
+
"and speech. Results are saved as files in the project; each call costs money on the user's xAI account. "
|
|
139
|
+
"xAI has no music generation.",
|
|
140
|
+
)
|
|
141
|
+
path_arg = {"type": "string", "description": "Where to save it (default: a new file in generated/)"}
|
|
142
|
+
|
|
143
|
+
@server.tool(
|
|
144
|
+
"generate_image",
|
|
145
|
+
"Generate images from a description with Grok Imagine and save them. Costs $0.02–0.05 per image.",
|
|
146
|
+
{
|
|
147
|
+
"prompt": {"type": "string", "description": "What the image shows, in detail"},
|
|
148
|
+
"n": {"type": "integer", "description": "How many images, 1–10 (default 1)"},
|
|
149
|
+
"aspect_ratio": {"type": "string", "enum": ASPECT_RATIOS, "description": "Default auto"},
|
|
150
|
+
"resolution": {"type": "string", "enum": ["1k", "2k"], "description": "Default 1k"},
|
|
151
|
+
"model": {"type": "string", "enum": IMAGE_MODELS, "description": f"Default {IMAGE_MODELS[0]}"},
|
|
152
|
+
"path": path_arg,
|
|
153
|
+
},
|
|
154
|
+
["prompt"],
|
|
155
|
+
)
|
|
156
|
+
def generate_image(prompt: str, n: int = 1, aspect_ratio: str = "auto", resolution: str = "1k",
|
|
157
|
+
model: str = IMAGE_MODELS[0], path: str = "") -> str: # fmt: skip
|
|
158
|
+
body = {"model": model, "prompt": prompt, "n": max(1, min(10, int(n))), "response_format": "b64_json"}
|
|
159
|
+
if aspect_ratio and aspect_ratio != "auto":
|
|
160
|
+
body["aspect_ratio"] = aspect_ratio
|
|
161
|
+
if resolution and resolution != "1k":
|
|
162
|
+
body["resolution"] = resolution
|
|
163
|
+
saved = save_images(api, api.post("/images/generations", body), prompt, path)
|
|
164
|
+
return f"Saved {len(saved)} image(s) from {model}:\n" + "\n".join(f"- {p}" for p in saved)
|
|
165
|
+
|
|
166
|
+
@server.tool(
|
|
167
|
+
"edit_image",
|
|
168
|
+
"Change an existing image (a local file) as described, with Grok Imagine, and save the result.",
|
|
169
|
+
{
|
|
170
|
+
"image": {"type": "string", "description": "Path of the image to edit (PNG, JPEG or WebP)"},
|
|
171
|
+
"prompt": {"type": "string", "description": "What to change"},
|
|
172
|
+
"model": {"type": "string", "enum": IMAGE_MODELS, "description": f"Default {IMAGE_MODELS[0]}"},
|
|
173
|
+
"path": path_arg,
|
|
174
|
+
},
|
|
175
|
+
["image", "prompt"],
|
|
176
|
+
)
|
|
177
|
+
def edit_image(image: str, prompt: str, model: str = IMAGE_MODELS[0], path: str = "") -> str:
|
|
178
|
+
body = {"model": model, "prompt": prompt, "image": {"url": data_url(image), "type": "image_url"},
|
|
179
|
+
"response_format": "b64_json"} # fmt: skip
|
|
180
|
+
saved = save_images(api, api.post("/images/edits", body), prompt, path)
|
|
181
|
+
return f"Saved the edited image: {saved[0]}"
|
|
182
|
+
|
|
183
|
+
def finish_video(request_id: str, prompt: str, path: str, wait: float) -> str:
|
|
184
|
+
deadline = time.monotonic() + wait
|
|
185
|
+
while True:
|
|
186
|
+
state = api.request("GET", f"/videos/{request_id}").json()
|
|
187
|
+
status = state.get("status")
|
|
188
|
+
if status == "done":
|
|
189
|
+
video = state.get("video") or {}
|
|
190
|
+
target = api.target(path, prompt or request_id, ".mp4")
|
|
191
|
+
target.write_bytes(api.download(video["url"]))
|
|
192
|
+
return f"Saved the video ({video.get('duration', '?')} s, {state.get('model', '')}): {target}"
|
|
193
|
+
if status in ("failed", "expired"):
|
|
194
|
+
raise ToolFailure(f"xAI couldn't make the video ({status}): {state.get('error') or state}")
|
|
195
|
+
if time.monotonic() > deadline:
|
|
196
|
+
return (
|
|
197
|
+
f"The video isn't ready yet (request {request_id}). Check later with "
|
|
198
|
+
f'video_status(request_id="{request_id}"), which downloads it when it\'s done.'
|
|
199
|
+
)
|
|
200
|
+
time.sleep(POLL)
|
|
201
|
+
|
|
202
|
+
@server.tool(
|
|
203
|
+
"generate_video",
|
|
204
|
+
"Generate a video with Grok Imagine, from a description and optionally a start image, and save it as "
|
|
205
|
+
"MP4. Takes a minute or more. Costs $0.02–0.08 per second of video.",
|
|
206
|
+
{
|
|
207
|
+
"prompt": {"type": "string", "description": "What happens in the video"},
|
|
208
|
+
"image": {"type": "string", "description": "A local image to start from (optional)"},
|
|
209
|
+
"duration": {"type": "integer", "description": "Seconds, 1–15 (default 8)"},
|
|
210
|
+
"aspect_ratio": {
|
|
211
|
+
"type": "string",
|
|
212
|
+
"enum": [r for r in ASPECT_RATIOS if r not in ("auto", "2:1", "1:2", "21:9")],
|
|
213
|
+
},
|
|
214
|
+
"resolution": {"type": "string", "enum": ["480p", "720p", "1080p"], "description": "Default 720p"},
|
|
215
|
+
"model": {"type": "string", "enum": VIDEO_MODELS, "description": f"Default {VIDEO_MODELS[0]}"},
|
|
216
|
+
"path": path_arg,
|
|
217
|
+
},
|
|
218
|
+
["prompt"],
|
|
219
|
+
)
|
|
220
|
+
def generate_video(prompt: str, image: str = "", duration: int = 8, aspect_ratio: str = "16:9",
|
|
221
|
+
resolution: str = "720p", model: str = VIDEO_MODELS[0], path: str = "") -> str: # fmt: skip
|
|
222
|
+
body: dict = {"model": model, "prompt": prompt, "duration": max(1, min(15, int(duration))),
|
|
223
|
+
"aspect_ratio": aspect_ratio, "resolution": resolution} # fmt: skip
|
|
224
|
+
if image:
|
|
225
|
+
body["image"] = {"url": data_url(image)}
|
|
226
|
+
request_id = api.post("/videos/generations", body).get("request_id")
|
|
227
|
+
if not request_id:
|
|
228
|
+
raise ToolFailure("xAI didn't start the video")
|
|
229
|
+
return finish_video(str(request_id), prompt, path, VIDEO_WAIT)
|
|
230
|
+
|
|
231
|
+
@server.tool(
|
|
232
|
+
"video_status",
|
|
233
|
+
"Check a video that generate_video started and that wasn't ready yet; saves it when it's done.",
|
|
234
|
+
{"request_id": {"type": "string"}, "path": path_arg},
|
|
235
|
+
["request_id"],
|
|
236
|
+
)
|
|
237
|
+
def video_status(request_id: str, path: str = "") -> str:
|
|
238
|
+
return finish_video(request_id, "", path, 0)
|
|
239
|
+
|
|
240
|
+
@server.tool(
|
|
241
|
+
"text_to_speech",
|
|
242
|
+
"Turn text into speech with an xAI voice and save it as an audio file. Billed per character.",
|
|
243
|
+
{
|
|
244
|
+
"text": {"type": "string", "description": "What to say (up to 60,000 characters)"},
|
|
245
|
+
"voice": {"type": "string", "enum": VOICES, "description": "Default eve"},
|
|
246
|
+
"language": {"type": "string", "description": "BCP-47 code such as en, de, fr, ja, or auto (default)"},
|
|
247
|
+
"format": {"type": "string", "enum": list(AUDIO_FORMATS), "description": "Default mp3"},
|
|
248
|
+
"speed": {"type": "number", "description": "1.0 is normal"},
|
|
249
|
+
"path": path_arg,
|
|
250
|
+
},
|
|
251
|
+
["text"],
|
|
252
|
+
)
|
|
253
|
+
def text_to_speech(text: str, voice: str = "eve", language: str = "auto", format: str = "mp3",
|
|
254
|
+
speed: float = 1.0, path: str = "") -> str: # fmt: skip
|
|
255
|
+
codec = format if format in AUDIO_FORMATS else "mp3"
|
|
256
|
+
body = {"text": text, "voice_id": voice.lower(), "language": language or "auto",
|
|
257
|
+
"output_format": {"codec": codec}, "speed": float(speed or 1.0)} # fmt: skip
|
|
258
|
+
r = api.request("POST", "/tts", json=body)
|
|
259
|
+
duration = ""
|
|
260
|
+
if "json" in r.headers.get("Content-Type", ""):
|
|
261
|
+
data = r.json()
|
|
262
|
+
audio = base64.b64decode(data.get("audio", ""))
|
|
263
|
+
duration = f", {data['duration']:.1f} s" if isinstance(data.get("duration"), (int, float)) else ""
|
|
264
|
+
else:
|
|
265
|
+
audio = r.content
|
|
266
|
+
if not audio:
|
|
267
|
+
raise ToolFailure("xAI returned no audio")
|
|
268
|
+
target = api.target(path, text[:40], AUDIO_FORMATS[codec])
|
|
269
|
+
target.write_bytes(audio)
|
|
270
|
+
return f"Saved the speech ({voice}{duration}): {target}"
|
|
271
|
+
|
|
272
|
+
return server
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def main(argv: list[str] | None = None) -> None:
|
|
276
|
+
parser = argparse.ArgumentParser(
|
|
277
|
+
prog="lcode-mcp-xai", description="MCP server for xAI's image, video and speech APIs (stdio)."
|
|
278
|
+
)
|
|
279
|
+
parser.add_argument("--output", default=os.environ.get("LCODE_XAI_OUTPUT", "generated"),
|
|
280
|
+
help="folder for the files it makes (default: generated/ in the project)") # fmt: skip
|
|
281
|
+
args = parser.parse_args(argv)
|
|
282
|
+
key = os.environ.get("XAI_API_KEY")
|
|
283
|
+
if not key:
|
|
284
|
+
parser.error("set XAI_API_KEY to an xAI API key (https://console.x.ai)")
|
|
285
|
+
build(XAI(key, os.environ.get("XAI_API_BASE", API), Path(args.output))).serve()
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__ == "__main__":
|
|
289
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0
|
|
4
4
|
Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
|
|
5
5
|
Project-URL: Homepage, https://nasser1941.github.io/lcode/
|
|
6
6
|
Project-URL: Documentation, https://nasser1941.github.io/lcode/
|
|
@@ -116,7 +116,8 @@ through [Ollama](https://ollama.com), and when it needs current information it c
|
|
|
116
116
|
(`lcode mcp add atlassian`), image generation with ComfyUI, or any other MCP server. Browser sign-in (OAuth) is built in.
|
|
117
117
|
- **Sees images.** Attach a screenshot or mockup with `@path` and lcode looks at it, using the
|
|
118
118
|
vision part of your model. With ComfyUI (`lcode mcp add comfyui`) it can also generate and edit
|
|
119
|
-
images with local models such as FLUX, SDXL and Qwen-Image
|
|
119
|
+
images with local models such as FLUX, SDXL and Qwen-Image, or, in the cloud, images, videos
|
|
120
|
+
and speech with xAI's Grok Imagine (`lcode mcp add xai`).
|
|
120
121
|
- **Web search when needed.** Looks up the latest versions, docs and error messages with Ollama web
|
|
121
122
|
search, Brave, Tavily or your own SearXNG, and reads pages as clean text.
|
|
122
123
|
- **Private.** The model runs on your machine, lcode never uploads your files and has no telemetry.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
lcode/__init__.py,sha256=
|
|
1
|
+
lcode/__init__.py,sha256=s1wi6lDwnIt_wcDMaRuhH7baVfU3UfYMQ2ejuBf_W9E,473
|
|
2
2
|
lcode/__main__.py,sha256=s5N5pryX2FAu93GGZ3FxUrIXHWGzxz66lKUoaV_h8Ws,35
|
|
3
3
|
lcode/acp.py,sha256=kRD_uuDDdGXt7xwvXZ8OqePK1HprEV76wWAWkV_gcak,28987
|
|
4
4
|
lcode/action.py,sha256=1r3abjheYFYQ-EUArvotls8pLmE3YjNMGHAy_iF0Z70,16198
|
|
@@ -40,7 +40,7 @@ lcode/web.py,sha256=vyxKbSRJTl1Lj3hj7Yd_NwTgbv0Xi6dup2YfUXjqp-Y,14579
|
|
|
40
40
|
lcode/mcp/__init__.py,sha256=XujtAmRoXW9S0uq1XPf_CAeCkHh_UvA8FCmoAKP9j1Y,171
|
|
41
41
|
lcode/mcp/auth.py,sha256=vyH3oolgIBNJLYabr2h5Soui99NTY1QxjQEgtczfF6A,16655
|
|
42
42
|
lcode/mcp/catalog.py,sha256=I_O10N7cBl0ZW6O7x2kwKn89pvEXUXODUh8FsPXnB-E,3790
|
|
43
|
-
lcode/mcp/catalog.toml,sha256=
|
|
43
|
+
lcode/mcp/catalog.toml,sha256=N8MKyyS_FeCo-RbV3RVZ3ZODro2yk55yqUlymXsWdcQ,12641
|
|
44
44
|
lcode/mcp/client.py,sha256=3Gen7zRvWehngXn_dAMOAdKqFkFkQZIQW96hQcW4N4k,7119
|
|
45
45
|
lcode/mcp/commands.py,sha256=mnp0G-GW5ZsARi2tkHuT3hl883sBGFUVRAV3AapA5oU,16478
|
|
46
46
|
lcode/mcp/config.py,sha256=Ej6WX2b_55a84H5WdAwSk9DzERKoq5ifUBQ9uDgSG_o,8780
|
|
@@ -52,8 +52,9 @@ lcode/mcp/stdio.py,sha256=kC6KHwzMMDjBr-yCZLmyVKS7PZXU299WpZdYRoX5eRQ,6292
|
|
|
52
52
|
lcode/servers/__init__.py,sha256=qrIhxEodEB8cjKEQ4Cicc0GsQhE2OH-9qlVHzr3-tq8,77
|
|
53
53
|
lcode/servers/encord.py,sha256=WSauawL1UKYZCUAG1oalcKL_An3DVof-JM6xIgFyQSE,12769
|
|
54
54
|
lcode/servers/valohai.py,sha256=e5xZGIoa4cja4f5i85q1GpL9c7cglUjV8jN2co1CFMY,14430
|
|
55
|
-
|
|
56
|
-
lcode_cli-0.
|
|
57
|
-
lcode_cli-0.
|
|
58
|
-
lcode_cli-0.
|
|
59
|
-
lcode_cli-0.
|
|
55
|
+
lcode/servers/xai.py,sha256=h2emAvHSnkuB9WmJInlynsDhvxJwNVD2AHFX7vqMXNc,13627
|
|
56
|
+
lcode_cli-0.22.0.dist-info/METADATA,sha256=3pd56TbC41-H0Ta-kGkl1jn461kw3OqcIOLp-2DZrJE,12620
|
|
57
|
+
lcode_cli-0.22.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
58
|
+
lcode_cli-0.22.0.dist-info/entry_points.txt,sha256=Hxsn_itbnbgft3U0FwxWdmEt0yoAV2LB-nJDnL9WT20,172
|
|
59
|
+
lcode_cli-0.22.0.dist-info/licenses/LICENSE,sha256=JWjmoL7nNIC1XUa-dZm2hwLUgjUQMFo6k8APb0idNzs,1074
|
|
60
|
+
lcode_cli-0.22.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|