owui-cli 0.4.0__tar.gz → 0.5.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.4.0 → owui_cli-0.5.0}/AGENTS.md +4 -0
- {owui_cli-0.4.0 → owui_cli-0.5.0}/PKG-INFO +1 -1
- {owui_cli-0.4.0 → owui_cli-0.5.0}/pyproject.toml +1 -1
- owui_cli-0.5.0/src/owui_cli/__init__.py +1 -0
- {owui_cli-0.4.0 → owui_cli-0.5.0}/src/owui_cli/cli.py +95 -58
- owui_cli-0.5.0/uv.lock +91 -0
- owui_cli-0.4.0/src/owui_cli/__init__.py +0 -1
- owui_cli-0.4.0/token-viewer.html +0 -103
- {owui_cli-0.4.0 → owui_cli-0.5.0}/.github/workflows/publish.yml +0 -0
- {owui_cli-0.4.0 → owui_cli-0.5.0}/.gitignore +0 -0
- {owui_cli-0.4.0 → owui_cli-0.5.0}/LICENSE +0 -0
- {owui_cli-0.4.0 → owui_cli-0.5.0}/README.md +0 -0
- {owui_cli-0.4.0 → owui_cli-0.5.0}/src/owui_cli/data/api-schema.json +0 -0
- {owui_cli-0.4.0 → owui_cli-0.5.0}/update-schema.py +0 -0
|
@@ -13,6 +13,10 @@ Bump both when cutting a release.
|
|
|
13
13
|
|
|
14
14
|
Every push to `main` auto-publishes to PyPI via GitHub Actions trusted publishing. Bump the version before pushing, and don't push broken code to main.
|
|
15
15
|
|
|
16
|
+
## Local development install
|
|
17
|
+
|
|
18
|
+
After making changes, reinstall locally with `uv tool install --force .` so your modified `owui-cli` is the one on your PATH.
|
|
19
|
+
|
|
16
20
|
## Testing before committing
|
|
17
21
|
|
|
18
22
|
Always verify changes work against a **production Open WebUI server** before committing. Set `OWUI_URL` and `OWUI_TOKEN` and run the relevant commands to confirm they succeed with real API responses — don't rely on local-only reasoning.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.0"
|
|
@@ -43,8 +43,8 @@ def _api(url: str, path: str) -> str:
|
|
|
43
43
|
return f"{url}{path}"
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def _get(c: httpx.Client, url: str, path: str, token: str) -> httpx.Response:
|
|
47
|
-
r = c.get(_api(url, path), headers=_headers(token))
|
|
46
|
+
def _get(c: httpx.Client, url: str, path: str, token: str, params=None) -> httpx.Response:
|
|
47
|
+
r = c.get(_api(url, path), headers=_headers(token), params=params)
|
|
48
48
|
r.raise_for_status()
|
|
49
49
|
return r
|
|
50
50
|
|
|
@@ -369,81 +369,79 @@ functions_res = Resource("functions", "/api/v1/functions",
|
|
|
369
369
|
workspace_path="/workspace/functions")
|
|
370
370
|
|
|
371
371
|
|
|
372
|
-
# ── valves (user
|
|
372
|
+
# ── valves (admin/global and per-user, for tools and functions) ──────
|
|
373
|
+
#
|
|
374
|
+
# OWUI plugins can declare two valve schemas:
|
|
375
|
+
# class Valves(BaseModel) -> /api/v1/{kind}/id/{id}/valves[/spec|/update]
|
|
376
|
+
# class UserValves(BaseModel) -> /api/v1/{kind}/id/{id}/valves/user[/spec|/update]
|
|
377
|
+
#
|
|
378
|
+
# The unprefixed `valves*` commands target the admin/global path (most common
|
|
379
|
+
# case: an admin-set API key). The `valves-user*` commands target the per-user
|
|
380
|
+
# path (the calling user's personal valves on the plugin).
|
|
373
381
|
|
|
374
|
-
def _valves_get(url, token, kind, item_id):
|
|
375
|
-
"""GET valves for a tool or function. kind is 'tools' or 'functions'."""
|
|
382
|
+
def _valves_get(url, token, kind, item_id, scope=""):
|
|
376
383
|
with httpx.Client(timeout=TIMEOUT) as c:
|
|
377
|
-
r = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves
|
|
384
|
+
r = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves{scope}", token)
|
|
378
385
|
out(r.json())
|
|
379
386
|
|
|
380
|
-
def _valves_spec(url, token, kind, item_id):
|
|
381
|
-
"""GET the UserValves spec (schema) for a tool or function."""
|
|
387
|
+
def _valves_spec(url, token, kind, item_id, scope=""):
|
|
382
388
|
with httpx.Client(timeout=TIMEOUT) as c:
|
|
383
|
-
r = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves/
|
|
389
|
+
r = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves{scope}/spec", token)
|
|
384
390
|
out(r.json())
|
|
385
391
|
|
|
386
|
-
def _valves_set(url, token, kind, item_id, json_path):
|
|
387
|
-
"""POST updated user valves from a JSON file."""
|
|
392
|
+
def _valves_set(url, token, kind, item_id, json_path, scope=""):
|
|
388
393
|
with open(json_path) as f:
|
|
389
394
|
payload = json.load(f)
|
|
390
395
|
with httpx.Client(timeout=TIMEOUT) as c:
|
|
391
|
-
r = _post(c, url, f"/api/v1/{kind}/id/{item_id}/valves/
|
|
396
|
+
r = _post(c, url, f"/api/v1/{kind}/id/{item_id}/valves{scope}/update", token, payload)
|
|
392
397
|
out(r.json())
|
|
393
398
|
|
|
394
|
-
def _valves_set_field(url, token, kind, item_id, key, value):
|
|
395
|
-
"""Set a single field
|
|
399
|
+
def _valves_set_field(url, token, kind, item_id, key, value, scope=""):
|
|
400
|
+
"""Set a single field. Value is parsed as JSON; falls back to string."""
|
|
396
401
|
try:
|
|
397
402
|
parsed = json.loads(value)
|
|
398
403
|
except (json.JSONDecodeError, ValueError):
|
|
399
404
|
parsed = value
|
|
400
405
|
with httpx.Client(timeout=TIMEOUT) as c:
|
|
401
|
-
current = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves
|
|
406
|
+
current = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves{scope}", token).json()
|
|
402
407
|
current[key] = parsed
|
|
403
|
-
r = _post(c, url, f"/api/v1/{kind}/id/{item_id}/valves/
|
|
408
|
+
r = _post(c, url, f"/api/v1/{kind}/id/{item_id}/valves{scope}/update", token, current)
|
|
404
409
|
out(r.json())
|
|
405
410
|
|
|
406
|
-
def _valves_unset_field(url, token, kind, item_id, key):
|
|
407
|
-
"""Remove a single field from the user valves."""
|
|
411
|
+
def _valves_unset_field(url, token, kind, item_id, key, scope=""):
|
|
408
412
|
with httpx.Client(timeout=TIMEOUT) as c:
|
|
409
|
-
current = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves
|
|
413
|
+
current = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves{scope}", token).json()
|
|
410
414
|
if key not in current:
|
|
411
415
|
die(f"key '{key}' not found in valves")
|
|
412
416
|
del current[key]
|
|
413
|
-
r = _post(c, url, f"/api/v1/{kind}/id/{item_id}/valves/
|
|
417
|
+
r = _post(c, url, f"/api/v1/{kind}/id/{item_id}/valves{scope}/update", token, current)
|
|
414
418
|
out(r.json())
|
|
415
419
|
|
|
416
|
-
# Wrappers
|
|
417
|
-
def tools_valves_get(url, token, item_id):
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
def
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
def
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
def
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
def
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
def
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
def
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
def functions_valves_set_field(url, token, item_id, key, value):
|
|
443
|
-
_valves_set_field(url, token, "functions", item_id, key, value)
|
|
444
|
-
|
|
445
|
-
def functions_valves_unset_field(url, token, item_id, key):
|
|
446
|
-
_valves_unset_field(url, token, "functions", item_id, key)
|
|
420
|
+
# Wrappers — admin/global valves (unprefixed: the common case)
|
|
421
|
+
def tools_valves_get(url, token, item_id): _valves_get(url, token, "tools", item_id)
|
|
422
|
+
def tools_valves_spec(url, token, item_id): _valves_spec(url, token, "tools", item_id)
|
|
423
|
+
def tools_valves_set(url, token, item_id, json_path): _valves_set(url, token, "tools", item_id, json_path)
|
|
424
|
+
def tools_valves_set_field(url, token, item_id, key, value):_valves_set_field(url, token, "tools", item_id, key, value)
|
|
425
|
+
def tools_valves_unset_field(url, token, item_id, key): _valves_unset_field(url, token, "tools", item_id, key)
|
|
426
|
+
|
|
427
|
+
def functions_valves_get(url, token, item_id): _valves_get(url, token, "functions", item_id)
|
|
428
|
+
def functions_valves_spec(url, token, item_id): _valves_spec(url, token, "functions", item_id)
|
|
429
|
+
def functions_valves_set(url, token, item_id, json_path): _valves_set(url, token, "functions", item_id, json_path)
|
|
430
|
+
def functions_valves_set_field(url, token, item_id, key, value):_valves_set_field(url, token, "functions", item_id, key, value)
|
|
431
|
+
def functions_valves_unset_field(url, token, item_id, key): _valves_unset_field(url, token, "functions", item_id, key)
|
|
432
|
+
|
|
433
|
+
# Wrappers — per-user valves (`valves-user*`)
|
|
434
|
+
def tools_valves_user_get(url, token, item_id): _valves_get(url, token, "tools", item_id, "/user")
|
|
435
|
+
def tools_valves_user_spec(url, token, item_id): _valves_spec(url, token, "tools", item_id, "/user")
|
|
436
|
+
def tools_valves_user_set(url, token, item_id, json_path): _valves_set(url, token, "tools", item_id, json_path, "/user")
|
|
437
|
+
def tools_valves_user_set_field(url, token, item_id, key, value):_valves_set_field(url, token, "tools", item_id, key, value, "/user")
|
|
438
|
+
def tools_valves_user_unset_field(url, token, item_id, key): _valves_unset_field(url, token, "tools", item_id, key, "/user")
|
|
439
|
+
|
|
440
|
+
def functions_valves_user_get(url, token, item_id): _valves_get(url, token, "functions", item_id, "/user")
|
|
441
|
+
def functions_valves_user_spec(url, token, item_id): _valves_spec(url, token, "functions", item_id, "/user")
|
|
442
|
+
def functions_valves_user_set(url, token, item_id, json_path): _valves_set(url, token, "functions", item_id, json_path, "/user")
|
|
443
|
+
def functions_valves_user_set_field(url, token, item_id, key, value):_valves_set_field(url, token, "functions", item_id, key, value, "/user")
|
|
444
|
+
def functions_valves_user_unset_field(url, token, item_id, key): _valves_unset_field(url, token, "functions", item_id, key, "/user")
|
|
447
445
|
|
|
448
446
|
|
|
449
447
|
class SkillsResource(Resource):
|
|
@@ -691,9 +689,20 @@ def models_pull_all(url, token, out_dir="."):
|
|
|
691
689
|
# ── knowledge (special: files subresource, file/remove is destructive) ─
|
|
692
690
|
|
|
693
691
|
def knowledge_list(url, token):
|
|
692
|
+
kbs = []
|
|
693
|
+
page = 1
|
|
694
694
|
with httpx.Client(timeout=TIMEOUT) as c:
|
|
695
|
-
|
|
696
|
-
|
|
695
|
+
while True:
|
|
696
|
+
data = _get(c, url, "/api/v1/knowledge/", token, params={"page": page}).json()
|
|
697
|
+
batch = data["items"] if isinstance(data, dict) else data
|
|
698
|
+
if not batch:
|
|
699
|
+
break
|
|
700
|
+
kbs.extend(batch)
|
|
701
|
+
if isinstance(data, dict):
|
|
702
|
+
if len(kbs) >= data.get("total", 0): break
|
|
703
|
+
else:
|
|
704
|
+
break
|
|
705
|
+
page += 1
|
|
697
706
|
rows = [{"id": k.get("id",""), "name": k.get("name",""),
|
|
698
707
|
"desc": (k.get("description") or "")[:50]}
|
|
699
708
|
for k in sorted(kbs, key=lambda k: k.get("name",""))]
|
|
@@ -744,12 +753,25 @@ def knowledge_remove_file(url, token, kb_id, file_id):
|
|
|
744
753
|
# ── files ─────────────────────────────────────────────────────────────
|
|
745
754
|
|
|
746
755
|
def files_list(url, token):
|
|
756
|
+
rows = []
|
|
757
|
+
page = 1
|
|
747
758
|
with httpx.Client(timeout=TIMEOUT) as c:
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
759
|
+
while True:
|
|
760
|
+
data = _get(c, url, "/api/v1/files/", token,
|
|
761
|
+
params={"page": page, "content": "false"}).json()
|
|
762
|
+
# OWUI >= 0.9 returns {items, total}; older versions return a bare list.
|
|
763
|
+
batch = data["items"] if isinstance(data, dict) else data
|
|
764
|
+
if not batch:
|
|
765
|
+
break
|
|
766
|
+
rows.extend({"id": f.get("id",""),
|
|
767
|
+
"name": (f.get("meta") or {}).get("name") or f.get("filename",""),
|
|
768
|
+
"size": str((f.get("meta") or {}).get("size","?"))}
|
|
769
|
+
for f in batch)
|
|
770
|
+
if isinstance(data, dict):
|
|
771
|
+
if len(rows) >= data.get("total", 0): break
|
|
772
|
+
else:
|
|
773
|
+
break # bare-list response is unpaginated
|
|
774
|
+
page += 1
|
|
753
775
|
out_table(rows, [("FILE_ID","id",36), ("NAME","name",30), ("SIZE","size",8)])
|
|
754
776
|
|
|
755
777
|
def files_show(url, token, file_id):
|
|
@@ -1119,11 +1141,21 @@ COMMANDS.update({
|
|
|
1119
1141
|
("tools", "valves-set"): (tools_valves_set, "<id> <valves.json>", (2, 2)),
|
|
1120
1142
|
("tools", "valves-set-field"): (tools_valves_set_field, "<id> <key> <value>", (3, 3)),
|
|
1121
1143
|
("tools", "valves-unset-field"):(tools_valves_unset_field, "<id> <key>", (2, 2)),
|
|
1144
|
+
("tools", "valves-user"): (tools_valves_user_get, "<id>", (1, 1)),
|
|
1145
|
+
("tools", "valves-user-spec"): (tools_valves_user_spec, "<id>", (1, 1)),
|
|
1146
|
+
("tools", "valves-user-set"): (tools_valves_user_set, "<id> <valves.json>", (2, 2)),
|
|
1147
|
+
("tools", "valves-user-set-field"): (tools_valves_user_set_field, "<id> <key> <value>", (3, 3)),
|
|
1148
|
+
("tools", "valves-user-unset-field"):(tools_valves_user_unset_field, "<id> <key>", (2, 2)),
|
|
1122
1149
|
("functions", "valves"): (functions_valves_get, "<id>", (1, 1)),
|
|
1123
1150
|
("functions", "valves-spec"): (functions_valves_spec, "<id>", (1, 1)),
|
|
1124
1151
|
("functions", "valves-set"): (functions_valves_set, "<id> <valves.json>", (2, 2)),
|
|
1125
1152
|
("functions", "valves-set-field"): (functions_valves_set_field, "<id> <key> <value>", (3, 3)),
|
|
1126
1153
|
("functions", "valves-unset-field"):(functions_valves_unset_field, "<id> <key>", (2, 2)),
|
|
1154
|
+
("functions", "valves-user"): (functions_valves_user_get, "<id>", (1, 1)),
|
|
1155
|
+
("functions", "valves-user-spec"): (functions_valves_user_spec, "<id>", (1, 1)),
|
|
1156
|
+
("functions", "valves-user-set"): (functions_valves_user_set, "<id> <valves.json>", (2, 2)),
|
|
1157
|
+
("functions", "valves-user-set-field"): (functions_valves_user_set_field, "<id> <key> <value>", (3, 3)),
|
|
1158
|
+
("functions", "valves-user-unset-field"):(functions_valves_user_unset_field, "<id> <key>", (2, 2)),
|
|
1127
1159
|
("models", "list"): (models_list, "", (0, 0)),
|
|
1128
1160
|
("models", "show"): (models_show, "<id>", (1, 1)),
|
|
1129
1161
|
("models", "create"): (models_create, "<model.json>", (1, 1)),
|
|
@@ -1217,7 +1249,12 @@ def main():
|
|
|
1217
1249
|
resource, command = args[0], args[1]
|
|
1218
1250
|
key = (resource, command)
|
|
1219
1251
|
|
|
1252
|
+
# Subcommands are plural (chats, models, etc.) to mirror the OWUI API
|
|
1253
|
+
# paths (/api/v1/chats/, /api/v1/models, ...), not typical CLI convention.
|
|
1220
1254
|
if key not in COMMANDS:
|
|
1255
|
+
plural = resource + "s"
|
|
1256
|
+
if (plural, command) in COMMANDS:
|
|
1257
|
+
die(f"resources are plural: use '{plural} {command}' not '{resource} {command}'")
|
|
1221
1258
|
die(f"unknown: {resource} {command}")
|
|
1222
1259
|
|
|
1223
1260
|
fn, arg_spec, (min_args, max_args) = COMMANDS[key]
|
owui_cli-0.5.0/uv.lock
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 2
|
|
3
|
+
requires-python = ">=3.11"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "anyio"
|
|
7
|
+
version = "4.13.0"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "idna" },
|
|
11
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
12
|
+
]
|
|
13
|
+
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
|
|
14
|
+
wheels = [
|
|
15
|
+
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[[package]]
|
|
19
|
+
name = "certifi"
|
|
20
|
+
version = "2026.2.25"
|
|
21
|
+
source = { registry = "https://pypi.org/simple" }
|
|
22
|
+
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
|
|
23
|
+
wheels = [
|
|
24
|
+
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[[package]]
|
|
28
|
+
name = "h11"
|
|
29
|
+
version = "0.16.0"
|
|
30
|
+
source = { registry = "https://pypi.org/simple" }
|
|
31
|
+
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
|
32
|
+
wheels = [
|
|
33
|
+
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[[package]]
|
|
37
|
+
name = "httpcore"
|
|
38
|
+
version = "1.0.9"
|
|
39
|
+
source = { registry = "https://pypi.org/simple" }
|
|
40
|
+
dependencies = [
|
|
41
|
+
{ name = "certifi" },
|
|
42
|
+
{ name = "h11" },
|
|
43
|
+
]
|
|
44
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
|
45
|
+
wheels = [
|
|
46
|
+
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[[package]]
|
|
50
|
+
name = "httpx"
|
|
51
|
+
version = "0.28.1"
|
|
52
|
+
source = { registry = "https://pypi.org/simple" }
|
|
53
|
+
dependencies = [
|
|
54
|
+
{ name = "anyio" },
|
|
55
|
+
{ name = "certifi" },
|
|
56
|
+
{ name = "httpcore" },
|
|
57
|
+
{ name = "idna" },
|
|
58
|
+
]
|
|
59
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
|
60
|
+
wheels = [
|
|
61
|
+
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[[package]]
|
|
65
|
+
name = "idna"
|
|
66
|
+
version = "3.11"
|
|
67
|
+
source = { registry = "https://pypi.org/simple" }
|
|
68
|
+
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
|
69
|
+
wheels = [
|
|
70
|
+
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[[package]]
|
|
74
|
+
name = "owui-cli"
|
|
75
|
+
version = "0.2.0"
|
|
76
|
+
source = { editable = "." }
|
|
77
|
+
dependencies = [
|
|
78
|
+
{ name = "httpx" },
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[package.metadata]
|
|
82
|
+
requires-dist = [{ name = "httpx", specifier = ">=0.27" }]
|
|
83
|
+
|
|
84
|
+
[[package]]
|
|
85
|
+
name = "typing-extensions"
|
|
86
|
+
version = "4.15.0"
|
|
87
|
+
source = { registry = "https://pypi.org/simple" }
|
|
88
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
|
89
|
+
wheels = [
|
|
90
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
91
|
+
]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.0"
|
owui_cli-0.4.0/token-viewer.html
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>OWUI Token Viewer</title>
|
|
7
|
-
<style>
|
|
8
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
-
body {
|
|
10
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
11
|
-
background: #1a1a2e;
|
|
12
|
-
color: #eee;
|
|
13
|
-
min-height: 100vh;
|
|
14
|
-
display: flex;
|
|
15
|
-
align-items: center;
|
|
16
|
-
justify-content: center;
|
|
17
|
-
padding: 1rem;
|
|
18
|
-
}
|
|
19
|
-
.card {
|
|
20
|
-
background: #16213e;
|
|
21
|
-
border-radius: 12px;
|
|
22
|
-
padding: 1.5rem;
|
|
23
|
-
max-width: 480px;
|
|
24
|
-
width: 100%;
|
|
25
|
-
box-shadow: 0 8px 32px rgba(0,0,0,.4);
|
|
26
|
-
}
|
|
27
|
-
h1 { font-size: 1.25rem; margin-bottom: .25rem; }
|
|
28
|
-
.sub { color: #888; font-size: .85rem; margin-bottom: 1rem; }
|
|
29
|
-
.token-box {
|
|
30
|
-
background: #0f0f23;
|
|
31
|
-
border: 1px solid #333;
|
|
32
|
-
border-radius: 8px;
|
|
33
|
-
padding: .75rem;
|
|
34
|
-
word-break: break-all;
|
|
35
|
-
font-family: "SF Mono", "Fira Code", monospace;
|
|
36
|
-
font-size: .8rem;
|
|
37
|
-
line-height: 1.5;
|
|
38
|
-
max-height: 40vh;
|
|
39
|
-
overflow-y: auto;
|
|
40
|
-
user-select: all;
|
|
41
|
-
-webkit-user-select: all;
|
|
42
|
-
}
|
|
43
|
-
.token-box.empty { color: #f55; font-family: sans-serif; font-size: .9rem; }
|
|
44
|
-
.btn {
|
|
45
|
-
display: inline-block;
|
|
46
|
-
margin-top: .75rem;
|
|
47
|
-
padding: .6rem 1.2rem;
|
|
48
|
-
background: #0f3460;
|
|
49
|
-
color: #eee;
|
|
50
|
-
border: none;
|
|
51
|
-
border-radius: 8px;
|
|
52
|
-
font-size: .9rem;
|
|
53
|
-
cursor: pointer;
|
|
54
|
-
-webkit-tap-highlight-color: transparent;
|
|
55
|
-
}
|
|
56
|
-
.btn:active { background: #1a5276; }
|
|
57
|
-
.copied { color: #5f8; font-size: .85rem; margin-left: .5rem; opacity: 0; transition: opacity .2s; }
|
|
58
|
-
.copied.show { opacity: 1; }
|
|
59
|
-
</style>
|
|
60
|
-
</head>
|
|
61
|
-
<body>
|
|
62
|
-
<div class="card">
|
|
63
|
-
<h1>🔑 OWUI Token</h1>
|
|
64
|
-
<p class="sub">Reads <code>token</code> from localStorage</p>
|
|
65
|
-
<div id="token" class="token-box empty">Checking…</div>
|
|
66
|
-
<button class="btn" id="copy" style="display:none">Copy to clipboard</button>
|
|
67
|
-
<span class="copied" id="copied">✓ Copied!</span>
|
|
68
|
-
</div>
|
|
69
|
-
<script>
|
|
70
|
-
const el = document.getElementById('token');
|
|
71
|
-
const copyBtn = document.getElementById('copy');
|
|
72
|
-
const copiedMsg = document.getElementById('copied');
|
|
73
|
-
const raw = localStorage.getItem('token');
|
|
74
|
-
|
|
75
|
-
if (raw) {
|
|
76
|
-
el.textContent = raw;
|
|
77
|
-
el.classList.remove('empty');
|
|
78
|
-
copyBtn.style.display = 'inline-block';
|
|
79
|
-
} else {
|
|
80
|
-
el.textContent = 'No "token" found in localStorage.\nMake sure you\'re on the same origin as Open WebUI.';
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
copyBtn.addEventListener('click', () => {
|
|
84
|
-
navigator.clipboard.writeText(raw).then(() => {
|
|
85
|
-
copiedMsg.classList.add('show');
|
|
86
|
-
setTimeout(() => copiedMsg.classList.remove('show'), 1500);
|
|
87
|
-
}).catch(() => {
|
|
88
|
-
// fallback for older mobile browsers
|
|
89
|
-
const ta = document.createElement('textarea');
|
|
90
|
-
ta.value = raw;
|
|
91
|
-
ta.style.position = 'fixed';
|
|
92
|
-
ta.style.opacity = 0;
|
|
93
|
-
document.body.appendChild(ta);
|
|
94
|
-
ta.select();
|
|
95
|
-
document.execCommand('copy');
|
|
96
|
-
document.body.removeChild(ta);
|
|
97
|
-
copiedMsg.classList.add('show');
|
|
98
|
-
setTimeout(() => copiedMsg.classList.remove('show'), 1500);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
</script>
|
|
102
|
-
</body>
|
|
103
|
-
</html>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|