tweekit 1.8.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.
- clients/__init__.py +1 -0
- clients/python/__init__.py +5 -0
- clients/python/tweekit_client.py +462 -0
- plugin_proxy.py +315 -0
- server.py +1056 -0
- tweekit/__init__.py +28 -0
- tweekit-1.8.0.data/data/share/tweekit_mcp/server.json +49 -0
- tweekit-1.8.0.dist-info/METADATA +1169 -0
- tweekit-1.8.0.dist-info/RECORD +14 -0
- tweekit-1.8.0.dist-info/WHEEL +5 -0
- tweekit-1.8.0.dist-info/entry_points.txt +3 -0
- tweekit-1.8.0.dist-info/licenses/LICENSE +21 -0
- tweekit-1.8.0.dist-info/top_level.txt +5 -0
- tweekit_mcp/__init__.py +62 -0
clients/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Client helper packages for TweekIT MCP."""
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async & direct Python SDK helpers for the TweekIT MCP server and REST API.
|
|
3
|
+
|
|
4
|
+
Published by AMPS dba Equilibrium (`tweekit-mcp` on PyPI).
|
|
5
|
+
|
|
6
|
+
Usage (MCP Client):
|
|
7
|
+
from tweekit_mcp import TweekitClient
|
|
8
|
+
import asyncio
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
async def main():
|
|
12
|
+
async with TweekitClient() as tweekit:
|
|
13
|
+
await tweekit.list_tools()
|
|
14
|
+
check = await tweekit.preflight("diagram.dwg", target_provider="claude")
|
|
15
|
+
output = await tweekit.convert_file(Path("input.pdf"), outfmt="png")
|
|
16
|
+
print(check, output)
|
|
17
|
+
|
|
18
|
+
asyncio.run(main())
|
|
19
|
+
|
|
20
|
+
Usage (Direct REST Client — no MCP hop required):
|
|
21
|
+
from tweekit_mcp import TweekitDirectClient
|
|
22
|
+
import asyncio
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
async with TweekitDirectClient() as api:
|
|
26
|
+
info = api.preflight("photo.cr2", target_provider="openai")
|
|
27
|
+
result = await api.convert_url("https://example.com/sample.tiff", outfmt="png")
|
|
28
|
+
print(info, result)
|
|
29
|
+
|
|
30
|
+
asyncio.run(main())
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import asyncio
|
|
36
|
+
import base64
|
|
37
|
+
import os
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
from fastmcp import Client
|
|
42
|
+
|
|
43
|
+
import server as _server_mod
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TweekitClient:
|
|
47
|
+
"""Async wrapper around the FastMCP Client for TweekIT MCP conversions and tools."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
server_url: str | None = None,
|
|
52
|
+
api_key: str | None = None,
|
|
53
|
+
api_secret: str | None = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
self.server_url = server_url or os.getenv("TWEEKIT_MCP_SERVER", "https://mcp.tweekit.io/mcp")
|
|
56
|
+
self.api_key = api_key or os.getenv("TWEEKIT_API_KEY")
|
|
57
|
+
self.api_secret = api_secret or os.getenv("TWEEKIT_API_SECRET")
|
|
58
|
+
self._client: Client | None = None
|
|
59
|
+
|
|
60
|
+
async def __aenter__(self) -> "TweekitClient":
|
|
61
|
+
self._client = Client(self.server_url)
|
|
62
|
+
await self._client.__aenter__()
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
async def __aexit__(self, exc_type, exc, tb) -> None: # type: ignore[override]
|
|
66
|
+
if self._client is not None:
|
|
67
|
+
await self._client.__aexit__(exc_type, exc, tb)
|
|
68
|
+
self._client = None
|
|
69
|
+
|
|
70
|
+
async def list_tools(self) -> list[str]:
|
|
71
|
+
client = self._ensure_client()
|
|
72
|
+
tools = await client.list_tools()
|
|
73
|
+
return [tool.name for tool in tools]
|
|
74
|
+
|
|
75
|
+
async def doctype(self, extension: str = "*", *, ext: str | None = None) -> Any:
|
|
76
|
+
client = self._ensure_client()
|
|
77
|
+
resolved_ext = ext if ext is not None else extension
|
|
78
|
+
payload = {
|
|
79
|
+
"extension": resolved_ext,
|
|
80
|
+
"apiKey": self._require_key(),
|
|
81
|
+
"apiSecret": self._require_secret(),
|
|
82
|
+
}
|
|
83
|
+
result = await client.call_tool("doctype", payload)
|
|
84
|
+
if result.is_error:
|
|
85
|
+
raise RuntimeError("TweekIT doctype tool returned an error response.")
|
|
86
|
+
return self._unwrap_result(result)
|
|
87
|
+
|
|
88
|
+
async def preflight(self, filename_or_ext: str, target_provider: str = "default") -> Any:
|
|
89
|
+
client = self._ensure_client()
|
|
90
|
+
payload = {
|
|
91
|
+
"filename_or_ext": filename_or_ext,
|
|
92
|
+
"target_provider": target_provider,
|
|
93
|
+
}
|
|
94
|
+
result = await client.call_tool("preflight", payload)
|
|
95
|
+
if result.is_error:
|
|
96
|
+
raise RuntimeError("TweekIT preflight tool returned an error response.")
|
|
97
|
+
return self._unwrap_result(result)
|
|
98
|
+
|
|
99
|
+
async def convert_bytes(
|
|
100
|
+
self,
|
|
101
|
+
data: bytes,
|
|
102
|
+
inext: str,
|
|
103
|
+
outfmt: str,
|
|
104
|
+
*,
|
|
105
|
+
no_rasterize: bool = False,
|
|
106
|
+
width: int = 0,
|
|
107
|
+
height: int = 0,
|
|
108
|
+
x1: int = 0,
|
|
109
|
+
y1: int = 0,
|
|
110
|
+
x2: int = 0,
|
|
111
|
+
y2: int = 0,
|
|
112
|
+
page: int = 1,
|
|
113
|
+
alpha: bool = True,
|
|
114
|
+
bgcolor: str = "",
|
|
115
|
+
) -> Any:
|
|
116
|
+
client = self._ensure_client()
|
|
117
|
+
payload: dict[str, Any] = {
|
|
118
|
+
"apiKey": self._require_key(),
|
|
119
|
+
"apiSecret": self._require_secret(),
|
|
120
|
+
"inext": inext.lstrip(".") or "bin",
|
|
121
|
+
"outfmt": outfmt,
|
|
122
|
+
"blob": base64.b64encode(data).decode("ascii"),
|
|
123
|
+
"noRasterize": no_rasterize,
|
|
124
|
+
"width": width,
|
|
125
|
+
"height": height,
|
|
126
|
+
"x1": x1,
|
|
127
|
+
"y1": y1,
|
|
128
|
+
"x2": x2,
|
|
129
|
+
"y2": y2,
|
|
130
|
+
"page": page,
|
|
131
|
+
"alpha": alpha,
|
|
132
|
+
"bgColor": bgcolor,
|
|
133
|
+
}
|
|
134
|
+
result = await client.call_tool("convert", payload)
|
|
135
|
+
if result.is_error:
|
|
136
|
+
raise RuntimeError("TweekIT convert tool returned an error response.")
|
|
137
|
+
return self._unwrap_result(result)
|
|
138
|
+
|
|
139
|
+
async def convert_file(
|
|
140
|
+
self,
|
|
141
|
+
path: Path | str,
|
|
142
|
+
outfmt: str,
|
|
143
|
+
*,
|
|
144
|
+
inext: str | None = None,
|
|
145
|
+
no_rasterize: bool = False,
|
|
146
|
+
width: int = 0,
|
|
147
|
+
height: int = 0,
|
|
148
|
+
x1: int = 0,
|
|
149
|
+
y1: int = 0,
|
|
150
|
+
x2: int = 0,
|
|
151
|
+
y2: int = 0,
|
|
152
|
+
page: int = 1,
|
|
153
|
+
alpha: bool = True,
|
|
154
|
+
bgcolor: str = "",
|
|
155
|
+
) -> Any:
|
|
156
|
+
file_path = Path(path)
|
|
157
|
+
client = self._ensure_client()
|
|
158
|
+
payload: dict[str, Any] = {
|
|
159
|
+
"apiKey": self._require_key(),
|
|
160
|
+
"apiSecret": self._require_secret(),
|
|
161
|
+
"inext": (inext or file_path.suffix).lstrip(".") or "bin",
|
|
162
|
+
"outfmt": outfmt,
|
|
163
|
+
"blob": self._encode_file(file_path),
|
|
164
|
+
"width": width,
|
|
165
|
+
"height": height,
|
|
166
|
+
"page": page,
|
|
167
|
+
"bgColor": bgcolor,
|
|
168
|
+
}
|
|
169
|
+
if no_rasterize:
|
|
170
|
+
payload["noRasterize"] = no_rasterize
|
|
171
|
+
if any((x1, y1, x2, y2)):
|
|
172
|
+
payload.update({"x1": x1, "y1": y1, "x2": x2, "y2": y2})
|
|
173
|
+
if not alpha:
|
|
174
|
+
payload["alpha"] = alpha
|
|
175
|
+
result = await client.call_tool("convert", payload)
|
|
176
|
+
if result.is_error:
|
|
177
|
+
raise RuntimeError("TweekIT convert tool returned an error response.")
|
|
178
|
+
return self._unwrap_result(result)
|
|
179
|
+
|
|
180
|
+
async def convert_url(
|
|
181
|
+
self,
|
|
182
|
+
url: str,
|
|
183
|
+
outfmt: str,
|
|
184
|
+
*,
|
|
185
|
+
inext: str | None = None,
|
|
186
|
+
no_rasterize: bool = False,
|
|
187
|
+
width: int = 0,
|
|
188
|
+
height: int = 0,
|
|
189
|
+
x1: int = 0,
|
|
190
|
+
y1: int = 0,
|
|
191
|
+
x2: int = 0,
|
|
192
|
+
y2: int = 0,
|
|
193
|
+
page: int = 1,
|
|
194
|
+
alpha: bool = True,
|
|
195
|
+
bgcolor: str = "",
|
|
196
|
+
fetch_headers: dict[str, str] | None = None,
|
|
197
|
+
) -> Any:
|
|
198
|
+
client = self._ensure_client()
|
|
199
|
+
payload: dict[str, Any] = {
|
|
200
|
+
"apiKey": self._require_key(),
|
|
201
|
+
"apiSecret": self._require_secret(),
|
|
202
|
+
"url": url,
|
|
203
|
+
"outfmt": outfmt,
|
|
204
|
+
"noRasterize": no_rasterize,
|
|
205
|
+
"width": width,
|
|
206
|
+
"height": height,
|
|
207
|
+
"x1": x1,
|
|
208
|
+
"y1": y1,
|
|
209
|
+
"x2": x2,
|
|
210
|
+
"y2": y2,
|
|
211
|
+
"page": page,
|
|
212
|
+
"alpha": alpha,
|
|
213
|
+
"bgColor": bgcolor,
|
|
214
|
+
}
|
|
215
|
+
if inext:
|
|
216
|
+
payload["inext"] = inext
|
|
217
|
+
if fetch_headers:
|
|
218
|
+
payload["fetchHeaders"] = fetch_headers
|
|
219
|
+
|
|
220
|
+
result = await client.call_tool("convert_url", payload)
|
|
221
|
+
if result.is_error:
|
|
222
|
+
raise RuntimeError("TweekIT convert_url tool returned an error response.")
|
|
223
|
+
return self._unwrap_result(result)
|
|
224
|
+
|
|
225
|
+
async def batch_convert(
|
|
226
|
+
self,
|
|
227
|
+
items: list[dict[str, Any]],
|
|
228
|
+
*,
|
|
229
|
+
concurrency: int = 5,
|
|
230
|
+
) -> list[Any]:
|
|
231
|
+
"""Convert multiple items concurrently (each item may have 'path', 'url', or 'data')."""
|
|
232
|
+
sem = asyncio.Semaphore(max(1, concurrency))
|
|
233
|
+
|
|
234
|
+
async def _process(item: dict[str, Any]) -> Any:
|
|
235
|
+
async with sem:
|
|
236
|
+
outfmt = item["outfmt"]
|
|
237
|
+
common = {
|
|
238
|
+
k: item[k]
|
|
239
|
+
for k in ("inext", "no_rasterize", "width", "height", "x1", "y1", "x2", "y2", "page", "alpha", "bgcolor")
|
|
240
|
+
if k in item
|
|
241
|
+
}
|
|
242
|
+
if "url" in item:
|
|
243
|
+
if "fetch_headers" in item:
|
|
244
|
+
common["fetch_headers"] = item["fetch_headers"]
|
|
245
|
+
return await self.convert_url(item["url"], outfmt=outfmt, **common)
|
|
246
|
+
if "data" in item:
|
|
247
|
+
inext = common.pop("inext", "bin")
|
|
248
|
+
return await self.convert_bytes(item["data"], inext=inext, outfmt=outfmt, **common)
|
|
249
|
+
if "path" in item:
|
|
250
|
+
return await self.convert_file(item["path"], outfmt=outfmt, **common)
|
|
251
|
+
raise ValueError("Each batch item must include 'path', 'url', or 'data'.")
|
|
252
|
+
|
|
253
|
+
return await asyncio.gather(*(_process(item) for item in items))
|
|
254
|
+
|
|
255
|
+
async def delete_document(self, doc_id: str) -> Any:
|
|
256
|
+
client = self._ensure_client()
|
|
257
|
+
payload = {
|
|
258
|
+
"docId": doc_id,
|
|
259
|
+
"apiKey": self._require_key(),
|
|
260
|
+
"apiSecret": self._require_secret(),
|
|
261
|
+
}
|
|
262
|
+
result = await client.call_tool("delete_document", payload)
|
|
263
|
+
if result.is_error:
|
|
264
|
+
raise RuntimeError("TweekIT delete_document tool returned an error response.")
|
|
265
|
+
return self._unwrap_result(result)
|
|
266
|
+
|
|
267
|
+
async def fetch(self, url: str) -> Any:
|
|
268
|
+
client = self._ensure_client()
|
|
269
|
+
result = await client.call_tool("fetch", {"url": url})
|
|
270
|
+
if result.is_error:
|
|
271
|
+
raise RuntimeError("TweekIT fetch tool returned an error response.")
|
|
272
|
+
return self._unwrap_result(result)
|
|
273
|
+
|
|
274
|
+
async def search(self, query: str, max_results: int = 5) -> Any:
|
|
275
|
+
client = self._ensure_client()
|
|
276
|
+
result = await client.call_tool("search", {"query": query, "max_results": max_results})
|
|
277
|
+
if result.is_error:
|
|
278
|
+
raise RuntimeError("TweekIT search tool returned an error response.")
|
|
279
|
+
return self._unwrap_result(result)
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
def _unwrap_result(result: Any) -> Any:
|
|
283
|
+
return result.data or result.structured_content or [block.model_dump() for block in result.content]
|
|
284
|
+
|
|
285
|
+
def _ensure_client(self) -> Client:
|
|
286
|
+
if self._client is None:
|
|
287
|
+
raise RuntimeError("Client not connected. Use 'async with TweekitClient()'.")
|
|
288
|
+
return self._client
|
|
289
|
+
|
|
290
|
+
def _require_key(self) -> str:
|
|
291
|
+
if not self.api_key:
|
|
292
|
+
raise RuntimeError("Missing TWEEKIT_API_KEY.")
|
|
293
|
+
return self.api_key
|
|
294
|
+
|
|
295
|
+
def _require_secret(self) -> str:
|
|
296
|
+
if not self.api_secret:
|
|
297
|
+
raise RuntimeError("Missing TWEEKIT_API_SECRET.")
|
|
298
|
+
return self.api_secret
|
|
299
|
+
|
|
300
|
+
@staticmethod
|
|
301
|
+
def _encode_file(path: Path) -> str:
|
|
302
|
+
data = path.read_bytes()
|
|
303
|
+
return base64.b64encode(data).decode("ascii")
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class TweekitDirectClient:
|
|
307
|
+
"""Direct async Python SDK for TweekIT REST API (no MCP server intermediary needed)."""
|
|
308
|
+
|
|
309
|
+
def __init__(
|
|
310
|
+
self,
|
|
311
|
+
api_key: str | None = None,
|
|
312
|
+
api_secret: str | None = None,
|
|
313
|
+
base_url: str | None = None,
|
|
314
|
+
) -> None:
|
|
315
|
+
self.api_key = api_key or os.getenv("TWEEKIT_API_KEY")
|
|
316
|
+
self.api_secret = api_secret or os.getenv("TWEEKIT_API_SECRET")
|
|
317
|
+
self.base_url = (base_url or os.getenv("TWEEKIT_API_BASE_URL") or _server_mod.BASE_URL).rstrip("/") + "/"
|
|
318
|
+
|
|
319
|
+
async def __aenter__(self) -> "TweekitDirectClient":
|
|
320
|
+
return self
|
|
321
|
+
|
|
322
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
def preflight(filename_or_ext: str, target_provider: str = "default") -> dict[str, Any]:
|
|
327
|
+
"""Run instant local AI Hub preflight check and format recommendation."""
|
|
328
|
+
return _server_mod.recommend_ai_format(filename_or_ext, target_provider=target_provider)
|
|
329
|
+
|
|
330
|
+
async def version(self) -> str:
|
|
331
|
+
fn = getattr(_server_mod.version, "fn", _server_mod.version)
|
|
332
|
+
return await fn()
|
|
333
|
+
|
|
334
|
+
async def doctype(self, extension: str = "*") -> dict[str, Any]:
|
|
335
|
+
fn = getattr(_server_mod.doctype, "fn", _server_mod.doctype)
|
|
336
|
+
return await fn(apiKey=self.api_key, apiSecret=self.api_secret, extension=extension)
|
|
337
|
+
|
|
338
|
+
async def convert_bytes(
|
|
339
|
+
self,
|
|
340
|
+
data: bytes,
|
|
341
|
+
inext: str,
|
|
342
|
+
outfmt: str,
|
|
343
|
+
*,
|
|
344
|
+
no_rasterize: bool = False,
|
|
345
|
+
width: int = 0,
|
|
346
|
+
height: int = 0,
|
|
347
|
+
x1: int = 0,
|
|
348
|
+
y1: int = 0,
|
|
349
|
+
x2: int = 0,
|
|
350
|
+
y2: int = 0,
|
|
351
|
+
page: int = 1,
|
|
352
|
+
alpha: bool = True,
|
|
353
|
+
bgcolor: str = "",
|
|
354
|
+
) -> Any:
|
|
355
|
+
key, secret = _server_mod._resolve_credentials(self.api_key, self.api_secret)
|
|
356
|
+
blob = base64.b64encode(data).decode("ascii")
|
|
357
|
+
return await _server_mod._convert_impl(
|
|
358
|
+
apiKey=key,
|
|
359
|
+
apiSecret=secret,
|
|
360
|
+
inext=_server_mod._normalize_extension(inext) or "bin",
|
|
361
|
+
outfmt=outfmt,
|
|
362
|
+
blob=blob,
|
|
363
|
+
noRasterize=no_rasterize,
|
|
364
|
+
width=width,
|
|
365
|
+
height=height,
|
|
366
|
+
x1=x1,
|
|
367
|
+
y1=y1,
|
|
368
|
+
x2=x2,
|
|
369
|
+
y2=y2,
|
|
370
|
+
page=page,
|
|
371
|
+
alpha=alpha,
|
|
372
|
+
bgColor=bgcolor,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
async def convert_file(
|
|
376
|
+
self,
|
|
377
|
+
path: Path | str,
|
|
378
|
+
outfmt: str,
|
|
379
|
+
*,
|
|
380
|
+
output_path: Path | str | None = None,
|
|
381
|
+
inext: str | None = None,
|
|
382
|
+
no_rasterize: bool = False,
|
|
383
|
+
width: int = 0,
|
|
384
|
+
height: int = 0,
|
|
385
|
+
x1: int = 0,
|
|
386
|
+
y1: int = 0,
|
|
387
|
+
x2: int = 0,
|
|
388
|
+
y2: int = 0,
|
|
389
|
+
page: int = 1,
|
|
390
|
+
alpha: bool = True,
|
|
391
|
+
bgcolor: str = "",
|
|
392
|
+
) -> Any:
|
|
393
|
+
fn = getattr(_server_mod.convert_file, "fn", _server_mod.convert_file)
|
|
394
|
+
return await fn(
|
|
395
|
+
file_path=str(path),
|
|
396
|
+
outfmt=outfmt,
|
|
397
|
+
output_path=str(output_path) if output_path else None,
|
|
398
|
+
apiKey=self.api_key,
|
|
399
|
+
apiSecret=self.api_secret,
|
|
400
|
+
inext=inext,
|
|
401
|
+
noRasterize=no_rasterize,
|
|
402
|
+
width=width,
|
|
403
|
+
height=height,
|
|
404
|
+
x1=x1,
|
|
405
|
+
y1=y1,
|
|
406
|
+
x2=x2,
|
|
407
|
+
y2=y2,
|
|
408
|
+
page=page,
|
|
409
|
+
alpha=alpha,
|
|
410
|
+
bgColor=bgcolor,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
async def convert_url(
|
|
414
|
+
self,
|
|
415
|
+
url: str,
|
|
416
|
+
outfmt: str,
|
|
417
|
+
*,
|
|
418
|
+
inext: str | None = None,
|
|
419
|
+
no_rasterize: bool = False,
|
|
420
|
+
width: int = 0,
|
|
421
|
+
height: int = 0,
|
|
422
|
+
x1: int = 0,
|
|
423
|
+
y1: int = 0,
|
|
424
|
+
x2: int = 0,
|
|
425
|
+
y2: int = 0,
|
|
426
|
+
page: int = 1,
|
|
427
|
+
alpha: bool = True,
|
|
428
|
+
bgcolor: str = "",
|
|
429
|
+
fetch_headers: dict[str, str] | None = None,
|
|
430
|
+
) -> Any:
|
|
431
|
+
key, secret = _server_mod._resolve_credentials(self.api_key, self.api_secret)
|
|
432
|
+
return await _server_mod._convert_url_impl(
|
|
433
|
+
apiKey=key,
|
|
434
|
+
apiSecret=secret,
|
|
435
|
+
url=url,
|
|
436
|
+
outfmt=outfmt,
|
|
437
|
+
inext=inext,
|
|
438
|
+
noRasterize=no_rasterize,
|
|
439
|
+
width=width,
|
|
440
|
+
height=height,
|
|
441
|
+
x1=x1,
|
|
442
|
+
y1=y1,
|
|
443
|
+
x2=x2,
|
|
444
|
+
y2=y2,
|
|
445
|
+
page=page,
|
|
446
|
+
alpha=alpha,
|
|
447
|
+
bgColor=bgcolor,
|
|
448
|
+
fetchHeaders=fetch_headers,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
async def delete_document(self, doc_id: str) -> dict[str, Any]:
|
|
452
|
+
fn = getattr(_server_mod.delete_document, "fn", _server_mod.delete_document)
|
|
453
|
+
return await fn(docId=doc_id, apiKey=self.api_key, apiSecret=self.api_secret)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
async def _demo() -> None:
|
|
457
|
+
async with TweekitClient() as tweekit:
|
|
458
|
+
print("Available tools:", await tweekit.list_tools())
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
if __name__ == "__main__":
|
|
462
|
+
asyncio.run(_demo())
|