owui-cli 0.1.0__tar.gz → 0.3.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.
- owui_cli-0.3.0/.github/workflows/publish.yml +16 -0
- {owui_cli-0.1.0 → owui_cli-0.3.0}/.gitignore +0 -1
- {owui_cli-0.1.0 → owui_cli-0.3.0}/PKG-INFO +7 -3
- {owui_cli-0.1.0 → owui_cli-0.3.0}/README.md +6 -2
- {owui_cli-0.1.0 → owui_cli-0.3.0}/pyproject.toml +1 -1
- owui_cli-0.3.0/src/owui_cli/__init__.py +1 -0
- {owui_cli-0.1.0 → owui_cli-0.3.0}/src/owui_cli/cli.py +173 -0
- owui_cli-0.3.0/update-schema.py +121 -0
- owui_cli-0.1.0/src/owui_cli/__init__.py +0 -1
- {owui_cli-0.1.0 → owui_cli-0.3.0}/LICENSE +0 -0
- {owui_cli-0.1.0 → owui_cli-0.3.0}/src/owui_cli/data/api-schema.json +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v6
|
|
15
|
+
- run: uv build
|
|
16
|
+
- run: uv publish --trusted-publishing always
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: owui-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Admin CLI for Open WebUI instances
|
|
5
5
|
Project-URL: Homepage, https://github.com/rndmcnlly/owui-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/rndmcnlly/owui-cli
|
|
@@ -30,9 +30,13 @@ uvx owui-cli models show gpt-4o
|
|
|
30
30
|
uvx owui-cli schema knowledge
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Install
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
```bash
|
|
36
|
+
uvx owui-cli help # run without installing
|
|
37
|
+
uv tool install owui-cli # or install globally
|
|
38
|
+
pip install owui-cli # or via pip
|
|
39
|
+
```
|
|
36
40
|
|
|
37
41
|
## Auth
|
|
38
42
|
|
|
@@ -11,9 +11,13 @@ uvx owui-cli models show gpt-4o
|
|
|
11
11
|
uvx owui-cli schema knowledge
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Install
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
```bash
|
|
17
|
+
uvx owui-cli help # run without installing
|
|
18
|
+
uv tool install owui-cli # or install globally
|
|
19
|
+
pip install owui-cli # or via pip
|
|
20
|
+
```
|
|
17
21
|
|
|
18
22
|
## Auth
|
|
19
23
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -10,6 +10,7 @@ Commands:
|
|
|
10
10
|
Use --json for machine-readable output on any command.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
import base64
|
|
13
14
|
import json
|
|
14
15
|
import os
|
|
15
16
|
import re
|
|
@@ -131,6 +132,34 @@ def _parse_frontmatter(raw: str) -> tuple[dict[str, str], str]:
|
|
|
131
132
|
return meta, raw[fm.end():]
|
|
132
133
|
|
|
133
134
|
|
|
135
|
+
def _write_file(path: str, content: str | bytes):
|
|
136
|
+
"""Write content to a file, creating parent directories as needed."""
|
|
137
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
138
|
+
mode = "wb" if isinstance(content, bytes) else "w"
|
|
139
|
+
with open(path, mode) as f:
|
|
140
|
+
f.write(content)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _write_json(path: str, obj):
|
|
144
|
+
"""Write JSON to a file, creating parent directories as needed."""
|
|
145
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
146
|
+
with open(path, "w") as f:
|
|
147
|
+
json.dump(obj, f, indent=2, ensure_ascii=False)
|
|
148
|
+
f.write("\n")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _extract_data_uri(data_uri: str) -> tuple[str, bytes] | None:
|
|
152
|
+
"""Decode a data:image/*;base64,... URI. Returns (ext, bytes) or None."""
|
|
153
|
+
if not data_uri or not data_uri.startswith("data:image/"):
|
|
154
|
+
return None
|
|
155
|
+
header, _, b64data = data_uri.partition(",")
|
|
156
|
+
if not b64data:
|
|
157
|
+
return None
|
|
158
|
+
mime = header.split(";")[0].replace("data:", "")
|
|
159
|
+
ext = mime.split("/")[1] if "/" in mime else "png"
|
|
160
|
+
return ext, base64.b64decode(b64data)
|
|
161
|
+
|
|
162
|
+
|
|
134
163
|
def _slugify(title: str) -> str:
|
|
135
164
|
return re.sub(r"[^a-z0-9]+", title.lower(), "").strip("_") if not title else re.sub(r"[^a-z0-9]+", "_", title.lower()).strip("_")
|
|
136
165
|
|
|
@@ -162,6 +191,7 @@ class Resource:
|
|
|
162
191
|
self._commands["show"] = (self.cmd_show, "<id>", (1, 1))
|
|
163
192
|
self._commands["deploy"] = (self.cmd_deploy, f"<source{self.deploy_ext}> [id]", (1, 2))
|
|
164
193
|
self._commands["pull"] = (self.cmd_pull, "<id>", (1, 1))
|
|
194
|
+
self._commands["pull-all"] = (self.cmd_pull_all, "[dir]", (0, 1))
|
|
165
195
|
self._commands["delete"] = (self.cmd_delete, "<id>", (1, 1))
|
|
166
196
|
|
|
167
197
|
def add_command(self, name, fn, arg_spec, arg_range):
|
|
@@ -281,6 +311,36 @@ class Resource:
|
|
|
281
311
|
if content and not content.endswith("\n"):
|
|
282
312
|
sys.stdout.write("\n")
|
|
283
313
|
|
|
314
|
+
def cmd_pull_all(self, url: str, token: str, out_dir: str = "."):
|
|
315
|
+
"""Pull all items into <out_dir>/<id>/source + <out_dir>/<id>/meta.json."""
|
|
316
|
+
src_name = {"tools": "tool.py", "functions": "function.py", "skills": "skill.md"}.get(self.name, "source")
|
|
317
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
318
|
+
data = _get(c, url, f"{self.prefix}/", token).json()
|
|
319
|
+
items = data if isinstance(data, list) else data.get("items", data.get("data", []))
|
|
320
|
+
ids = sorted(item.get("id", "") for item in items)
|
|
321
|
+
if not ids:
|
|
322
|
+
out("(none)")
|
|
323
|
+
return
|
|
324
|
+
count = 0
|
|
325
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
326
|
+
for item_id in ids:
|
|
327
|
+
r = c.get(_api(url, self.item_path(item_id)), headers=_headers(token))
|
|
328
|
+
if r.status_code == 404:
|
|
329
|
+
print(f" skip {item_id} (not found)", file=sys.stderr)
|
|
330
|
+
continue
|
|
331
|
+
r.raise_for_status()
|
|
332
|
+
item = r.json()
|
|
333
|
+
item_dir = os.path.join(out_dir, item_id)
|
|
334
|
+
# Write source
|
|
335
|
+
content = item.pop(self.content_key, "")
|
|
336
|
+
_write_file(os.path.join(item_dir, src_name), content if content.endswith("\n") else content + "\n")
|
|
337
|
+
# Write metadata (everything except source content)
|
|
338
|
+
_write_json(os.path.join(item_dir, "meta.json"), item)
|
|
339
|
+
count += 1
|
|
340
|
+
if not JSON_OUTPUT:
|
|
341
|
+
print(f" {item_id}")
|
|
342
|
+
out(f"pulled {count} {self.name}")
|
|
343
|
+
|
|
284
344
|
def cmd_delete(self, url: str, token: str, item_id: str):
|
|
285
345
|
with httpx.Client(timeout=TIMEOUT) as c:
|
|
286
346
|
r = c.get(_api(url, self.item_path(item_id)), headers=_headers(token))
|
|
@@ -356,6 +416,36 @@ class SkillsResource(Resource):
|
|
|
356
416
|
if content and not content.endswith("\n"):
|
|
357
417
|
sys.stdout.write("\n")
|
|
358
418
|
|
|
419
|
+
def cmd_pull_all(self, url: str, token: str, out_dir: str = "."):
|
|
420
|
+
"""Pull all skills into <out_dir>/<id>/skill.md (with frontmatter) + meta.json."""
|
|
421
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
422
|
+
data = _get(c, url, f"{self.prefix}/", token).json()
|
|
423
|
+
items = data if isinstance(data, list) else data.get("items", data.get("data", []))
|
|
424
|
+
ids = sorted(item.get("id", "") for item in items)
|
|
425
|
+
if not ids:
|
|
426
|
+
out("(none)")
|
|
427
|
+
return
|
|
428
|
+
count = 0
|
|
429
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
430
|
+
for item_id in ids:
|
|
431
|
+
r = c.get(_api(url, self.item_path(item_id)), headers=_headers(token))
|
|
432
|
+
if r.status_code == 404:
|
|
433
|
+
print(f" skip {item_id} (not found)", file=sys.stderr)
|
|
434
|
+
continue
|
|
435
|
+
r.raise_for_status()
|
|
436
|
+
item = r.json()
|
|
437
|
+
item_dir = os.path.join(out_dir, item_id)
|
|
438
|
+
# Write skill.md with frontmatter
|
|
439
|
+
content = item.pop("content", "")
|
|
440
|
+
fm = f"---\nid: {item_id}\nname: {item.get('name','')}\ndescription: {item.get('description','')}\n---\n"
|
|
441
|
+
_write_file(os.path.join(item_dir, "skill.md"), fm + content + ("" if content.endswith("\n") else "\n"))
|
|
442
|
+
# Write metadata
|
|
443
|
+
_write_json(os.path.join(item_dir, "meta.json"), item)
|
|
444
|
+
count += 1
|
|
445
|
+
if not JSON_OUTPUT:
|
|
446
|
+
print(f" {item_id}")
|
|
447
|
+
out(f"pulled {count} skills")
|
|
448
|
+
|
|
359
449
|
def cmd_toggle(self, url: str, token: str, skill_id: str):
|
|
360
450
|
with httpx.Client(timeout=TIMEOUT) as c:
|
|
361
451
|
r = _post(c, url, f"{self.item_path(skill_id)}/toggle", token)
|
|
@@ -415,6 +505,7 @@ def models_show(url, token, model_id):
|
|
|
415
505
|
("base", info.get("base_model_id","(none)")),
|
|
416
506
|
("active", str(info.get("is_active","?"))),
|
|
417
507
|
("tools", ", ".join(meta.get("toolIds") or []) or "(none)"),
|
|
508
|
+
("filters", ", ".join(params.get("filter_ids") or []) or "(none)"),
|
|
418
509
|
("knowledge", ", ".join(k.get("name","?") for k in (meta.get("knowledge") or [])) or "(none)"),
|
|
419
510
|
("system", f"{len(params.get('system',''))} chars"),
|
|
420
511
|
("grants", str(len(info.get("access_grants") or [])))]
|
|
@@ -440,6 +531,85 @@ def models_delete(url, token, model_id):
|
|
|
440
531
|
_post(c, url, "/api/v1/models/model/delete", token, {"id": model_id})
|
|
441
532
|
out(f"deleted {model_id}")
|
|
442
533
|
|
|
534
|
+
def _models_fetch(c, url, token, model_id):
|
|
535
|
+
"""Fetch a model by ID, returning the parsed JSON."""
|
|
536
|
+
r = _get(c, url, f"/api/v1/models/model?id={model_id}", token)
|
|
537
|
+
return r.json()
|
|
538
|
+
|
|
539
|
+
def models_set_tools(url, token, model_id, *tool_ids):
|
|
540
|
+
"""Set the tool bindings for a workspace model (pass no IDs to clear)."""
|
|
541
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
542
|
+
model = _models_fetch(c, url, token, model_id)
|
|
543
|
+
info = model.get("info") or {}
|
|
544
|
+
meta = info.setdefault("meta", {})
|
|
545
|
+
params = info.setdefault("params", {})
|
|
546
|
+
ids = list(tool_ids)
|
|
547
|
+
meta["toolIds"] = ids
|
|
548
|
+
# keep params.tool_ids in sync (used by some OWUI versions)
|
|
549
|
+
params["tool_ids"] = ids
|
|
550
|
+
model["info"] = info
|
|
551
|
+
r = _post(c, url, "/api/v1/models/model/update", token, model)
|
|
552
|
+
label = ", ".join(ids) if ids else "(none)"
|
|
553
|
+
out(f"tools for {model_id}: {label}")
|
|
554
|
+
|
|
555
|
+
def models_set_filters(url, token, model_id, *filter_ids):
|
|
556
|
+
"""Set the filter bindings for a workspace model (pass no IDs to clear)."""
|
|
557
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
558
|
+
model = _models_fetch(c, url, token, model_id)
|
|
559
|
+
info = model.get("info") or {}
|
|
560
|
+
params = info.setdefault("params", {})
|
|
561
|
+
ids = list(filter_ids)
|
|
562
|
+
params["filter_ids"] = ids
|
|
563
|
+
model["info"] = info
|
|
564
|
+
r = _post(c, url, "/api/v1/models/model/update", token, model)
|
|
565
|
+
label = ", ".join(ids) if ids else "(none)"
|
|
566
|
+
out(f"filters for {model_id}: {label}")
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def models_pull_all(url, token, out_dir="."):
|
|
570
|
+
"""Pull all workspace models into <out_dir>/<id>/model.json, extracting profile images."""
|
|
571
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
572
|
+
data = _get(c, url, "/api/v1/models", token).json().get("data", [])
|
|
573
|
+
|
|
574
|
+
# Filter to workspace models: those with a base_model_id in their info
|
|
575
|
+
# (raw connection proxies have no info or no base_model_id)
|
|
576
|
+
workspace = []
|
|
577
|
+
for m in data:
|
|
578
|
+
info = m.get("info") or {}
|
|
579
|
+
base = info.get("base_model_id", "")
|
|
580
|
+
if base:
|
|
581
|
+
workspace.append(m)
|
|
582
|
+
|
|
583
|
+
if not workspace:
|
|
584
|
+
out("(no workspace models)")
|
|
585
|
+
return
|
|
586
|
+
|
|
587
|
+
count = 0
|
|
588
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
589
|
+
for m in sorted(workspace, key=lambda m: m.get("id", "")):
|
|
590
|
+
model_id = m.get("id", "")
|
|
591
|
+
# Fetch full model data
|
|
592
|
+
r = _get(c, url, f"/api/v1/models/model?id={model_id}", token)
|
|
593
|
+
model = r.json()
|
|
594
|
+
model_dir = os.path.join(out_dir, model_id)
|
|
595
|
+
|
|
596
|
+
# Extract profile image from data URI (top-level meta, not info.meta)
|
|
597
|
+
top_meta = model.get("meta") or {}
|
|
598
|
+
img_url = top_meta.get("profile_image_url", "")
|
|
599
|
+
extracted = _extract_data_uri(img_url)
|
|
600
|
+
if extracted:
|
|
601
|
+
ext, img_bytes = extracted
|
|
602
|
+
_write_file(os.path.join(model_dir, f"profile.{ext}"), img_bytes)
|
|
603
|
+
top_meta["profile_image_url"] = f"profile.{ext}"
|
|
604
|
+
|
|
605
|
+
_write_json(os.path.join(model_dir, "model.json"), model)
|
|
606
|
+
count += 1
|
|
607
|
+
if not JSON_OUTPUT:
|
|
608
|
+
img_note = f" +profile.{ext}" if extracted else ""
|
|
609
|
+
print(f" {model_id}{img_note}")
|
|
610
|
+
|
|
611
|
+
out(f"pulled {count} models")
|
|
612
|
+
|
|
443
613
|
|
|
444
614
|
# ── knowledge (special: files subresource, file/remove is destructive) ─
|
|
445
615
|
|
|
@@ -872,6 +1042,9 @@ COMMANDS.update({
|
|
|
872
1042
|
("models", "create"): (models_create, "<model.json>", (1, 1)),
|
|
873
1043
|
("models", "update"): (models_update, "<model.json>", (1, 1)),
|
|
874
1044
|
("models", "delete"): (models_delete, "<id>", (1, 1)),
|
|
1045
|
+
("models", "set-tools"): (models_set_tools, "<id> [tool-id]...", (1, 999)),
|
|
1046
|
+
("models", "set-filters"): (models_set_filters, "<id> [filter-id]...", (1, 999)),
|
|
1047
|
+
("models", "pull-all"): (models_pull_all, "[dir]", (0, 1)),
|
|
875
1048
|
("knowledge", "list"): (knowledge_list, "", (0, 0)),
|
|
876
1049
|
("knowledge", "show"): (knowledge_show, "<id>", (1, 1)),
|
|
877
1050
|
("knowledge", "files"): (knowledge_files, "<id>", (1, 1)),
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.11"
|
|
3
|
+
# dependencies = ["httpx"]
|
|
4
|
+
# ///
|
|
5
|
+
"""Fetch Open WebUI router sources and emit an LLM prompt to regenerate api-schema.json.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
uv run --script update-schema.py [tag-or-branch]
|
|
9
|
+
|
|
10
|
+
Default branch: main. Pass a tag like "v0.8.12" to pin to a release.
|
|
11
|
+
|
|
12
|
+
This fetches all backend router files from GitHub, then prints a self-contained
|
|
13
|
+
prompt to stdout. Pipe it to an LLM (or paste into a session) to produce the
|
|
14
|
+
updated api-schema.json.
|
|
15
|
+
|
|
16
|
+
Example workflow:
|
|
17
|
+
uv run --script update-schema.py v0.9.0 > /tmp/schema-prompt.txt
|
|
18
|
+
# Feed /tmp/schema-prompt.txt to your preferred LLM
|
|
19
|
+
# Save the JSON output to src/owui_cli/data/api-schema.json
|
|
20
|
+
# Update _meta.source and _meta.extracted fields
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
import httpx
|
|
25
|
+
|
|
26
|
+
ROUTERS = [
|
|
27
|
+
"configs", "groups", "chats", "prompts", "users", "auths",
|
|
28
|
+
"models", "tools", "functions", "knowledge", "files", "skills",
|
|
29
|
+
"memories", "channels", "folders", "notes", "evaluations",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
BASE_URL = "https://raw.githubusercontent.com/open-webui/open-webui"
|
|
33
|
+
ROUTER_PATH = "backend/open_webui/routers"
|
|
34
|
+
|
|
35
|
+
SCHEMA_FORMAT = """\
|
|
36
|
+
{
|
|
37
|
+
"_meta": {
|
|
38
|
+
"source": "open-webui/open-webui <REF>",
|
|
39
|
+
"extracted": "<DATE>",
|
|
40
|
+
"auth_levels": {"A": "admin", "V": "verified user", "C": "any authenticated", "N": "no auth"},
|
|
41
|
+
"format": "[METHOD, path, auth, body_or_null, description]"
|
|
42
|
+
},
|
|
43
|
+
"<resource>": {
|
|
44
|
+
"prefix": "/api/v1/<resource>",
|
|
45
|
+
"note": "optional note about quirks",
|
|
46
|
+
"endpoints": [
|
|
47
|
+
["GET", "/", "V", null, "List items"],
|
|
48
|
+
["POST", "/create", "A", "{name,description?}", "Create item"],
|
|
49
|
+
...
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
...
|
|
53
|
+
}"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def fetch_routers(ref: str) -> dict[str, str]:
|
|
57
|
+
"""Fetch all router source files. Returns {name: source_code}."""
|
|
58
|
+
sources = {}
|
|
59
|
+
with httpx.Client(timeout=30.0, follow_redirects=True) as client:
|
|
60
|
+
for name in ROUTERS:
|
|
61
|
+
url = f"{BASE_URL}/{ref}/{ROUTER_PATH}/{name}.py"
|
|
62
|
+
print(f" fetching {name}.py ...", file=sys.stderr)
|
|
63
|
+
r = client.get(url)
|
|
64
|
+
if r.status_code == 404:
|
|
65
|
+
print(f" WARNING: {name}.py not found at {ref}", file=sys.stderr)
|
|
66
|
+
continue
|
|
67
|
+
r.raise_for_status()
|
|
68
|
+
sources[name] = r.text
|
|
69
|
+
return sources
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def emit_prompt(ref: str, sources: dict[str, str]):
|
|
73
|
+
"""Print the LLM prompt to stdout."""
|
|
74
|
+
print(f"""\
|
|
75
|
+
I need you to produce a JSON file called api-schema.json by reading the Open WebUI
|
|
76
|
+
router source files below. This schema is used by a CLI tool for agent-driven API
|
|
77
|
+
introspection.
|
|
78
|
+
|
|
79
|
+
## Output format
|
|
80
|
+
|
|
81
|
+
Emit ONLY valid JSON matching this structure (no markdown fences, no commentary):
|
|
82
|
+
|
|
83
|
+
{SCHEMA_FORMAT}
|
|
84
|
+
|
|
85
|
+
## Rules
|
|
86
|
+
|
|
87
|
+
1. Every @router.get, @router.post, @router.delete, @router.put, @router.patch
|
|
88
|
+
decorator becomes one entry in the endpoints array.
|
|
89
|
+
2. Path is relative to the router prefix (e.g. "/" not "/api/v1/tools/").
|
|
90
|
+
3. Auth level is determined by the dependency:
|
|
91
|
+
- get_admin_user -> "A"
|
|
92
|
+
- get_verified_user -> "V"
|
|
93
|
+
- get_current_user -> "C"
|
|
94
|
+
- no auth dependency -> "N"
|
|
95
|
+
4. Body description is a compact string like "{{name,description?}}" showing the
|
|
96
|
+
Pydantic model fields. Use ? for Optional fields. null if no request body.
|
|
97
|
+
5. Include a "note" field on resources with path quirks (e.g. /id/{{id}} vs /{{id}},
|
|
98
|
+
query params instead of path params, destructive operations).
|
|
99
|
+
6. Set _meta.source to "open-webui/open-webui {ref}"
|
|
100
|
+
7. Be exhaustive. Every endpoint. No omissions.
|
|
101
|
+
|
|
102
|
+
## Router source files ({len(sources)} files from {ref})
|
|
103
|
+
""")
|
|
104
|
+
|
|
105
|
+
for name, source in sources.items():
|
|
106
|
+
print(f"### {name}.py\n")
|
|
107
|
+
print(f"```python\n{source}\n```\n")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main():
|
|
111
|
+
ref = sys.argv[1] if len(sys.argv) > 1 else "main"
|
|
112
|
+
print(f"Fetching routers from {ref} ...", file=sys.stderr)
|
|
113
|
+
sources = fetch_routers(ref)
|
|
114
|
+
print(f"Fetched {len(sources)} router files.", file=sys.stderr)
|
|
115
|
+
emit_prompt(ref, sources)
|
|
116
|
+
print(f"\nPrompt written to stdout ({sum(len(s) for s in sources.values())} chars of source).", file=sys.stderr)
|
|
117
|
+
print(f"Feed this to an LLM and save the JSON output to src/owui_cli/data/api-schema.json", file=sys.stderr)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
File without changes
|
|
File without changes
|