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.
- {owui_cli-0.5.2 → owui_cli-0.5.3}/PKG-INFO +1 -1
- {owui_cli-0.5.2 → owui_cli-0.5.3}/pyproject.toml +1 -1
- owui_cli-0.5.3/src/owui_cli/__init__.py +1 -0
- {owui_cli-0.5.2 → owui_cli-0.5.3}/src/owui_cli/cli.py +88 -0
- {owui_cli-0.5.2 → owui_cli-0.5.3}/uv.lock +1 -1
- owui_cli-0.5.2/src/owui_cli/__init__.py +0 -1
- {owui_cli-0.5.2 → owui_cli-0.5.3}/.github/workflows/publish.yml +0 -0
- {owui_cli-0.5.2 → owui_cli-0.5.3}/.gitignore +0 -0
- {owui_cli-0.5.2 → owui_cli-0.5.3}/AGENTS.md +0 -0
- {owui_cli-0.5.2 → owui_cli-0.5.3}/LICENSE +0 -0
- {owui_cli-0.5.2 → owui_cli-0.5.3}/README.md +0 -0
- {owui_cli-0.5.2 → owui_cli-0.5.3}/src/owui_cli/data/api-schema.json +0 -0
- {owui_cli-0.5.2 → owui_cli-0.5.3}/update-schema.py +0 -0
|
@@ -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)),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|