owui-cli 0.3.0__tar.gz → 0.4.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 → owui_cli-0.4.0}/.gitignore +1 -0
- owui_cli-0.4.0/AGENTS.md +45 -0
- {owui_cli-0.3.0 → owui_cli-0.4.0}/PKG-INFO +1 -1
- {owui_cli-0.3.0 → owui_cli-0.4.0}/pyproject.toml +1 -1
- owui_cli-0.4.0/src/owui_cli/__init__.py +1 -0
- {owui_cli-0.3.0 → owui_cli-0.4.0}/src/owui_cli/cli.py +87 -0
- owui_cli-0.4.0/token-viewer.html +103 -0
- owui_cli-0.3.0/src/owui_cli/__init__.py +0 -1
- {owui_cli-0.3.0 → owui_cli-0.4.0}/.github/workflows/publish.yml +0 -0
- {owui_cli-0.3.0 → owui_cli-0.4.0}/LICENSE +0 -0
- {owui_cli-0.3.0 → owui_cli-0.4.0}/README.md +0 -0
- {owui_cli-0.3.0 → owui_cli-0.4.0}/src/owui_cli/data/api-schema.json +0 -0
- {owui_cli-0.3.0 → owui_cli-0.4.0}/update-schema.py +0 -0
owui_cli-0.4.0/AGENTS.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# AGENTS.md — owui-cli
|
|
2
|
+
|
|
3
|
+
## Version management
|
|
4
|
+
|
|
5
|
+
The version number appears in two places and **must be kept in sync**:
|
|
6
|
+
|
|
7
|
+
- `pyproject.toml` → `version = "X.Y.Z"`
|
|
8
|
+
- `src/owui_cli/__init__.py` → `__version__ = "X.Y.Z"`
|
|
9
|
+
|
|
10
|
+
Bump both when cutting a release.
|
|
11
|
+
|
|
12
|
+
## Publishing
|
|
13
|
+
|
|
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
|
+
|
|
16
|
+
## Testing before committing
|
|
17
|
+
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
Single-file CLI in `src/owui_cli/cli.py`. The `Resource` base class handles generic CRUD (list, show, deploy, pull, pull-all, delete) for tools, functions, and skills. Special resources (models, knowledge, files, groups, users, chats, configs, prompts) use standalone functions. All commands are registered in the `COMMANDS` dispatch table at the bottom of the file.
|
|
23
|
+
|
|
24
|
+
## API endpoint patterns
|
|
25
|
+
|
|
26
|
+
The Open WebUI API is not fully consistent across resources:
|
|
27
|
+
|
|
28
|
+
- **Tools/functions/skills:** `/api/v1/{resource}/id/{id}`
|
|
29
|
+
- **Models:** `/api/v1/models/model?id={id}`
|
|
30
|
+
- **Knowledge/users:** `/api/v1/{resource}/{id}`
|
|
31
|
+
- **Groups:** `/api/v1/groups/id/{id}`
|
|
32
|
+
|
|
33
|
+
Always verify the actual endpoint against a running instance when adding new commands.
|
|
34
|
+
|
|
35
|
+
## Output conventions
|
|
36
|
+
|
|
37
|
+
All commands support `--json` for machine-readable output. New commands should use the existing output helpers which handle JSON mode automatically:
|
|
38
|
+
|
|
39
|
+
- `out(data)` — generic output (JSON in `--json` mode, pretty-printed otherwise)
|
|
40
|
+
- `out_table(rows, cols)` — aligned columnar table
|
|
41
|
+
- `out_kv(pairs)` — key-value display
|
|
42
|
+
|
|
43
|
+
## Schema updates
|
|
44
|
+
|
|
45
|
+
`update-schema.py` fetches OWUI router sources from GitHub and generates a prompt to feed an LLM. The LLM output goes in `src/owui_cli/data/api-schema.json`. See the script header for the full workflow.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|
|
@@ -369,6 +369,83 @@ functions_res = Resource("functions", "/api/v1/functions",
|
|
|
369
369
|
workspace_path="/workspace/functions")
|
|
370
370
|
|
|
371
371
|
|
|
372
|
+
# ── valves (user valves for tools and functions) ─────────────────────
|
|
373
|
+
|
|
374
|
+
def _valves_get(url, token, kind, item_id):
|
|
375
|
+
"""GET valves for a tool or function. kind is 'tools' or 'functions'."""
|
|
376
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
377
|
+
r = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves/user", token)
|
|
378
|
+
out(r.json())
|
|
379
|
+
|
|
380
|
+
def _valves_spec(url, token, kind, item_id):
|
|
381
|
+
"""GET the UserValves spec (schema) for a tool or function."""
|
|
382
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
383
|
+
r = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves/user/spec", token)
|
|
384
|
+
out(r.json())
|
|
385
|
+
|
|
386
|
+
def _valves_set(url, token, kind, item_id, json_path):
|
|
387
|
+
"""POST updated user valves from a JSON file."""
|
|
388
|
+
with open(json_path) as f:
|
|
389
|
+
payload = json.load(f)
|
|
390
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
391
|
+
r = _post(c, url, f"/api/v1/{kind}/id/{item_id}/valves/user/update", token, payload)
|
|
392
|
+
out(r.json())
|
|
393
|
+
|
|
394
|
+
def _valves_set_field(url, token, kind, item_id, key, value):
|
|
395
|
+
"""Set a single field in the user valves. Value is parsed as JSON; falls back to string."""
|
|
396
|
+
try:
|
|
397
|
+
parsed = json.loads(value)
|
|
398
|
+
except (json.JSONDecodeError, ValueError):
|
|
399
|
+
parsed = value
|
|
400
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
401
|
+
current = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves/user", token).json()
|
|
402
|
+
current[key] = parsed
|
|
403
|
+
r = _post(c, url, f"/api/v1/{kind}/id/{item_id}/valves/user/update", token, current)
|
|
404
|
+
out(r.json())
|
|
405
|
+
|
|
406
|
+
def _valves_unset_field(url, token, kind, item_id, key):
|
|
407
|
+
"""Remove a single field from the user valves."""
|
|
408
|
+
with httpx.Client(timeout=TIMEOUT) as c:
|
|
409
|
+
current = _get(c, url, f"/api/v1/{kind}/id/{item_id}/valves/user", token).json()
|
|
410
|
+
if key not in current:
|
|
411
|
+
die(f"key '{key}' not found in valves")
|
|
412
|
+
del current[key]
|
|
413
|
+
r = _post(c, url, f"/api/v1/{kind}/id/{item_id}/valves/user/update", token, current)
|
|
414
|
+
out(r.json())
|
|
415
|
+
|
|
416
|
+
# Wrappers that bind kind='tools'
|
|
417
|
+
def tools_valves_get(url, token, item_id):
|
|
418
|
+
_valves_get(url, token, "tools", item_id)
|
|
419
|
+
|
|
420
|
+
def tools_valves_spec(url, token, item_id):
|
|
421
|
+
_valves_spec(url, token, "tools", item_id)
|
|
422
|
+
|
|
423
|
+
def tools_valves_set(url, token, item_id, json_path):
|
|
424
|
+
_valves_set(url, token, "tools", item_id, json_path)
|
|
425
|
+
|
|
426
|
+
def tools_valves_set_field(url, token, item_id, key, value):
|
|
427
|
+
_valves_set_field(url, token, "tools", item_id, key, value)
|
|
428
|
+
|
|
429
|
+
def tools_valves_unset_field(url, token, item_id, key):
|
|
430
|
+
_valves_unset_field(url, token, "tools", item_id, key)
|
|
431
|
+
|
|
432
|
+
# Wrappers that bind kind='functions'
|
|
433
|
+
def functions_valves_get(url, token, item_id):
|
|
434
|
+
_valves_get(url, token, "functions", item_id)
|
|
435
|
+
|
|
436
|
+
def functions_valves_spec(url, token, item_id):
|
|
437
|
+
_valves_spec(url, token, "functions", item_id)
|
|
438
|
+
|
|
439
|
+
def functions_valves_set(url, token, item_id, json_path):
|
|
440
|
+
_valves_set(url, token, "functions", item_id, json_path)
|
|
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)
|
|
447
|
+
|
|
448
|
+
|
|
372
449
|
class SkillsResource(Resource):
|
|
373
450
|
"""Skills use frontmatter and have grant/revoke commands."""
|
|
374
451
|
|
|
@@ -1037,6 +1114,16 @@ for res in [tools_res, functions_res, skills_res]:
|
|
|
1037
1114
|
|
|
1038
1115
|
# Register special resources
|
|
1039
1116
|
COMMANDS.update({
|
|
1117
|
+
("tools", "valves"): (tools_valves_get, "<id>", (1, 1)),
|
|
1118
|
+
("tools", "valves-spec"): (tools_valves_spec, "<id>", (1, 1)),
|
|
1119
|
+
("tools", "valves-set"): (tools_valves_set, "<id> <valves.json>", (2, 2)),
|
|
1120
|
+
("tools", "valves-set-field"): (tools_valves_set_field, "<id> <key> <value>", (3, 3)),
|
|
1121
|
+
("tools", "valves-unset-field"):(tools_valves_unset_field, "<id> <key>", (2, 2)),
|
|
1122
|
+
("functions", "valves"): (functions_valves_get, "<id>", (1, 1)),
|
|
1123
|
+
("functions", "valves-spec"): (functions_valves_spec, "<id>", (1, 1)),
|
|
1124
|
+
("functions", "valves-set"): (functions_valves_set, "<id> <valves.json>", (2, 2)),
|
|
1125
|
+
("functions", "valves-set-field"): (functions_valves_set_field, "<id> <key> <value>", (3, 3)),
|
|
1126
|
+
("functions", "valves-unset-field"):(functions_valves_unset_field, "<id> <key>", (2, 2)),
|
|
1040
1127
|
("models", "list"): (models_list, "", (0, 0)),
|
|
1041
1128
|
("models", "show"): (models_show, "<id>", (1, 1)),
|
|
1042
1129
|
("models", "create"): (models_create, "<model.json>", (1, 1)),
|
|
@@ -0,0 +1,103 @@
|
|
|
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>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|