owui-cli 0.5.0__tar.gz → 0.5.2__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: owui-cli
3
- Version: 0.5.0
3
+ Version: 0.5.2
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "owui-cli"
3
- version = "0.5.0"
3
+ version = "0.5.2"
4
4
  description = "Admin CLI for Open WebUI instances"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1 @@
1
+ __version__ = "0.5.2"
@@ -444,6 +444,23 @@ def functions_valves_user_set_field(url, token, item_id, key, value):_valves_set
444
444
  def functions_valves_user_unset_field(url, token, item_id, key): _valves_unset_field(url, token, "functions", item_id, key, "/user")
445
445
 
446
446
 
447
+ # ── functions toggle (activate / deactivate, and global) ─────────────
448
+
449
+ def functions_toggle(url, token, item_id):
450
+ """Toggle a function's is_active flag."""
451
+ with httpx.Client(timeout=TIMEOUT) as c:
452
+ r = _post(c, url, f"/api/v1/functions/id/{item_id}/toggle", token)
453
+ f = r.json()
454
+ out(f"{item_id} {'active' if f.get('is_active') else 'inactive'}")
455
+
456
+ def functions_toggle_global(url, token, item_id):
457
+ """Toggle a function's is_global flag (applies to all models)."""
458
+ with httpx.Client(timeout=TIMEOUT) as c:
459
+ r = _post(c, url, f"/api/v1/functions/id/{item_id}/toggle/global", token)
460
+ f = r.json()
461
+ out(f"{item_id} {'global' if f.get('is_global') else 'not global'}")
462
+
463
+
447
464
  class SkillsResource(Resource):
448
465
  """Skills use frontmatter and have grant/revoke commands."""
449
466
 
@@ -573,33 +590,68 @@ def models_show(url, token, model_id):
573
590
  if JSON_OUTPUT:
574
591
  out(m)
575
592
  return
576
- info = m.get("info") or {}
577
- meta = info.get("meta") or {}
578
- params = info.get("params") or {}
593
+ meta = m.get("meta") or {}
594
+ params = m.get("params") or {}
579
595
  pairs = [("id", m.get("id","")), ("name", m.get("name","")),
580
- ("base", info.get("base_model_id","(none)")),
581
- ("active", str(info.get("is_active","?"))),
596
+ ("base", m.get("base_model_id","(none)") or "(none)"),
597
+ ("active", str(m.get("is_active","?"))),
582
598
  ("tools", ", ".join(meta.get("toolIds") or []) or "(none)"),
583
- ("filters", ", ".join(params.get("filter_ids") or []) or "(none)"),
599
+ ("filters", ", ".join(meta.get("filterIds") or []) or "(none)"),
584
600
  ("knowledge", ", ".join(k.get("name","?") for k in (meta.get("knowledge") or [])) or "(none)"),
585
- ("system", f"{len(params.get('system',''))} chars"),
586
- ("grants", str(len(info.get("access_grants") or [])))]
601
+ ("system", f"{len(params.get('system') or '')} chars"),
602
+ ("grants", str(len(m.get("access_grants") or [])))]
587
603
  out_kv(pairs)
588
604
 
605
+ _MIME_BY_EXT = {
606
+ "png": "image/png",
607
+ "jpg": "image/jpeg",
608
+ "jpeg": "image/jpeg",
609
+ "gif": "image/gif",
610
+ "webp": "image/webp",
611
+ }
612
+
613
+
614
+ def _inline_sibling_images(json_path: str, payload: dict) -> list[str]:
615
+ """Replace bare sibling-image references in meta.profile_image_url with data URIs.
616
+
617
+ The repo convention stores the icon as a sibling file referenced by bare
618
+ filename (e.g. "profile.png"). OWUI's stored-model validator (utils/validate.py)
619
+ rejects bare filenames and silently clears them to null, so push must inline
620
+ the sibling image instead — the mirror of models_pull_all's extraction.
621
+ Returns the list of inlined filenames.
622
+ """
623
+ inlined = []
624
+ meta = payload.get("meta") if isinstance(payload, dict) else None
625
+ ref = (meta or {}).get("profile_image_url")
626
+ if not isinstance(ref, str) or not ref or ref.startswith(("data:", "http://", "https://", "/")):
627
+ return inlined
628
+ image_path = os.path.join(os.path.dirname(os.path.abspath(json_path)), ref)
629
+ ext = os.path.splitext(ref)[1].lstrip(".").lower()
630
+ mime = _MIME_BY_EXT.get(ext)
631
+ if mime and os.path.isfile(image_path):
632
+ with open(image_path, "rb") as f:
633
+ b64 = base64.b64encode(f.read()).decode()
634
+ payload["meta"]["profile_image_url"] = f"data:{mime};base64,{b64}"
635
+ inlined.append(ref)
636
+ return inlined
637
+
638
+
589
639
  def models_create(url, token, json_path):
590
640
  with open(json_path) as f:
591
641
  payload = json.load(f)
642
+ inlined = _inline_sibling_images(json_path, payload)
592
643
  with httpx.Client(timeout=TIMEOUT) as c:
593
644
  r = _post(c, url, "/api/v1/models/create", token, payload)
594
645
  m = r.json()
595
- out(f"created {m.get('id')}")
646
+ out(f"created {m.get('id')}" + (f" (inlined {', '.join(inlined)})" if inlined else ""))
596
647
 
597
648
  def models_update(url, token, json_path):
598
649
  with open(json_path) as f:
599
650
  payload = json.load(f)
651
+ inlined = _inline_sibling_images(json_path, payload)
600
652
  with httpx.Client(timeout=TIMEOUT) as c:
601
653
  r = _post(c, url, "/api/v1/models/model/update", token, payload)
602
- out(f"updated {r.json().get('id')}")
654
+ out(f"updated {r.json().get('id')}" + (f" (inlined {', '.join(inlined)})" if inlined else ""))
603
655
 
604
656
  def models_delete(url, token, model_id):
605
657
  with httpx.Client(timeout=TIMEOUT) as c:
@@ -607,23 +659,38 @@ def models_delete(url, token, model_id):
607
659
  out(f"deleted {model_id}")
608
660
 
609
661
  def _models_fetch(c, url, token, model_id):
610
- """Fetch a model by ID, returning the parsed JSON."""
662
+ """Fetch a model by ID, returning the parsed JSON (flat ModelModel shape)."""
611
663
  r = _get(c, url, f"/api/v1/models/model?id={model_id}", token)
612
664
  return r.json()
613
665
 
666
+ def _models_form(model):
667
+ """Build a ModelForm update payload from a fetched flat model.
668
+
669
+ OWUI's Model schema is flat: top-level `meta`, `params`, `base_model_id`,
670
+ `name`, `is_active`, `access_grants`. The /model/update endpoint accepts a
671
+ ModelForm of the same shape (extra keys ignored). Rebuilding explicitly
672
+ avoids leaking response-only fields (user, write_access, timestamps) and
673
+ ensures `meta`/`params` round-trip intact.
674
+ """
675
+ return {
676
+ "id": model.get("id"),
677
+ "base_model_id": model.get("base_model_id"),
678
+ "name": model.get("name", model.get("id", "")),
679
+ "meta": model.get("meta") or {},
680
+ "params": model.get("params") or {},
681
+ "access_grants": model.get("access_grants") or [],
682
+ "is_active": model.get("is_active", True),
683
+ }
684
+
614
685
  def models_set_tools(url, token, model_id, *tool_ids):
615
686
  """Set the tool bindings for a workspace model (pass no IDs to clear)."""
616
687
  with httpx.Client(timeout=TIMEOUT) as c:
617
688
  model = _models_fetch(c, url, token, model_id)
618
- info = model.get("info") or {}
619
- meta = info.setdefault("meta", {})
620
- params = info.setdefault("params", {})
689
+ form = _models_form(model)
621
690
  ids = list(tool_ids)
622
- meta["toolIds"] = ids
623
- # keep params.tool_ids in sync (used by some OWUI versions)
624
- params["tool_ids"] = ids
625
- model["info"] = info
626
- r = _post(c, url, "/api/v1/models/model/update", token, model)
691
+ # OWUI binds per-model tools via meta.toolIds.
692
+ form["meta"]["toolIds"] = ids
693
+ r = _post(c, url, "/api/v1/models/model/update", token, form)
627
694
  label = ", ".join(ids) if ids else "(none)"
628
695
  out(f"tools for {model_id}: {label}")
629
696
 
@@ -631,12 +698,12 @@ def models_set_filters(url, token, model_id, *filter_ids):
631
698
  """Set the filter bindings for a workspace model (pass no IDs to clear)."""
632
699
  with httpx.Client(timeout=TIMEOUT) as c:
633
700
  model = _models_fetch(c, url, token, model_id)
634
- info = model.get("info") or {}
635
- params = info.setdefault("params", {})
701
+ form = _models_form(model)
636
702
  ids = list(filter_ids)
637
- params["filter_ids"] = ids
638
- model["info"] = info
639
- r = _post(c, url, "/api/v1/models/model/update", token, model)
703
+ # OWUI reads per-model filters from meta.filterIds
704
+ # (see backend/open_webui/utils/filter.py).
705
+ form["meta"]["filterIds"] = ids
706
+ r = _post(c, url, "/api/v1/models/model/update", token, form)
640
707
  label = ", ".join(ids) if ids else "(none)"
641
708
  out(f"filters for {model_id}: {label}")
642
709
 
@@ -1156,6 +1223,8 @@ COMMANDS.update({
1156
1223
  ("functions", "valves-user-set"): (functions_valves_user_set, "<id> <valves.json>", (2, 2)),
1157
1224
  ("functions", "valves-user-set-field"): (functions_valves_user_set_field, "<id> <key> <value>", (3, 3)),
1158
1225
  ("functions", "valves-user-unset-field"):(functions_valves_user_unset_field, "<id> <key>", (2, 2)),
1226
+ ("functions", "toggle"): (functions_toggle, "<id>", (1, 1)),
1227
+ ("functions", "toggle-global"): (functions_toggle_global, "<id>", (1, 1)),
1159
1228
  ("models", "list"): (models_list, "", (0, 0)),
1160
1229
  ("models", "show"): (models_show, "<id>", (1, 1)),
1161
1230
  ("models", "create"): (models_create, "<model.json>", (1, 1)),
@@ -72,7 +72,7 @@ wheels = [
72
72
 
73
73
  [[package]]
74
74
  name = "owui-cli"
75
- version = "0.2.0"
75
+ version = "0.5.2"
76
76
  source = { editable = "." }
77
77
  dependencies = [
78
78
  { name = "httpx" },
@@ -1 +0,0 @@
1
- __version__ = "0.5.0"
File without changes
File without changes
File without changes
File without changes
File without changes