nodecanvas-cli 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.
- nodecanvas_cli-0.1.0/.claude-plugin/marketplace.json +14 -0
- nodecanvas_cli-0.1.0/.claude-plugin/plugin.json +12 -0
- nodecanvas_cli-0.1.0/.github/workflows/ci.yml +31 -0
- nodecanvas_cli-0.1.0/.github/workflows/release.yml +36 -0
- nodecanvas_cli-0.1.0/.gitignore +10 -0
- nodecanvas_cli-0.1.0/CHANGELOG.md +20 -0
- nodecanvas_cli-0.1.0/LICENSE +21 -0
- nodecanvas_cli-0.1.0/PKG-INFO +228 -0
- nodecanvas_cli-0.1.0/README.md +197 -0
- nodecanvas_cli-0.1.0/pyproject.toml +53 -0
- nodecanvas_cli-0.1.0/scripts/roundtrip_check.py +30 -0
- nodecanvas_cli-0.1.0/skills/nodecanvas-cli/SKILL.md +256 -0
- nodecanvas_cli-0.1.0/skills/nodecanvas-cli/references/ops.md +160 -0
- nodecanvas_cli-0.1.0/skills/nodecanvas-cli/scripts/nodecanvas-cli +15 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/__init__.py +1 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/__main__.py +5 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/assets.py +627 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/bindings.py +332 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/build.py +696 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/builtin.py +425 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/cli.py +330 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/config.py +57 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/container.py +215 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/csharp.py +521 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/dialogue.py +422 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/discover.py +155 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/explain.py +540 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/fsjson.py +100 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/layout.py +259 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/lint.py +421 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/mcp_server.py +305 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/model.py +236 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/notation.txt +6 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/ops.py +789 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/owners.py +833 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/project.py +168 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/refactor.py +398 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/refs.py +108 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/render.py +612 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/scaffold.py +261 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/schema.py +451 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/spec.py +363 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/typenames.py +193 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/unity_verify.cs.txt +135 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/verify.py +372 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/views.py +230 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/workspace.py +165 -0
- nodecanvas_cli-0.1.0/src/nodecanvas_cli/yamlscalar.py +235 -0
- nodecanvas_cli-0.1.0/tests/conftest.py +38 -0
- nodecanvas_cli-0.1.0/tests/fixtures/generate.py +841 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Blackboards/Shared.asset +18 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Blackboards/Shared.asset.meta +4 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Game/Bot.cs +36 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Game/Bot.cs.meta +2 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Game/GuardOwner.cs +4 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Game/GuardOwner.cs.meta +2 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Bot_BT.asset +34 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Bot_BT.asset.meta +8 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Bot_FSM.asset +29 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Bot_FSM.asset.meta +8 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Broken_BT.asset +25 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Broken_BT.asset.meta +8 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Patrol_BT.asset +27 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Patrol_BT.asset.meta +8 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Sub_BT.asset +27 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Sub_BT.asset.meta +8 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Talk_DT.asset +29 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Graphs/Talk_DT.asset.meta +8 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Prefabs/Guard.prefab +75 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Prefabs/Guard.prefab.meta +4 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Prefabs/Sentry.prefab +37 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Prefabs/Sentry.prefab.meta +4 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Scenes/World.unity +91 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Scenes/World.unity.meta +4 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Stubs/NodeCanvasStubs.cs +423 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/Assets/Stubs/NodeCanvasStubs.cs.meta +2 -0
- nodecanvas_cli-0.1.0/tests/fixtures/project/ProjectSettings/ProjectVersion.txt +1 -0
- nodecanvas_cli-0.1.0/tests/snapshots/Bot_BT.txt +12 -0
- nodecanvas_cli-0.1.0/tests/snapshots/Bot_FSM.txt +9 -0
- nodecanvas_cli-0.1.0/tests/snapshots/Broken_BT.txt +11 -0
- nodecanvas_cli-0.1.0/tests/snapshots/Patrol_BT.txt +8 -0
- nodecanvas_cli-0.1.0/tests/snapshots/Sentry.txt +3 -0
- nodecanvas_cli-0.1.0/tests/snapshots/Sub_BT.txt +6 -0
- nodecanvas_cli-0.1.0/tests/snapshots/Talk_DT.txt +16 -0
- nodecanvas_cli-0.1.0/tests/test_apply.py +622 -0
- nodecanvas_cli-0.1.0/tests/test_assets.py +129 -0
- nodecanvas_cli-0.1.0/tests/test_codec.py +73 -0
- nodecanvas_cli-0.1.0/tests/test_csharp.py +105 -0
- nodecanvas_cli-0.1.0/tests/test_dialogue.py +140 -0
- nodecanvas_cli-0.1.0/tests/test_explain.py +80 -0
- nodecanvas_cli-0.1.0/tests/test_lint.py +274 -0
- nodecanvas_cli-0.1.0/tests/test_mcp.py +68 -0
- nodecanvas_cli-0.1.0/tests/test_owners.py +116 -0
- nodecanvas_cli-0.1.0/tests/test_read.py +134 -0
- nodecanvas_cli-0.1.0/tests/test_refactor.py +168 -0
- nodecanvas_cli-0.1.0/tests/test_scaffold.py +60 -0
- nodecanvas_cli-0.1.0/tests/test_spec.py +42 -0
- nodecanvas_cli-0.1.0/tests/test_verify.py +131 -0
- nodecanvas_cli-0.1.0/uv.lock +1154 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nodecanvas-cli",
|
|
3
|
+
"description": "NodeCanvas graphs (Behaviour Trees, FSMs, Dialogue Trees) for coding agents.",
|
|
4
|
+
"owner": {
|
|
5
|
+
"name": "Aleksandr Esipov"
|
|
6
|
+
},
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "nodecanvas-cli",
|
|
10
|
+
"source": "./",
|
|
11
|
+
"description": "Read, search, lint and edit NodeCanvas graphs (Behaviour Trees, FSMs, Dialogue Trees) in Unity projects."
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nodecanvas-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Read, search, lint and edit NodeCanvas graphs (Behaviour Trees, FSMs, Dialogue Trees) in Unity projects through the nodecanvas-cli command, without opening Unity.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Aleksandr Esipov"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/Primeer/nodecanvas-cli",
|
|
9
|
+
"repository": "https://github.com/Primeer/nodecanvas-cli",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": ["unity", "nodecanvas", "behaviour-tree", "fsm", "dialogue-tree", "game-ai"]
|
|
12
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
test:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
strategy:
|
|
7
|
+
matrix:
|
|
8
|
+
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
- uses: actions/setup-python@v5
|
|
12
|
+
with:
|
|
13
|
+
python-version: ${{ matrix.python }}
|
|
14
|
+
- run: pip install -e ".[dev]"
|
|
15
|
+
- run: pytest -q
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
- run: pip install build twine
|
|
24
|
+
- run: python -m build
|
|
25
|
+
- run: twine check --strict dist/*
|
|
26
|
+
- name: Smoke-test the wheel
|
|
27
|
+
run: |
|
|
28
|
+
python -m venv /tmp/w && /tmp/w/bin/pip install dist/*.whl
|
|
29
|
+
/tmp/w/bin/nodecanvas-cli --version
|
|
30
|
+
/tmp/w/bin/nodecanvas-cli --project tests/fixtures/project list
|
|
31
|
+
/tmp/w/bin/python -c "import nodecanvas_cli.verify as v; assert v.SNIPPET.exists()"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
# Publishes to PyPI through Trusted Publishing (no tokens in the repository).
|
|
3
|
+
# One-time setup on pypi.org: add a trusted publisher for this repository,
|
|
4
|
+
# workflow "release.yml", environment "pypi".
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.12"
|
|
16
|
+
- name: Tag matches the package version
|
|
17
|
+
run: |
|
|
18
|
+
v=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
19
|
+
test "v$v" = "${GITHUB_REF_NAME}" || { echo "tag ${GITHUB_REF_NAME} != v$v"; exit 1; }
|
|
20
|
+
- run: pip install build twine && python -m build && twine check --strict dist/*
|
|
21
|
+
- uses: actions/upload-artifact@v4
|
|
22
|
+
with:
|
|
23
|
+
name: dist
|
|
24
|
+
path: dist/
|
|
25
|
+
publish:
|
|
26
|
+
needs: build
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment: pypi
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/download-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-10-12
|
|
4
|
+
|
|
5
|
+
First public release.
|
|
6
|
+
|
|
7
|
+
- `list`, `show`, `vars`, `node`, `diff`: compact views of Behaviour Trees, FSMs and Dialogue Trees in graph
|
|
8
|
+
assets and in graphs bound to GraphOwners in prefabs and scenes.
|
|
9
|
+
- `refs`, `why`, `paths`, `stats`: reverse lookup of members, subtrees, variables and task types across all
|
|
10
|
+
graphs; conditions on the way to a node; every call of a member or write of a variable.
|
|
11
|
+
- `lint`: missing types, dangling blackboard references, broken reflection bindings, subgraph mappings,
|
|
12
|
+
connection limits, unreachable nodes, owner and global variables.
|
|
13
|
+
- `apply`: atomic, validated batches of edit operations with short value forms and `--dry-run` diffs;
|
|
14
|
+
`new`, `extract`, `inline`, `copy`, `refactor rename-member|rename-type|retarget`, `scaffold`.
|
|
15
|
+
- `dt export` / `dt import` / `dt lint`: dialogue lines and translations to CSV/JSON and back.
|
|
16
|
+
- `owners`, `blackboards`, `types`, `members`.
|
|
17
|
+
- `verify --unity`: load the edited graphs in the open Unity Editor and report what NodeCanvas sees.
|
|
18
|
+
- `mcp`: stdio MCP server (`nodecanvas-cli[mcp]`, Python 3.10+).
|
|
19
|
+
- Claude Code plugin with a skill and a bundled launcher.
|
|
20
|
+
- Byte-exact round-trip of unchanged graphs; Unity's YAML folding and FullSerializer key order on save.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aleksandr Esipov
|
|
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,228 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nodecanvas-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Read, query, lint and edit NodeCanvas graphs (Behaviour Trees, FSMs, Dialogue Trees) in Unity projects from the command line
|
|
5
|
+
Project-URL: Homepage, https://github.com/Primeer/nodecanvas-cli
|
|
6
|
+
Project-URL: Issues, https://github.com/Primeer/nodecanvas-cli/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/Primeer/nodecanvas-cli/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Aleksandr Esipov
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agents,behaviour-tree,cli,dialogue-tree,fsm,nodecanvas,unity
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Games/Entertainment
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mcp>=2.3; (python_version >= '3.10') and extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
28
|
+
Provides-Extra: mcp
|
|
29
|
+
Requires-Dist: mcp>=2.3; (python_version >= '3.10') and extra == 'mcp'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# nodecanvas-cli
|
|
33
|
+
|
|
34
|
+
Read, search, lint and edit [NodeCanvas](https://nodecanvas.paradoxnotion.com/) graphs — Behaviour Trees,
|
|
35
|
+
FSMs and Dialogue Trees — in a Unity project from the command line, without opening Unity.
|
|
36
|
+
|
|
37
|
+
Built for coding agents (it ships as a Claude Code plugin) and just as usable by hand.
|
|
38
|
+
|
|
39
|
+
NodeCanvas stores a graph as one long JSON string inside a Unity YAML asset. A 120-node behaviour tree
|
|
40
|
+
is ~85 KB of folded JSON with GUID-linked variables and index-linked nodes. `nodecanvas-cli` shows the same tree
|
|
41
|
+
in ~10 KB of readable text, answers "where is this used?", and applies edits as validated batches that
|
|
42
|
+
are written back exactly the way Unity writes them.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
$ nodecanvas-cli show Bot_BT
|
|
46
|
+
Bot_BT [BT, repeat] Assets/Graphs/Bot_BT.asset nodes=10
|
|
47
|
+
vars: Self:Bot(pub) Target:Unit Alert:bool Cool time:float Speed:float
|
|
48
|
+
#0 Selector
|
|
49
|
+
├─#1 ? @Self.IsDead
|
|
50
|
+
├─#2 Sequencer "Fight"
|
|
51
|
+
│ ├─#3 ? !$Alert
|
|
52
|
+
│ ├─#4 Cooldown [coolDownTime=$Cool time]
|
|
53
|
+
│ │ └─#5 @Self.Attack($Target)
|
|
54
|
+
│ └─#6 ProbabilitySelector
|
|
55
|
+
│ ├─(w=1.0) #7 @Self.MoveTo((1.0, 0, -2.5))
|
|
56
|
+
│ └─(w=3.0) #8 SubTree → Sub_BT {Self←$Self, Alert→$Alert}
|
|
57
|
+
└─[off] #9 $Speed *= 2.0 // double the speed
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
- **show** — compact tree (BT), state list with ordered transitions (FSM), or script (Dialogue Tree).
|
|
63
|
+
Default values are shown with their real meaning, read from the project's own C# sources.
|
|
64
|
+
- **refs** — reverse lookup across all graphs: who calls `Bot.Attack`, which graphs use a
|
|
65
|
+
subtree, a variable, or a task type. Graphs bind C# members by name and break silently on renames;
|
|
66
|
+
this finds them.
|
|
67
|
+
- **lint** — missing task/variable types, dangling blackboard references, reflection bindings to members
|
|
68
|
+
that no longer exist, missing subgraphs and stale variable mappings, too many children for a node
|
|
69
|
+
type, unreachable nodes.
|
|
70
|
+
- **apply** — atomic batches of edit operations: add / remove / move / wrap / unwrap / replace nodes, set
|
|
71
|
+
fields, connect and disable, blackboard variables, subgraph mappings, FSM transitions, dialogue
|
|
72
|
+
choices. Short value forms (`"$Var"`, enum names, `[x, y, z]`, `{"call": "Type.Method", ...}`) are
|
|
73
|
+
converted using the C# field types. Batches that introduce lint errors are not saved.
|
|
74
|
+
- **dt export / import / lint** — dialogue lines (statements, choices, translations) to CSV/JSON for writers
|
|
75
|
+
and translators and back, keyed by node UIDs, with conflict detection; checks for empty lines,
|
|
76
|
+
dangling choices and jumps, unknown actors and missing translations.
|
|
77
|
+
- **why / paths / stats** — why control reaches a node (conditions along the path from the root, FSM
|
|
78
|
+
transitions in, dialogue choices), the same for every call of a member or write of a variable, and
|
|
79
|
+
graph statistics.
|
|
80
|
+
- **owners / blackboards** — which prefabs and scenes run a graph (GraphOwner components, exposed
|
|
81
|
+
variable overrides, owner blackboards), graphs assigned nowhere, and Blackboard / GlobalBlackboard /
|
|
82
|
+
AssetBlackboard contents; lint checks global variables and owner overrides against them.
|
|
83
|
+
- **types / members** — discover task and node types with their fields, defaults and enum members, and the
|
|
84
|
+
properties/methods reflection tasks can call.
|
|
85
|
+
- Works on graph assets (`.asset`) and graphs bound to a `GraphOwner` in prefabs and scenes.
|
|
86
|
+
- Byte-exact: unchanged graphs round-trip identically; edited graphs are re-serialized with Unity's
|
|
87
|
+
YAML folding and FullSerializer's key order, so diffs stay minimal.
|
|
88
|
+
- **verify --unity** — after editing, let the open Unity Editor import, deserialize and validate the graphs
|
|
89
|
+
(through the [Unity CLI](https://docs.unity.com/) and the project's `com.unity.pipeline` package) and
|
|
90
|
+
report NodeCanvas errors, Missing types, broken reflection bindings and console messages.
|
|
91
|
+
- Pure Python ≥ 3.9, no dependencies, no Unity required (except for `verify --unity`); the optional MCP
|
|
92
|
+
server needs `nodecanvas-cli[mcp]`.
|
|
93
|
+
|
|
94
|
+
## Install
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pipx install nodecanvas-cli # once published to PyPI
|
|
98
|
+
uvx --from git+https://github.com/Primeer/nodecanvas-cli nodecanvas-cli --help # straight from the repository
|
|
99
|
+
python -m nodecanvas_cli --help # from a checkout, with src/ on PYTHONPATH
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The Claude Code plugin also bundles a launcher, `skills/nodecanvas-cli/scripts/nodecanvas-cli`, which runs the CLI
|
|
103
|
+
from the plugin's own sources with any Python 3.9+.
|
|
104
|
+
|
|
105
|
+
### Claude Code plugin
|
|
106
|
+
|
|
107
|
+
The repository is also a Claude Code plugin marketplace containing the `nodecanvas-cli` plugin, which adds a
|
|
108
|
+
skill that teaches the agent when and how to use the CLI:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
/plugin marketplace add Primeer/nodecanvas-cli
|
|
112
|
+
/plugin install nodecanvas-cli@nodecanvas-cli
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### MCP server (Cursor, Codex, Gemini and other agents)
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pipx install "nodecanvas-cli[mcp]" # Python 3.10+
|
|
119
|
+
nodecanvas-cli --project /path/to/UnityProject mcp
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
A stdio MCP server whose tools wrap the commands (`show`, `node`, `refs`, `lint`, `apply`, `refactor_*`,
|
|
123
|
+
`why`, `owners`, …) and return the same compact text. Tools that write files (`apply`, `refactor_*`,
|
|
124
|
+
`new_graph`, `extract`, `inline`, `copy`) default to `dry_run=true`; pass `dry_run=false` to save.
|
|
125
|
+
Example client config:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{"mcpServers": {"nodecanvas-cli": {"command": "nodecanvas-cli", "args": ["--project", "/path/to/UnityProject", "mcp"]}}}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Usage
|
|
132
|
+
|
|
133
|
+
Run inside a Unity project (any subdirectory) or pass `--project <root>`.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
nodecanvas-cli list [--kind bt|fsm|dt]
|
|
137
|
+
nodecanvas-cli show <graph> [--node N] [--depth D] [--expand]
|
|
138
|
+
nodecanvas-cli vars <graph>
|
|
139
|
+
nodecanvas-cli node <graph> <id>... [--raw] # spec form accepted by apply (or raw JSON)
|
|
140
|
+
nodecanvas-cli diff <graph> [--rev HEAD] # compact view vs a git revision
|
|
141
|
+
nodecanvas-cli refs <Type.Member | GraphName | $Variable | TaskType>
|
|
142
|
+
nodecanvas-cli dt export [graph...] [--locales en,ru] [--format csv|json] [-o file] | dt import <file> | dt lint
|
|
143
|
+
nodecanvas-cli why <graph> <node> [--expand] # conditions on the way from the root to a node
|
|
144
|
+
nodecanvas-cli paths [graph] --to <Type.Member | $Var> [--reads]
|
|
145
|
+
nodecanvas-cli stats [graph] [--json]
|
|
146
|
+
nodecanvas-cli scaffold action|condition|decorator|composite <Name> --dir Assets/... [--field "x:BBParameter<float>=1"]
|
|
147
|
+
nodecanvas-cli owners [<graph> | --all] # GraphOwners in prefabs/scenes, parent graphs
|
|
148
|
+
nodecanvas-cli blackboards [--global]
|
|
149
|
+
nodecanvas-cli lint [graph...] [--min info|warning|error] [--json]
|
|
150
|
+
nodecanvas-cli types [query] [--role action|condition|bt|composite|decorator|fsm|dt]
|
|
151
|
+
nodecanvas-cli members <Type> [filter]
|
|
152
|
+
nodecanvas-cli refactor rename-member|rename-type|retarget ... [--dry-run]
|
|
153
|
+
nodecanvas-cli new <path.asset> --kind bt|fsm|dt [--var "Name:type[=value][:pub]"]
|
|
154
|
+
nodecanvas-cli extract <graph> <node> <new.asset> | inline <graph> <node> | copy <src> <node> <dst> <parent>
|
|
155
|
+
nodecanvas-cli apply <graph> <ops.json | -> [--dry-run] [--force]
|
|
156
|
+
nodecanvas-cli verify --unity [graph... | --changed] [--resave] # load the graphs in the open Unity Editor
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`<graph>` is a path, an asset name, a unique prefix, or `Prefab.prefab:GameObject` for bound graphs;
|
|
160
|
+
`--kind bt|fsm|dt` disambiguates names shared by graphs of different kinds.
|
|
161
|
+
|
|
162
|
+
Layout of created/moved nodes can be tuned with `nodecanvas-cli.json` in the Unity project root:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{"layout": {"grid": 20, "level_gap": 200, "sibling_gap": 300}, "scaffold": {"comments": false}}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Editing example
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
[
|
|
172
|
+
{"op": "add", "parent": "3", "index": 0, "id": "w",
|
|
173
|
+
"node": {"$type": "ActionNode", "_action": {"$type": "Wait", "waitTime": "$Delay"}}},
|
|
174
|
+
{"op": "wrap", "node": "w", "with": {"$type": "Cooldown", "coolDownTime": 2}},
|
|
175
|
+
{"op": "add", "parent": "3", "node": {"$type": "ConditionNode",
|
|
176
|
+
"_condition": {"check": "Bot.DistanceTo", "args": ["$Target"], "agent": "$Owner", "compare": ">", "value": "$Range"}}},
|
|
177
|
+
{"op": "var-add", "name": "Range", "type": "float", "value": 4},
|
|
178
|
+
{"op": "remove", "node": "12", "recursive": true}
|
|
179
|
+
]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
nodecanvas-cli apply Bot_BT ops.json --dry-run # prints a before/after diff of the compact view
|
|
184
|
+
nodecanvas-cli apply Bot_BT ops.json
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
All operations and value forms: [skills/nodecanvas-cli/references/ops.md](skills/nodecanvas-cli/references/ops.md).
|
|
188
|
+
|
|
189
|
+
## How it works
|
|
190
|
+
|
|
191
|
+
- **Container codec** — finds `_serializedGraph` (assets) or `_boundGraphSerialization` (GraphOwner
|
|
192
|
+
components) and decodes Unity's folded single-quoted YAML scalar; encoding reproduces Unity's folding
|
|
193
|
+
rule exactly, so untouched graphs stay byte-identical.
|
|
194
|
+
- **FullSerializer JSON** — numbers keep their original text and keys their order; new objects are
|
|
195
|
+
written in FullSerializer's field order. Semantics of omitted values follow FullSerializer: a missing
|
|
196
|
+
field keeps its C# initializer, while a serialized parameter without `_value` holds `default(T)`.
|
|
197
|
+
- **Schema from sources** — NodeCanvas ships as source code, so nodecanvas-cli scans the project's C# files
|
|
198
|
+
(lazily, with an mtime cache in `~/.cache/nodecanvas-cli`) for task/node fields, initializers, enums,
|
|
199
|
+
display names, connection limits and the methods available to reflection tasks. Without NodeCanvas
|
|
200
|
+
sources, reading and editing still work with reduced detail.
|
|
201
|
+
- **Normalization** — after an edit, nodes are renumbered the way the NodeCanvas editor does
|
|
202
|
+
(depth-first from the root), `$id`s are written only on referenced nodes, and BT children keep a
|
|
203
|
+
left-to-right position order matching their execution order.
|
|
204
|
+
|
|
205
|
+
## Limitations
|
|
206
|
+
|
|
207
|
+
- NodeCanvas 3.x serialization only.
|
|
208
|
+
- Graphs serialized to an external JSON file (`_externalSerializationFile`) are not supported.
|
|
209
|
+
- C# scanning is heuristic (no compiler): unusual declarations may be missed; lint reports unresolved
|
|
210
|
+
members as warnings when a type's base classes live outside the project sources.
|
|
211
|
+
- Close the graph in the NodeCanvas editor (or let Unity reimport) before editing it externally.
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pip install -e ".[dev]"
|
|
217
|
+
pytest
|
|
218
|
+
python tests/fixtures/generate.py # regenerate synthetic fixtures
|
|
219
|
+
UPDATE_SNAPSHOTS=1 pytest # refresh show snapshots
|
|
220
|
+
python scripts/roundtrip_check.py <UnityProject> # verify byte-exact round-trip on a real project
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Tests run against synthetic graphs and small stubs of the NodeCanvas API surface
|
|
224
|
+
(`tests/fixtures/project/Assets/Stubs`); NodeCanvas itself is a commercial asset and is not included.
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT. NodeCanvas is a product of Paradox Notion; this project is not affiliated with it.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# nodecanvas-cli
|
|
2
|
+
|
|
3
|
+
Read, search, lint and edit [NodeCanvas](https://nodecanvas.paradoxnotion.com/) graphs — Behaviour Trees,
|
|
4
|
+
FSMs and Dialogue Trees — in a Unity project from the command line, without opening Unity.
|
|
5
|
+
|
|
6
|
+
Built for coding agents (it ships as a Claude Code plugin) and just as usable by hand.
|
|
7
|
+
|
|
8
|
+
NodeCanvas stores a graph as one long JSON string inside a Unity YAML asset. A 120-node behaviour tree
|
|
9
|
+
is ~85 KB of folded JSON with GUID-linked variables and index-linked nodes. `nodecanvas-cli` shows the same tree
|
|
10
|
+
in ~10 KB of readable text, answers "where is this used?", and applies edits as validated batches that
|
|
11
|
+
are written back exactly the way Unity writes them.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ nodecanvas-cli show Bot_BT
|
|
15
|
+
Bot_BT [BT, repeat] Assets/Graphs/Bot_BT.asset nodes=10
|
|
16
|
+
vars: Self:Bot(pub) Target:Unit Alert:bool Cool time:float Speed:float
|
|
17
|
+
#0 Selector
|
|
18
|
+
├─#1 ? @Self.IsDead
|
|
19
|
+
├─#2 Sequencer "Fight"
|
|
20
|
+
│ ├─#3 ? !$Alert
|
|
21
|
+
│ ├─#4 Cooldown [coolDownTime=$Cool time]
|
|
22
|
+
│ │ └─#5 @Self.Attack($Target)
|
|
23
|
+
│ └─#6 ProbabilitySelector
|
|
24
|
+
│ ├─(w=1.0) #7 @Self.MoveTo((1.0, 0, -2.5))
|
|
25
|
+
│ └─(w=3.0) #8 SubTree → Sub_BT {Self←$Self, Alert→$Alert}
|
|
26
|
+
└─[off] #9 $Speed *= 2.0 // double the speed
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **show** — compact tree (BT), state list with ordered transitions (FSM), or script (Dialogue Tree).
|
|
32
|
+
Default values are shown with their real meaning, read from the project's own C# sources.
|
|
33
|
+
- **refs** — reverse lookup across all graphs: who calls `Bot.Attack`, which graphs use a
|
|
34
|
+
subtree, a variable, or a task type. Graphs bind C# members by name and break silently on renames;
|
|
35
|
+
this finds them.
|
|
36
|
+
- **lint** — missing task/variable types, dangling blackboard references, reflection bindings to members
|
|
37
|
+
that no longer exist, missing subgraphs and stale variable mappings, too many children for a node
|
|
38
|
+
type, unreachable nodes.
|
|
39
|
+
- **apply** — atomic batches of edit operations: add / remove / move / wrap / unwrap / replace nodes, set
|
|
40
|
+
fields, connect and disable, blackboard variables, subgraph mappings, FSM transitions, dialogue
|
|
41
|
+
choices. Short value forms (`"$Var"`, enum names, `[x, y, z]`, `{"call": "Type.Method", ...}`) are
|
|
42
|
+
converted using the C# field types. Batches that introduce lint errors are not saved.
|
|
43
|
+
- **dt export / import / lint** — dialogue lines (statements, choices, translations) to CSV/JSON for writers
|
|
44
|
+
and translators and back, keyed by node UIDs, with conflict detection; checks for empty lines,
|
|
45
|
+
dangling choices and jumps, unknown actors and missing translations.
|
|
46
|
+
- **why / paths / stats** — why control reaches a node (conditions along the path from the root, FSM
|
|
47
|
+
transitions in, dialogue choices), the same for every call of a member or write of a variable, and
|
|
48
|
+
graph statistics.
|
|
49
|
+
- **owners / blackboards** — which prefabs and scenes run a graph (GraphOwner components, exposed
|
|
50
|
+
variable overrides, owner blackboards), graphs assigned nowhere, and Blackboard / GlobalBlackboard /
|
|
51
|
+
AssetBlackboard contents; lint checks global variables and owner overrides against them.
|
|
52
|
+
- **types / members** — discover task and node types with their fields, defaults and enum members, and the
|
|
53
|
+
properties/methods reflection tasks can call.
|
|
54
|
+
- Works on graph assets (`.asset`) and graphs bound to a `GraphOwner` in prefabs and scenes.
|
|
55
|
+
- Byte-exact: unchanged graphs round-trip identically; edited graphs are re-serialized with Unity's
|
|
56
|
+
YAML folding and FullSerializer's key order, so diffs stay minimal.
|
|
57
|
+
- **verify --unity** — after editing, let the open Unity Editor import, deserialize and validate the graphs
|
|
58
|
+
(through the [Unity CLI](https://docs.unity.com/) and the project's `com.unity.pipeline` package) and
|
|
59
|
+
report NodeCanvas errors, Missing types, broken reflection bindings and console messages.
|
|
60
|
+
- Pure Python ≥ 3.9, no dependencies, no Unity required (except for `verify --unity`); the optional MCP
|
|
61
|
+
server needs `nodecanvas-cli[mcp]`.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pipx install nodecanvas-cli # once published to PyPI
|
|
67
|
+
uvx --from git+https://github.com/Primeer/nodecanvas-cli nodecanvas-cli --help # straight from the repository
|
|
68
|
+
python -m nodecanvas_cli --help # from a checkout, with src/ on PYTHONPATH
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The Claude Code plugin also bundles a launcher, `skills/nodecanvas-cli/scripts/nodecanvas-cli`, which runs the CLI
|
|
72
|
+
from the plugin's own sources with any Python 3.9+.
|
|
73
|
+
|
|
74
|
+
### Claude Code plugin
|
|
75
|
+
|
|
76
|
+
The repository is also a Claude Code plugin marketplace containing the `nodecanvas-cli` plugin, which adds a
|
|
77
|
+
skill that teaches the agent when and how to use the CLI:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
/plugin marketplace add Primeer/nodecanvas-cli
|
|
81
|
+
/plugin install nodecanvas-cli@nodecanvas-cli
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### MCP server (Cursor, Codex, Gemini and other agents)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pipx install "nodecanvas-cli[mcp]" # Python 3.10+
|
|
88
|
+
nodecanvas-cli --project /path/to/UnityProject mcp
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A stdio MCP server whose tools wrap the commands (`show`, `node`, `refs`, `lint`, `apply`, `refactor_*`,
|
|
92
|
+
`why`, `owners`, …) and return the same compact text. Tools that write files (`apply`, `refactor_*`,
|
|
93
|
+
`new_graph`, `extract`, `inline`, `copy`) default to `dry_run=true`; pass `dry_run=false` to save.
|
|
94
|
+
Example client config:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{"mcpServers": {"nodecanvas-cli": {"command": "nodecanvas-cli", "args": ["--project", "/path/to/UnityProject", "mcp"]}}}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
Run inside a Unity project (any subdirectory) or pass `--project <root>`.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
nodecanvas-cli list [--kind bt|fsm|dt]
|
|
106
|
+
nodecanvas-cli show <graph> [--node N] [--depth D] [--expand]
|
|
107
|
+
nodecanvas-cli vars <graph>
|
|
108
|
+
nodecanvas-cli node <graph> <id>... [--raw] # spec form accepted by apply (or raw JSON)
|
|
109
|
+
nodecanvas-cli diff <graph> [--rev HEAD] # compact view vs a git revision
|
|
110
|
+
nodecanvas-cli refs <Type.Member | GraphName | $Variable | TaskType>
|
|
111
|
+
nodecanvas-cli dt export [graph...] [--locales en,ru] [--format csv|json] [-o file] | dt import <file> | dt lint
|
|
112
|
+
nodecanvas-cli why <graph> <node> [--expand] # conditions on the way from the root to a node
|
|
113
|
+
nodecanvas-cli paths [graph] --to <Type.Member | $Var> [--reads]
|
|
114
|
+
nodecanvas-cli stats [graph] [--json]
|
|
115
|
+
nodecanvas-cli scaffold action|condition|decorator|composite <Name> --dir Assets/... [--field "x:BBParameter<float>=1"]
|
|
116
|
+
nodecanvas-cli owners [<graph> | --all] # GraphOwners in prefabs/scenes, parent graphs
|
|
117
|
+
nodecanvas-cli blackboards [--global]
|
|
118
|
+
nodecanvas-cli lint [graph...] [--min info|warning|error] [--json]
|
|
119
|
+
nodecanvas-cli types [query] [--role action|condition|bt|composite|decorator|fsm|dt]
|
|
120
|
+
nodecanvas-cli members <Type> [filter]
|
|
121
|
+
nodecanvas-cli refactor rename-member|rename-type|retarget ... [--dry-run]
|
|
122
|
+
nodecanvas-cli new <path.asset> --kind bt|fsm|dt [--var "Name:type[=value][:pub]"]
|
|
123
|
+
nodecanvas-cli extract <graph> <node> <new.asset> | inline <graph> <node> | copy <src> <node> <dst> <parent>
|
|
124
|
+
nodecanvas-cli apply <graph> <ops.json | -> [--dry-run] [--force]
|
|
125
|
+
nodecanvas-cli verify --unity [graph... | --changed] [--resave] # load the graphs in the open Unity Editor
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`<graph>` is a path, an asset name, a unique prefix, or `Prefab.prefab:GameObject` for bound graphs;
|
|
129
|
+
`--kind bt|fsm|dt` disambiguates names shared by graphs of different kinds.
|
|
130
|
+
|
|
131
|
+
Layout of created/moved nodes can be tuned with `nodecanvas-cli.json` in the Unity project root:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{"layout": {"grid": 20, "level_gap": 200, "sibling_gap": 300}, "scaffold": {"comments": false}}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Editing example
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
[
|
|
141
|
+
{"op": "add", "parent": "3", "index": 0, "id": "w",
|
|
142
|
+
"node": {"$type": "ActionNode", "_action": {"$type": "Wait", "waitTime": "$Delay"}}},
|
|
143
|
+
{"op": "wrap", "node": "w", "with": {"$type": "Cooldown", "coolDownTime": 2}},
|
|
144
|
+
{"op": "add", "parent": "3", "node": {"$type": "ConditionNode",
|
|
145
|
+
"_condition": {"check": "Bot.DistanceTo", "args": ["$Target"], "agent": "$Owner", "compare": ">", "value": "$Range"}}},
|
|
146
|
+
{"op": "var-add", "name": "Range", "type": "float", "value": 4},
|
|
147
|
+
{"op": "remove", "node": "12", "recursive": true}
|
|
148
|
+
]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
nodecanvas-cli apply Bot_BT ops.json --dry-run # prints a before/after diff of the compact view
|
|
153
|
+
nodecanvas-cli apply Bot_BT ops.json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
All operations and value forms: [skills/nodecanvas-cli/references/ops.md](skills/nodecanvas-cli/references/ops.md).
|
|
157
|
+
|
|
158
|
+
## How it works
|
|
159
|
+
|
|
160
|
+
- **Container codec** — finds `_serializedGraph` (assets) or `_boundGraphSerialization` (GraphOwner
|
|
161
|
+
components) and decodes Unity's folded single-quoted YAML scalar; encoding reproduces Unity's folding
|
|
162
|
+
rule exactly, so untouched graphs stay byte-identical.
|
|
163
|
+
- **FullSerializer JSON** — numbers keep their original text and keys their order; new objects are
|
|
164
|
+
written in FullSerializer's field order. Semantics of omitted values follow FullSerializer: a missing
|
|
165
|
+
field keeps its C# initializer, while a serialized parameter without `_value` holds `default(T)`.
|
|
166
|
+
- **Schema from sources** — NodeCanvas ships as source code, so nodecanvas-cli scans the project's C# files
|
|
167
|
+
(lazily, with an mtime cache in `~/.cache/nodecanvas-cli`) for task/node fields, initializers, enums,
|
|
168
|
+
display names, connection limits and the methods available to reflection tasks. Without NodeCanvas
|
|
169
|
+
sources, reading and editing still work with reduced detail.
|
|
170
|
+
- **Normalization** — after an edit, nodes are renumbered the way the NodeCanvas editor does
|
|
171
|
+
(depth-first from the root), `$id`s are written only on referenced nodes, and BT children keep a
|
|
172
|
+
left-to-right position order matching their execution order.
|
|
173
|
+
|
|
174
|
+
## Limitations
|
|
175
|
+
|
|
176
|
+
- NodeCanvas 3.x serialization only.
|
|
177
|
+
- Graphs serialized to an external JSON file (`_externalSerializationFile`) are not supported.
|
|
178
|
+
- C# scanning is heuristic (no compiler): unusual declarations may be missed; lint reports unresolved
|
|
179
|
+
members as warnings when a type's base classes live outside the project sources.
|
|
180
|
+
- Close the graph in the NodeCanvas editor (or let Unity reimport) before editing it externally.
|
|
181
|
+
|
|
182
|
+
## Development
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
pip install -e ".[dev]"
|
|
186
|
+
pytest
|
|
187
|
+
python tests/fixtures/generate.py # regenerate synthetic fixtures
|
|
188
|
+
UPDATE_SNAPSHOTS=1 pytest # refresh show snapshots
|
|
189
|
+
python scripts/roundtrip_check.py <UnityProject> # verify byte-exact round-trip on a real project
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Tests run against synthetic graphs and small stubs of the NodeCanvas API surface
|
|
193
|
+
(`tests/fixtures/project/Assets/Stubs`); NodeCanvas itself is a commercial asset and is not included.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT. NodeCanvas is a product of Paradox Notion; this project is not affiliated with it.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.18"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nodecanvas-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Read, query, lint and edit NodeCanvas graphs (Behaviour Trees, FSMs, Dialogue Trees) in Unity projects from the command line"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "Aleksandr Esipov" }]
|
|
13
|
+
keywords = ["unity", "nodecanvas", "behaviour-tree", "fsm", "dialogue-tree", "cli", "ai-agents"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Programming Language :: Python :: 3.9",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Environment :: Console",
|
|
25
|
+
"Topic :: Games/Entertainment",
|
|
26
|
+
"Topic :: Software Development :: Libraries",
|
|
27
|
+
]
|
|
28
|
+
dependencies = []
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/Primeer/nodecanvas-cli"
|
|
32
|
+
Issues = "https://github.com/Primeer/nodecanvas-cli/issues"
|
|
33
|
+
Changelog = "https://github.com/Primeer/nodecanvas-cli/blob/main/CHANGELOG.md"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
nodecanvas-cli = "nodecanvas_cli.cli:main"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
mcp = ["mcp>=2.3; python_version >= '3.10'"]
|
|
40
|
+
dev = ["pytest>=7", "mcp>=2.3; python_version >= '3.10'"]
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/nodecanvas_cli"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
line-length = 120
|
|
50
|
+
target-version = "py39"
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["E", "F", "W", "I"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Load and re-store every graph of a Unity project; report files that change."""
|
|
3
|
+
|
|
4
|
+
import pathlib
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "src"))
|
|
8
|
+
from nodecanvas_cli.container import UnityFile
|
|
9
|
+
from nodecanvas_cli.project import Project, find_project_root
|
|
10
|
+
|
|
11
|
+
proj = Project(find_project_root(pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else None))
|
|
12
|
+
ok = total = 0
|
|
13
|
+
for rel in proj.containers:
|
|
14
|
+
f = UnityFile(proj.abs(rel), rel)
|
|
15
|
+
original = list(f.lines)
|
|
16
|
+
slots = f.slots()
|
|
17
|
+
for slot in slots:
|
|
18
|
+
total += 1
|
|
19
|
+
g = f.load(slot)
|
|
20
|
+
f.store(g)
|
|
21
|
+
if f.lines == original:
|
|
22
|
+
ok += len(slots)
|
|
23
|
+
else:
|
|
24
|
+
for i, (a, b) in enumerate(zip(original, f.lines)):
|
|
25
|
+
if a != b:
|
|
26
|
+
print(rel, "line", i + 1, "\n O:", a[:150], "\n N:", b[:150])
|
|
27
|
+
break
|
|
28
|
+
else:
|
|
29
|
+
print(rel, "line count", len(original), len(f.lines))
|
|
30
|
+
print(f"{ok}/{total} graphs round-trip identically")
|