gdharness 0.13.8 → 0.13.9
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/build/cli.js +48 -22
- package/build/index.js +48 -22
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -15740,7 +15740,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15740
15740
|
{
|
|
15741
15741
|
name: "editor_launch",
|
|
15742
15742
|
description: "Opens the Godot editor on a project, in a window on this machine, or restarts the one already connected. An editor goes on serving the addon it read at startup, so restart is what puts a gdharness upgrade into effect; it saves open scenes on the way out and answers with the version that came back. Only an editor with a window can be restarted, because the engine hands back none of the arguments it was started with. editor_status says which editor is connected and whether it is holding an old addon. A restart saves project.godot on the way out, and Godot writes only what differs from its own defaults, so a key named deliberately at its default value is dropped: settingsDropped names any that went, because nothing else will say so until something depends on one.",
|
|
15743
|
-
parameters: { projectPath: PROJECT_PATH },
|
|
15743
|
+
parameters: { projectPath: { ...PROJECT_PATH, ops: ["open"] } },
|
|
15744
15744
|
requires: [],
|
|
15745
15745
|
operations: {
|
|
15746
15746
|
open: { summary: "open the editor on a project", requires: ["projectPath"] },
|
|
@@ -15752,7 +15752,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15752
15752
|
name: "editor_run",
|
|
15753
15753
|
description: "The run: starting the project, stopping it, or booting it once to see whether it comes up clean. start keeps it running and collecting output until it quits or is stopped, windowed where there is a display and headless where there is not, unless headless says otherwise; only runtime_capture needs the window. A run that quits on its own is kept, so a scene that prints an answer and quits is start, then editor_output until running is false. What runs is a scene: a SceneTree script is not an entry point here, so put the script on the root of a scene of its own and name that in scene. args hands the game its own flags, the ones it reads with OS.get_cmdline_user_args(), and a run carrying any is started by this server rather than by the editor. check boots it headless for a few frames, waits for it to quit, and answers with the verdict: whether it came up, and every error and warning it printed on the way. A start waits for the game to become something the runtime_* tools can talk to and says which it is under runtime: listening with the port it took, or why not, so the first call after a start does not have to be made twice. When it is not listening, mayYetAnnounce says whether that is final: false is a runtime that is not coming, true is a game still on its way up, which editor_status will see and runtimeWaitMs waits longer for. A start this server made also answers with transcript, the file both the run's streams are written to, so a watch on it can be armed off the start rather than off a second call.",
|
|
15754
15754
|
parameters: {
|
|
15755
|
-
projectPath: PROJECT_PATH,
|
|
15755
|
+
projectPath: { ...PROJECT_PATH, ops: ["start", "check"] },
|
|
15756
15756
|
scene: {
|
|
15757
15757
|
type: "string",
|
|
15758
15758
|
ops: ["start", "check"],
|
|
@@ -15781,11 +15781,17 @@ var init_tool_definitions = __esm(() => {
|
|
|
15781
15781
|
description: "check: how long to give the boot before it is called hung. Default 60000. wait: how long to wait for the run to end before answering anyway. Default 600000."
|
|
15782
15782
|
}
|
|
15783
15783
|
},
|
|
15784
|
-
requires: [
|
|
15784
|
+
requires: [],
|
|
15785
15785
|
operations: {
|
|
15786
|
-
start: {
|
|
15786
|
+
start: {
|
|
15787
|
+
summary: "run the project until it quits or is stopped",
|
|
15788
|
+
requires: ["projectPath"]
|
|
15789
|
+
},
|
|
15787
15790
|
stop: { summary: "end the run and answer with what it printed last", requires: [] },
|
|
15788
|
-
check: {
|
|
15791
|
+
check: {
|
|
15792
|
+
summary: "boot headless, quit after a few frames, and report the verdict",
|
|
15793
|
+
requires: ["projectPath"]
|
|
15794
|
+
},
|
|
15789
15795
|
wait: { summary: "wait for the run to end, then answer as editor_output does", requires: [] }
|
|
15790
15796
|
},
|
|
15791
15797
|
defaultOperation: "start"
|
|
@@ -31168,6 +31174,7 @@ class GodotDAPClient {
|
|
|
31168
31174
|
pendingRequests;
|
|
31169
31175
|
reader = new FrameReader;
|
|
31170
31176
|
outputBuffer = [];
|
|
31177
|
+
onOutput = null;
|
|
31171
31178
|
maxOutputLines = 1000;
|
|
31172
31179
|
initialized = false;
|
|
31173
31180
|
attached = false;
|
|
@@ -31335,6 +31342,13 @@ class GodotDAPClient {
|
|
|
31335
31342
|
if (outputText.length > 0) {
|
|
31336
31343
|
const lines = outputText.split(/\r?\n/).filter((line) => line.length > 0);
|
|
31337
31344
|
this.outputBuffer.push(...lines);
|
|
31345
|
+
if (this.onOutput !== null) {
|
|
31346
|
+
for (const line of lines) {
|
|
31347
|
+
try {
|
|
31348
|
+
this.onOutput(line);
|
|
31349
|
+
} catch {}
|
|
31350
|
+
}
|
|
31351
|
+
}
|
|
31338
31352
|
}
|
|
31339
31353
|
if (this.outputBuffer.length > this.maxOutputLines) {
|
|
31340
31354
|
this.outputBuffer = this.outputBuffer.slice(this.outputBuffer.length - this.maxOutputLines);
|
|
@@ -31391,6 +31405,9 @@ class GodotDAPClient {
|
|
|
31391
31405
|
await this.sendRequest("configurationDone", {});
|
|
31392
31406
|
this.attached = true;
|
|
31393
31407
|
}
|
|
31408
|
+
setOutputSink(sink) {
|
|
31409
|
+
this.onOutput = sink;
|
|
31410
|
+
}
|
|
31394
31411
|
getOutput(clear = false) {
|
|
31395
31412
|
const lines = [...this.outputBuffer];
|
|
31396
31413
|
if (clear) {
|
|
@@ -36567,8 +36584,7 @@ import {
|
|
|
36567
36584
|
readdirSync as readdirSync7,
|
|
36568
36585
|
readFileSync as readFileSync15,
|
|
36569
36586
|
readSync,
|
|
36570
|
-
rmSync as rmSync9
|
|
36571
|
-
statSync as statSync4
|
|
36587
|
+
rmSync as rmSync9
|
|
36572
36588
|
} from "node:fs";
|
|
36573
36589
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
36574
36590
|
import { basename as basename3, dirname as dirname9, join as join17, normalize as normalize3 } from "node:path";
|
|
@@ -37122,9 +37138,10 @@ class GodotServer {
|
|
|
37122
37138
|
if (op !== null) {
|
|
37123
37139
|
const elsewhere = Object.keys(args).filter((key) => key !== "op" && !opTakes(spec, op, key));
|
|
37124
37140
|
if (elsewhere.length > 0) {
|
|
37141
|
+
const takes = argumentsOf(spec, op);
|
|
37125
37142
|
return {
|
|
37126
37143
|
ok: false,
|
|
37127
|
-
response: this.createErrorResponse(`${spec.name} ${op} does not take ${elsewhere.join(", ")}. ${op} takes: ${
|
|
37144
|
+
response: this.createErrorResponse(`${spec.name} ${op} does not take ${elsewhere.join(", ")}. ${takes.length === 0 ? `${op} takes no arguments.` : `${op} takes: ${takes.join(", ")}.`}`)
|
|
37128
37145
|
};
|
|
37129
37146
|
}
|
|
37130
37147
|
}
|
|
@@ -37874,9 +37891,27 @@ class GodotServer {
|
|
|
37874
37891
|
if (this.dapClient !== null && this.dapClient.port !== port) {
|
|
37875
37892
|
this.dapClient = null;
|
|
37876
37893
|
}
|
|
37877
|
-
this.dapClient
|
|
37894
|
+
if (this.dapClient === null) {
|
|
37895
|
+
this.dapClient = new GodotDAPClient(port);
|
|
37896
|
+
this.dapClient.setOutputSink((line) => {
|
|
37897
|
+
this.writeToTranscript(line);
|
|
37898
|
+
});
|
|
37899
|
+
}
|
|
37878
37900
|
return this.dapClient;
|
|
37879
37901
|
}
|
|
37902
|
+
writeToTranscript(line) {
|
|
37903
|
+
const run = this.activeProcess;
|
|
37904
|
+
if (run?.throughEditor !== true || run.transcript === null) {
|
|
37905
|
+
return;
|
|
37906
|
+
}
|
|
37907
|
+
try {
|
|
37908
|
+
appendFileSync(run.transcript, line.endsWith(`
|
|
37909
|
+
`) ? line : `${line}
|
|
37910
|
+
`);
|
|
37911
|
+
} catch (error) {
|
|
37912
|
+
this.logDebug(`Could not write an editor-played run's transcript: ${errorMessage(error)}`);
|
|
37913
|
+
}
|
|
37914
|
+
}
|
|
37880
37915
|
editorServes(field, variable, fallback) {
|
|
37881
37916
|
const named = portFromEnvOrNull(variable);
|
|
37882
37917
|
if (named !== null) {
|
|
@@ -38235,20 +38270,11 @@ class GodotServer {
|
|
|
38235
38270
|
if (!game.throughEditor || !this.dapClient) {
|
|
38236
38271
|
return;
|
|
38237
38272
|
}
|
|
38238
|
-
|
|
38239
|
-
|
|
38240
|
-
|
|
38273
|
+
if (game.transcript === null) {
|
|
38274
|
+
for (const line of this.dapClient.getOutput(true)) {
|
|
38275
|
+
game.log.append("stdout", line.endsWith(`
|
|
38241
38276
|
`) ? line : `${line}
|
|
38242
|
-
|
|
38243
|
-
game.log.append("stdout", ended);
|
|
38244
|
-
heard.push(ended);
|
|
38245
|
-
}
|
|
38246
|
-
if (heard.length > 0 && game.transcript !== null) {
|
|
38247
|
-
try {
|
|
38248
|
-
appendFileSync(game.transcript, heard.join(""));
|
|
38249
|
-
game.readOffset = statSync4(game.transcript).size;
|
|
38250
|
-
} catch (error) {
|
|
38251
|
-
this.logDebug(`Could not write an editor-played run's transcript: ${errorMessage(error)}`);
|
|
38277
|
+
`);
|
|
38252
38278
|
}
|
|
38253
38279
|
}
|
|
38254
38280
|
if (!this.dapClient.isConnected()) {
|
package/build/index.js
CHANGED
|
@@ -10152,8 +10152,7 @@ import {
|
|
|
10152
10152
|
readdirSync as readdirSync6,
|
|
10153
10153
|
readFileSync as readFileSync11,
|
|
10154
10154
|
readSync,
|
|
10155
|
-
rmSync as rmSync5
|
|
10156
|
-
statSync as statSync4
|
|
10155
|
+
rmSync as rmSync5
|
|
10157
10156
|
} from "node:fs";
|
|
10158
10157
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
10159
10158
|
import { basename as basename2, dirname as dirname4, join as join11, normalize as normalize2 } from "node:path";
|
|
@@ -25011,6 +25010,7 @@ class GodotDAPClient {
|
|
|
25011
25010
|
pendingRequests;
|
|
25012
25011
|
reader = new FrameReader;
|
|
25013
25012
|
outputBuffer = [];
|
|
25013
|
+
onOutput = null;
|
|
25014
25014
|
maxOutputLines = 1000;
|
|
25015
25015
|
initialized = false;
|
|
25016
25016
|
attached = false;
|
|
@@ -25178,6 +25178,13 @@ class GodotDAPClient {
|
|
|
25178
25178
|
if (outputText.length > 0) {
|
|
25179
25179
|
const lines = outputText.split(/\r?\n/).filter((line) => line.length > 0);
|
|
25180
25180
|
this.outputBuffer.push(...lines);
|
|
25181
|
+
if (this.onOutput !== null) {
|
|
25182
|
+
for (const line of lines) {
|
|
25183
|
+
try {
|
|
25184
|
+
this.onOutput(line);
|
|
25185
|
+
} catch {}
|
|
25186
|
+
}
|
|
25187
|
+
}
|
|
25181
25188
|
}
|
|
25182
25189
|
if (this.outputBuffer.length > this.maxOutputLines) {
|
|
25183
25190
|
this.outputBuffer = this.outputBuffer.slice(this.outputBuffer.length - this.maxOutputLines);
|
|
@@ -25234,6 +25241,9 @@ class GodotDAPClient {
|
|
|
25234
25241
|
await this.sendRequest("configurationDone", {});
|
|
25235
25242
|
this.attached = true;
|
|
25236
25243
|
}
|
|
25244
|
+
setOutputSink(sink) {
|
|
25245
|
+
this.onOutput = sink;
|
|
25246
|
+
}
|
|
25237
25247
|
getOutput(clear = false) {
|
|
25238
25248
|
const lines = [...this.outputBuffer];
|
|
25239
25249
|
if (clear) {
|
|
@@ -28792,7 +28802,7 @@ var TOOL_SPECS = [
|
|
|
28792
28802
|
{
|
|
28793
28803
|
name: "editor_launch",
|
|
28794
28804
|
description: "Opens the Godot editor on a project, in a window on this machine, or restarts the one already connected. An editor goes on serving the addon it read at startup, so restart is what puts a gdharness upgrade into effect; it saves open scenes on the way out and answers with the version that came back. Only an editor with a window can be restarted, because the engine hands back none of the arguments it was started with. editor_status says which editor is connected and whether it is holding an old addon. A restart saves project.godot on the way out, and Godot writes only what differs from its own defaults, so a key named deliberately at its default value is dropped: settingsDropped names any that went, because nothing else will say so until something depends on one.",
|
|
28795
|
-
parameters: { projectPath: PROJECT_PATH },
|
|
28805
|
+
parameters: { projectPath: { ...PROJECT_PATH, ops: ["open"] } },
|
|
28796
28806
|
requires: [],
|
|
28797
28807
|
operations: {
|
|
28798
28808
|
open: { summary: "open the editor on a project", requires: ["projectPath"] },
|
|
@@ -28804,7 +28814,7 @@ var TOOL_SPECS = [
|
|
|
28804
28814
|
name: "editor_run",
|
|
28805
28815
|
description: "The run: starting the project, stopping it, or booting it once to see whether it comes up clean. start keeps it running and collecting output until it quits or is stopped, windowed where there is a display and headless where there is not, unless headless says otherwise; only runtime_capture needs the window. A run that quits on its own is kept, so a scene that prints an answer and quits is start, then editor_output until running is false. What runs is a scene: a SceneTree script is not an entry point here, so put the script on the root of a scene of its own and name that in scene. args hands the game its own flags, the ones it reads with OS.get_cmdline_user_args(), and a run carrying any is started by this server rather than by the editor. check boots it headless for a few frames, waits for it to quit, and answers with the verdict: whether it came up, and every error and warning it printed on the way. A start waits for the game to become something the runtime_* tools can talk to and says which it is under runtime: listening with the port it took, or why not, so the first call after a start does not have to be made twice. When it is not listening, mayYetAnnounce says whether that is final: false is a runtime that is not coming, true is a game still on its way up, which editor_status will see and runtimeWaitMs waits longer for. A start this server made also answers with transcript, the file both the run's streams are written to, so a watch on it can be armed off the start rather than off a second call.",
|
|
28806
28816
|
parameters: {
|
|
28807
|
-
projectPath: PROJECT_PATH,
|
|
28817
|
+
projectPath: { ...PROJECT_PATH, ops: ["start", "check"] },
|
|
28808
28818
|
scene: {
|
|
28809
28819
|
type: "string",
|
|
28810
28820
|
ops: ["start", "check"],
|
|
@@ -28833,11 +28843,17 @@ var TOOL_SPECS = [
|
|
|
28833
28843
|
description: "check: how long to give the boot before it is called hung. Default 60000. wait: how long to wait for the run to end before answering anyway. Default 600000."
|
|
28834
28844
|
}
|
|
28835
28845
|
},
|
|
28836
|
-
requires: [
|
|
28846
|
+
requires: [],
|
|
28837
28847
|
operations: {
|
|
28838
|
-
start: {
|
|
28848
|
+
start: {
|
|
28849
|
+
summary: "run the project until it quits or is stopped",
|
|
28850
|
+
requires: ["projectPath"]
|
|
28851
|
+
},
|
|
28839
28852
|
stop: { summary: "end the run and answer with what it printed last", requires: [] },
|
|
28840
|
-
check: {
|
|
28853
|
+
check: {
|
|
28854
|
+
summary: "boot headless, quit after a few frames, and report the verdict",
|
|
28855
|
+
requires: ["projectPath"]
|
|
28856
|
+
},
|
|
28841
28857
|
wait: { summary: "wait for the run to end, then answer as editor_output does", requires: [] }
|
|
28842
28858
|
},
|
|
28843
28859
|
defaultOperation: "start"
|
|
@@ -29876,9 +29892,10 @@ class GodotServer {
|
|
|
29876
29892
|
if (op !== null) {
|
|
29877
29893
|
const elsewhere = Object.keys(args).filter((key) => key !== "op" && !opTakes(spec, op, key));
|
|
29878
29894
|
if (elsewhere.length > 0) {
|
|
29895
|
+
const takes = argumentsOf(spec, op);
|
|
29879
29896
|
return {
|
|
29880
29897
|
ok: false,
|
|
29881
|
-
response: this.createErrorResponse(`${spec.name} ${op} does not take ${elsewhere.join(", ")}. ${op} takes: ${
|
|
29898
|
+
response: this.createErrorResponse(`${spec.name} ${op} does not take ${elsewhere.join(", ")}. ${takes.length === 0 ? `${op} takes no arguments.` : `${op} takes: ${takes.join(", ")}.`}`)
|
|
29882
29899
|
};
|
|
29883
29900
|
}
|
|
29884
29901
|
}
|
|
@@ -30628,9 +30645,27 @@ class GodotServer {
|
|
|
30628
30645
|
if (this.dapClient !== null && this.dapClient.port !== port) {
|
|
30629
30646
|
this.dapClient = null;
|
|
30630
30647
|
}
|
|
30631
|
-
this.dapClient
|
|
30648
|
+
if (this.dapClient === null) {
|
|
30649
|
+
this.dapClient = new GodotDAPClient(port);
|
|
30650
|
+
this.dapClient.setOutputSink((line) => {
|
|
30651
|
+
this.writeToTranscript(line);
|
|
30652
|
+
});
|
|
30653
|
+
}
|
|
30632
30654
|
return this.dapClient;
|
|
30633
30655
|
}
|
|
30656
|
+
writeToTranscript(line) {
|
|
30657
|
+
const run = this.activeProcess;
|
|
30658
|
+
if (run?.throughEditor !== true || run.transcript === null) {
|
|
30659
|
+
return;
|
|
30660
|
+
}
|
|
30661
|
+
try {
|
|
30662
|
+
appendFileSync(run.transcript, line.endsWith(`
|
|
30663
|
+
`) ? line : `${line}
|
|
30664
|
+
`);
|
|
30665
|
+
} catch (error) {
|
|
30666
|
+
this.logDebug(`Could not write an editor-played run's transcript: ${errorMessage(error)}`);
|
|
30667
|
+
}
|
|
30668
|
+
}
|
|
30634
30669
|
editorServes(field, variable, fallback) {
|
|
30635
30670
|
const named = portFromEnvOrNull(variable);
|
|
30636
30671
|
if (named !== null) {
|
|
@@ -30989,20 +31024,11 @@ class GodotServer {
|
|
|
30989
31024
|
if (!game.throughEditor || !this.dapClient) {
|
|
30990
31025
|
return;
|
|
30991
31026
|
}
|
|
30992
|
-
|
|
30993
|
-
|
|
30994
|
-
|
|
31027
|
+
if (game.transcript === null) {
|
|
31028
|
+
for (const line of this.dapClient.getOutput(true)) {
|
|
31029
|
+
game.log.append("stdout", line.endsWith(`
|
|
30995
31030
|
`) ? line : `${line}
|
|
30996
|
-
|
|
30997
|
-
game.log.append("stdout", ended);
|
|
30998
|
-
heard.push(ended);
|
|
30999
|
-
}
|
|
31000
|
-
if (heard.length > 0 && game.transcript !== null) {
|
|
31001
|
-
try {
|
|
31002
|
-
appendFileSync(game.transcript, heard.join(""));
|
|
31003
|
-
game.readOffset = statSync4(game.transcript).size;
|
|
31004
|
-
} catch (error) {
|
|
31005
|
-
this.logDebug(`Could not write an editor-played run's transcript: ${errorMessage(error)}`);
|
|
31031
|
+
`);
|
|
31006
31032
|
}
|
|
31007
31033
|
}
|
|
31008
31034
|
if (!this.dapClient.isConnected()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.9",
|
|
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",
|