gdharness 0.10.1 → 0.10.2
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 +37 -7
- package/build/index.js +37 -7
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -155,6 +155,9 @@ function runArguments(options) {
|
|
|
155
155
|
if (options.scene !== null) {
|
|
156
156
|
args.push(`res://${options.scene}`);
|
|
157
157
|
}
|
|
158
|
+
if (options.userArgs !== undefined && options.userArgs.length > 0) {
|
|
159
|
+
args.push("--", ...options.userArgs);
|
|
160
|
+
}
|
|
158
161
|
return args;
|
|
159
162
|
}
|
|
160
163
|
function editorArguments(projectPath, ports) {
|
|
@@ -15680,10 +15683,15 @@ var init_tool_definitions = __esm(() => {
|
|
|
15680
15683
|
},
|
|
15681
15684
|
{
|
|
15682
15685
|
name: "editor_run",
|
|
15683
|
-
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. 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.",
|
|
15686
|
+
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.",
|
|
15684
15687
|
parameters: {
|
|
15685
15688
|
projectPath: PROJECT_PATH,
|
|
15686
15689
|
scene: { type: "string", description: "A scene to run instead of the main scene." },
|
|
15690
|
+
args: {
|
|
15691
|
+
type: "array",
|
|
15692
|
+
items: { type: "string" },
|
|
15693
|
+
description: `The game's own arguments, what OS.get_cmdline_user_args() answers, such as ["--level=2"]. The separator is added here. A run with any is started by this server rather than by the editor, which fixes the game's command line when it opens the project, so the debug_* tools do not answer for it.`
|
|
15694
|
+
},
|
|
15687
15695
|
headless: { type: "boolean", description: "start: force a window or no window." },
|
|
15688
15696
|
frames: { type: "number", description: "check: frames to run before quitting. Default 3." },
|
|
15689
15697
|
timeoutMs: {
|
|
@@ -36776,6 +36784,21 @@ class GodotServer {
|
|
|
36776
36784
|
}
|
|
36777
36785
|
return { ok: true, value: { path, file } };
|
|
36778
36786
|
}
|
|
36787
|
+
gameArguments(args) {
|
|
36788
|
+
if (args["args"] === undefined) {
|
|
36789
|
+
return { ok: true, value: [] };
|
|
36790
|
+
}
|
|
36791
|
+
const given = readStringArray(args, "args");
|
|
36792
|
+
if (given === undefined) {
|
|
36793
|
+
return {
|
|
36794
|
+
ok: false,
|
|
36795
|
+
response: this.createErrorResponse("args must be a list of strings.", [
|
|
36796
|
+
`Pass the game's own flags, such as ["--level=2"]`
|
|
36797
|
+
])
|
|
36798
|
+
};
|
|
36799
|
+
}
|
|
36800
|
+
return { ok: true, value: given };
|
|
36801
|
+
}
|
|
36779
36802
|
async debuggedGame() {
|
|
36780
36803
|
const game = this.activeProcess;
|
|
36781
36804
|
if (game !== null && game.exitCode !== null) {
|
|
@@ -37436,6 +37459,10 @@ class GodotServer {
|
|
|
37436
37459
|
if (sceneToRun && !sceneToRun.ok) {
|
|
37437
37460
|
return this.createErrorResponse(sceneToRun.reason, PATH_SOLUTIONS);
|
|
37438
37461
|
}
|
|
37462
|
+
const given = this.gameArguments(args);
|
|
37463
|
+
if (!given.ok) {
|
|
37464
|
+
return given.response;
|
|
37465
|
+
}
|
|
37439
37466
|
if (!sceneToRun && !hasMainScene(project.value.file)) {
|
|
37440
37467
|
return this.createErrorResponse("The project sets no main scene, so there is nothing to run.", [
|
|
37441
37468
|
"Pass scene to run one scene",
|
|
@@ -37452,7 +37479,7 @@ class GodotServer {
|
|
|
37452
37479
|
}
|
|
37453
37480
|
const sceneArgument = sceneToRun?.ok ? sceneToRun.relativePath : null;
|
|
37454
37481
|
if (op === "check") {
|
|
37455
|
-
return await this.checkBoot(engine.value, project.value.path, sceneArgument, args);
|
|
37482
|
+
return await this.checkBoot(engine.value, project.value.path, sceneArgument, args, given.value);
|
|
37456
37483
|
}
|
|
37457
37484
|
if (this.activeProcess?.exitCode === null) {
|
|
37458
37485
|
this.logDebug("Ending the running game before starting another");
|
|
@@ -37463,17 +37490,20 @@ class GodotServer {
|
|
|
37463
37490
|
platform: process.platform,
|
|
37464
37491
|
variables: process.env
|
|
37465
37492
|
});
|
|
37466
|
-
|
|
37493
|
+
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
37494
|
+
if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0) {
|
|
37467
37495
|
return await this.playThroughEditor(sceneArgument, refreshed.value, project.value.path, alreadyPlaying);
|
|
37468
37496
|
}
|
|
37469
37497
|
const cmdArgs = runArguments({
|
|
37470
37498
|
projectPath: project.value.path,
|
|
37471
37499
|
headless,
|
|
37472
|
-
scene: sceneArgument
|
|
37500
|
+
scene: sceneArgument,
|
|
37501
|
+
userArgs: given.value
|
|
37473
37502
|
});
|
|
37474
37503
|
this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
37475
37504
|
const started = this.spawnGame(engine.value, cmdArgs);
|
|
37476
37505
|
this.activeProcess = started;
|
|
37506
|
+
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
|
|
37477
37507
|
return this.jsonTextResponse({
|
|
37478
37508
|
started: true,
|
|
37479
37509
|
through: "gdharness",
|
|
@@ -37481,7 +37511,7 @@ class GodotServer {
|
|
|
37481
37511
|
arguments: cmdArgs,
|
|
37482
37512
|
refreshedClasses: refreshed.value,
|
|
37483
37513
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying),
|
|
37484
|
-
message:
|
|
37514
|
+
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}`
|
|
37485
37515
|
});
|
|
37486
37516
|
}
|
|
37487
37517
|
async runtimeUp(projectPath, before) {
|
|
@@ -37598,10 +37628,10 @@ class GodotServer {
|
|
|
37598
37628
|
});
|
|
37599
37629
|
return started;
|
|
37600
37630
|
}
|
|
37601
|
-
async checkBoot(godotPath, projectPath, scene, args) {
|
|
37631
|
+
async checkBoot(godotPath, projectPath, scene, args, userArgs) {
|
|
37602
37632
|
const frames = readPositiveNumber(args, "frames") ?? 3;
|
|
37603
37633
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 60000;
|
|
37604
|
-
const cmdArgs = runArguments({ projectPath, headless: true, scene, quitAfter: frames });
|
|
37634
|
+
const cmdArgs = runArguments({ projectPath, headless: true, scene, quitAfter: frames, userArgs });
|
|
37605
37635
|
const boot = this.spawnGame(godotPath, cmdArgs);
|
|
37606
37636
|
const hung = await new Promise((resolve) => {
|
|
37607
37637
|
const timer = setTimeout(() => {
|
package/build/index.js
CHANGED
|
@@ -26154,6 +26154,9 @@ function runArguments(options) {
|
|
|
26154
26154
|
if (options.scene !== null) {
|
|
26155
26155
|
args.push(`res://${options.scene}`);
|
|
26156
26156
|
}
|
|
26157
|
+
if (options.userArgs !== undefined && options.userArgs.length > 0) {
|
|
26158
|
+
args.push("--", ...options.userArgs);
|
|
26159
|
+
}
|
|
26157
26160
|
return args;
|
|
26158
26161
|
}
|
|
26159
26162
|
function editorArguments(projectPath, ports) {
|
|
@@ -28313,10 +28316,15 @@ var TOOL_SPECS = [
|
|
|
28313
28316
|
},
|
|
28314
28317
|
{
|
|
28315
28318
|
name: "editor_run",
|
|
28316
|
-
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. 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.",
|
|
28319
|
+
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.",
|
|
28317
28320
|
parameters: {
|
|
28318
28321
|
projectPath: PROJECT_PATH,
|
|
28319
28322
|
scene: { type: "string", description: "A scene to run instead of the main scene." },
|
|
28323
|
+
args: {
|
|
28324
|
+
type: "array",
|
|
28325
|
+
items: { type: "string" },
|
|
28326
|
+
description: `The game's own arguments, what OS.get_cmdline_user_args() answers, such as ["--level=2"]. The separator is added here. A run with any is started by this server rather than by the editor, which fixes the game's command line when it opens the project, so the debug_* tools do not answer for it.`
|
|
28327
|
+
},
|
|
28320
28328
|
headless: { type: "boolean", description: "start: force a window or no window." },
|
|
28321
28329
|
frames: { type: "number", description: "check: frames to run before quitting. Default 3." },
|
|
28322
28330
|
timeoutMs: {
|
|
@@ -29558,6 +29566,21 @@ class GodotServer {
|
|
|
29558
29566
|
}
|
|
29559
29567
|
return { ok: true, value: { path, file } };
|
|
29560
29568
|
}
|
|
29569
|
+
gameArguments(args) {
|
|
29570
|
+
if (args["args"] === undefined) {
|
|
29571
|
+
return { ok: true, value: [] };
|
|
29572
|
+
}
|
|
29573
|
+
const given = readStringArray(args, "args");
|
|
29574
|
+
if (given === undefined) {
|
|
29575
|
+
return {
|
|
29576
|
+
ok: false,
|
|
29577
|
+
response: this.createErrorResponse("args must be a list of strings.", [
|
|
29578
|
+
`Pass the game's own flags, such as ["--level=2"]`
|
|
29579
|
+
])
|
|
29580
|
+
};
|
|
29581
|
+
}
|
|
29582
|
+
return { ok: true, value: given };
|
|
29583
|
+
}
|
|
29561
29584
|
async debuggedGame() {
|
|
29562
29585
|
const game = this.activeProcess;
|
|
29563
29586
|
if (game !== null && game.exitCode !== null) {
|
|
@@ -30218,6 +30241,10 @@ class GodotServer {
|
|
|
30218
30241
|
if (sceneToRun && !sceneToRun.ok) {
|
|
30219
30242
|
return this.createErrorResponse(sceneToRun.reason, PATH_SOLUTIONS);
|
|
30220
30243
|
}
|
|
30244
|
+
const given = this.gameArguments(args);
|
|
30245
|
+
if (!given.ok) {
|
|
30246
|
+
return given.response;
|
|
30247
|
+
}
|
|
30221
30248
|
if (!sceneToRun && !hasMainScene(project.value.file)) {
|
|
30222
30249
|
return this.createErrorResponse("The project sets no main scene, so there is nothing to run.", [
|
|
30223
30250
|
"Pass scene to run one scene",
|
|
@@ -30234,7 +30261,7 @@ class GodotServer {
|
|
|
30234
30261
|
}
|
|
30235
30262
|
const sceneArgument = sceneToRun?.ok ? sceneToRun.relativePath : null;
|
|
30236
30263
|
if (op === "check") {
|
|
30237
|
-
return await this.checkBoot(engine.value, project.value.path, sceneArgument, args);
|
|
30264
|
+
return await this.checkBoot(engine.value, project.value.path, sceneArgument, args, given.value);
|
|
30238
30265
|
}
|
|
30239
30266
|
if (this.activeProcess?.exitCode === null) {
|
|
30240
30267
|
this.logDebug("Ending the running game before starting another");
|
|
@@ -30245,17 +30272,20 @@ class GodotServer {
|
|
|
30245
30272
|
platform: process.platform,
|
|
30246
30273
|
variables: process.env
|
|
30247
30274
|
});
|
|
30248
|
-
|
|
30275
|
+
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
30276
|
+
if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0) {
|
|
30249
30277
|
return await this.playThroughEditor(sceneArgument, refreshed.value, project.value.path, alreadyPlaying);
|
|
30250
30278
|
}
|
|
30251
30279
|
const cmdArgs = runArguments({
|
|
30252
30280
|
projectPath: project.value.path,
|
|
30253
30281
|
headless,
|
|
30254
|
-
scene: sceneArgument
|
|
30282
|
+
scene: sceneArgument,
|
|
30283
|
+
userArgs: given.value
|
|
30255
30284
|
});
|
|
30256
30285
|
this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
30257
30286
|
const started = this.spawnGame(engine.value, cmdArgs);
|
|
30258
30287
|
this.activeProcess = started;
|
|
30288
|
+
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
|
|
30259
30289
|
return this.jsonTextResponse({
|
|
30260
30290
|
started: true,
|
|
30261
30291
|
through: "gdharness",
|
|
@@ -30263,7 +30293,7 @@ class GodotServer {
|
|
|
30263
30293
|
arguments: cmdArgs,
|
|
30264
30294
|
refreshedClasses: refreshed.value,
|
|
30265
30295
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying),
|
|
30266
|
-
message:
|
|
30296
|
+
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}`
|
|
30267
30297
|
});
|
|
30268
30298
|
}
|
|
30269
30299
|
async runtimeUp(projectPath, before) {
|
|
@@ -30380,10 +30410,10 @@ class GodotServer {
|
|
|
30380
30410
|
});
|
|
30381
30411
|
return started;
|
|
30382
30412
|
}
|
|
30383
|
-
async checkBoot(godotPath, projectPath, scene, args) {
|
|
30413
|
+
async checkBoot(godotPath, projectPath, scene, args, userArgs) {
|
|
30384
30414
|
const frames = readPositiveNumber(args, "frames") ?? 3;
|
|
30385
30415
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 60000;
|
|
30386
|
-
const cmdArgs = runArguments({ projectPath, headless: true, scene, quitAfter: frames });
|
|
30416
|
+
const cmdArgs = runArguments({ projectPath, headless: true, scene, quitAfter: frames, userArgs });
|
|
30387
30417
|
const boot = this.spawnGame(godotPath, cmdArgs);
|
|
30388
30418
|
const hung = await new Promise((resolve) => {
|
|
30389
30419
|
const timer = setTimeout(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
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",
|