uro-cli 0.2.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.
- uro_cli-0.2.0/.gitignore +27 -0
- uro_cli-0.2.0/LICENSE +21 -0
- uro_cli-0.2.0/PKG-INFO +56 -0
- uro_cli-0.2.0/README.md +38 -0
- uro_cli-0.2.0/pyproject.toml +40 -0
- uro_cli-0.2.0/tests/test_cli_scaffold.py +8 -0
- uro_cli-0.2.0/tests/test_wiring.py +119 -0
- uro_cli-0.2.0/uro_cli/__init__.py +8 -0
- uro_cli-0.2.0/uro_cli/main.py +1090 -0
- uro_cli-0.2.0/uro_cli/py.typed +0 -0
- uro_cli-0.2.0/uro_cli/wiring.py +197 -0
uro_cli-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
.python-version.local
|
|
9
|
+
|
|
10
|
+
# Tooling caches
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.coverage
|
|
15
|
+
htmlcov/
|
|
16
|
+
|
|
17
|
+
# Deployment config holds credential references — commit uro.example.toml instead (docs/14)
|
|
18
|
+
uro.toml
|
|
19
|
+
.env
|
|
20
|
+
|
|
21
|
+
# IDE / OS
|
|
22
|
+
.idea/
|
|
23
|
+
.vscode/
|
|
24
|
+
.DS_Store
|
|
25
|
+
|
|
26
|
+
# Stray local data dumps — never commit (a PII file named `data` was found here once)
|
|
27
|
+
/data
|
uro_cli-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cupskeee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
uro_cli-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: uro-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Uro Engine reference client — play, dry-run, and dev tools against the engine
|
|
5
|
+
Project-URL: Homepage, https://github.com/cupskeee/uro
|
|
6
|
+
Project-URL: Repository, https://github.com/cupskeee/uro
|
|
7
|
+
Project-URL: Issues, https://github.com/cupskeee/uro/issues
|
|
8
|
+
Author: cupskeee
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Requires-Dist: rich>=13.9
|
|
13
|
+
Requires-Dist: typer>=0.15
|
|
14
|
+
Requires-Dist: uro-core[llm,postgres]==0.2.0
|
|
15
|
+
Requires-Dist: uro-server==0.2.0
|
|
16
|
+
Requires-Dist: websockets>=16.1
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# uro-cli
|
|
20
|
+
|
|
21
|
+
**The Uro Engine reference client — play, dry-run, and dev tools against the engine.**
|
|
22
|
+
|
|
23
|
+
`uro-cli` installs the `uro` command: create worlds, play campaigns (offline with a
|
|
24
|
+
deterministic stub or against a real model), fork timelines, run mechanics, and drive every
|
|
25
|
+
engine subsystem from the terminal. It is the reference consumer of
|
|
26
|
+
[`uro-core`](https://pypi.org/project/uro-core/) — the only "frontend" the project ships.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install uro-cli
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This pulls `uro-core` with its `postgres` and `llm` extras — the client wires the Postgres
|
|
35
|
+
store and the LLM providers for you.
|
|
36
|
+
|
|
37
|
+
## Quickstart
|
|
38
|
+
|
|
39
|
+
Uro's only store is **PostgreSQL 17 + pgvector**. Start it (the reference setup uses Docker on
|
|
40
|
+
host port 5433), then:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
docker compose up -d --wait # from the project repository
|
|
44
|
+
uro db migrate
|
|
45
|
+
uro world new "Ashfall" # prints a campaign id
|
|
46
|
+
uro play <campaign> # offline, deterministic stub
|
|
47
|
+
uro play <campaign> --provider anthropic # or a real model (needs ANTHROPIC_API_KEY)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If the database isn't reachable, `uro` tells you exactly how to start it. Point at a different
|
|
51
|
+
database with `URO_DATABASE_URL`.
|
|
52
|
+
|
|
53
|
+
## Links
|
|
54
|
+
|
|
55
|
+
- Source and docs: https://github.com/cupskeee/uro
|
|
56
|
+
- License: MIT
|
uro_cli-0.2.0/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# uro-cli
|
|
2
|
+
|
|
3
|
+
**The Uro Engine reference client — play, dry-run, and dev tools against the engine.**
|
|
4
|
+
|
|
5
|
+
`uro-cli` installs the `uro` command: create worlds, play campaigns (offline with a
|
|
6
|
+
deterministic stub or against a real model), fork timelines, run mechanics, and drive every
|
|
7
|
+
engine subsystem from the terminal. It is the reference consumer of
|
|
8
|
+
[`uro-core`](https://pypi.org/project/uro-core/) — the only "frontend" the project ships.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pip install uro-cli
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This pulls `uro-core` with its `postgres` and `llm` extras — the client wires the Postgres
|
|
17
|
+
store and the LLM providers for you.
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
Uro's only store is **PostgreSQL 17 + pgvector**. Start it (the reference setup uses Docker on
|
|
22
|
+
host port 5433), then:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
docker compose up -d --wait # from the project repository
|
|
26
|
+
uro db migrate
|
|
27
|
+
uro world new "Ashfall" # prints a campaign id
|
|
28
|
+
uro play <campaign> # offline, deterministic stub
|
|
29
|
+
uro play <campaign> --provider anthropic # or a real model (needs ANTHROPIC_API_KEY)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If the database isn't reachable, `uro` tells you exactly how to start it. Point at a different
|
|
33
|
+
database with `URO_DATABASE_URL`.
|
|
34
|
+
|
|
35
|
+
## Links
|
|
36
|
+
|
|
37
|
+
- Source and docs: https://github.com/cupskeee/uro
|
|
38
|
+
- License: MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "uro-cli"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Uro Engine reference client — play, dry-run, and dev tools against the engine"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
license-files = ["LICENSE"]
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
authors = [{ name = "cupskeee" }]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
# The reference client drives the whole engine: it wires the Postgres store and the LLM
|
|
11
|
+
# providers, so it pulls both uro-core extras (D-43). The internal deps are pinned to the exact
|
|
12
|
+
# lockstep version (the workspace is single-versioned) so a partial `pip install --upgrade` can't
|
|
13
|
+
# pair a new uro-cli with an old uro-core; `just release` bumps all three together, and the
|
|
14
|
+
# `{workspace = true}` sources make a drifted pin fail `uv sync` locally.
|
|
15
|
+
dependencies = [
|
|
16
|
+
"uro-core[postgres,llm]==0.2.0",
|
|
17
|
+
"uro-server==0.2.0",
|
|
18
|
+
"typer>=0.15",
|
|
19
|
+
"rich>=13.9",
|
|
20
|
+
"websockets>=16.1",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/cupskeee/uro"
|
|
25
|
+
Repository = "https://github.com/cupskeee/uro"
|
|
26
|
+
Issues = "https://github.com/cupskeee/uro/issues"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
uro = "uro_cli.main:app"
|
|
30
|
+
|
|
31
|
+
[tool.uv.sources]
|
|
32
|
+
uro-core = { workspace = true }
|
|
33
|
+
uro-server = { workspace = true }
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["hatchling"]
|
|
37
|
+
build-backend = "hatchling.build"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["uro_cli"]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""CLI wiring: embedder role, multi-role provider specs, and role config."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from uro_cli.wiring import build_embedder, build_router, load_role_specs, provider_from_spec
|
|
7
|
+
from uro_core.providers.adapters.anthropic import AnthropicProvider
|
|
8
|
+
from uro_core.providers.adapters.stub import StubProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_stub_needs_no_separate_embedder() -> None:
|
|
12
|
+
assert build_embedder("stub") is None # StubProvider.embed works
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_real_providers_bind_a_separate_embedder() -> None:
|
|
16
|
+
# Real providers distinguish chat vs embedding endpoints — a separate binding is
|
|
17
|
+
# required so the embedder role does not POST a chat model to /embeddings (review 1.3).
|
|
18
|
+
local = build_embedder("local")
|
|
19
|
+
assert local is not None and "embedder" in build_router("local", None)._bindings
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def test_stub_router_embeds_via_default() -> None:
|
|
23
|
+
router = build_router("stub", None)
|
|
24
|
+
vectors = await router.embed("embedder", ["hello world"])
|
|
25
|
+
assert len(vectors) == 1 and len(vectors[0]) == 256
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_provider_from_spec_parses_kind_and_model(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
29
|
+
monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key")
|
|
30
|
+
assert isinstance(provider_from_spec("stub"), StubProvider)
|
|
31
|
+
anthropic = provider_from_spec("anthropic:claude-sonnet-5")
|
|
32
|
+
assert isinstance(anthropic, AnthropicProvider)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_role_specs_load_from_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
36
|
+
cfg = tmp_path / "uro.toml"
|
|
37
|
+
cfg.write_text('[llm.roles]\nnarrator = "anthropic:claude-sonnet-5"\nextractor = "stub"\n')
|
|
38
|
+
monkeypatch.setenv("URO_CONFIG", str(cfg))
|
|
39
|
+
assert load_role_specs() == {"narrator": "anthropic:claude-sonnet-5", "extractor": "stub"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_role_config_binds_roles(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
43
|
+
monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key")
|
|
44
|
+
cfg = tmp_path / "uro.toml"
|
|
45
|
+
cfg.write_text('[llm.roles]\nnarrator = "anthropic:claude-sonnet-5"\n')
|
|
46
|
+
monkeypatch.setenv("URO_CONFIG", str(cfg))
|
|
47
|
+
router = build_router("stub", None)
|
|
48
|
+
assert isinstance(router._bindings["narrator"], AnthropicProvider) # role override applied
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_build_router_tolerates_an_unbuildable_role(
|
|
52
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
53
|
+
) -> None:
|
|
54
|
+
# A pinned role whose key is missing must be skipped (fall back to default), not
|
|
55
|
+
# crash the whole router — else an unused/optional role bricks play (review inc 4).
|
|
56
|
+
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
57
|
+
cfg = tmp_path / "uro.toml"
|
|
58
|
+
cfg.write_text('[llm.roles]\nextractor = "openai:gpt-4o-mini"\n')
|
|
59
|
+
monkeypatch.setenv("URO_CONFIG", str(cfg))
|
|
60
|
+
router = build_router("stub", None) # no OpenAI key → extractor binding skipped
|
|
61
|
+
assert "extractor" not in router._bindings # fell back to the default provider
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_config_errors_loudly_on_a_missing_explicit_path(
|
|
65
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
66
|
+
) -> None:
|
|
67
|
+
monkeypatch.setenv("URO_CONFIG", "/no/such/uro.toml")
|
|
68
|
+
with pytest.raises(RuntimeError):
|
|
69
|
+
load_role_specs()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_parse_role_models_parses_and_rejects_malformed() -> None:
|
|
73
|
+
from uro_cli.wiring import parse_role_models
|
|
74
|
+
|
|
75
|
+
assert parse_role_models(["planner=openai:gpt-4o", "extractor=gpt-4o-mini"]) == {
|
|
76
|
+
"planner": "openai:gpt-4o",
|
|
77
|
+
"extractor": "gpt-4o-mini",
|
|
78
|
+
}
|
|
79
|
+
assert parse_role_models(None) == {}
|
|
80
|
+
for bad in ["planner", "=gpt-4o", "planner="]:
|
|
81
|
+
with pytest.raises(ValueError):
|
|
82
|
+
parse_role_models([bad])
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_cli_role_model_override_full_spec(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
86
|
+
# --role-model narrator=local:llama3.1 binds that role to a distinct provider (no key needed).
|
|
87
|
+
from uro_core.providers.adapters.openai_compat import OpenAICompatProvider
|
|
88
|
+
|
|
89
|
+
router = build_router("stub", None, {"narrator": "local:llama3.1"})
|
|
90
|
+
assert isinstance(router._bindings["narrator"], OpenAICompatProvider) # override applied
|
|
91
|
+
assert isinstance(router._default, StubProvider) # other roles still use the default
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_cli_role_model_bare_model_uses_default_kind() -> None:
|
|
95
|
+
# A bare 'model' (no 'kind:') reuses the default provider kind — the common "cheap default,
|
|
96
|
+
# strong planner" case: planner=<model> stays on the same provider.
|
|
97
|
+
from uro_core.providers.adapters.openai_compat import OpenAICompatProvider
|
|
98
|
+
|
|
99
|
+
router = build_router("local", None, {"planner": "some-big-model"})
|
|
100
|
+
planner = router._bindings["planner"]
|
|
101
|
+
assert isinstance(planner, OpenAICompatProvider) and planner._model == "some-big-model"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_cli_override_wins_over_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
105
|
+
from uro_core.providers.adapters.openai_compat import OpenAICompatProvider
|
|
106
|
+
|
|
107
|
+
cfg = tmp_path / "uro.toml"
|
|
108
|
+
cfg.write_text('[llm.roles]\nnarrator = "stub"\n')
|
|
109
|
+
monkeypatch.setenv("URO_CONFIG", str(cfg))
|
|
110
|
+
router = build_router("stub", None, {"narrator": "local:llama3.1"})
|
|
111
|
+
assert isinstance(router._bindings["narrator"], OpenAICompatProvider) # CLI beats uro.toml
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_cli_override_fails_loudly_unlike_config(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
115
|
+
# A config role missing its key is skipped with a warning; an explicit CLI override is intent,
|
|
116
|
+
# so it raises rather than silently falling back.
|
|
117
|
+
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
118
|
+
with pytest.raises(RuntimeError):
|
|
119
|
+
build_router("stub", None, {"planner": "openai:gpt-4o"})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Uro Engine CLI reference client. Command surface: docs/08."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("uro-cli")
|
|
7
|
+
except PackageNotFoundError: # pragma: no cover - source checkout without an install
|
|
8
|
+
__version__ = "0.0.0+unknown"
|