onlyworlds 0.1.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.
- onlyworlds-0.1.0/.gitattributes +3 -0
- onlyworlds-0.1.0/.github/workflows/ci.yml +35 -0
- onlyworlds-0.1.0/.gitignore +6 -0
- onlyworlds-0.1.0/CHANGELOG.md +12 -0
- onlyworlds-0.1.0/LICENSE +21 -0
- onlyworlds-0.1.0/PKG-INFO +122 -0
- onlyworlds-0.1.0/README.md +111 -0
- onlyworlds-0.1.0/codegen/generate_schema.py +201 -0
- onlyworlds-0.1.0/codegen/schema-dist/.gitattributes +1 -0
- onlyworlds-0.1.0/codegen/schema-dist/LICENSE +21 -0
- onlyworlds-0.1.0/codegen/schema-dist/MANIFEST.json +73 -0
- onlyworlds-0.1.0/codegen/schema-dist/README.md +88 -0
- onlyworlds-0.1.0/codegen/schema-dist/VERSION +3 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/ability/piano-playing--60195ff8.json +31 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/character/mr-steinway--678bd862.json +46 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/character/rachmaninov--4c0d52b4.json +49 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/character/the-consul--8b711e25.json +56 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/collective/pack-of-bellowing-saurians--1218fc88.json +32 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/construct/hegemony-standard--e40d0001.json +59 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/construct/rachmaninov-s-prelude--6ebf1c1e.json +53 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/construct/steinway-piano--d0a334b1.json +39 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/creature/great-green-saurian--8406227d.json +35 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/creature/greater-green-saurian--e79a4c7c.json +35 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/event/the-primal-performance--8b461fb6.json +45 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/event/the-raging-of-the-beasts--95bb5602.json +39 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/institution/the-hegemony--b6872c95.json +26 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/language/western-musical-notation--f856e44c.json +21 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/location/unnamed-planet--6283dd71.json +44 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/map/map-of-unnamed-planet--153cd291.json +20 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/marker/swamps-point-0--f42a6e15.json +19 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/marker/swamps-point-1--3f8fe28c.json +19 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/marker/swamps-point-2--811d2c15.json +19 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/marker/swamps-point-3--ea668c31.json +19 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/marker/swamps-point-4--4e843a51.json +19 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/marker/swamps-point-5--48b3cfa2.json +19 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/narrative/chapter-1--39fd9536.json +41 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/narrative/hyperion--bb17eaba.json +41 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/narrative/new-book--08afd034.json +41 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/narrative/new-page--2f7bc2f5.json +41 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/narrative/story-of-the-old-piano--2a6553ca.json +45 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/object/ebony-spaceship--f4e8e286.json +30 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/object/spaceship-balcony--dd75923a.json +30 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/object/the-consul-s-steinway--75bfd994.json +30 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/phenomenon/piano-music--5be90c7d.json +23 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/pin/the-consul-s-balcony-seat--df789513.json +19 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/species/saurians--d24e2d1e.json +33 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/title/consul-of-the-hegemony--241a68ab.json +39 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/trait/swamp-rage--dff0708a.json +27 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/elements/zone/swamps--d138f887.json +27 -0
- onlyworlds-0.1.0/codegen/schema-dist/example/hyperion/world.json +20 -0
- onlyworlds-0.1.0/codegen/schema-dist/presentation.json +129 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/ability.yaml +75 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/base_properties.yaml +26 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/character.yaml +168 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/collective.yaml +80 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/construct.yaml +145 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/creature.yaml +92 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/event.yaml +128 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/family.yaml +74 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/institution.yaml +65 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/language.yaml +42 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/law.yaml +64 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/location.yaml +178 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/map.yaml +30 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/marker.yaml +29 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/narrative.yaml +156 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/object.yaml +78 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/phenomenon.yaml +57 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/pin.yaml +26 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/relation.yaml +133 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/species.yaml +90 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/title.yaml +132 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/trait.yaml +74 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/world.yaml +71 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema/zone.yaml +53 -0
- onlyworlds-0.1.0/codegen/schema-dist/schema.json +2901 -0
- onlyworlds-0.1.0/codegen/schema-dist/walk/rulings.yaml +213 -0
- onlyworlds-0.1.0/codegen/schema-dist/walk/schema_walk.py +321 -0
- onlyworlds-0.1.0/codegen/schema-pin.json +13 -0
- onlyworlds-0.1.0/pyproject.toml +46 -0
- onlyworlds-0.1.0/src/onlyworlds/__init__.py +79 -0
- onlyworlds-0.1.0/src/onlyworlds/_ids.py +28 -0
- onlyworlds-0.1.0/src/onlyworlds/_jsonfmt.py +135 -0
- onlyworlds-0.1.0/src/onlyworlds/_schema.py +556 -0
- onlyworlds-0.1.0/src/onlyworlds/_version.py +1 -0
- onlyworlds-0.1.0/src/onlyworlds/errors.py +133 -0
- onlyworlds-0.1.0/src/onlyworlds/export.py +41 -0
- onlyworlds-0.1.0/src/onlyworlds/folder.py +413 -0
- onlyworlds-0.1.0/src/onlyworlds/http.py +606 -0
- onlyworlds-0.1.0/src/onlyworlds/push.py +375 -0
- onlyworlds-0.1.0/src/onlyworlds/py.typed +0 -0
- onlyworlds-0.1.0/tests/conftest.py +72 -0
- onlyworlds-0.1.0/tests/test_client.py +360 -0
- onlyworlds-0.1.0/tests/test_export.py +40 -0
- onlyworlds-0.1.0/tests/test_filenames.py +67 -0
- onlyworlds-0.1.0/tests/test_jsonfmt.py +76 -0
- onlyworlds-0.1.0/tests/test_live.py +42 -0
- onlyworlds-0.1.0/tests/test_push.py +297 -0
- onlyworlds-0.1.0/tests/test_reader.py +119 -0
- onlyworlds-0.1.0/tests/test_schema.py +56 -0
- onlyworlds-0.1.0/tests/test_staging_writes.py +264 -0
- onlyworlds-0.1.0/tests/test_wire.py +167 -0
- onlyworlds-0.1.0/tests/test_writer.py +143 -0
- onlyworlds-0.1.0/uv.lock +418 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
gates:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, windows-latest]
|
|
14
|
+
runs-on: ${{ matrix.os }}
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
- run: uv sync
|
|
19
|
+
- run: uv run pytest -q
|
|
20
|
+
- run: uv run ruff check .
|
|
21
|
+
- run: uv run mypy --strict src
|
|
22
|
+
- run: uv run python codegen/generate_schema.py --check
|
|
23
|
+
|
|
24
|
+
wire:
|
|
25
|
+
# keel's own OpenAPI document and the public demo world, read only. Red here with
|
|
26
|
+
# the gates above green means the server moved under the client, not that a change broke it.
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
env:
|
|
29
|
+
OW_OPENAPI: https://www.onlyworlds.com/api/v2/openapi.json
|
|
30
|
+
OW_LIVE: "1"
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: astral-sh/setup-uv@v5
|
|
34
|
+
- run: uv sync
|
|
35
|
+
- run: uv run pytest -q tests/test_wire.py tests/test_live.py
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
The package version runs on its own semver; the schema it is generated from is `SCHEMA_DIST_TAG`.
|
|
4
|
+
|
|
5
|
+
## 0.1.0 (2026-10-10)
|
|
6
|
+
|
|
7
|
+
The first release on PyPI. Python 3.12+, no runtime dependencies.
|
|
8
|
+
|
|
9
|
+
- **The v2 API client** (`Client`): read, write, link operations, bulk, the change feed (`ChangeWalk`), and the error envelope as `ApiError`. It retries busy and gateway answers (`429`, `502`, `503`, `504`) and honours `Retry-After` (`RetryPolicy`). It never retries a wrong PIN, since each retry counts toward locking the key. The transport is swappable (`Transport`).
|
|
10
|
+
- **World folders** (format v0.3.7): `read_folder` and `write_folder`, filenames that match Atlas's, and the World's three unit settings (`length_unit`, `mass_unit`, `distance_unit`) carried like any world key. Reading never changes a file.
|
|
11
|
+
- **Export and push:** `export_world` writes an online world to a folder. `push` sends a folder's edits back as the difference from a copy exported before editing (`plan_push` to preview it, a log to resume it). A run stops at the first answer that refuses the key itself. What push never sends is listed in `NOT_SENT`.
|
|
12
|
+
- **The schema**, generated from schema-dist `v0.31.0-dist.19` (canonical `00.31.00`): `ELEMENT_TYPES`, `FIELD_KINDS`, and the pinned manifest's hash.
|
onlyworlds-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 OnlyWorlds
|
|
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.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: onlyworlds
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OnlyWorlds for Python: the world folder format (read, write, push) and a client for the v2 API.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Typing :: Typed
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# onlyworlds (Python)
|
|
13
|
+
|
|
14
|
+
OnlyWorlds is an open standard for worldbuilding data: 22 element types (characters, locations, events, laws and the rest) that any tool can read and write. This is a Python client for its API, and a reader and writer for OnlyWorlds world folders (a world as a folder of JSON files). Python 3.12+, no runtime dependencies.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install onlyworlds
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The newest unreleased code: `pip install git+https://github.com/OnlyWorlds/python-sdk`.
|
|
23
|
+
|
|
24
|
+
## First run
|
|
25
|
+
|
|
26
|
+
Moppetopia is a public demo world. Its read-only key, `0000000001`, needs no account:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from onlyworlds import Client
|
|
30
|
+
|
|
31
|
+
client = Client("0000000001")
|
|
32
|
+
print(client.get_world()["name"])
|
|
33
|
+
for c in client.list_page("character", filter={"name__icontains": "fluffington"})["data"]:
|
|
34
|
+
print(" ", c["name"])
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Moppetopia
|
|
39
|
+
Admiral Fluffington
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Your own world
|
|
43
|
+
|
|
44
|
+
Create a world at [onlyworlds.com](https://www.onlyworlds.com); its keys are on the world's page. A new key is shown once, so copy it then.
|
|
45
|
+
|
|
46
|
+
- An `ow_r_` key reads, with no PIN: `Client("ow_r_…")`.
|
|
47
|
+
- An `ow_w_` key reads and writes. Writes also send a PIN: `Client("ow_w_…", "…")`.
|
|
48
|
+
- For code that writes, give it its own [agent seat](https://onlyworlds.github.io/docs/development/agents), a key for one tool or script. The seat's key and its `ow_s_` secret (passed as the PIN) work in one world, and you can remove them without touching your account PIN.
|
|
49
|
+
|
|
50
|
+
Elements are plain dicts:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
writer = Client("ow_w_…", "ow_s_…")
|
|
54
|
+
peak = writer.create("location", {"name": "Dragon Peak"})
|
|
55
|
+
writer.patch("location", peak["id"], {"supertype": "Mountain"})
|
|
56
|
+
|
|
57
|
+
for character in client.iter_elements("character", expand=["species"]):
|
|
58
|
+
print(character["name"])
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## World folders
|
|
62
|
+
|
|
63
|
+
A world folder is one JSON file per element, in [the world folder format](https://onlyworlds.github.io/docs/schema/folders). [Atlas](https://atlas.onlyworlds.com), the OnlyWorlds editor, reads and writes the same format.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from onlyworlds import export_world, read_folder
|
|
67
|
+
|
|
68
|
+
export_world("moppetopia", api_key="0000000001")
|
|
69
|
+
folder = read_folder("moppetopia")
|
|
70
|
+
print(folder.world["name"], len(folder.elements), "elements")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`write_folder(root, world, elements)` writes a folder from a world dict and a dict of element lists by type. Reading never changes a file, and a file without an id is skipped and left alone.
|
|
74
|
+
|
|
75
|
+
To send a folder's edits back, keep a copy exported before you edited, then push the difference:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from onlyworlds import push
|
|
79
|
+
|
|
80
|
+
result = push("moppetopia-before", "moppetopia", api_key="ow_w_…", pin="ow_s_…", log_path="push.jsonl")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`push` sends only the fields that changed, retries what the server asks it to retry, checks that what came back is what it sent, and logs every write, so running it again skips what already landed. `dry_run=True` shows the plan without sending it.
|
|
84
|
+
|
|
85
|
+
## Errors
|
|
86
|
+
|
|
87
|
+
A failed call raises `ApiError` with the API's `code`, `param` (the field that failed) and `doc_url`, and flags for the cases you handle differently: `is_validation_error`, `is_auth_error`, `is_not_author`, `is_owner_only`, `is_id_conflict`, `is_resync_required`, `is_busy`.
|
|
88
|
+
|
|
89
|
+
## The change feed
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
walk = client.walk_changes(saved_cursor) # None for everything
|
|
93
|
+
for op in walk:
|
|
94
|
+
... # op["op"] is "upsert" or "delete"
|
|
95
|
+
saved_cursor = walk.cursor # opaque: keep it, don't parse it
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Schema
|
|
99
|
+
|
|
100
|
+
The 22 element types and each field's kind are generated from the published [schema distribution](https://github.com/OnlyWorlds/schema-dist); `onlyworlds.SCHEMA_VERSION` and `onlyworlds.SCHEMA_DIST_TAG` say which one. The package's own version doesn't track the schema's.
|
|
101
|
+
|
|
102
|
+
Not yet: typed element models, the account routes, a snapshot writer.
|
|
103
|
+
|
|
104
|
+
## Develop
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv sync
|
|
108
|
+
uv run pytest -q
|
|
109
|
+
uv run ruff check . && uv run mypy --strict src
|
|
110
|
+
uv run python codegen/generate_schema.py --check
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Three groups of tests run only when asked:
|
|
114
|
+
|
|
115
|
+
- `OW_CONFORMANCE_FIXTURE=<path>`: the folder-format conformance fixture from the Atlas repo.
|
|
116
|
+
- `OW_OPENAPI=https://www.onlyworlds.com/api/v2/openapi.json` (or a saved copy): checks the client's routes, parameters and fields against the API's own OpenAPI document.
|
|
117
|
+
- `OW_LIVE=1`: read-only calls against the demo world.
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# onlyworlds (Python)
|
|
2
|
+
|
|
3
|
+
OnlyWorlds is an open standard for worldbuilding data: 22 element types (characters, locations, events, laws and the rest) that any tool can read and write. This is a Python client for its API, and a reader and writer for OnlyWorlds world folders (a world as a folder of JSON files). Python 3.12+, no runtime dependencies.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install onlyworlds
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The newest unreleased code: `pip install git+https://github.com/OnlyWorlds/python-sdk`.
|
|
12
|
+
|
|
13
|
+
## First run
|
|
14
|
+
|
|
15
|
+
Moppetopia is a public demo world. Its read-only key, `0000000001`, needs no account:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from onlyworlds import Client
|
|
19
|
+
|
|
20
|
+
client = Client("0000000001")
|
|
21
|
+
print(client.get_world()["name"])
|
|
22
|
+
for c in client.list_page("character", filter={"name__icontains": "fluffington"})["data"]:
|
|
23
|
+
print(" ", c["name"])
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Moppetopia
|
|
28
|
+
Admiral Fluffington
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Your own world
|
|
32
|
+
|
|
33
|
+
Create a world at [onlyworlds.com](https://www.onlyworlds.com); its keys are on the world's page. A new key is shown once, so copy it then.
|
|
34
|
+
|
|
35
|
+
- An `ow_r_` key reads, with no PIN: `Client("ow_r_…")`.
|
|
36
|
+
- An `ow_w_` key reads and writes. Writes also send a PIN: `Client("ow_w_…", "…")`.
|
|
37
|
+
- For code that writes, give it its own [agent seat](https://onlyworlds.github.io/docs/development/agents), a key for one tool or script. The seat's key and its `ow_s_` secret (passed as the PIN) work in one world, and you can remove them without touching your account PIN.
|
|
38
|
+
|
|
39
|
+
Elements are plain dicts:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
writer = Client("ow_w_…", "ow_s_…")
|
|
43
|
+
peak = writer.create("location", {"name": "Dragon Peak"})
|
|
44
|
+
writer.patch("location", peak["id"], {"supertype": "Mountain"})
|
|
45
|
+
|
|
46
|
+
for character in client.iter_elements("character", expand=["species"]):
|
|
47
|
+
print(character["name"])
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## World folders
|
|
51
|
+
|
|
52
|
+
A world folder is one JSON file per element, in [the world folder format](https://onlyworlds.github.io/docs/schema/folders). [Atlas](https://atlas.onlyworlds.com), the OnlyWorlds editor, reads and writes the same format.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from onlyworlds import export_world, read_folder
|
|
56
|
+
|
|
57
|
+
export_world("moppetopia", api_key="0000000001")
|
|
58
|
+
folder = read_folder("moppetopia")
|
|
59
|
+
print(folder.world["name"], len(folder.elements), "elements")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`write_folder(root, world, elements)` writes a folder from a world dict and a dict of element lists by type. Reading never changes a file, and a file without an id is skipped and left alone.
|
|
63
|
+
|
|
64
|
+
To send a folder's edits back, keep a copy exported before you edited, then push the difference:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from onlyworlds import push
|
|
68
|
+
|
|
69
|
+
result = push("moppetopia-before", "moppetopia", api_key="ow_w_…", pin="ow_s_…", log_path="push.jsonl")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`push` sends only the fields that changed, retries what the server asks it to retry, checks that what came back is what it sent, and logs every write, so running it again skips what already landed. `dry_run=True` shows the plan without sending it.
|
|
73
|
+
|
|
74
|
+
## Errors
|
|
75
|
+
|
|
76
|
+
A failed call raises `ApiError` with the API's `code`, `param` (the field that failed) and `doc_url`, and flags for the cases you handle differently: `is_validation_error`, `is_auth_error`, `is_not_author`, `is_owner_only`, `is_id_conflict`, `is_resync_required`, `is_busy`.
|
|
77
|
+
|
|
78
|
+
## The change feed
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
walk = client.walk_changes(saved_cursor) # None for everything
|
|
82
|
+
for op in walk:
|
|
83
|
+
... # op["op"] is "upsert" or "delete"
|
|
84
|
+
saved_cursor = walk.cursor # opaque: keep it, don't parse it
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Schema
|
|
88
|
+
|
|
89
|
+
The 22 element types and each field's kind are generated from the published [schema distribution](https://github.com/OnlyWorlds/schema-dist); `onlyworlds.SCHEMA_VERSION` and `onlyworlds.SCHEMA_DIST_TAG` say which one. The package's own version doesn't track the schema's.
|
|
90
|
+
|
|
91
|
+
Not yet: typed element models, the account routes, a snapshot writer.
|
|
92
|
+
|
|
93
|
+
## Develop
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uv sync
|
|
97
|
+
uv run pytest -q
|
|
98
|
+
uv run ruff check . && uv run mypy --strict src
|
|
99
|
+
uv run python codegen/generate_schema.py --check
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Three groups of tests run only when asked:
|
|
103
|
+
|
|
104
|
+
- `OW_CONFORMANCE_FIXTURE=<path>`: the folder-format conformance fixture from the Atlas repo.
|
|
105
|
+
- `OW_OPENAPI=https://www.onlyworlds.com/api/v2/openapi.json` (or a saved copy): checks the client's routes, parameters and fields against the API's own OpenAPI document.
|
|
106
|
+
- `OW_LIVE=1`: read-only calls against the demo world.
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate src/onlyworlds/_schema.py from the vendored schema distribution.
|
|
3
|
+
|
|
4
|
+
Build-time only: needs PyYAML (the walk imports it). The generated module has no
|
|
5
|
+
dependencies, so the published package does not need PyYAML at runtime.
|
|
6
|
+
|
|
7
|
+
The type list comes from THE walk (codegen/schema-dist/walk/schema_walk.py),
|
|
8
|
+
imported, never re-implemented. A second witness is checked on every run: the
|
|
9
|
+
walk's list must equal the dist's schema/*.yaml stems minus the two non-element
|
|
10
|
+
files (base_properties, world). If they ever disagree, generation fails instead
|
|
11
|
+
of picking one.
|
|
12
|
+
|
|
13
|
+
Order of checks matters: the dist is authenticated (MANIFEST.json against the
|
|
14
|
+
pin) and its contents verified (every file against MANIFEST.json) BEFORE the
|
|
15
|
+
walk is imported, because importing unverified code to answer a question about
|
|
16
|
+
the verified tree would be the complicit-manifest case.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
python codegen/generate_schema.py write the module
|
|
20
|
+
python codegen/generate_schema.py --check exit 1 if the committed module is stale
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import hashlib
|
|
27
|
+
import importlib.util
|
|
28
|
+
import json
|
|
29
|
+
import sys
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
HERE = Path(__file__).resolve().parent
|
|
34
|
+
DIST = HERE / "schema-dist"
|
|
35
|
+
PIN = HERE / "schema-pin.json"
|
|
36
|
+
OUT = HERE.parent / "src" / "onlyworlds" / "_schema.py"
|
|
37
|
+
|
|
38
|
+
NON_ELEMENT_SCHEMA_FILES = {"base_properties", "world"}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _sha256(path: Path) -> str:
|
|
42
|
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def verify_dist() -> dict[str, object]:
|
|
46
|
+
"""Authenticate the vendored tree. Returns the pin record. Raises SystemExit on failure."""
|
|
47
|
+
pin: dict[str, object] = json.loads(PIN.read_text(encoding="utf-8"))
|
|
48
|
+
manifest = DIST / "MANIFEST.json"
|
|
49
|
+
got = _sha256(manifest)
|
|
50
|
+
if got != pin["manifest_sha256"]:
|
|
51
|
+
raise SystemExit(
|
|
52
|
+
f"schema-dist MANIFEST.json sha256 {got} != pinned {pin['manifest_sha256']} "
|
|
53
|
+
"(moved tag, or a re-vendor without a re-pin)"
|
|
54
|
+
)
|
|
55
|
+
files: dict[str, str] = json.loads(manifest.read_text(encoding="utf-8"))["files"]
|
|
56
|
+
bad = [f for f, h in sorted(files.items()) if not (DIST / f).is_file() or _sha256(DIST / f) != h]
|
|
57
|
+
if bad:
|
|
58
|
+
raise SystemExit(f"schema-dist files do not match MANIFEST.json: {bad}")
|
|
59
|
+
version = dict(
|
|
60
|
+
line.split(": ", 1) for line in (DIST / "VERSION").read_text(encoding="utf-8").splitlines() if ": " in line
|
|
61
|
+
)
|
|
62
|
+
if version.get("canonical") != pin["canonical_version"] or int(version.get("serial", -1)) != pin["dist_serial"]:
|
|
63
|
+
raise SystemExit(f"schema-dist VERSION {version} disagrees with the pin record")
|
|
64
|
+
return pin
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# The walk's kinds -> the wire vocabulary the npm SDK's FIELD_SCHEMA uses.
|
|
68
|
+
KIND_NAMES = {"scalar_str": "text", "scalar_int": "integer", "single": "single_link", "multi": "multi_link"}
|
|
69
|
+
# Notes the walk is known to emit on this pin. Any OTHER note fails generation
|
|
70
|
+
# (rulings.yaml: unknown-field-types-must-be-surfaced): silence is how a newer
|
|
71
|
+
# schema meets an older decoder and drops a field.
|
|
72
|
+
EXPECTED_NOTES = ("pin.element: `generic-link`", "relation.events: field declared more than once")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_walk() -> Any:
|
|
76
|
+
spec = importlib.util.spec_from_file_location("schema_walk", DIST / "walk" / "schema_walk.py")
|
|
77
|
+
assert spec is not None and spec.loader is not None
|
|
78
|
+
walk = importlib.util.module_from_spec(spec)
|
|
79
|
+
spec.loader.exec_module(walk)
|
|
80
|
+
return walk
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def field_kinds(walk: Any, types: list[str]) -> dict[str, dict[str, str]]:
|
|
84
|
+
"""type -> {wire field name: kind}. Base fields by a DECLARED mapping (the SDK's):
|
|
85
|
+
base_properties.yaml names lowercased; `Id` and `World` dropped (identity, and a
|
|
86
|
+
field the API rejects in bodies). The generic link (pin.element) is served as
|
|
87
|
+
the pair element_type (text) + element_id (single_link), as the SDK emits it."""
|
|
88
|
+
schema = DIST / "schema"
|
|
89
|
+
base_doc = walk.load_yaml(schema, "base_properties")
|
|
90
|
+
base = {
|
|
91
|
+
name.lower(): "text"
|
|
92
|
+
for name, spec in base_doc["properties"].items()
|
|
93
|
+
if name.lower() not in ("id", "world") and spec.get("type") == "string"
|
|
94
|
+
}
|
|
95
|
+
notes: list[str] = []
|
|
96
|
+
out: dict[str, dict[str, str]] = {}
|
|
97
|
+
for t in types:
|
|
98
|
+
kinds = dict(base)
|
|
99
|
+
for f in walk.flatten_fields(walk.load_yaml(schema, t), t, note=notes.append):
|
|
100
|
+
if f["kind"] == "generic":
|
|
101
|
+
kinds[f"{f['name']}_type"] = "text"
|
|
102
|
+
kinds[f"{f['name']}_id"] = "single_link"
|
|
103
|
+
elif f["kind"] in KIND_NAMES:
|
|
104
|
+
kinds[f["name"]] = KIND_NAMES[f["kind"]]
|
|
105
|
+
else:
|
|
106
|
+
raise SystemExit(f"{t}.{f['name']}: unknown kind {f['kind']!r} from the walk")
|
|
107
|
+
out[t] = kinds
|
|
108
|
+
unexpected = [n for n in notes if not n.startswith(EXPECTED_NOTES)]
|
|
109
|
+
if unexpected:
|
|
110
|
+
raise SystemExit(f"the walk raised notes this generator does not know: {unexpected}")
|
|
111
|
+
return out
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def generic_pairs(walk: Any, types: list[str]) -> dict[str, list[tuple[str, str]]]:
|
|
115
|
+
"""type -> [(type column, id column)] for each generic link: keel refuses a write that sets one half."""
|
|
116
|
+
schema = DIST / "schema"
|
|
117
|
+
out: dict[str, list[tuple[str, str]]] = {}
|
|
118
|
+
for t in types:
|
|
119
|
+
pairs = [
|
|
120
|
+
(f"{f['name']}_type", f"{f['name']}_id")
|
|
121
|
+
for f in walk.flatten_fields(walk.load_yaml(schema, t), t, note=lambda _n: None)
|
|
122
|
+
if f["kind"] == "generic"
|
|
123
|
+
]
|
|
124
|
+
if pairs:
|
|
125
|
+
out[t] = pairs
|
|
126
|
+
return out
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def element_types(walk: Any) -> list[str]:
|
|
130
|
+
from_walk: list[str] = list(walk.ELEMENT_TYPES)
|
|
131
|
+
from_files = sorted(p.stem for p in (DIST / "schema").glob("*.yaml") if p.stem not in NON_ELEMENT_SCHEMA_FILES)
|
|
132
|
+
if sorted(from_walk) != from_files or len(set(from_walk)) != len(from_walk):
|
|
133
|
+
raise SystemExit(f"the walk's ELEMENT_TYPES {from_walk} disagree with the dist's schema files {from_files}")
|
|
134
|
+
return from_walk
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def render(
|
|
138
|
+
pin: dict[str, object],
|
|
139
|
+
types: list[str],
|
|
140
|
+
kinds: dict[str, dict[str, str]],
|
|
141
|
+
pairs: dict[str, list[tuple[str, str]]],
|
|
142
|
+
) -> str:
|
|
143
|
+
lines = [
|
|
144
|
+
"# GENERATED by codegen/generate_schema.py from the vendored schema distribution. Do not hand-edit.",
|
|
145
|
+
"# Regenerate: python codegen/generate_schema.py (CI/test: --check)",
|
|
146
|
+
f"# Source: {pin['repo']} @ {pin['tag']} ({pin['commit']})",
|
|
147
|
+
"",
|
|
148
|
+
'"""Schema provenance and the 22 element types, generated from the pinned schema distribution."""',
|
|
149
|
+
"",
|
|
150
|
+
f'SCHEMA_DIST_TAG = "{pin["tag"]}"',
|
|
151
|
+
f'SCHEMA_VERSION = "{pin["canonical_version"]}"',
|
|
152
|
+
f"SCHEMA_DIST_SERIAL = {pin['dist_serial']}",
|
|
153
|
+
f'SCHEMA_MANIFEST_SHA256 = "{pin["manifest_sha256"]}"',
|
|
154
|
+
"",
|
|
155
|
+
"# Canonical order, as the walk declares it.",
|
|
156
|
+
"ELEMENT_TYPES: tuple[str, ...] = (",
|
|
157
|
+
*[f' "{t}",' for t in types],
|
|
158
|
+
")",
|
|
159
|
+
"",
|
|
160
|
+
"# type -> {wire field name: text | integer | single_link | multi_link}. Schema fields only:",
|
|
161
|
+
"# anything else on a body is server-managed, a tool's extension, or unknown.",
|
|
162
|
+
"FIELD_KINDS: dict[str, dict[str, str]] = {",
|
|
163
|
+
]
|
|
164
|
+
for t in types:
|
|
165
|
+
lines.append(f' "{t}": {{')
|
|
166
|
+
lines.extend(f' "{name}": "{kind}",' for name, kind in kinds[t].items())
|
|
167
|
+
lines.append(" },")
|
|
168
|
+
lines += [
|
|
169
|
+
"}",
|
|
170
|
+
"",
|
|
171
|
+
"# type -> ((type column, id column), ...): each generic link's two halves. A write sets both or neither.",
|
|
172
|
+
"GENERIC_PAIRS: dict[str, tuple[tuple[str, str], ...]] = {",
|
|
173
|
+
*[f' "{t}": ({", ".join(f'("{a}", "{b}")' for a, b in pairs[t])},),' for t in types if t in pairs],
|
|
174
|
+
"}",
|
|
175
|
+
"",
|
|
176
|
+
]
|
|
177
|
+
return "\n".join(lines)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def main() -> int:
|
|
181
|
+
ap = argparse.ArgumentParser()
|
|
182
|
+
ap.add_argument("--check", action="store_true")
|
|
183
|
+
args = ap.parse_args()
|
|
184
|
+
pin = verify_dist()
|
|
185
|
+
walk = load_walk()
|
|
186
|
+
types = element_types(walk)
|
|
187
|
+
text = render(pin, types, field_kinds(walk, types), generic_pairs(walk, types))
|
|
188
|
+
if args.check:
|
|
189
|
+
current = OUT.read_bytes().decode("utf-8") if OUT.is_file() else ""
|
|
190
|
+
if current != text:
|
|
191
|
+
print(f"STALE: {OUT} does not match what the pinned dist generates", file=sys.stderr)
|
|
192
|
+
return 1
|
|
193
|
+
print(f"ok: {OUT.name} matches {pin['tag']}")
|
|
194
|
+
return 0
|
|
195
|
+
OUT.write_bytes(text.encode("utf-8")) # bytes: LF on every platform
|
|
196
|
+
print(f"wrote {OUT} ({pin['tag']})")
|
|
197
|
+
return 0
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
if __name__ == "__main__":
|
|
201
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* -text
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 OnlyWorlds
|
|
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.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": {
|
|
3
|
+
".gitattributes": "705fd4d6451a31d36b3df7de96f83f30ac976c9b4a6d1e51671d8e2f33e2d0da",
|
|
4
|
+
"example/hyperion/elements/ability/piano-playing--60195ff8.json": "a1ff537bb6db03944aa9aec97daef4d5847ed4bfa7150b71df9d533220f33cf6",
|
|
5
|
+
"example/hyperion/elements/character/mr-steinway--678bd862.json": "c0c93bff76d76f481a611b8772741d57a8adbb72d05e4465f9d30cf5ade3090c",
|
|
6
|
+
"example/hyperion/elements/character/rachmaninov--4c0d52b4.json": "15d2b8ee04ba6085b40bc06ef00e65e4fd392472ea47466e2e580b39248fcdee",
|
|
7
|
+
"example/hyperion/elements/character/the-consul--8b711e25.json": "f2a8c37169628c6109cdbe3bb3416a84ee9d8e1b3439e181cfc347b0be93ef04",
|
|
8
|
+
"example/hyperion/elements/collective/pack-of-bellowing-saurians--1218fc88.json": "02b87475b540d173ebc3418d8e825e207364cb3e6f87afebd738d6e35609f9f3",
|
|
9
|
+
"example/hyperion/elements/construct/hegemony-standard--e40d0001.json": "8db776fd7a3faa3a22626f2ca80e825853fa024ca2f4b18ae4070fbd82f5299f",
|
|
10
|
+
"example/hyperion/elements/construct/rachmaninov-s-prelude--6ebf1c1e.json": "588b1c509827371c4176c1cefd26f562e01e04664c134a3621607f193a38f1b5",
|
|
11
|
+
"example/hyperion/elements/construct/steinway-piano--d0a334b1.json": "2180305399f14be914b260d95d4859b0ae20a90e1767e103fc8015ae13ac10ad",
|
|
12
|
+
"example/hyperion/elements/creature/great-green-saurian--8406227d.json": "5dfc5230c54117b2974dee958a9c50623fd31a4c2a2823e255b1b12e75345d80",
|
|
13
|
+
"example/hyperion/elements/creature/greater-green-saurian--e79a4c7c.json": "af102041d4a69dc745ed29ce1e22db28da79af7c1e5a5927df92a72a2cb59947",
|
|
14
|
+
"example/hyperion/elements/event/the-primal-performance--8b461fb6.json": "df38b54bad28cf3ba84fed34f34912d389dfe502d34754d9818a8fe51b5fc1ba",
|
|
15
|
+
"example/hyperion/elements/event/the-raging-of-the-beasts--95bb5602.json": "d1b544e76495ab4bb4cd6bb19e9fa8d68ef7830187e107d4aafc82bb56d7b1d1",
|
|
16
|
+
"example/hyperion/elements/institution/the-hegemony--b6872c95.json": "69f99fa1e6f1f172eb941059c3b20f9c4e68dce2e6ed97c252c495b1c68e56cd",
|
|
17
|
+
"example/hyperion/elements/language/western-musical-notation--f856e44c.json": "d9ae2b48e93a8bf079d82520ea51d199baf401d666e107a4d25be198e0a89ae9",
|
|
18
|
+
"example/hyperion/elements/location/unnamed-planet--6283dd71.json": "f6249cc4920a66330ee90ee66ea5e592648b6812fc8dc0e893a6e00b1d2ea259",
|
|
19
|
+
"example/hyperion/elements/map/map-of-unnamed-planet--153cd291.json": "d65670dc9baeed1b4c67bcb0e0d7e4349f0d6b2b3bc1a9d25e1fd7a0d989a472",
|
|
20
|
+
"example/hyperion/elements/marker/swamps-point-0--f42a6e15.json": "925d954946e51a3cbf62e89ee917a71ec5454c9a67ed29fb1dfeeae432dfd3a8",
|
|
21
|
+
"example/hyperion/elements/marker/swamps-point-1--3f8fe28c.json": "0f8e4d0af04f75a3f6d1a2cd8f3fdd305e88aaa5ea72ccdbafee23488c374826",
|
|
22
|
+
"example/hyperion/elements/marker/swamps-point-2--811d2c15.json": "9157c840604764336ae40088949d2ec298fef482352bebfa2d31d784725ee2ce",
|
|
23
|
+
"example/hyperion/elements/marker/swamps-point-3--ea668c31.json": "4cc41e877b2a65ccd98f7ba99269f690a7376077b23d73b210ee3c88fafc8509",
|
|
24
|
+
"example/hyperion/elements/marker/swamps-point-4--4e843a51.json": "24b489a8a2a569aea6508e0c7b63c087c90fc03d9d7261bca0ef6593149a7efe",
|
|
25
|
+
"example/hyperion/elements/marker/swamps-point-5--48b3cfa2.json": "46106b8c97e2f95e4973384558e2e5d243fa1e8330c9d5751a3fb2c830e16ba7",
|
|
26
|
+
"example/hyperion/elements/narrative/chapter-1--39fd9536.json": "551f6a41ab05c7d8b45ca659c540bb8cdd0c2a492341d7e457190fcf2ebfd020",
|
|
27
|
+
"example/hyperion/elements/narrative/hyperion--bb17eaba.json": "a60867b9d2df1f9451498ac0d17ceb4fb7b911dd624ba02b8d5398d7c4e1feae",
|
|
28
|
+
"example/hyperion/elements/narrative/new-book--08afd034.json": "294a4efc6067c891dec3eaeec12e4f2137eca9ffc637210bda345dcf6e3bebdf",
|
|
29
|
+
"example/hyperion/elements/narrative/new-page--2f7bc2f5.json": "1fe86a75bf223a87b44f27b0555604abb9d63661a9ddceee2b0b2514092069b5",
|
|
30
|
+
"example/hyperion/elements/narrative/story-of-the-old-piano--2a6553ca.json": "dd0db85f8cebe8662c6d518a86cbd45b1301d251bf4538a302d30ad540314ef6",
|
|
31
|
+
"example/hyperion/elements/object/ebony-spaceship--f4e8e286.json": "ca58edba90b6a50016dac127ee2526247c1ab7b552774faddf1bd83e4ae263fe",
|
|
32
|
+
"example/hyperion/elements/object/spaceship-balcony--dd75923a.json": "4853b29c222c05d278ab947eb45b7d6dab23135b20db51c015629c167553dafe",
|
|
33
|
+
"example/hyperion/elements/object/the-consul-s-steinway--75bfd994.json": "514914783bfbd8e91d887cfc06068f020536523dee897aa7d94619c5c9973cdc",
|
|
34
|
+
"example/hyperion/elements/phenomenon/piano-music--5be90c7d.json": "020b2cc3e7c710ef43ec5ca948e40a911dc698b89da34d2da0dc7ff7c627d699",
|
|
35
|
+
"example/hyperion/elements/pin/the-consul-s-balcony-seat--df789513.json": "a6f91a3badcc97ef2a1e45cbda608934f773990e212bc4c83f9370b6f1a562c2",
|
|
36
|
+
"example/hyperion/elements/species/saurians--d24e2d1e.json": "90133114fb8c5f1642370741555f303f05580fbaa0cc63f50bba249d6f5720c6",
|
|
37
|
+
"example/hyperion/elements/title/consul-of-the-hegemony--241a68ab.json": "4723020b824c3abd22b3d2e20d23521ac68fbd3e2198527539fadfabf91cf1f3",
|
|
38
|
+
"example/hyperion/elements/trait/swamp-rage--dff0708a.json": "6bd7a73f73f9a8c37d8ca603af8209c75251ad524aa485fc06538a63c0123588",
|
|
39
|
+
"example/hyperion/elements/zone/swamps--d138f887.json": "d9e9caf3360daaecee51dab9716c7e4a444b47736284c0a6b4269d8b84ae2c45",
|
|
40
|
+
"example/hyperion/world.json": "eeaa6f45d6c232e7aae2114a40eb68a7d30fa7eadcb9e51e6a77908db8d738bb",
|
|
41
|
+
"LICENSE": "ab9469f20388ba53771315ed7cb94461d5e3de6e0113bd8f0280f3364434fe18",
|
|
42
|
+
"presentation.json": "776b5e48d9b27c1d99baabfdaffe7d2fe3995580450452c26e5ab7e30176264f",
|
|
43
|
+
"README.md": "0478b0be204b8683b3f8ea2da37d3ba3cc968a3ed6aa81c994c99242e5773892",
|
|
44
|
+
"schema/ability.yaml": "9fb53aa50af02134714e804b7e4c54652b63dd7adc11dd4aef991c64503205fc",
|
|
45
|
+
"schema/base_properties.yaml": "9d1d6b11ebae8405a3d9845fe8b186059c57e8f7474c9a0665d229025396cb2c",
|
|
46
|
+
"schema/character.yaml": "8bf572cb676024e86c4f7502a0b7f5bdfb2531071633f6f184e446f4e39efa3a",
|
|
47
|
+
"schema/collective.yaml": "da660c6bb2bfb4d2a2aeef958590a334dd86112477ae8112d9bf520b1a7d06f8",
|
|
48
|
+
"schema/construct.yaml": "0f2233208e03fa8cfd60ac1fd51cae2078713e8ca3f227437f4d3e520e7e3a6f",
|
|
49
|
+
"schema/creature.yaml": "66c8b7123614c4ef506386aae9a077d11babbb1ad365986ffcdb879100403c58",
|
|
50
|
+
"schema/event.yaml": "5843fd04bdfca18934c37c31c1f21777780bad7381bf4b3a8e731f21e4d99558",
|
|
51
|
+
"schema/family.yaml": "2f3b9225d46c15138b714d4ac5157b1ea2f837a2046431454c22ded226a1f5ba",
|
|
52
|
+
"schema/institution.yaml": "8f1912ce0588315eb982b7b32b72dbd2d641dce235533e33527b51767e7075e4",
|
|
53
|
+
"schema/language.yaml": "272fdc87bfaf3b91ca19df7354ba0a1986d9933612a740e0c8cfcc9e86033f78",
|
|
54
|
+
"schema/law.yaml": "51a35f12128ed6cb0069086aebc2523694373a50fff659657109210264ef3dd8",
|
|
55
|
+
"schema/location.yaml": "6833171e55989468f11105cfad7ac2b1d7812b112392765dc2572fd03a80ab57",
|
|
56
|
+
"schema/map.yaml": "5a3ee73f68f4c096c9969b4bcec44e0c4d6be4e826d0496f1cf7e81736d10375",
|
|
57
|
+
"schema/marker.yaml": "2843eff80fc4360e0d8e9fca5ade1c9337d88c99089a79182e3454e8056d3ed0",
|
|
58
|
+
"schema/narrative.yaml": "d5ab13a6278e8fc72ffca1ed2429bc2dc73e6ea999396c3825a0ac68b80f4e21",
|
|
59
|
+
"schema/object.yaml": "697a853295302aa036fbf26190b8b754a5d177675d97421cab52026ffe1bb2f9",
|
|
60
|
+
"schema/phenomenon.yaml": "27dc2d0557fb4d5e9b08769b989dfb19c61ead77670c34e152ef1462918fdd16",
|
|
61
|
+
"schema/pin.yaml": "2554639d3bc0ef3be6477253c3486521b01e26f7eb6ec38c870120e9257486b1",
|
|
62
|
+
"schema/relation.yaml": "40be0989442bddeb84d11762250c92e6026065d174ae32d3a00c2c650d857823",
|
|
63
|
+
"schema/species.yaml": "6df722fe713b1b5adbde591b4664e808292126b489d99c7b469349b5af7e6cd5",
|
|
64
|
+
"schema/title.yaml": "36ef98440f238c731bdb433102fda890794cde31031e05ce8fe56b70801e96ad",
|
|
65
|
+
"schema/trait.yaml": "261df723f603809d71c9ddf5faf27a0f0ba92186f544e56cea95911051d2359d",
|
|
66
|
+
"schema/world.yaml": "f4e875e80802a77f21afe8334cdb5918299d64cb52007abd050b131d59cf9d04",
|
|
67
|
+
"schema/zone.yaml": "9474902bf71703ac82495b28ad253266edc0ffc69894ac8846e3934b268c81a5",
|
|
68
|
+
"schema.json": "9ebf22d585e30d72dd4d17a1b09016d620a5ef0b2b806c5d0800efb0588bbb2e",
|
|
69
|
+
"VERSION": "5a20f5f044a10a717a369d6424dacf43d299b8cebfa3e1e23cad12cfda42aea8",
|
|
70
|
+
"walk/rulings.yaml": "75fff534c7dd47763ba79d489a31c7792b4f0569e5d3cbbf44f46e677e92deef",
|
|
71
|
+
"walk/schema_walk.py": "519f431f496e28b2745d6c3773ecbd0174b26175b10fcf49eb527aaed43b895b"
|
|
72
|
+
}
|
|
73
|
+
}
|