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.
@@ -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
@@ -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,8 @@
1
+ from typer.testing import CliRunner
2
+ from uro_cli.main import app
3
+
4
+
5
+ def test_cli_version_runs() -> None:
6
+ result = CliRunner().invoke(app, ["version"])
7
+ assert result.exit_code == 0
8
+ assert "uro-core" in result.output
@@ -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"