Metadata-Version: 2.5
Name: nodecanvas-cli
Version: 0.2.0
Summary: Read, query, lint and edit NodeCanvas graphs (Behaviour Trees, FSMs, Dialogue Trees) in Unity projects from the command line
Project-URL: Homepage, https://github.com/Primeer/nodecanvas-cli
Project-URL: Issues, https://github.com/Primeer/nodecanvas-cli/issues
Project-URL: Changelog, https://github.com/Primeer/nodecanvas-cli/blob/main/CHANGELOG.md
Author: Aleksandr Esipov
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agents,behaviour-tree,cli,dialogue-tree,fsm,nodecanvas,unity
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: mcp>=2.3; (python_version >= '3.10') and extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: mcp>=2.3; (python_version >= '3.10') and extra == 'mcp'
Description-Content-Type: text/markdown

# nodecanvas-cli

Read, search, lint and edit [NodeCanvas](https://nodecanvas.paradoxnotion.com/) graphs — Behaviour Trees,
FSMs and Dialogue Trees — in a Unity project from the command line, without opening Unity.

Built for coding agents (it ships as a Claude Code plugin) and just as usable by hand.

![nodecanvas-cli: show, refs, apply --dry-run, lint](https://raw.githubusercontent.com/Primeer/nodecanvas-cli/main/docs/demo/demo.gif)

NodeCanvas stores a graph as one long JSON string inside a Unity YAML asset. A 120-node behaviour tree
is ~85 KB of folded JSON with GUID-linked variables and index-linked nodes. `nodecanvas-cli` shows the same tree
in ~10 KB of readable text, answers "where is this used?", and applies edits as validated batches that
are written back exactly the way Unity writes them.

```
$ nodecanvas-cli show Bot_BT
Bot_BT  [BT, repeat]  Assets/Graphs/Bot_BT.asset  nodes=10
vars: Self:Bot(pub)  Target:Unit  Alert:bool  Cool time:float  Speed:float
#0 Selector
├─#1 ? @Self.IsDead
├─#2 Sequencer "Fight"
│ ├─#3 ? !$Alert
│ ├─#4 Cooldown [coolDownTime=$Cool time]
│ │ └─#5 @Self.Attack($Target)
│ └─#6 ProbabilitySelector
│   ├─(w=1.0) #7 @Self.MoveTo((1.0, 0, -2.5))
│   └─(w=3.0) #8 SubTree → Sub_BT {Self←$Self, Alert→$Alert}
└─[off] #9 $Speed *= 2.0 // double the speed
```

## Features

- **show** — compact tree (BT), state list with ordered transitions (FSM), or script (Dialogue Tree).
  Default values are shown with their real meaning, read from the project's own C# sources.
- **refs** — reverse lookup across all graphs: who calls `Bot.Attack`, which graphs use a
  subtree, a variable, or a task type. Graphs bind C# members by name and break silently on renames;
  this finds them.
- **lint** — missing task/variable types, dangling blackboard references, reflection bindings to members
  that no longer exist, missing subgraphs and stale variable mappings, too many children for a node
  type, unreachable nodes, graph assets whose `m_Name` differs from the file name.
- **apply** — atomic batches of edit operations: add / remove / move / wrap / unwrap / replace nodes, set
  fields, connect and disable, blackboard variables, subgraph mappings, FSM transitions, dialogue
  choices. Short value forms (`"$Var"`, enum names, `[x, y, z]`, `{"call": "Type.Method", ...}`) are
  converted using the C# field types. Batches that introduce lint errors are not saved.
- **dt export / import / lint** — dialogue lines (statements, choices, translations) to CSV/JSON for writers
  and translators and back, keyed by node UIDs, with conflict detection; checks for empty lines,
  dangling choices and jumps, unknown actors and missing translations.
- **why / paths / stats** — why control reaches a node (conditions along the path from the root, FSM
  transitions in, dialogue choices), the same for every call of a member or write of a variable, and
  graph statistics.
- **owners / blackboards** — which prefabs and scenes run a graph (GraphOwner components, exposed
  variable overrides, owner blackboards), graphs assigned nowhere, and Blackboard / GlobalBlackboard /
  AssetBlackboard contents; lint checks global variables and owner overrides against them.
- **types / members** — discover task and node types with their fields, defaults and enum members, and the
  properties/methods reflection tasks can call.
- Works on graph assets (`.asset`) and graphs bound to a `GraphOwner` in prefabs and scenes.
- Byte-exact: unchanged graphs round-trip identically; edited graphs are re-serialized with Unity's
  YAML folding and FullSerializer's key order, so diffs stay minimal.
- **verify --unity** — after editing, let the open Unity Editor import, deserialize and validate the graphs
  (through the [Unity CLI](https://docs.unity.com/) and the project's `com.unity.pipeline` package) and
  report NodeCanvas errors, Missing types, broken reflection bindings and console messages.
- Pure Python ≥ 3.9, no dependencies, no Unity required (except for `verify --unity`); the optional MCP
  server needs `nodecanvas-cli[mcp]`.

## Install

```bash
pipx install nodecanvas-cli                                                     # once published to PyPI
uvx --from git+https://github.com/Primeer/nodecanvas-cli nodecanvas-cli --help  # straight from the repository
python -m nodecanvas_cli --help                                                 # from a checkout, with src/ on PYTHONPATH
```

### Claude Code plugin

The repository is also a Claude Code plugin marketplace containing the `nodecanvas-cli` plugin, which adds a
skill that teaches the agent when and how to use the CLI:

```
/plugin marketplace add Primeer/nodecanvas-cli
/plugin install nodecanvas-cli@nodecanvas-cli
```

The plugin also puts `nodecanvas-cli` on the agent's PATH (Claude Code 2.1.91+), so no separate install is needed:
its `bin/` launcher runs the CLI from the plugin's own sources with any Python 3.9+, always in step with the skill.
The launcher is a POSIX `sh` script — it works on macOS, Linux, WSL and in Git Bash on Windows; it can also be
symlinked into a directory on your PATH. Set `NODECANVAS_PYTHON` to pick the interpreter. Older Claude Code
versions and agents that load the skill on its own can call `skills/nodecanvas-cli/scripts/nodecanvas-cli`.

Install the CLI with pipx as well when you want it in your own terminal or as an MCP server, or when the launcher
does not fit (native Windows shells without `sh`): pipx creates a regular `nodecanvas-cli` executable, `.exe` on
Windows, and the agent uses it the same way — whichever comes first on PATH wins. Update it with
`pipx upgrade nodecanvas-cli` together with the plugin, so the skill and the CLI stay on the same version.

### MCP server (Cursor, Codex, Gemini and other agents)

```bash
pipx install "nodecanvas-cli[mcp]"            # Python 3.10+
nodecanvas-cli --project /path/to/UnityProject mcp
```

A stdio MCP server whose tools wrap the commands (`show`, `node`, `refs`, `lint`, `apply`, `refactor_*`,
`why`, `owners`, …) and return the same compact text. Tools that write files (`apply`, `refactor_*`,
`new_graph`, `extract`, `inline`, `copy`) default to `dry_run=true`; pass `dry_run=false` to save.
Example client config:

```json
{"mcpServers": {"nodecanvas-cli": {"command": "nodecanvas-cli", "args": ["--project", "/path/to/UnityProject", "mcp"]}}}
```

## Usage

Run inside a Unity project (any subdirectory) or pass `--project <root>`.

```bash
nodecanvas-cli list [--kind bt|fsm|dt]
nodecanvas-cli show <graph> [--node N] [--depth D] [--expand]
nodecanvas-cli vars <graph>
nodecanvas-cli node <graph> <id>... [--raw]         # spec form accepted by apply (or raw JSON)
nodecanvas-cli diff <graph> [--rev HEAD]            # compact view vs a git revision
nodecanvas-cli refs <Type.Member | GraphName | $Variable | TaskType>
nodecanvas-cli dt export [graph...] [--locales en,ru] [--format csv|json] [-o file] | dt import <file> | dt lint
nodecanvas-cli why <graph> <node> [--expand]       # conditions on the way from the root to a node
nodecanvas-cli paths [graph] --to <Type.Member | $Var> [--reads]
nodecanvas-cli stats [graph] [--json]
nodecanvas-cli scaffold action|condition|decorator|composite <Name> --dir Assets/... [--field "x:BBParameter<float>=1"]
nodecanvas-cli owners [<graph> | --all]            # GraphOwners in prefabs/scenes, parent graphs
nodecanvas-cli blackboards [--global]
nodecanvas-cli lint [graph...] [--min info|warning|error] [--json]
nodecanvas-cli types [query] [--role action|condition|bt|composite|decorator|fsm|dt]
nodecanvas-cli members <Type> [filter]
nodecanvas-cli refactor rename-member|rename-type|retarget ... [--dry-run]
nodecanvas-cli new <path.asset> --kind bt|fsm|dt [--var "Name:type[=value][:pub]"]
nodecanvas-cli extract <graph> <node> <new.asset> | inline <graph> <node> | copy <src> <node> <dst> <parent>
nodecanvas-cli apply <graph> <ops.json | -> [--dry-run] [--force]
nodecanvas-cli verify --unity [graph... | --changed] [--resave]   # load the graphs in the open Unity Editor
```

`<graph>` is a path, an asset name, a unique prefix, or `Prefab.prefab:GameObject` for bound graphs;
`--kind bt|fsm|dt` disambiguates names shared by graphs of different kinds.

Layout of created/moved nodes can be tuned with `nodecanvas-cli.json` in the Unity project root:

```json
{"layout": {"grid": 20, "level_gap": 200, "sibling_gap": 300}, "scaffold": {"comments": false}}
```

### Editing example

```json
[
  {"op": "add", "parent": "3", "index": 0, "id": "w",
   "node": {"$type": "ActionNode", "_action": {"$type": "Wait", "waitTime": "$Delay"}}},
  {"op": "wrap", "node": "w", "with": {"$type": "Cooldown", "coolDownTime": 2}},
  {"op": "add", "parent": "3", "node": {"$type": "ConditionNode",
   "_condition": {"check": "Bot.DistanceTo", "args": ["$Target"], "agent": "$Owner", "compare": ">", "value": "$Range"}}},
  {"op": "var-add", "name": "Range", "type": "float", "value": 4},
  {"op": "remove", "node": "12", "recursive": true}
]
```

```bash
nodecanvas-cli apply Bot_BT ops.json --dry-run   # prints a before/after diff of the compact view
nodecanvas-cli apply Bot_BT ops.json
```

All operations and value forms: [skills/nodecanvas-cli/references/ops.md](skills/nodecanvas-cli/references/ops.md).

## How it works

- **Container codec** — finds `_serializedGraph` (assets) or `_boundGraphSerialization` (GraphOwner
  components) and decodes Unity's folded single-quoted YAML scalar; encoding reproduces Unity's folding
  rule exactly, so untouched graphs stay byte-identical.
- **FullSerializer JSON** — numbers keep their original text and keys their order; new objects are
  written in FullSerializer's field order. Semantics of omitted values follow FullSerializer: a missing
  field keeps its C# initializer, while a serialized parameter without `_value` holds `default(T)`.
- **Schema from sources** — NodeCanvas ships as source code, so nodecanvas-cli scans the project's C# files
  (lazily, with an mtime cache in `~/.cache/nodecanvas-cli`) for task/node fields, initializers, enums,
  display names, connection limits and the methods available to reflection tasks. Without NodeCanvas
  sources, reading and editing still work with reduced detail.
- **Normalization** — after an edit, nodes are renumbered the way the NodeCanvas editor does
  (depth-first from the root), `$id`s are written only on referenced nodes, and BT children keep a
  left-to-right position order matching their execution order.

## Limitations

- NodeCanvas 3.x serialization only.
- Graphs serialized to an external JSON file (`_externalSerializationFile`) are not supported.
- C# scanning is heuristic (no compiler): unusual declarations may be missed; lint reports unresolved
  members as warnings when a type's base classes live outside the project sources.
- Close the graph in the NodeCanvas editor (or let Unity reimport) before editing it externally.

## Development

```bash
pip install -e ".[dev]"
pytest
python tests/fixtures/generate.py      # regenerate synthetic fixtures
UPDATE_SNAPSHOTS=1 pytest              # refresh show snapshots
python scripts/roundtrip_check.py <UnityProject>   # verify byte-exact round-trip on a real project
```

Tests run against synthetic graphs and small stubs of the NodeCanvas API surface
(`tests/fixtures/project/Assets/Stubs`); NodeCanvas itself is a commercial asset and is not included.

## License

MIT. NodeCanvas is a product of Paradox Notion; this project is not affiliated with it.
