CLI for the OntologyEngine REST API — build, validate, and run ontology graphs
Project description
ontology-builder-cli
A typed Python CLI for the OntologyEngine REST API. Designed to be driven by agents (Claude Code, scripts, CI) more than by humans, but works fine for both.
What is this
ontology-builder-cli is a thin httpx + Pydantic + Typer wrapper around the
OntologyBuilder backend at http://localhost:5286. Every resource the BE
exposes (projects, nodes, edges, routing rules, assertions, validation, runs)
has a corresponding command group, and every wire-format quirk (camelCase,
int-encoded enums, JSON-string fields, discriminated condition trees) is
hidden behind named flags and string enum choices.
There is no authentication today — the BE has no auth scheme registered.
Run ontology-cli auth check to verify this against your environment.
Install
From PyPI
pip install ontology-builder-cli
# or
uv add ontology-builder-cli
From source
cd OntologyBuilderSkills
uv sync
uv run ontology-cli --help
uv creates a .venv in the project root. Use uv run ontology-cli ...
or activate the venv directly.
Configuration
| variable | default | meaning |
|---|---|---|
ONTOLOGY_API_URL |
http://localhost:5286 |
Base URL of the BE. |
--base-url flag |
(env or default) | Per-invocation override. |
Output formats
Every read or write command supports --json. Without it, the CLI prints
a Rich panel/table for humans. With it, you get raw JSON to stdout — that
is the form agents and scripts should use:
PID=$(uv run ontology-cli project create --name demo --json | jq -r .id)
Errors
API errors are caught and printed as HTTP <status> <method> <url> followed
by the BE's code and message (when present). Exit code is 1 for
4xx/5xx, 0 for success, 2 for the validate command when error-severity
issues exist.
The BE returns stable string error codes you can branch on:
- Routing-rule conditions:
CONDITION_UNKNOWN_FIELD,CONDITION_OPERATOR_NOT_SUPPORTED,CONDITION_VALUE_SHAPE_MISMATCH,CONDITION_VALUE_TYPE_MISMATCH,CONDITION_EMPTY_GROUP,CONDITION_NESTING_TOO_DEEP. - Output value rules:
OUTPUT_VALUE_EMPTY_RULE,OUTPUT_VALUE_MISSING_DEFAULT,OUTPUT_VALUE_DEFAULT_NOT_LAST,OUTPUT_VALUE_TYPE_MISMATCH,OUTPUT_VALUE_CONDITION_INVALID. - Assertions:
ASSERTION_EXPECTATIONS_CONFLICT(terminal + coverage expectations combined),ASSERTION_PATH_NODE_NOT_FOUND(unknown node id inexpectedPath),ASSERTION_EXPECTED_OUTPUT_UNKNOWN_FIELD(node or output name inexpectedNodeOutputsnot declared in the project). - Nodes:
DUPLICATE_IO_NAME(two inputs or two outputs on the same node share a name). - Runs:
RUN_BLOCKED_BY_STATIC_ERRORS,RUN_NO_ASSERTIONS,RUN_NO_ENTRY,RUN_AMBIGUOUS_ENTRY. - Validation issue codes (returned in
validateresults, not as errors):EDGE_INTEGRITY,REFERENCE_INTEGRITY(rule's target node has no edge from the source — common when modelling error routes; create a sink node and an edge first),ROUTING_COMPLETENESS,UNREACHABLE_NODE,CYCLE_NO_EXIT,CONDITION_INTEGRITY.
Command map
ontology-cli
├── blueprint compile write a .ont YAML, compile + import (preferred for new graphs)
├── auth check probe whether auth is enforced
├── project list / get / create / patch / delete / export / import
├── node list / get / create / delete (per project)
├── edge list / create / delete (per project)
├── rule list / create / delete (per node)
├── assertion list / create / delete (per project)
├── imports list / get / cancel (project import jobs)
├── run start / list / get (per project)
└── validate (per project)
Use --help on any subcommand to see flags. Every command that takes
project-scoped IDs takes them as positional arguments in the order they
appear in the URL path.
Quickstart: build an ontology from a .ont blueprint (preferred)
The blueprint workflow is the recommended path for creating new ontologies — especially for AI agents. You write one YAML file with human-readable names, no UUIDs, and the CLI handles ID generation, expression parsing, and upload in a single command.
cat > auth_flow.ont <<'EOF'
name: AuthFlow
nodes:
- name: entry
inputs:
- {name: user_type, type: string, values: [admin, guest]}
- name: dashboard
- name: public_view
edges:
- {from: entry, to: dashboard, label: to admin}
- {from: entry, to: public_view, label: to guest}
rules:
- {node: entry, to: dashboard, when: "user_type == 'admin'"}
- {node: entry, to: public_view, default: true}
assertions:
- {name: admin, given: {user_type: admin}, expect: dashboard}
- {name: guest, given: {user_type: guest}, expect: public_view}
EOF
uv run ontology-cli blueprint compile auth_flow.ont --import
This compiles the YAML to a valid .ob, posts it to the BE, polls
until the import completes, and prints the new projectId. See the
build-ontology skill in skills/build-ontology/SKILL.md for the
full .ont reference (expressions, error routes, assertion
semantics).
Advanced: build an ontology by individual REST calls
If you need to update an existing project, or your tooling can't write files, the imperative path is still available. The pattern is: create project → create nodes → create edges → create rules → create assertions → validate → run.
# 1. Project
PID=$(uv run ontology-cli project create --name "auth-flow" --json | jq -r .id)
# 2. Two nodes. The first declares an input we'll branch on.
cat > inputs.json <<'EOF'
[{"name":"user_type","type":"string","required":true,"values":["admin","guest"]}]
EOF
cat > outputs.json <<'EOF'
[{"name":"route","type":"string","required":true}]
EOF
CLASSIFY=$(uv run ontology-cli node create $PID \
--label "Classify" --inputs @inputs.json --outputs @outputs.json --json | jq -r .id)
DASHBOARD=$(uv run ontology-cli node create $PID --label "Dashboard" --json | jq -r .id)
# 3. Edge connecting them.
EDGE=$(uv run ontology-cli edge create $PID --source $CLASSIFY --target $DASHBOARD --json \
| jq -r .id)
# 4. Routing rule: when user_type == "admin", route to Dashboard.
# The fieldId is the input id from the Classify node — read it back:
FIELD=$(uv run ontology-cli node get $PID $CLASSIFY --json | jq -r '.inputs[0].id')
cat > condition.json <<EOF
{"kind":"clause","fieldId":"$FIELD","compare":0,"value":{"type":"literal","value":"admin"}}
EOF
uv run ontology-cli rule create $PID $CLASSIFY \
--target $DASHBOARD --condition @condition.json --order 0 --json
# 5. Assertions: given user_type=admin, expect the run to reach Dashboard.
cat > given.json <<'EOF'
{"user_type":"admin"}
EOF
# Terminal expectation — assert the run ends at Dashboard.
uv run ontology-cli assertion create $PID \
--name "admin -> dashboard" --scope e2e \
--given @given.json --expected-target-node $DASHBOARD --json
# Coverage expectation — assert the path and what Dashboard produced.
cat > path.json <<EOF
["$DASHBOARD"]
EOF
cat > outputs.json <<EOF
{"$DASHBOARD": {"route": "admin"}}
EOF
uv run ontology-cli assertion create $PID \
--name "admin path + outputs" --scope e2e \
--given @given.json --expected-path @path.json --expected-node-outputs @outputs.json --json
# 6. Validate, then run.
uv run ontology-cli validate $PID --json
uv run ontology-cli run start $PID --json
Notes on PowerShell: avoid passing JSON as an inline string — PowerShell
mangles double quotes. Always use @file.json for --inputs, --outputs,
--condition, --given, --expected-path, --expected-node-outputs, and
--expected-side-effects. To capture an id
from --json output in PowerShell:
$pid = (uv run ontology-cli project create --name demo --json | ConvertFrom-Json).id
Modelling error routes
--error-route does not create a new sink — it only marks a rule
as fired by a raised error. You still need a real target node with an
outbound edge from the source. Create a terminal node per error class
(e.g. BadPasswordError), add an edge from the raising node to it,
then create the rule with --error-route --error-name "bad_password" --target $ERR_NODE.
Otherwise validate returns REFERENCE_INTEGRITY.
Bulk create via import
To seed a whole project at once, assemble an .ob file (raw UTF-8 JSON
matching ExportDocumentV1, documented in BE-done.md § Project Export)
and upload it:
# Fire-and-forget — returns the importJobId immediately.
uv run ontology-cli project import path/to/project.ob --name "optional-rename"
# Wait for the job to finish and print the final header + logs.
uv run ontology-cli project import path/to/project.ob --wait --json
Inspect or cancel jobs through ontology-cli imports:
uv run ontology-cli imports list --json
uv run ontology-cli imports list --project-id <pid> --json
uv run ontology-cli imports get <jobId> --json
uv run ontology-cli imports cancel <jobId>
To round-trip an existing project, project export <id> writes a .ob
file you can re-import. Pass --out to override the filename.
Known gotchas
--expected-target-nodeconflicts with--expected-path/--expected-node-outputs. Combining them on the same assertion returnsASSERTION_EXPECTATIONS_CONFLICT. Use one or the other, or omit--expected-target-nodewhen using coverage expectations.--expected-errorcan combine freely with either style.--givenscoped overrides for downstream stubs. For E2E assertions, keys of the form"NodeLabel.outputName"in the Given JSON inject a fixed output value when the engine reaches that node — no separate stub command needed. The BE warns withNODE_LABEL_NOT_UNIQUEif two nodes share a label and a scoped key is ambiguous.- Node-scoped assertions still enter at the graph's entry node.
--target-nodeonly narrows what is asserted; the engine still walks from the entry. Your--givenpayload must include the entry node's inputs even when targeting a downstream node. rule deleteisdelete <project-id> <node-id> <rule-id>(three positional args, no--json). It is the only resource where delete takes a parent id beyond the project.
Reference: condition trees
Routing rules and (per-output) value rules use the same discriminated tree:
// Single comparison
{ "kind": "clause", "fieldId": "<input-uuid>", "compare": 0,
"value": { "type": "literal", "value": "admin" } }
// AND/OR group
{ "kind": "group", "op": 0, "children": [<clause>, <clause>] }
op: 0 = AND, 1 = OR.
compare codes:
| code | name | requires value | valid types |
|---|---|---|---|
| 0 | eq | literal | any |
| 1 | neq | literal | any |
| 2 | gt | literal | number, date |
| 3 | gte | literal | number, date |
| 4 | lt | literal | number, date |
| 5 | lte | literal | number, date |
| 6 | contains | literal | string |
| 7 | startsWith | literal | string |
| 8 | endsWith | literal | string |
| 9 | isEmpty | (omit value) |
string, array |
| 10 | isNotEmpty | (omit value) |
string, array |
| 11 | isNull | (omit value) |
any |
| 12 | isNotNull | (omit value) |
any |
| 13 | in | list | any |
| 14 | notIn | list | any |
Max nesting depth is 4. Empty groups are rejected.
Reference: enums on input
The CLI accepts these as named strings (lowercase). They map to the BE's int wire format internally.
| flag | choices |
|---|---|
node create --mode |
simulated (default), implemented |
assertion --scope |
e2e (default), node (coming soon) |
rule --async-behavior |
immediate (default), delayed, conditional |
Reference: enums in responses
Responses contain bare ints. Decode them with the table from BE-done.md
(also mirrored in src/ontology_builder_cli/enums.py):
node.mode: 0 simulated, 1 implementedassertion.scope: 0 node, 1 e2erule.asyncBehavior: 0 immediate, 1 delayed, 2 conditionalvalidation issue.severity: 0 error, 1 warningrun.status: 0 running, 1 passed, 2 failed, 3 erroredrunStep.kind: 0 assertionStarted, 1 nodeEntered, 2 nodeExited, 3 edgeTraversed, 4 assertionFinished, 5 errorrunStep.payload[outcome](when kind=4): 0 passed, 1 failed, 2 errored
Project layout
OntologyCli/
├── pyproject.toml
├── README.md
└── src/ontology_builder_cli/
├── __init__.py
├── cli.py # root Typer app + auth probe
├── client.py # httpx wrapper, one method per BE route
├── enums.py # IntEnums mirroring the BE wire format
├── models.py # Pydantic models, camelCase aliases
├── _io.py # printing, JSON loading, error decorator
└── commands/
├── projects.py
├── nodes.py
├── edges.py
├── rules.py
├── assertions.py
└── runs.py
Each file is small enough to read at a glance. Wire-format quirks live in
exactly one place (models.py aliases, enums.py ints), so the CLI surface
stays clean.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ontology_builder_cli-0.0.1.tar.gz.
File metadata
- Download URL: ontology_builder_cli-0.0.1.tar.gz
- Upload date:
- Size: 24.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2700a27d92211ae70840ec1748226c5e86573aebc991eebfa7c38bc65b7c7b8e
|
|
| MD5 |
ce1563af7c4069407b4d3c1ac22c6c47
|
|
| BLAKE2b-256 |
b28ff18cd8163b4b26021850785d565a43f0118a599577d0a261822dabcc59b0
|
Provenance
The following attestation bundles were made for ontology_builder_cli-0.0.1.tar.gz:
Publisher:
publish.yml on radumocanu1/OntologyBuilderSkills
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ontology_builder_cli-0.0.1.tar.gz -
Subject digest:
2700a27d92211ae70840ec1748226c5e86573aebc991eebfa7c38bc65b7c7b8e - Sigstore transparency entry: 1630422560
- Sigstore integration time:
-
Permalink:
radumocanu1/OntologyBuilderSkills@c4d5d18811227f3a466baf855ac85fb6a7077b3a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/radumocanu1
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c4d5d18811227f3a466baf855ac85fb6a7077b3a -
Trigger Event:
push
-
Statement type:
File details
Details for the file ontology_builder_cli-0.0.1-py3-none-any.whl.
File metadata
- Download URL: ontology_builder_cli-0.0.1-py3-none-any.whl
- Upload date:
- Size: 33.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e40397bb00d553189770bd65846ecb8c652dc5e391dd4a3c47239791365b760b
|
|
| MD5 |
c28a29a5ae2dfc138e0770d7ffa80fa6
|
|
| BLAKE2b-256 |
ea9f80ec446092b371c6e3b7fcfd09dcd8a3f32b06f748e6cb3dd72f02c16683
|
Provenance
The following attestation bundles were made for ontology_builder_cli-0.0.1-py3-none-any.whl:
Publisher:
publish.yml on radumocanu1/OntologyBuilderSkills
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ontology_builder_cli-0.0.1-py3-none-any.whl -
Subject digest:
e40397bb00d553189770bd65846ecb8c652dc5e391dd4a3c47239791365b760b - Sigstore transparency entry: 1630422589
- Sigstore integration time:
-
Permalink:
radumocanu1/OntologyBuilderSkills@c4d5d18811227f3a466baf855ac85fb6a7077b3a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/radumocanu1
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c4d5d18811227f3a466baf855ac85fb6a7077b3a -
Trigger Event:
push
-
Statement type: