owui-cli 0.5.2__tar.gz → 0.5.3__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
1
  Metadata-Version: 2.5
2
2
  Name: owui-cli
3
- Version: 0.5.2
3
+ Version: 0.5.3
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.2"
3
+ version = "0.5.3"
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.3"
@@ -15,6 +15,7 @@ import json
15
15
  import os
16
16
  import re
17
17
  import sys
18
+ import time
18
19
  from importlib.resources import files
19
20
 
20
21
  import httpx
@@ -1074,6 +1075,90 @@ def chats_delete(url, token, chat_id):
1074
1075
  out(f"deleted {chat_id}")
1075
1076
 
1076
1077
 
1078
+ _STREAM_TIMEOUT = httpx.Timeout(TIMEOUT, read=3600.0)
1079
+
1080
+
1081
+ def _iter_chats_all(url, token):
1082
+ """Stream GET /api/v1/chats/all as NDJSON, yielding one chat per line.
1083
+
1084
+ On v0.11.x the endpoint returns an application/x-ndjson stream whose
1085
+ export can run to hundreds of MB — never buffered server-side. Parse
1086
+ incrementally so clients (and pull-all) don't need the whole thing in
1087
+ memory.
1088
+
1089
+ Decode-buffer manually over raw bytes: httpx's iter_lines can truncate a
1090
+ long line at a decode-chunk boundary (observed with model_dump_json
1091
+ exports whose message trees run large), so split b'\n' ourselves and
1092
+ decode each complete line.
1093
+ """
1094
+ with httpx.Client(timeout=_STREAM_TIMEOUT) as c:
1095
+ with c.stream("GET", _api(url, "/api/v1/chats/all"), headers=_headers(token)) as r:
1096
+ r.raise_for_status()
1097
+ buf = b""
1098
+ for chunk in r.iter_raw():
1099
+ buf += chunk
1100
+ while True:
1101
+ idx = buf.find(b"\n")
1102
+ if idx < 0:
1103
+ break
1104
+ line = buf[:idx]
1105
+ buf = buf[idx + 1:]
1106
+ if line.strip():
1107
+ yield json.loads(line.decode("utf-8"))
1108
+
1109
+
1110
+ def chats_all(url, token):
1111
+ count = 0
1112
+ chats = []
1113
+ for chat in _iter_chats_all(url, token):
1114
+ count += 1
1115
+ if JSON_OUTPUT:
1116
+ chats.append(chat)
1117
+ else:
1118
+ title = chat.get("chat", {}).get("title") or chat.get("title") or "(untitled)"
1119
+ print(f"{count}\t{chat.get('id','')}\t{str(title)[:60]}")
1120
+ if JSON_OUTPUT:
1121
+ out(chats)
1122
+ else:
1123
+ print(f"-- {count} chats", file=sys.stderr)
1124
+
1125
+ def chats_pull_all(url, token, out_dir="chats"):
1126
+ count = 0
1127
+ os.makedirs(out_dir, exist_ok=True)
1128
+ for chat in _iter_chats_all(url, token):
1129
+ chat_id = chat.get("id", "")
1130
+ if not chat_id:
1131
+ continue
1132
+ _write_json(os.path.join(out_dir, f"{chat_id}.json"), chat)
1133
+ count += 1
1134
+ if count % 100 == 0:
1135
+ print(f"... {count}", file=sys.stderr)
1136
+ out(f"pulled {count} chats into {out_dir}")
1137
+
1138
+ def chats_stats(url, token, page="1"):
1139
+ with httpx.Client(timeout=_STREAM_TIMEOUT) as c:
1140
+ r = _get(c, url, f"/api/v1/chats/stats/export?page={page}", token)
1141
+ data = r.json()
1142
+ if JSON_OUTPUT:
1143
+ out(data)
1144
+ return
1145
+ items = data.get("items", data) if isinstance(data, dict) else data
1146
+ total = data.get("total") if isinstance(data, dict) else None
1147
+ rows = []
1148
+ for s in items:
1149
+ st = s.get("stats") or {}
1150
+ models = st.get("models") or st.get("history_models") or {}
1151
+ rows.append({
1152
+ "id": s.get("id", s.get("chat_id", "")),
1153
+ "msgs": st.get("message_count", st.get("history_message_count", "?")),
1154
+ "models": ", ".join(models.keys())[:30],
1155
+ "updated": time.strftime("%Y-%m-%d", time.gmtime(s.get("updated_at"))) if s.get("updated_at") else "",
1156
+ })
1157
+ out_table(rows, [("CHAT_ID","id",36), ("MSGS","msgs",6), ("MODELS","models",30), ("UPDATED","updated",10)])
1158
+ if total is not None:
1159
+ print(f"-- page {page}: {len(items)} chats, total {total}")
1160
+
1161
+
1077
1162
  # ── configs ──────────────────────────────────────────────────────────
1078
1163
 
1079
1164
  def configs_show(url, token):
@@ -1255,6 +1340,9 @@ COMMANDS.update({
1255
1340
  ("chats", "list"): (chats_list, "[page]", (0, 1)),
1256
1341
  ("chats", "search"): (chats_search, "<query> [page]", (1, 2)),
1257
1342
  ("chats", "show"): (chats_show, "<id>", (1, 1)),
1343
+ ("chats", "all"): (chats_all, "", (0, 0)),
1344
+ ("chats", "pull-all"): (chats_pull_all, "[dir]", (0, 1)),
1345
+ ("chats", "stats"): (chats_stats, "[page]", (0, 1)),
1258
1346
  ("chats", "delete"): (chats_delete, "<id>", (1, 1)),
1259
1347
  ("users", "list"): (users_list, "", (0, 0)),
1260
1348
  ("users", "find"): (users_find, "<query>", (1, 1)),
@@ -72,7 +72,7 @@ wheels = [
72
72
 
73
73
  [[package]]
74
74
  name = "owui-cli"
75
- version = "0.5.2"
75
+ version = "0.5.3"
76
76
  source = { editable = "." }
77
77
  dependencies = [
78
78
  { name = "httpx" },
@@ -1 +0,0 @@
1
- __version__ = "0.5.2"
File without changes
File without changes
File without changes
File without changes
File without changes