| 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
digbp status
digbp stop
The first command in a session will be slow (UE4 startup). All subsequent commands are fast.
Commands
Export a Blueprint
digbp export --path=/Game/Path/To/Blueprint
digbp export --path=/Game/Path/To/Blueprint --mode=compact
digbp export --path=/Game/Path/To/Blueprint --mode=skeleton
digbp export --path=/Game/Path/To/Blueprint --analyze
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)
digbp refview --path=/Game/Path/To/Blueprint
digbp refview --path=/Game/Path/To/Blueprint --refdepth=2 --referdepth=2
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
digbp findvaruses --dir=/Game/ --var=Health
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
digbp nativeevents --dir=/Game/
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/
digbp cpp-audit --dir=/Game/ --out=cpp-refs.json
digbp cpp-audit --dir=/Game/Showdown/ --pretty
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
digbp cppgen upropertys --path=/Game/UI/MyPanel_BP
digbp cppgen upropertys --path=/Game/UI/MyPanel_BP \
--vars="Current XP,XP Level Threshold,OnReady" \
--category="MyPanel"
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.
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
Blueprint Metadata
digbp edit reparent --path=/Game/BP_Foo --parent-class=/Script/Engine.Actor
digbp edit add-interface --path=/Game/BP_Foo --interface=/Script/MyGame.MyInterface
digbp edit remove-interface --path=/Game/BP_Foo --interface=/Script/MyGame.MyInterface
digbp edit set-flags --path=/Game/BP_Foo --is-abstract --description="Base class for pickups"
Variables
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"
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/
digbp edit variable lift --path=/Game/BP_Foo \
--vars="..." --no-scan-external
digbp edit variable lift --path=/Game/BP_Foo \
--vars="..." --refresh-external-after-lift
digbp edit variable unshadow --path=/Game/BP_Foo --dry-run
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
digbp edit cdo set --path=/Game/BP_Foo --property=RelativeLocation --value="(X=0,Y=0,Z=100)"
digbp edit cdo set --path=/Game/BP_Foo --property=CardClass --value="...SD_Card_C" --save-and-compile
Functions
digbp edit function add --path=/Game/BP_Foo --name=GetHealthPct --pure --category="Stats"
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
digbp edit function remove --path=/Game/BP_Foo --name="Play SFX" --retarget-external-to=PlaySFX
digbp edit external rewrite-call --old-class=USDPlayFabClientSubsystem \
--old-name=LoadInventory --new-name=FetchInventory --dry-run
digbp edit external rewrite-call --old-class=USDPlayFabClientSubsystem \
--new-class=USDClientSubsystem --old-name=Get --new-name=Get
digbp edit external rewrite-delegate --old-class=USDPlayFabClientSubsystem \
--old-name=OnTitleNewsUpdated --new-name=OnNewsUpdated
digbp edit function override --path=/Game/BP_Foo --function=ReceiveBeginPlay
Events
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
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:
digbp widget-tree --dir=/Game/UI/
digbp widget-tree --dir=/Game/ --flat
digbp widget-tree --dir=/Game/ --flat --class=TextBlock,ComboBoxString,EditableTextBox
digbp widget-tree --dir=/Game/ --flat --class=TextBlock --properties=Font,ColorAndOpacity
digbp widget-tree --dir=/Game/ --flat \
--class=TextBlock,ComboBoxString,EditableTextBox \
--properties=Font \
--where='Font~/Engine/EngineFonts'
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:
digbp edit widget set-property --path=/Game/UI/SP_Matchmaker --widget=StatusText \
--property=Text --value="Connecting..."
digbp edit widget set-property --path=/Game/UI/SP_Matchmaker --widget=StatusText \
--property=Font.Size --value=24
digbp edit widget set-property --path=/Game/UI/SP_Matchmaker --widget=StatusText \
--property=Font.FontObject --value=/Game/UI/Fonts/Title.Title
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)"
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)
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
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.
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
digbp edit node add-generic --path=/Game/BP_Foo --graph=EventGraph \
--node-class=/Script/BlueprintGraph.K2Node_Self
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>
digbp edit node refresh-variables --path=/Game/BP_ExternalCaller
Pin Wiring
digbp edit pin connect --path=/Game/BP_Foo --graph=EventGraph \
--from-node=<GUID_A> --from-pin=ReturnValue \
--to-node=<GUID_B> --to-pin=Condition
digbp edit pin disconnect --path=/Game/BP_Foo --graph=EventGraph \
--from-node=<GUID_A> --from-pin=Then \
--to-node=<GUID_B> --to-pin=Execute
digbp edit pin break-all-links --path=/Game/BP_Foo --graph=EventGraph \
--node-guid=<GUID> --pin-name=Exec
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
digbp edit variable add --path=/Game/BP_Foo --name=Health --type=float --default-value=100 --public
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
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)
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
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).
digbp edit variable lift --path=/Game/UI/SDAccountInfoPanel_BP \
--vars="Current XP,XP Level Threshold,OnReady" --dry-run --pretty
digbp cppgen upropertys --path=/Game/UI/SDAccountInfoPanel_BP \
--vars="Current XP,XP Level Threshold,OnReady" \
--category="AccountInfoPanel" --pretty
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
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:
digbp edit node add-function-call --function=MyBPEvent --class=BP_Foo_C
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
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.