| name | digbp |
| description | Analyze and edit Unreal Engine Blueprints via the digbp persistent server. Use when the user asks to inspect, export, search, understand, or MUTATE Blueprints — including editing variables, functions, components, nodes, and pin connections, reparenting, implementing interfaces, compiling, and saving. Also handles finding function callers, variable get/set sites, native events, asset references, generating C++ migration stubs, **auditing which BPs reference a C++ symbol before deletion (cpp-audit)**, **generating C++ UPROPERTY declarations from BP metadata (cppgen upropertys)**, and the **BP→C++ variable lift workflow** (edit variable lift / unshadow for recovery from the shadow-rename trap). |
| allowed-tools | Bash |
| argument-hint | [command] [--flags] |
digbp — Blueprint Analyzer CLI
digbp communicates with a persistent UE4 Blueprint Analyzer server over named pipes. The server stays running between commands, eliminating the ~60s UE4 startup cost on every query.
It supports both read operations (export, list, search, references) and edit operations (mutate variables, functions, components, nodes, save, compile). All read responses are JSON. All edit responses are JSON of the shape {"success": bool, "error": "...", "path": "...", ...op-specific}.
Configuration lives in ~/.digbp.yaml. The binary is at C:\tools\digbp.exe.
Server Lifecycle
The server auto-starts on first command. You can also manage it explicitly:
digbp start # Launch UE4 server
digbp status # Check if running
digbp stop # Shutdown server
The first command in a session will be slow (UE4 startup). All subsequent commands are fast.
Commands
Export a Blueprint
# JSON output (default)
digbp export --path=/Game/Path/To/Blueprint
# Compact pseudocode
digbp export --path=/Game/Path/To/Blueprint --mode=compact
# C++ migration skeleton
digbp export --path=/Game/Path/To/Blueprint --mode=skeleton
# With complexity analysis
digbp export --path=/Game/Path/To/Blueprint --analyze
# Pretty-printed
digbp export --path=/Game/Path/To/Blueprint --pretty
List Blueprints
digbp list --dir=/Game/UI/
digbp list --dir=/Game/UI/ --no-recurse
C++ Function Usage
digbp cppusage --path=/Game/Path/To/Blueprint
Asset References
digbp references --path=/Game/Path/To/Blueprint
Dependency Graph
digbp graph --path=/Game/Path/To/Blueprint --depth=3
Reference Viewer (Bidirectional)
# Default depths (3)
digbp refview --path=/Game/Path/To/Blueprint
# Custom depths
digbp refview --path=/Game/Path/To/Blueprint --refdepth=2 --referdepth=2
# Blueprints only (filter out textures, materials, etc.)
digbp refview --path=/Game/Path/To/Blueprint --bponly
Find Function Callers
digbp findcallers --dir=/Game/ --func=GetPlayerController
digbp findcallers --dir=/Game/ --func=GetPlayerController --class=UGameplayStatics
Find Variable Get/Set Sites
# All K2Node_VariableGet / K2Node_VariableSet sites
digbp findvaruses --dir=/Game/ --var=Health
# Restrict to read-only or write-only access
digbp findvaruses --dir=/Game/ --var=Health --kind=get
digbp findvaruses --dir=/Game/ --var=Health --kind=set
Inherited UPROPERTYs (accessed via self-scope refs in child BPs) resolve correctly to their native parent class — results include the variable_class field so you can tell which class actually owns the var.
Find Event Implementations
# Native events (BlueprintNativeEvent)
digbp nativeevents --dir=/Game/
# Implementable events (BlueprintImplementableEvent)
digbp findevents --dir=/Game/ --event=ReceiveBeginPlay
Find by CDO Property
digbp findprop --dir=/Game/ --prop=bCanBeDamaged --value=true
digbp findprop --dir=/Game/ --prop=bCanBeDamaged --parentclass=APawn
C++ Reference Audit (pre-deletion safety check)
Before deleting a C++ class, function, delegate, struct, or UPROPERTY, run cpp-audit to find every Blueprint that references it. Walks parent class, K2Node_CallFunction, VariableGet/Set, BaseMCDelegate subclasses (delegate bind/call), DynamicCast, Make/BreakStruct, native event overrides, and BP member-variable types.
digbp cpp-audit --dir=/Game/ # reverse-index to stdout
digbp cpp-audit --dir=/Game/ --out=cpp-refs.json # or to a file (recommended for large trees)
digbp cpp-audit --dir=/Game/Showdown/ --pretty # scoped + readable
Output shape:
symbols[] — reverse index. Each entry: {name, owner, kind, callers[], caller_count}. kind is one of ParentClass, UClass, USTRUCT, UFUNCTION, UPROPERTY, BlueprintAssignable, BlueprintNativeEvent, BlueprintImplementableEvent.
bps[] — forward index. Each entry: {blueprint_path, parent_cpp_class, references[]} where each reference is {name, owner, kind}.
Common use: jq '.symbols[] | select(.name == "SomeClassOrFunction")' to find all BPs that would break if it's removed. Includes dormant references (unused member-var fields) that grep-over-C++ won't catch.
Generate C++ UPROPERTY Declarations
# All BP variables + dispatchers as C++ UPROPERTY lines
digbp cppgen upropertys --path=/Game/UI/MyPanel_BP
# Filter + override category
digbp cppgen upropertys --path=/Game/UI/MyPanel_BP \
--vars="Current XP,XP Level Threshold,OnReady" \
--category="MyPanel"
# Preserve raw BP names verbatim (produces invalid C++ if names contain spaces)
digbp cppgen upropertys --path=/Game/UI/MyPanel_BP --raw-names
Output is in the code field of the response — a single string with DECLARE_DYNAMIC_MULTICAST_DELEGATE_N(...) macros (belong above the UCLASS) followed by UPROPERTY(...) lines (belong inside the class body). By default, BP names are transformed into C++-friendly identifiers ("Current Level" → CurrentLevel) — the same rule edit variable lift applies, so the two commands stay in sync.
Dispatcher UPROPERTYs always get both BlueprintAssignable and BlueprintCallable. Omitting BlueprintCallable compiles fine but causes K2Node_CallDelegate nodes in Blueprint to fail with "Event Dispatcher is not BlueprintCallable" at compile time.
BP variable metadata is preserved — ExposeOnSpawn, UIMin/UIMax, ClampMin/ClampMax, EditCondition, BlueprintBaseOnly, and any custom keys the BP author set are emitted as meta=(Key="Value", ...) on the generated UPROPERTY. Critical for ExposeOnSpawn: without it, external BPs that bound the lifted var as a K2Node_CreateWidget exposed pin silently orphan after the lift.
Level / World Composition / Landscape (digbp level ..., read-only)
For map-construction questions (where tiles sit, what actors a sublevel
contains, what the terrain looks like) that Blueprint export can't answer.
Loads the .umap headless — no world init, no streaming. World Composition
tiles are read from package summaries (never fully loaded).
# Every map package under a folder
digbp level list --dir=/Game/Showdown/Maps/Sunrise/Sunrise_SubLevels
# World Composition overview: per tile → package, layer (+streaming distance,
# distance-streaming on/off), position, absolute_position, bounds, z_order,
# LODs (package + distances), streaming_class, parent_package. Plus the
# persistent level's own actors (bounds-only shape) and a per-layer summary.
digbp level tiles --path=/Game/Showdown/Maps/Sunrise/Sunrise --pretty --out=tiles.json
# Actor dump for one level. Per actor: name/label, class, native_class
# (nearest C++ ancestor), blueprint_chain, folder, tags, transform, bounds,
# world_location/world_bounds (tile offset applied), components:
# StaticMesh → static_mesh, materials, bounds
# ISM/HISM → instance_count (+ instances with --instances)
# ChildActor → child_actor_class (loot generators live here in interiors)
# Spline → points (location/tangents), Brush/volumes → bounds
# Type summaries: landscape {component_count, section size, layers},
# foliage [{foliage_type, static_mesh, instance_count}], volume {class, bounds}.
digbp level actors --path=/Game/Showdown/Maps/Sunrise/Sunrise_SubLevels/Tile_X3_Y2 --pretty
digbp level actors --path=... --class=Volume,BP_LootGenerator --bounds-only # matches class, BP parents, native ancestors
digbp level actors --dir=/Game/Showdown/Maps/Sunrise/Sunrise_SubLevels --out=E:/tmp/sunrise_actors # batch: one JSON per map
# Landscape sampling: extent, transform, component grid, layer infos, and
# samples {qx,qy, world{x,y,z}, height_raw, normal, slope_deg, dominant_layer, weights}
digbp level landscape --path=/Game/Showdown/Maps/Landscape/Sunrise_Landscape --grid=64 --layers --csv=E:/tmp/hm.csv
digbp level landscape --path=/Game/Showdown/Maps/Landscape/Sunrise_Landscape --points="12000,-3400;0,0"
Coordinates: tile actors are stored tile-local; use world_* fields (or
add tile.absolute_position) for map space. Grid mode caps at --grid=1024.
Editing Blueprints (digbp edit ...)
All mutation commands live under digbp edit. They share a core discipline:
- Edits stage in memory. Nothing is written to disk until you explicitly call
digbp edit save. Nothing recompiles until you call digbp edit compile.
- No undo, no dry-run. Source control (p4/git on the
.uasset) is the rollback story. Before large changes, confirm the user is OK with it and ensure the file is checked in.
- Batch where possible. Make all related edits first, then
digbp edit save-and-compile once at the end. Avoid compile-per-edit loops.
- Success shape. Every response is
{"success": true|false, "path": "...", ...}. Check .success before assuming an edit landed. Use --pretty for readable output when debugging.
Lifecycle
digbp edit compile --path=/Game/Path/BP_Foo
digbp edit save --path=/Game/Path/BP_Foo
digbp edit save-and-compile --path=/Game/Path/BP_Foo # common ending
Blueprint Metadata
# Change parent class (pass full /Script/... or /Game/... path)
digbp edit reparent --path=/Game/BP_Foo --parent-class=/Script/Engine.Actor
# Interfaces
digbp edit add-interface --path=/Game/BP_Foo --interface=/Script/MyGame.MyInterface
digbp edit remove-interface --path=/Game/BP_Foo --interface=/Script/MyGame.MyInterface
# Flags — only the ones you pass are applied; omit to leave unchanged
digbp edit set-flags --path=/Game/BP_Foo --is-abstract --description="Base class for pickups"
Variables
# Add a variable. Type syntax mirrors the reader's output:
# primitives: bool, byte, int, int64, float, string, name, text
# structs: vector, rotator, transform, color, linearcolor (aliases) or struct<Name>
# object refs: object<ClassName> or object</Script/Engine.Actor>
# class refs: class<ClassName>
# containers: TArray<T>, TSet<T>, TMap<K,V>
digbp edit variable add --path=/Game/BP_Foo --name=Health --type=float \
--default-value=100 --public --replicated --category="Stats"
digbp edit variable remove --path=/Game/BP_Foo --name=Health
digbp edit variable rename --path=/Game/BP_Foo --old-name=HP --new-name=Health
digbp edit variable set-type --path=/Game/BP_Foo --name=Target --type=object<Actor>
digbp edit variable set-default --path=/Game/BP_Foo --name=Health --value=75
digbp edit variable set-flags --path=/Game/BP_Foo --name=Health --replicated --replication-condition=OwnerOnly
digbp edit variable set-metadata --path=/Game/BP_Foo --name=Health --key=tooltip --value="Current health"
# Atomic multi-var rename-to-C++-friendly + remove (BP→C++ lift step 3).
# Input names are raw BP names (with spaces). Final names are stripped +
# PascalCased: "XP Level Threshold" → "XPLevelThreshold". Dry-run preview first.
#
# External-BP retargeting is ON by default: after the rename+remove, lift scans
# --scope (default /Game/) for K2Node_VariableGet/Set in OTHER BPs that bound
# against the pre-rename name on this BP's class, retargets them, and saves
# the affected BPs. Without this, a rename silently orphans cross-BP callers.
# Use --no-scan-external to skip; verify with 'findvaruses --var=<OldName>' first.
digbp edit variable lift --path=/Game/BP_Foo \
--vars="XP Level Threshold,Current XP,OnReady" --dry-run
digbp edit variable lift --path=/Game/BP_Foo \
--vars="XP Level Threshold,Current XP,OnReady"
digbp edit variable lift --path=/Game/BP_Foo \
--vars="..." --scope=/Game/UI/ # narrow external scan
digbp edit variable lift --path=/Game/BP_Foo \
--vars="..." --no-scan-external # skip scan entirely (advanced)
# --refresh-external-after-lift: after retargeting FMemberReference entries in
# external BPs, also ReconstructNode every K2Node_Variable/CallFunction/Delegate
# on each affected BP. This is the real unlock for the "Target pin no longer
# exists" compile failure that surfaces when UE's pin re-resolution narrows
# the Target pin type from the BP class to its C++ parent. Recommended for
# any lift with upcast-dependent external callers.
digbp edit variable lift --path=/Game/BP_Foo \
--vars="..." --refresh-external-after-lift
# Recovery from the "<X>_0 shadow" trap: when a C++ UPROPERTY is added to the
# parent class BEFORE the BP var is removed, UE renames the BP var to <X>_0
# and retargets K2Node_VariableGet/Set + delegate nodes. Undo that:
digbp edit variable unshadow --path=/Game/BP_Foo --dry-run # idempotent preview
digbp edit variable unshadow --path=/Game/BP_Foo
CDO Properties (parent-class defaults)
For properties on the parent C++ class that don't live in NewVariables (e.g. AActor::bCanBeDamaged), use the CDO ops. Read first to learn the expected text format, then write:
digbp edit cdo get --path=/Game/BP_Foo --property=bCanBeDamaged
digbp edit cdo set --path=/Game/BP_Foo --property=bCanBeDamaged --value=true
# Struct values use UE's native text form:
digbp edit cdo set --path=/Game/BP_Foo --property=RelativeLocation --value="(X=0,Y=0,Z=100)"
# cdo set STAGES the change in memory (response: staged:true, persist_with). It is
# NOT on disk until you persist — either a separate 'edit save-and-compile', or in
# one call with --save-and-compile (recommended for CDO defaults; --save skips the
# recompile). A staged-but-unsaved set reads back via 'cdo get' (live CDO) yet loads
# null after an editor restart — persist it.
digbp edit cdo set --path=/Game/BP_Foo --property=CardClass --value="...SD_Card_C" --save-and-compile
Functions
# Create a new user function
digbp edit function add --path=/Game/BP_Foo --name=GetHealthPct --pure --category="Stats"
# Parameters (add-param direction must be 'input' or 'output')
digbp edit function add-param --path=/Game/BP_Foo --function=GetHealthPct --name=MaxHp --type=float --direction=input
digbp edit function add-param --path=/Game/BP_Foo --function=GetHealthPct --name=Pct --type=float --direction=output
digbp edit function remove-param --path=/Game/BP_Foo --function=GetHealthPct --name=MaxHp
digbp edit function rename --path=/Game/BP_Foo --old-name=Foo --new-name=Bar
digbp edit function set-flags --path=/Game/BP_Foo --function=Bar --pure --const
digbp edit function remove --path=/Game/BP_Foo --name=Bar
# Remove + retarget external K2Node_CallFunction refs (function-lift companion
# to variable lift). After removing a BP function that's now a C++ UFUNCTION
# on the parent class with a different identifier (e.g. 'Play SFX' -> PlaySFX),
# this scans --scope for external callers and rewrites their FunctionReference.
# Same --no-scan-external escape hatch as variable lift.
digbp edit function remove --path=/Game/BP_Foo --name="Play SFX" --retarget-external-to=PlaySFX
# Non-destructive cross-BP K2Node_CallFunction rewrite — for following a C++
# UFUNCTION rename without removing the source. Pure rename:
digbp edit external rewrite-call --old-class=USDPlayFabClientSubsystem \
--old-name=LoadInventory --new-name=FetchInventory --dry-run
# Class + name rename in one shot:
digbp edit external rewrite-call --old-class=USDPlayFabClientSubsystem \
--new-class=USDClientSubsystem --old-name=Get --new-name=Get
# Same shape for BlueprintAssignable multicast delegate renames — targets
# K2Node_AddDelegate / RemoveDelegate / ClearDelegate / CallDelegate /
# AssignDelegate (UK2Node_BaseMCDelegate subclasses).
digbp edit external rewrite-delegate --old-class=USDPlayFabClientSubsystem \
--old-name=OnTitleNewsUpdated --new-name=OnNewsUpdated
# Create an override graph for a parent-class BlueprintImplementable/NativeEvent
digbp edit function override --path=/Game/BP_Foo --function=ReceiveBeginPlay
Events
# Custom event with typed parameters. --param is repeatable as Name:Type.
digbp edit event add-custom --path=/Game/BP_Foo --name=OnDeath \
--param="Killer:object<Controller>" --param="Damage:float"
digbp edit event remove --path=/Game/BP_Foo --name=OnDeath
# Stub implementation of a parent BlueprintEvent
digbp edit event implement --path=/Game/BP_Foo --event=ReceiveBeginPlay
Bulk UMG Audit (digbp widget-tree)
For audits that span many WidgetBlueprints (font usage, color consistency,
hidden-widget hunts, hardcoded-text scans), digbp widget-tree --dir=/Game/
walks every UWidgetBlueprint under the dir and emits the WidgetTrees.
Server-side filters keep the wire size manageable:
# All widgets, nested per-BP (default — large for /Game/)
digbp widget-tree --dir=/Game/UI/
# Flat mode: one row per widget across all BPs
digbp widget-tree --dir=/Game/ --flat
# Class allowlist (matches with or without 'U' prefix)
digbp widget-tree --dir=/Game/ --flat --class=TextBlock,ComboBoxString,EditableTextBox
# Property-key allowlist — only emit Font + ColorAndOpacity in each row
digbp widget-tree --dir=/Game/ --flat --class=TextBlock --properties=Font,ColorAndOpacity
# --where=Prop~Substring filters by property value (substring match, not regex).
# Repeatable for AND. Drops widgets where the named property value doesn't
# contain the substring.
digbp widget-tree --dir=/Game/ --flat \
--class=TextBlock,ComboBoxString,EditableTextBox \
--properties=Font \
--where='Font~/Engine/EngineFonts'
# → only widgets using engine-fallback fonts
# Use --out for large dumps (matches digbp cpp-audit --out shape)
digbp widget-tree --dir=/Game/ --out=widgets.json
Response shape (flat): {success, search_paths, flat: true, bp_count, matched_bp_count, row_count, rows: [{blueprint_path, widget_name, widget_class, parent_slot_class, properties}]}. Nested mode swaps rows
for blueprints: [{blueprint_path, widget_tree: [...nested children...]}]
and prunes subtrees with no surviving widgets.
UMG Widgets (WidgetBlueprint WidgetTree)
For UWidgetBlueprint assets, digbp export --path=... includes a widget_tree
field with the recursive widget hierarchy (Name, Class, ParentSlotClass,
Children) plus selected style properties (Visibility, ToolTipText, Text, Font,
ColorAndOpacity, ShadowColorAndOpacity, ShadowOffset, etc.) emitted as UE-text
strings via ExportTextItem.
Edit side mirrors edit component set-property but targets UMG widgets
addressed by name, with dotted property paths through struct fields:
# Set the displayed text on a TextBlock
digbp edit widget set-property --path=/Game/UI/SP_Matchmaker --widget=StatusText \
--property=Text --value="Connecting..."
# Set font size (dotted path into the Font FSlateFontInfo struct)
digbp edit widget set-property --path=/Game/UI/SP_Matchmaker --widget=StatusText \
--property=Font.Size --value=24
# Swap the font asset (object<Font> path)
digbp edit widget set-property --path=/Game/UI/SP_Matchmaker --widget=StatusText \
--property=Font.FontObject --value=/Game/UI/Fonts/Title.Title
# Color (FLinearColor or FSlateColor in UE text)
digbp edit widget set-property --path=/Game/UI/SP_Matchmaker --widget=StatusText \
--property=ColorAndOpacity --value="(R=1.0,G=0.8,B=0.4,A=1.0)"
# Rename a widget and retarget every internal reference in one shot
digbp edit widget rename --path=/Game/UI/SD_EmoteSelectMenu \
--old-name=EmoteCardHolder --new-name=CardHolder
widget rename is a headless port of the UMG editor's own rename. It retargets
the widget's FName + its auto-generated UWidget* member variable, every
K2Node_VariableGet/Set that reads the widget, K2Node_ComponentBoundEvent
bindings tied to the widget's delegates, and property/animation/navigation
bindings. --new-name is sanitized into an FName the same way the editor does
(the resolved FName comes back as new_name).
Use it to make WidgetTree names match a C++ base's meta=(BindWidget) members
before reparenting BP widgets onto that base — one C++ base with a single
BindWidget CardHolder instead of per-BP names like EmoteCardHolder. When the
parent class already declares a meta=(BindWidget) property of the new name (of
a compatible widget class), the name-collision check is bypassed so the rename
onto that exact name succeeds.
Workflow: p4 edit the .uasset, mutate, then edit save-and-compile. The
WidgetTree mutation is on the design-time archetype, so it persists across
PIE / packaged runs once saved.
Components (Simple Construction Script)
# Pass class by full path; --parent is optional (omit to attach at root)
digbp edit component add --path=/Game/BP_Foo --name=Mesh \
--class=/Script/Engine.StaticMeshComponent --parent=DefaultSceneRoot
digbp edit component remove --path=/Game/BP_Foo --name=Mesh
digbp edit component reparent --path=/Game/BP_Foo --name=Mesh --new-parent=Body
# Set a property on the component template (archetype). Value uses UE text format.
digbp edit component set-property --path=/Game/BP_Foo --component=Mesh \
--property=RelativeLocation --value="(X=0,Y=0,Z=50)"
Node Graph Editing
All node ops take --path + --graph. Graph name is the function name, or EventGraph for the main ubergraph. Nodes are addressed by GUID (returned when you create them). Pins are addressed by {node_guid, pin_name} — pins have no stable GUID in UE4.
Important workflow: add-* responses include "node": {"guid": "...", "pins": [...]}. Parse the guid to wire subsequent connections. Don't re-export the entire BP just to get GUIDs.
# High-level curated builders (cover ~90% of use cases)
digbp edit node add-variable-get --path=/Game/BP_Foo --graph=EventGraph --variable=Health --x=0 --y=0
digbp edit node add-variable-set --path=/Game/BP_Foo --graph=EventGraph --variable=Health --x=400 --y=0
digbp edit node add-function-call --path=/Game/BP_Foo --graph=EventGraph --function=Less_FloatFloat --class=UKismetMathLibrary --x=200 --y=0
digbp edit node add-branch --path=/Game/BP_Foo --graph=EventGraph --x=400 --y=0
digbp edit node add-sequence --path=/Game/BP_Foo --graph=EventGraph --then-pin-count=3
digbp edit node add-cast --path=/Game/BP_Foo --graph=EventGraph --target-class=/Script/Engine.Pawn --pure
digbp edit node add-make-struct --path=/Game/BP_Foo --graph=EventGraph --struct=Vector
digbp edit node add-break-struct --path=/Game/BP_Foo --graph=EventGraph --struct=Transform
digbp edit node add-literal --path=/Game/BP_Foo --graph=EventGraph --object=/Game/Textures/T_Icon.T_Icon
# Escape hatch for anything not in the curated set
digbp edit node add-generic --path=/Game/BP_Foo --graph=EventGraph \
--node-class=/Script/BlueprintGraph.K2Node_Self
# Node edits (need the GUID from a prior add response or from `digbp export`)
digbp edit node move --path=/Game/BP_Foo --graph=EventGraph --node-guid=<GUID> --x=500 --y=100
digbp edit node remove --path=/Game/BP_Foo --graph=EventGraph --node-guid=<GUID>
# Post-lift cleanup: ReconstructNode every K2Node_Variable/CallFunction/Delegate
# in the BP. Use when an external lift left stale pin topology or display
# names ("Get Is Locked" persists alongside "Get IsLocked") in caller BPs.
digbp edit node refresh-variables --path=/Game/BP_ExternalCaller
Pin Wiring
# Connect output pin -> input pin
digbp edit pin connect --path=/Game/BP_Foo --graph=EventGraph \
--from-node=<GUID_A> --from-pin=ReturnValue \
--to-node=<GUID_B> --to-pin=Condition
# Disconnect a specific link
digbp edit pin disconnect --path=/Game/BP_Foo --graph=EventGraph \
--from-node=<GUID_A> --from-pin=Then \
--to-node=<GUID_B> --to-pin=Execute
# Break all connections on a single pin
digbp edit pin break-all-links --path=/Game/BP_Foo --graph=EventGraph \
--node-guid=<GUID> --pin-name=Exec
# Set a literal default on an input pin (prefer this over add-literal for scalars)
digbp edit pin set-default --path=/Game/BP_Foo --graph=EventGraph \
--node-guid=<GUID> --pin-name=B --value=0.5
Worked Example: build a small graph from scratch
# 1. Add a Health variable
digbp edit variable add --path=/Game/BP_Foo --name=Health --type=float --default-value=100 --public
# 2. Create a pure helper function
digbp edit function add --path=/Game/BP_Foo --name=IsAlive --pure
digbp edit function add-param --path=/Game/BP_Foo --function=IsAlive --name=ReturnValue --type=bool --direction=output
# 3. Wire the graph — capture each node's GUID from the response JSON
GET_JSON=$(digbp edit node add-variable-get --path=/Game/BP_Foo --graph=IsAlive --variable=Health --x=0 --y=0 --pretty)
GET_GUID=$(echo "$GET_JSON" | jq -r .node.guid)
GT_JSON=$(digbp edit node add-function-call --path=/Game/BP_Foo --graph=IsAlive --function=Greater_FloatFloat --class=UKismetMathLibrary --x=200 --y=0 --pretty)
GT_GUID=$(echo "$GT_JSON" | jq -r .node.guid)
# 4. Connect Health -> A on the comparison, set B default to 0
digbp edit pin connect --path=/Game/BP_Foo --graph=IsAlive \
--from-node=$GET_GUID --from-pin=Health \
--to-node=$GT_GUID --to-pin=A
digbp edit pin set-default --path=/Game/BP_Foo --graph=IsAlive \
--node-guid=$GT_GUID --pin-name=B --value=0
# 5. Commit
digbp edit save-and-compile --path=/Game/BP_Foo
Worked Example: BP→C++ Variable Lift
Lifting BP-defined member variables into C++ UPROPERTYs on a parent class. The order matters — do it out of order and you hit the <X>_0 shadow trap (see unshadow below for recovery).
# 1. Preview the lift. Names transform to C++ identifiers (strip spaces, PascalCase).
# --vars uses the RAW BP names (with spaces) regardless of transform.
digbp edit variable lift --path=/Game/UI/SDAccountInfoPanel_BP \
--vars="Current XP,XP Level Threshold,OnReady" --dry-run --pretty
# response.lifted[].final_name = ["CurrentXP", "XPLevelThreshold", "OnReady"]
# 2. Generate the matching C++ UPROPERTY block. Same --vars list. Same name
# transform — so the emitted C++ agrees with what lift will produce.
digbp cppgen upropertys --path=/Game/UI/SDAccountInfoPanel_BP \
--vars="Current XP,XP Level Threshold,OnReady" \
--category="AccountInfoPanel" --pretty
# 3. HUMAN STEP: paste response.code into the parent .h (DECLARE macros above
# the UCLASS, UPROPERTY block inside it), rebuild the plugin, restart the
# UE4 server (digbp stop && digbp start) so it picks up the new UPROPERTYs.
# 4. Atomic rename + remove. K2Node_VariableGet/Set refs auto-retarget to the
# now-inherited parent properties. --refresh-external-after-lift forces a
# ReconstructNode on every external-caller BP, which is the real unlock
# for the Target-pin-loss failure mode — without it, compile errors like
# "pin Target no longer exists on node Get IsLocked" can persist on
# external BPs even with correctly-retargeted FMemberReference entries.
digbp edit variable lift --path=/Game/UI/SDAccountInfoPanel_BP \
--vars="Current XP,XP Level Threshold,OnReady" \
--refresh-external-after-lift
digbp edit save-and-compile --path=/Game/UI/SDAccountInfoPanel_BP
If you skip step 3 or do step 4 first, UE shadow-renames the colliding BP vars to <X>_0 and retargets every K2Node_VariableGet/Set/BaseMCDelegate node. The .uasset now has CurrentXP_0 variables and nodes pointing at them. Recover with:
digbp edit variable unshadow --path=/Game/UI/SDAccountInfoPanel_BP --dry-run
# response.shadows_detected = [{shadow_name: "CurrentXP_0", parent_name: "CurrentXP"}, ...]
digbp edit variable unshadow --path=/Game/UI/SDAccountInfoPanel_BP
digbp edit save-and-compile --path=/Game/UI/SDAccountInfoPanel_BP
Idempotent: running on an already-clean BP reports zero actions.
Common Failure Modes
- "Missing required param: X" — you omitted a
--flag. Every required flag is documented in digbp edit <command> --help.
- "Could not resolve parent class: X" — use the full path (
/Script/Engine.Actor) or the short name with or without the A/U prefix. The plugin tries several fallbacks but isn't magic.
- "TryCreateConnection failed: " — the schema rejected the connection. The error includes the schema's diagnostic (type mismatch, direction mismatch, etc.). Read it, fix the pin types, retry.
- "Blueprint has no GeneratedClass; compile it first" —
edit cdo get/set needs a compiled class. Run digbp edit compile --path=... first.
- Compile errors after structural edits — run
digbp edit compile --path=... and inspect the compile.status field. error means the BP is broken; check the editor for the actual message.
Known Gotchas
Git Bash Path Conversion
Git Bash on Windows converts /Game/... paths to local filesystem paths (e.g. C:/Users/.../Git/Game/...), which makes digbp return empty results. The fix is to set MSYS_NO_PATHCONV=1 as a Windows user environment variable (Win+R → sysdm.cpl → Advanced → Environment Variables → User variables → New). After setting it, fully restart Claude Code so the harness inherits it.
If this is unset for any reason, every command must be prefixed inline:
MSYS_NO_PATHCONV=1 digbp export --path=/Game/Showdown/...
Quick check: echo $MSYS_NO_PATHCONV should print 1. If empty, the env var did not propagate — fall back to the inline prefix.
Note: MSYS2_ARG_CONV_EXCL=/Game/;/Script/;/Engine/ does NOT work for digbp's --path=/Game/... style arguments. The exclusion list matches the start of the entire argument (--path=), not the embedded path. Use MSYS_NO_PATHCONV=1 instead.
Calling BP-Defined Functions/Events with add-function-call
When calling a function or custom event defined in the blueprint itself (not on a C++ parent class):
--class=MyBlueprint_C will fail — the generated class may not expose the function to the lookup.
--class=SKEL_MyBlueprint_C works — use the skeleton class prefix.
Example:
# FAILS:
digbp edit node add-function-call --function=MyBPEvent --class=BP_Foo_C
# WORKS:
digbp edit node add-function-call --function=MyBPEvent --class=SKEL_BP_Foo_C
Newly Added Custom Events Are Not Immediately Callable
After digbp edit event add-custom, the event exists in the graph but is not yet registered as a callable function on the class. You must compile first before other nodes can call it:
digbp edit event add-custom --path=/Game/BP_Foo --name=MyEvent --param="Tag:name"
digbp edit compile --path=/Game/BP_Foo # <-- required before add-function-call
digbp edit node add-function-call --function=MyEvent --class=SKEL_BP_Foo_C ...
p4 Edit Before Mutating
The .uasset must be writable. Always p4 edit the content file before any digbp edit commands. The content path follows the pattern:
Showdown/Content/Showdown/UI/... (not Showdown/Content/UI/...)
Use find Showdown/Content -iname "BlueprintName*" if unsure of the exact path.
Server Persistence Across Sessions
The server does not survive between Claude Code sessions. If you get EOF or server not running errors, the server died. Any command will auto-restart it (~60s), or use digbp start explicitly.
Output
All commands return JSON to stdout. Use --pretty for indented output. Errors go to stderr.
When the user asks to analyze a blueprint by name without a full path, first use digbp list to find it:
MSYS_NO_PATHCONV=1 digbp list --dir=/Game/ | jq '.blueprints[] | select(.name | test("PartialName"; "i"))'
Resolving Partial Names
If the user gives a short name like "GameInstance" instead of a full path:
- Use
digbp list --dir=/Game/ --pretty and search the output for a matching name
- Then use the full path from the result in subsequent commands
Timeout
Use a Bash timeout of at least 120000ms for the first command in a session (server startup). Subsequent commands complete in seconds.