gdharness 0.11.1 → 0.12.1
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.
package/README.md
CHANGED
|
@@ -73,11 +73,12 @@ and integration suites run against a real engine on all three before anything is
|
|
|
73
73
|
|
|
74
74
|
What each kind of release means here:
|
|
75
75
|
|
|
76
|
-
- **Patch**:
|
|
77
|
-
corrected is a fix rather than a feature
|
|
78
|
-
how a tool stops being worth asking.
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
- **Patch**: nearly everything. An answer that was wrong is now right, and the output may change
|
|
77
|
+
shape for it, because a wrong answer corrected is a fix rather than a feature and leaving one
|
|
78
|
+
wrong to protect whoever parsed it is how a tool stops being worth asking. An addition nothing
|
|
79
|
+
has to adapt to is a patch too: a new argument, a new field, an argument reaching further than it
|
|
80
|
+
did. Ignore all of it and your calls still work.
|
|
81
|
+
- **Minor**: a new tool or a new op, meaning this does something it could not do before.
|
|
81
82
|
- **Major**: a tool, an op, an argument or a field was renamed or taken away. Nothing else earns
|
|
82
83
|
one.
|
|
83
84
|
|
package/build/cli.js
CHANGED
|
@@ -15799,7 +15799,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15799
15799
|
property: {
|
|
15800
15800
|
type: "string",
|
|
15801
15801
|
ops: ["property", "find"],
|
|
15802
|
-
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through
|
|
15802
|
+
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A negative number counts from the end. A step that is not there is named, and says what was there instead.'
|
|
15803
15803
|
},
|
|
15804
15804
|
depth: { type: "number", ops: ["tree"], description: "tree: levels to descend. Default 3." },
|
|
15805
15805
|
includeProperties: {
|
|
@@ -15873,7 +15873,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15873
15873
|
property: {
|
|
15874
15874
|
type: "string",
|
|
15875
15875
|
ops: ["set"],
|
|
15876
|
-
description: 'set: which one to write. Colons write through
|
|
15876
|
+
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end. The answer reads back off the same holder, so a write a typed container refused shows as an unchanged value.'
|
|
15877
15877
|
},
|
|
15878
15878
|
value: {
|
|
15879
15879
|
ops: ["set"],
|
|
@@ -15883,7 +15883,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15883
15883
|
method: {
|
|
15884
15884
|
type: "string",
|
|
15885
15885
|
ops: ["call"],
|
|
15886
|
-
description: 'call: which one to call. Colons call through
|
|
15886
|
+
description: 'call: which one to call. Colons call through what a node holds, "_game:run:advance", the same way a property is written through them, and a list or a map is stepped into by index or key, "_game:run:roster:0:retire".'
|
|
15887
15887
|
},
|
|
15888
15888
|
args: {
|
|
15889
15889
|
type: "array",
|
|
@@ -39477,6 +39477,9 @@ async function enablePlugins(engine, projectPath, names) {
|
|
|
39477
39477
|
}
|
|
39478
39478
|
return outcomes;
|
|
39479
39479
|
}
|
|
39480
|
+
function autoloadIsOurs(named) {
|
|
39481
|
+
return named === null || named === `res://${RUNTIME_AUTOLOAD.path}`;
|
|
39482
|
+
}
|
|
39480
39483
|
async function setRuntime(engine, projectPath, on) {
|
|
39481
39484
|
return on ? await runOperation2(engine, "add_autoload", { name: RUNTIME_AUTOLOAD.name, path: `res://${RUNTIME_AUTOLOAD.path}`, enabled: true }, projectPath) : await runOperation2(engine, "remove_autoload", { name: RUNTIME_AUTOLOAD.name }, projectPath);
|
|
39482
39485
|
}
|
|
@@ -39533,7 +39536,9 @@ function inspectProject(projectPath) {
|
|
|
39533
39536
|
}
|
|
39534
39537
|
}
|
|
39535
39538
|
const autoload = settings["autoload"];
|
|
39536
|
-
const
|
|
39539
|
+
const registered = autoload === undefined ? undefined : readString(autoload, RUNTIME_AUTOLOAD.name);
|
|
39540
|
+
const runtimeAutoload = typeof registered === "string";
|
|
39541
|
+
const runtimeAutoloadPath = typeof registered === "string" ? registered.replace(/^\*/, "") : null;
|
|
39537
39542
|
const named = autoloadPaths(settings);
|
|
39538
39543
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
39539
39544
|
for (const [name, path] of named) {
|
|
@@ -39553,6 +39558,7 @@ function inspectProject(projectPath) {
|
|
|
39553
39558
|
addons,
|
|
39554
39559
|
pluginsEnabled,
|
|
39555
39560
|
runtimeAutoload,
|
|
39561
|
+
runtimeAutoloadPath,
|
|
39556
39562
|
staleClasses,
|
|
39557
39563
|
classCacheExists: cached !== null,
|
|
39558
39564
|
problems
|
|
@@ -39839,6 +39845,18 @@ function said2(outcome, what) {
|
|
|
39839
39845
|
console.error(` engine ${entry.severity}: ${entry.text}`);
|
|
39840
39846
|
}
|
|
39841
39847
|
}
|
|
39848
|
+
async function registerRuntime(godot, projectPath) {
|
|
39849
|
+
const named = inspectProject(projectPath).runtimeAutoloadPath;
|
|
39850
|
+
if (autoloadIsOurs(named)) {
|
|
39851
|
+
said2(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
|
|
39852
|
+
console.log(`${RUNTIME_AUTOLOAD.name} autoload registered at res://${RUNTIME_AUTOLOAD.path}`);
|
|
39853
|
+
return;
|
|
39854
|
+
}
|
|
39855
|
+
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
39856
|
+
if (!existsSync13(join17(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
39857
|
+
console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
|
|
39858
|
+
}
|
|
39859
|
+
}
|
|
39842
39860
|
var SETUP_FLAGS = new Set(["--runtime", "--no-runtime", "--no-connect", "--no-skill", "--json", "--yes"]);
|
|
39843
39861
|
function namedHarnesses() {
|
|
39844
39862
|
const flags = args.filter((arg) => arg.startsWith("--") && !SETUP_FLAGS.has(arg));
|
|
@@ -39941,8 +39959,7 @@ async function setup() {
|
|
|
39941
39959
|
console.log(`${name}: ${outcome.ok ? String(outcome.payload["action"]) : "failed"}`);
|
|
39942
39960
|
}
|
|
39943
39961
|
if (runtime) {
|
|
39944
|
-
|
|
39945
|
-
console.log(`${RUNTIME_AUTOLOAD.name} autoload registered`);
|
|
39962
|
+
await registerRuntime(godot, projectPath);
|
|
39946
39963
|
}
|
|
39947
39964
|
const classes = await runOperation(godot, "refresh_class_cache", {}, projectPath);
|
|
39948
39965
|
said2(classes, "rebuilding the class list");
|
|
@@ -40042,7 +40059,7 @@ async function upgrade() {
|
|
|
40042
40059
|
said2(outcome, `enabling ${EDITOR_PLUGINS[index] ?? ""}`);
|
|
40043
40060
|
}
|
|
40044
40061
|
if (before.runtimeAutoload) {
|
|
40045
|
-
|
|
40062
|
+
await registerRuntime(godot, projectPath);
|
|
40046
40063
|
}
|
|
40047
40064
|
said2(await runOperation(godot, "refresh_class_cache", {}, projectPath), "rebuilding the class list");
|
|
40048
40065
|
const launch = launchFor(version, godot.godotPath, projectPath);
|
|
@@ -40092,8 +40109,12 @@ async function runtime3() {
|
|
|
40092
40109
|
}
|
|
40093
40110
|
const projectPath = projectArgument(2);
|
|
40094
40111
|
const godot = await engine();
|
|
40095
|
-
|
|
40096
|
-
|
|
40112
|
+
if (state === "on") {
|
|
40113
|
+
await registerRuntime(godot, projectPath);
|
|
40114
|
+
return;
|
|
40115
|
+
}
|
|
40116
|
+
said2(await setRuntime(godot, projectPath, false), "turning the runtime off");
|
|
40117
|
+
console.log(`${RUNTIME_AUTOLOAD.name} autoload removed`);
|
|
40097
40118
|
}
|
|
40098
40119
|
async function classes() {
|
|
40099
40120
|
const projectPath = projectArgument(1);
|
|
@@ -172,12 +172,12 @@ func _found(node: Node, wanted_property: String) -> Dictionary:
|
|
|
172
172
|
if reached.has("message"):
|
|
173
173
|
entry["has_property"] = false
|
|
174
174
|
return entry
|
|
175
|
-
var holder:
|
|
175
|
+
var holder: Variant = reached["holder"]
|
|
176
176
|
var named: String = reached["name"]
|
|
177
|
-
var has: bool =
|
|
177
|
+
var has: bool = _can_read(holder, named)
|
|
178
178
|
entry["has_property"] = has
|
|
179
179
|
if has:
|
|
180
|
-
entry["value"] = _values.serialize(holder
|
|
180
|
+
entry["value"] = _values.serialize(_read(holder, named))
|
|
181
181
|
return entry
|
|
182
182
|
|
|
183
183
|
|
|
@@ -500,18 +500,18 @@ func get_property(params: Dictionary) -> Dictionary:
|
|
|
500
500
|
if reached.has("message"):
|
|
501
501
|
return reached
|
|
502
502
|
|
|
503
|
-
var holder:
|
|
503
|
+
var holder: Variant = reached["holder"]
|
|
504
504
|
var named: String = reached["name"]
|
|
505
|
-
# Asked of the
|
|
506
|
-
#
|
|
507
|
-
if not
|
|
508
|
-
return {"type": "error", "message":
|
|
505
|
+
# Asked of the holder rather than read and compared to null, because a property the holder does
|
|
506
|
+
# not have and a property that is null both read as null.
|
|
507
|
+
if not _can_read(holder, named):
|
|
508
|
+
return {"type": "error", "message": _nothing_there(holder, named, str(reached["called"]))}
|
|
509
509
|
|
|
510
510
|
return {
|
|
511
511
|
"type": "property",
|
|
512
512
|
"path": node_path,
|
|
513
513
|
"property": property,
|
|
514
|
-
"value": _values.serialize(holder
|
|
514
|
+
"value": _values.serialize(_read(holder, named)),
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
|
|
@@ -532,21 +532,115 @@ func get_property(params: Dictionary) -> Dictionary:
|
|
|
532
532
|
## clocks" is a typo found, and "no property _game:clocks:speed" is a puzzle.
|
|
533
533
|
func _reached(node: Node, node_path: String, reaching: String) -> Dictionary:
|
|
534
534
|
var parts: PackedStringArray = reaching.split(":")
|
|
535
|
-
var holder:
|
|
535
|
+
var holder: Variant = node
|
|
536
536
|
var called: String = node_path
|
|
537
537
|
for step: int in parts.size() - 1:
|
|
538
538
|
var named: String = parts[step]
|
|
539
|
-
if not
|
|
540
|
-
return {"type": "error", "message":
|
|
541
|
-
|
|
539
|
+
if not _can_read(holder, named):
|
|
540
|
+
return {"type": "error", "message": _nothing_there(holder, named, called)}
|
|
541
|
+
holder = _read(holder, named)
|
|
542
542
|
called = "%s:%s" % [called, named]
|
|
543
|
-
if not
|
|
543
|
+
if not _can_hold(holder):
|
|
544
544
|
var wanted: String = parts[step + 1]
|
|
545
545
|
return {"type": "error", "message": "%s holds no object to read %s off" % [called, wanted]}
|
|
546
|
-
holder = next
|
|
547
546
|
return {"holder": holder, "name": parts[parts.size() - 1], "called": called}
|
|
548
547
|
|
|
549
548
|
|
|
549
|
+
## Whether a step can be taken into [param value] at all: an object, a list or a map can be walked
|
|
550
|
+
## into, and a number or a string is where a path ends whether the caller meant it to or not.
|
|
551
|
+
static func _can_hold(value: Variant) -> bool:
|
|
552
|
+
return value is Object or value is Array or value is Dictionary
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
## Whether [param named] is something [param holder] has.
|
|
556
|
+
##
|
|
557
|
+
## Three kinds of holder, because the state a game keeps is not all properties of objects: a roster
|
|
558
|
+
## is a list and an in-tray is a map, and a path that could not step into either stopped at the
|
|
559
|
+
## first one. An index reads the way a property does, and a negative one counts from the end the
|
|
560
|
+
## way GDScript's own does, so reading the last of something does not mean asking how many first.
|
|
561
|
+
static func _can_read(holder: Variant, named: String) -> bool:
|
|
562
|
+
if holder is Array:
|
|
563
|
+
var items: Array = holder
|
|
564
|
+
return _index_in(named, items.size()) != -1
|
|
565
|
+
if holder is Dictionary:
|
|
566
|
+
var map: Dictionary = holder
|
|
567
|
+
return map.has(named) or map.has(StringName(named))
|
|
568
|
+
if holder is Object:
|
|
569
|
+
return _has_property(holder, named)
|
|
570
|
+
return false
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
## Put [param value] where [param named] points. Only ever called once [method _can_read] agrees,
|
|
574
|
+
## and lists and maps are reference types, so writing into one reaches the game's own copy.
|
|
575
|
+
static func _write(holder: Variant, named: String, value: Variant) -> void:
|
|
576
|
+
if holder is Array:
|
|
577
|
+
var items: Array = holder
|
|
578
|
+
items[_index_in(named, items.size())] = value
|
|
579
|
+
return
|
|
580
|
+
if holder is Dictionary:
|
|
581
|
+
var map: Dictionary = holder
|
|
582
|
+
# Written back under the key it was found under, since a map keyed by StringName and one
|
|
583
|
+
# keyed by String both read the same way and a write to the wrong one adds a second entry.
|
|
584
|
+
if map.has(named):
|
|
585
|
+
map[named] = value
|
|
586
|
+
else:
|
|
587
|
+
map[StringName(named)] = value
|
|
588
|
+
return
|
|
589
|
+
var object: Object = holder
|
|
590
|
+
object.set(named, value)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
## What [param holder] holds under [param named]. Only ever called once [method _can_read] agrees.
|
|
594
|
+
static func _read(holder: Variant, named: String) -> Variant:
|
|
595
|
+
if holder is Array:
|
|
596
|
+
var items: Array = holder
|
|
597
|
+
return items[_index_in(named, items.size())]
|
|
598
|
+
if holder is Dictionary:
|
|
599
|
+
var map: Dictionary = holder
|
|
600
|
+
if map.has(named):
|
|
601
|
+
return map[named]
|
|
602
|
+
return map[StringName(named)]
|
|
603
|
+
var object: Object = holder
|
|
604
|
+
return object.get(named)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
## Where [param named] lands in a list of [param size], or -1 for a step that is not in it.
|
|
608
|
+
##
|
|
609
|
+
## -1 for "nowhere" is safe because every answer this gives is an index into a list that long, so a
|
|
610
|
+
## real one is never negative by the time it is returned.
|
|
611
|
+
static func _index_in(named: String, size: int) -> int:
|
|
612
|
+
if not named.is_valid_int():
|
|
613
|
+
return -1
|
|
614
|
+
var index: int = int(named)
|
|
615
|
+
if index < 0:
|
|
616
|
+
index += size
|
|
617
|
+
return index if index >= 0 and index < size else -1
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
## Why a step could not be taken, said in the terms of what was actually there.
|
|
621
|
+
##
|
|
622
|
+
## "has no property 0" about a list is a true sentence that sends somebody looking for a property,
|
|
623
|
+
## so a list says how long it is and a map says what it is keyed by. The step is named rather than
|
|
624
|
+
## the whole path, because the step is the typo.
|
|
625
|
+
static func _nothing_there(holder: Variant, named: String, called: String) -> String:
|
|
626
|
+
if holder is Array:
|
|
627
|
+
var items: Array = holder
|
|
628
|
+
return "%s is a list of %d, so there is no %s in it" % [called, items.size(), named]
|
|
629
|
+
if holder is Dictionary:
|
|
630
|
+
var map: Dictionary = holder
|
|
631
|
+
var keys: Array = map.keys()
|
|
632
|
+
# Eight of them, because a map keyed by something unexpected is told by the first few and a
|
|
633
|
+
# map of two hundred would bury the sentence saying which key was missing.
|
|
634
|
+
var some: PackedStringArray = []
|
|
635
|
+
for key: Variant in keys.slice(0, 8):
|
|
636
|
+
some.append(str(key))
|
|
637
|
+
var rest: String = ""
|
|
638
|
+
if keys.size() > 8:
|
|
639
|
+
rest = " and %d more" % [keys.size() - 8]
|
|
640
|
+
return "%s has no key %s; it is keyed by %s%s" % [called, named, ", ".join(some), rest]
|
|
641
|
+
return "%s has no property %s" % [called, named]
|
|
642
|
+
|
|
643
|
+
|
|
550
644
|
## Whether [param holder] declares [param named]. Its own function because three ops ask it and a
|
|
551
645
|
## property read back as null answers it wrongly.
|
|
552
646
|
static func _has_property(holder: Object, named: String) -> bool:
|
|
@@ -576,17 +670,19 @@ func set_property(params: Dictionary) -> Dictionary:
|
|
|
576
670
|
if reached.has("message"):
|
|
577
671
|
return reached
|
|
578
672
|
|
|
579
|
-
var holder:
|
|
673
|
+
var holder: Variant = reached["holder"]
|
|
580
674
|
var named: String = reached["name"]
|
|
581
|
-
|
|
582
|
-
|
|
675
|
+
if not _can_read(holder, named):
|
|
676
|
+
return {"type": "error", "message": _nothing_there(holder, named, str(reached["called"]))}
|
|
677
|
+
var old_value: Variant = _read(holder, named)
|
|
678
|
+
_write(holder, named, _values.fitted(value, typeof(old_value)))
|
|
583
679
|
|
|
584
680
|
return {
|
|
585
681
|
"type": "property_set",
|
|
586
682
|
"path": node_path,
|
|
587
683
|
"property": property,
|
|
588
684
|
"old_value": _values.serialize(old_value),
|
|
589
|
-
"new_value": _values.serialize(holder
|
|
685
|
+
"new_value": _values.serialize(_read(holder, named))
|
|
590
686
|
}
|
|
591
687
|
|
|
592
688
|
|
|
@@ -610,6 +706,19 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
610
706
|
if reached.has("message"):
|
|
611
707
|
return reached
|
|
612
708
|
|
|
709
|
+
# A list or a map can be stepped through on the way, and is not a thing with methods when the
|
|
710
|
+
# path stops on one: said in those terms rather than as "has no method", which reads as a
|
|
711
|
+
# misspelling of a method that was never going to be there.
|
|
712
|
+
if not reached["holder"] is Object:
|
|
713
|
+
var sort: String = "a list" if reached["holder"] is Array else "a map"
|
|
714
|
+
return {
|
|
715
|
+
"type": "error",
|
|
716
|
+
"message":
|
|
717
|
+
(
|
|
718
|
+
"%s is %s, which has no methods: name the element to call it on, as in %s:0:%s"
|
|
719
|
+
% [reached["called"], sort, reached["called"], reached["name"]]
|
|
720
|
+
)
|
|
721
|
+
}
|
|
613
722
|
var holder: Object = reached["holder"]
|
|
614
723
|
var named: String = reached["name"]
|
|
615
724
|
if not holder.has_method(named):
|
|
@@ -136,7 +136,36 @@ func _serialize_object(value: Object) -> Dictionary:
|
|
|
136
136
|
var node: Node = value
|
|
137
137
|
if node.is_inside_tree():
|
|
138
138
|
return {"_type": "Node", "class": node.get_class(), "path": str(node.get_path())}
|
|
139
|
-
return
|
|
139
|
+
return _describe_object(value)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
## A plain object, said in enough detail to tell it from the one beside it.
|
|
143
|
+
##
|
|
144
|
+
## `{"class": "RefCounted"}` was all of it, so a roster of twelve people came back as the word
|
|
145
|
+
## RefCounted twelve times: correct, useless, and indistinguishable from a list of twelve of
|
|
146
|
+
## anything else. The state worth looking at in a game is lists of these, so each one carries what
|
|
147
|
+
## it is (the `class_name` its script declares, or the script's path when it has no name), what it
|
|
148
|
+
## says about itself when the game has given it a `_to_string`, and an id that is at least
|
|
149
|
+
## different per object. None of it is invented: every field is left out when there is nothing to
|
|
150
|
+
## put in it, because a key holding "" is a worse answer than a key that is not there.
|
|
151
|
+
func _describe_object(value: Object) -> Dictionary:
|
|
152
|
+
var described: Dictionary = {"_type": "Object", "class": value.get_class()}
|
|
153
|
+
# Asked as a Variant and narrowed, because get_script answers one and an object without a
|
|
154
|
+
# script answers null: casting that to Script is the unsafe cast the engine refuses to compile.
|
|
155
|
+
var attached: Variant = value.get_script()
|
|
156
|
+
if attached is Script:
|
|
157
|
+
var script: Script = attached
|
|
158
|
+
var declared: String = String(script.get_global_name())
|
|
159
|
+
if not declared.is_empty():
|
|
160
|
+
described["script_class"] = declared
|
|
161
|
+
elif not script.resource_path.is_empty():
|
|
162
|
+
described["script"] = script.resource_path
|
|
163
|
+
# Only when the game wrote one. The default is "<RefCounted#31098236>", which is the id again
|
|
164
|
+
# in a costume and reads as if the object had been asked and had nothing to say.
|
|
165
|
+
if value.has_method("_to_string"):
|
|
166
|
+
described["says"] = value.to_string()
|
|
167
|
+
described["id"] = value.get_instance_id()
|
|
168
|
+
return described
|
|
140
169
|
|
|
141
170
|
|
|
142
171
|
## Rebuilds a Godot value from the shape serialize gave it.
|
package/build/index.js
CHANGED
|
@@ -28603,7 +28603,7 @@ var TOOL_SPECS = [
|
|
|
28603
28603
|
property: {
|
|
28604
28604
|
type: "string",
|
|
28605
28605
|
ops: ["property", "find"],
|
|
28606
|
-
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through
|
|
28606
|
+
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A negative number counts from the end. A step that is not there is named, and says what was there instead.'
|
|
28607
28607
|
},
|
|
28608
28608
|
depth: { type: "number", ops: ["tree"], description: "tree: levels to descend. Default 3." },
|
|
28609
28609
|
includeProperties: {
|
|
@@ -28677,7 +28677,7 @@ var TOOL_SPECS = [
|
|
|
28677
28677
|
property: {
|
|
28678
28678
|
type: "string",
|
|
28679
28679
|
ops: ["set"],
|
|
28680
|
-
description: 'set: which one to write. Colons write through
|
|
28680
|
+
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end. The answer reads back off the same holder, so a write a typed container refused shows as an unchanged value.'
|
|
28681
28681
|
},
|
|
28682
28682
|
value: {
|
|
28683
28683
|
ops: ["set"],
|
|
@@ -28687,7 +28687,7 @@ var TOOL_SPECS = [
|
|
|
28687
28687
|
method: {
|
|
28688
28688
|
type: "string",
|
|
28689
28689
|
ops: ["call"],
|
|
28690
|
-
description: 'call: which one to call. Colons call through
|
|
28690
|
+
description: 'call: which one to call. Colons call through what a node holds, "_game:run:advance", the same way a property is written through them, and a list or a map is stepped into by index or key, "_game:run:roster:0:retire".'
|
|
28691
28691
|
},
|
|
28692
28692
|
args: {
|
|
28693
28693
|
type: "array",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|