gdharness 0.9.2 → 0.9.3

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 CHANGED
@@ -173,6 +173,7 @@ function userDataIn(home, variables = process.env) {
173
173
  }
174
174
  return carried;
175
175
  }
176
+ var OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER";
176
177
 
177
178
  // node_modules/yaml/dist/nodes/identity.js
178
179
  var require_identity = __commonJS(function(exports) {
@@ -34499,6 +34500,7 @@ var init_godot_bridge = __esm(() => {
34499
34500
  lspPort: this.connectionInfo?.lspPort,
34500
34501
  dapPort: this.connectionInfo?.dapPort,
34501
34502
  debugPort: this.connectionInfo?.debugPort,
34503
+ openedByAServer: this.connectionInfo?.openedByAServer,
34502
34504
  pendingRequests: this.pendingRequests.size,
34503
34505
  queuedResources: this.resourceQueues.size
34504
34506
  };
@@ -34639,6 +34641,7 @@ var init_godot_bridge = __esm(() => {
34639
34641
  this.connectionInfo.lspPort = servedPort(message.lsp_port);
34640
34642
  this.connectionInfo.dapPort = servedPort(message.dap_port);
34641
34643
  this.connectionInfo.debugPort = servedPort(message.debug_port);
34644
+ this.connectionInfo.openedByAServer = message.opened_by_a_server === true;
34642
34645
  this.log("info", `Godot ready: ${message.project_path}`);
34643
34646
  this.emitBridgeEvent("godot_connected", { projectPath: message.project_path });
34644
34647
  }
@@ -35999,6 +36002,17 @@ import { promisify as promisify5 } from "node:util";
35999
36002
  function patienceForFrames(frames, atLeast) {
36000
36003
  return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
36001
36004
  }
36005
+ function alive(pid) {
36006
+ if (pid === undefined) {
36007
+ return false;
36008
+ }
36009
+ try {
36010
+ process.kill(pid, 0);
36011
+ return true;
36012
+ } catch {
36013
+ return false;
36014
+ }
36015
+ }
36002
36016
  function isOfType(value, kind) {
36003
36017
  switch (kind) {
36004
36018
  case "string":
@@ -36234,12 +36248,7 @@ class GodotServer {
36234
36248
  if (announced === null || announced.pid === process.pid) {
36235
36249
  return null;
36236
36250
  }
36237
- try {
36238
- process.kill(announced.pid, 0);
36239
- return announced.pid;
36240
- } catch {
36241
- return null;
36242
- }
36251
+ return alive(announced.pid) ? announced.pid : null;
36243
36252
  }
36244
36253
  stopWatchingForASuccessor() {
36245
36254
  if (this.successorWatch !== null) {
@@ -37206,7 +37215,8 @@ class GodotServer {
37206
37215
  "editor_status says whether the bridge is up and what has reached it"
37207
37216
  ]);
37208
37217
  }
37209
- const asked = await this.handleViaBridge("restart_editor", {});
37218
+ const mine = before.openedByAServer === true && before.projectPath !== undefined;
37219
+ const asked = mine ? await this.startItAgain(before.projectPath ?? "", before.editorPid) : await this.handleViaBridge("restart_editor", {});
37210
37220
  if (asked.isError === true) {
37211
37221
  return asked;
37212
37222
  }
@@ -37234,6 +37244,26 @@ class GodotServer {
37234
37244
  tookMs: Date.now() - began
37235
37245
  });
37236
37246
  }
37247
+ async startItAgain(projectPath, editorPid) {
37248
+ const engine = await this.engine();
37249
+ if (!engine.ok) {
37250
+ return engine.response;
37251
+ }
37252
+ const ports = {
37253
+ lsp: this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT),
37254
+ dap: this.editorServes("dapPort", "GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT)
37255
+ };
37256
+ const asked = await this.handleViaBridge("quit_editor", {});
37257
+ if (asked.isError === true) {
37258
+ return asked;
37259
+ }
37260
+ await this.waitForBridge(() => !alive(editorPid), Date.now() + EDITOR_RESTART_TIMEOUT_MS);
37261
+ const opened = await this.openAnEditor(engine.value, projectPath, ports);
37262
+ if (opened.error !== null) {
37263
+ return this.createErrorResponse(`The editor was asked to go and could not be started again: ${opened.error}`, [`editor_launch opens one on ${projectPath}`]);
37264
+ }
37265
+ return this.jsonTextResponse({ restarting: true, pid: opened.pid });
37266
+ }
37237
37267
  async waitForBridge(reached, deadline) {
37238
37268
  for (;; ) {
37239
37269
  if (reached()) {
@@ -37263,14 +37293,28 @@ class GodotServer {
37263
37293
  }
37264
37294
  this.logDebug(`Launching Godot editor for project: ${project.value.path}`);
37265
37295
  const ports = await this.portsForAnEditor();
37266
- const editor = spawn(engine.value, editorArguments(project.value.path, ports), {
37296
+ const opened = await this.openAnEditor(engine.value, project.value.path, ports);
37297
+ if (opened.error !== null) {
37298
+ return this.createErrorResponse(`Could not start the editor: ${opened.error}`);
37299
+ }
37300
+ return this.jsonTextResponse({
37301
+ launched: true,
37302
+ pid: opened.pid,
37303
+ projectPath: project.value.path,
37304
+ lspPort: ports.lsp,
37305
+ dapPort: ports.dap
37306
+ });
37307
+ }
37308
+ async openAnEditor(engine, projectPath, ports) {
37309
+ const editor = spawn(engine, editorArguments(projectPath, ports), {
37267
37310
  stdio: "ignore",
37268
37311
  detached: true,
37269
37312
  env: {
37270
37313
  ...process.env,
37271
37314
  GDHARNESS_RUNTIME_DIR: runtimeDirectory(),
37272
37315
  GDHARNESS_LSP_PORT: String(ports.lsp),
37273
- GDHARNESS_DAP_PORT: String(ports.dap)
37316
+ GDHARNESS_DAP_PORT: String(ports.dap),
37317
+ [OPENED_BY_A_SERVER]: "1"
37274
37318
  }
37275
37319
  });
37276
37320
  const started = await new Promise((resolve) => {
@@ -37282,16 +37326,10 @@ class GodotServer {
37282
37326
  });
37283
37327
  });
37284
37328
  if (started !== null) {
37285
- return this.createErrorResponse(`Could not start the editor: ${started}`);
37329
+ return { pid: null, error: started };
37286
37330
  }
37287
37331
  editor.unref();
37288
- return this.jsonTextResponse({
37289
- launched: true,
37290
- pid: editor.pid ?? null,
37291
- projectPath: project.value.path,
37292
- lspPort: ports.lsp,
37293
- dapPort: ports.dap
37294
- });
37332
+ return { pid: editor.pid ?? null, error: null };
37295
37333
  }
37296
37334
  async portsForAnEditor() {
37297
37335
  const lsp = portFromEnvOrNull("GDHARNESS_LSP_PORT") ?? await freePort(DEFAULT_LSP_PORT);
@@ -38412,27 +38450,42 @@ function displayPath(harness, platform = process3.platform) {
38412
38450
  }
38413
38451
  return `${harness.scope === "home" ? "~/" : ""}${file.replaceAll("\\", "/")}`;
38414
38452
  }
38415
- function entryFor(shape, launch) {
38453
+ var OURS = ["GODOT_PATH", "GDHARNESS_PROJECT"];
38454
+ function environmentFor(launch, held) {
38455
+ const kept = {};
38456
+ if (typeof held === "object" && held !== null && !Array.isArray(held)) {
38457
+ for (const [name, value] of Object.entries(held)) {
38458
+ if (typeof value === "string" && !OURS.includes(name)) {
38459
+ kept[name] = value;
38460
+ }
38461
+ }
38462
+ }
38463
+ return { ...kept, ...launch.env };
38464
+ }
38465
+ function entryFor(shape, launch, held) {
38466
+ const before = typeof held === "object" && held !== null && !Array.isArray(held) ? { ...held } : {};
38416
38467
  if (shape === "opencode") {
38417
38468
  return {
38469
+ ...before,
38418
38470
  type: "local",
38419
38471
  command: [launch.command, ...launch.args],
38420
- environment: { ...launch.env },
38472
+ environment: environmentFor(launch, before["environment"]),
38421
38473
  enabled: true
38422
38474
  };
38423
38475
  }
38424
38476
  return {
38477
+ ...before,
38425
38478
  ...shape === "typed" ? { type: "stdio" } : {},
38426
38479
  command: launch.command,
38427
38480
  args: [...launch.args],
38428
- env: { ...launch.env }
38481
+ env: environmentFor(launch, before["env"])
38429
38482
  };
38430
38483
  }
38431
38484
  function merged(existing, harness, launch) {
38432
38485
  const root = typeof existing === "object" && existing !== null && !Array.isArray(existing) ? { ...existing } : {};
38433
38486
  const held = root[harness.container];
38434
38487
  const servers = typeof held === "object" && held !== null && !Array.isArray(held) ? { ...held } : {};
38435
- servers[SERVER_KEY] = entryFor(harness.shape, launch);
38488
+ servers[SERVER_KEY] = entryFor(harness.shape, launch, servers[SERVER_KEY]);
38436
38489
  root[harness.container] = servers;
38437
38490
  return root;
38438
38491
  }
@@ -23,8 +23,9 @@ const MAX_RECONNECT_DELAY: float = 30.0
23
23
  const ELSEWHERE_CLOSE_CODE: int = 4001
24
24
 
25
25
  ## Where Godot keeps the three ports an editor serves. None of them is per editor: the settings
26
- ## file is one for every editor on the machine, so two open at once want the same three and the
27
- ## second one binds nothing.
26
+ ## file is one for every editor of this engine version on the machine, so two open at once want the
27
+ ## same three and the second one binds nothing. Read here and never written, for the reason
28
+ ## [method _serves] gives.
28
29
  const LSP_SETTING: String = "network/language_server/remote_port"
29
30
  const DAP_SETTING: String = "network/debug_adapter/remote_port"
30
31
  const DEBUGGER_SETTING: String = "network/debug/remote_port"
@@ -32,6 +33,9 @@ const DEBUGGER_SETTING: String = "network/debug/remote_port"
32
33
  ## the command line. Kept in step with `editorArguments` in src/launch.ts.
33
34
  const LSP_ASKED: String = "GDHARNESS_LSP_PORT"
34
35
  const DAP_ASKED: String = "GDHARNESS_DAP_PORT"
36
+ ## What a server that opened this editor says about itself, so that being opened by one is stated
37
+ ## rather than guessed at from the ports. Kept in step with `OPENED_BY_A_SERVER` in src/launch.ts.
38
+ const OPENED_BY_A_SERVER: String = "GDHARNESS_OPENED_BY_A_SERVER"
35
39
 
36
40
  ## How long one attempt is given before the address is called a bad one.
37
41
  ##
@@ -81,7 +85,6 @@ var _connecting_for: float = 0.0
81
85
  func _ready() -> void:
82
86
  _project_path = ProjectSettings.globalize_path("res://")
83
87
  version_at_load = _loaded_version()
84
- _keep_the_ports_this_editor_was_given()
85
88
 
86
89
  _reconnect_timer = Timer.new()
87
90
  _reconnect_timer.one_shot = true
@@ -273,39 +276,41 @@ func _handle_connect() -> void:
273
276
  "editor_pid": OS.get_process_id(),
274
277
  "lsp_port": _serves(LSP_ASKED, LSP_SETTING),
275
278
  "dap_port": _serves(DAP_ASKED, DAP_SETTING),
276
- "debug_port": _serving(DEBUGGER_SETTING)
279
+ "debug_port": _serving(DEBUGGER_SETTING),
280
+ # Whether a server started this editor, which is the only kind that can be started again
281
+ # as itself: the engine hands back none of the arguments it was given, so the arguments
282
+ # have to come from whoever wrote them. See `restart_editor` in play_tools.gd.
283
+ "opened_by_a_server": _opened_by_a_server()
277
284
  }
278
285
  )
279
286
 
280
287
  connected.emit()
281
288
 
282
289
 
283
- ## Writes the ports this editor was started on into the settings it reads them from.
290
+ ## Whether a gdharness server started this editor, which it says in the environment.
284
291
  ##
285
- ## The command line moved the language server and the debug adapter for this run and the engine
286
- ## keeps that override to itself: the setting still reads whatever it read before, so an editor
287
- ## that restarts itself comes back on the old number and lands on top of whichever editor holds
288
- ## it. Writing it here is what makes the move survive a restart, and what leaves one place either
289
- ## side has to read.
290
- func _keep_the_ports_this_editor_was_given() -> void:
291
- if not Engine.is_editor_hint():
292
- return
293
- _keep_port(LSP_ASKED, LSP_SETTING)
294
- _keep_port(DAP_ASKED, DAP_SETTING)
295
-
296
-
297
- func _keep_port(variable: String, setting: String) -> void:
298
- var port: int = _asked_for(variable)
299
- if port < 1 or port == _serving(setting):
300
- return
301
- var settings: EditorSettings = EditorInterface.get_editor_settings()
302
- if settings != null:
303
- settings.set_setting(setting, port)
292
+ ## It decides who may restart this editor by starting it again. Only a server that wrote the
293
+ ## arguments can write them a second time, and only an editor started that way needs it: one opened
294
+ ## by hand is on the ports its own settings name and comes back on them by itself.
295
+ ##
296
+ ## Said outright rather than read off the two port variables, which a server also sets and anybody
297
+ ## else may: an editor started from a shell that exports them is one somebody opened themselves,
298
+ ## and taking it for a server's is taking an editor off somebody's desk and putting a new one there.
299
+ static func _opened_by_a_server() -> bool:
300
+ return not OS.get_environment(OPENED_BY_A_SERVER).is_empty()
304
301
 
305
302
 
306
- ## What this editor serves: what it was told to when a server opened it, and what its settings
307
- ## say otherwise. The first is the one that counts, because the settings are the thing the command
308
- ## line was overriding.
303
+ ## What this editor serves: what it was told to when a server opened it, and what its settings say
304
+ ## otherwise. The first is the one that counts, because the settings are what the command line was
305
+ ## overriding.
306
+ ##
307
+ ## The settings are read and never written. They were written for a while, because the engine keeps
308
+ ## a command-line port override to itself and hands none of it back, so an editor restarting itself
309
+ ## came up on whatever the settings said. But there is one settings file for every editor of this
310
+ ## engine version on the machine, so writing a port chosen for one project made it the number every
311
+ ## other project's editor came up on: an editor opened by hand for something else inherited it and
312
+ ## collided with the editor it was moved away from. Restarting is done by starting the editor again
313
+ ## with the same arguments now, which is `editor_launch restart`, so there is nothing to persist.
309
314
  func _serves(variable: String, setting: String) -> int:
310
315
  var asked: int = _asked_for(variable)
311
316
  return asked if asked > 0 else _serving(setting)
@@ -77,6 +77,7 @@ func _init_tools() -> void:
77
77
  # Animation tools
78
78
  "play_scene": [_play_tools, "play_scene"],
79
79
  "restart_editor": [_play_tools, "restart_editor"],
80
+ "quit_editor": [_play_tools, "quit_editor"],
80
81
  "stop_playing": [_play_tools, "stop_playing"],
81
82
  "playing_status": [_play_tools, "playing_status"],
82
83
  "create_animation": [_animation_tools, "create_animation"],
@@ -125,6 +125,33 @@ func restart_editor(_args: Dictionary) -> Dictionary:
125
125
  return {"ok": true, "restarting": true, "saved": true}
126
126
 
127
127
 
128
+ ## Ends this editor, having saved, so that whoever opened it can open it again.
129
+ ##
130
+ ## The editor's half of restarting one a server started. Godot's own restart cannot carry the ports
131
+ ## such an editor was given, for the reason above: the engine consumes the arguments and hands none
132
+ ## of them back. So the server starts it again itself, and this is the part only the editor can do.
133
+ ##
134
+ ## Saved first, exactly as the restart above saves. The editor is going either way, and unsaved
135
+ ## scenes are not this tool's to lose.
136
+ func quit_editor(_args: Dictionary) -> Dictionary:
137
+ if EditorInterface.is_playing_scene():
138
+ EditorInterface.stop_playing_scene()
139
+
140
+ EditorInterface.save_all_scenes()
141
+
142
+ # Deferred so this answer is on its way out before the editor goes.
143
+ _leave.call_deferred()
144
+
145
+ return {"ok": true, "quitting": true, "saved": true}
146
+
147
+
148
+ ## Asked of the tree the editor's own window is in, because this object is not in one.
149
+ static func _leave() -> void:
150
+ var base: Control = EditorInterface.get_base_control()
151
+ if base != null and base.get_tree() != null:
152
+ base.get_tree().quit()
153
+
154
+
128
155
  func playing_status(_args: Dictionary) -> Dictionary:
129
156
  var playing: bool = EditorInterface.is_playing_scene()
130
157
  # The debugger's port with it, read now rather than remembered: it is taken again before every
package/build/index.js CHANGED
@@ -25718,6 +25718,7 @@ class GodotBridge extends EventEmitter {
25718
25718
  lspPort: this.connectionInfo?.lspPort,
25719
25719
  dapPort: this.connectionInfo?.dapPort,
25720
25720
  debugPort: this.connectionInfo?.debugPort,
25721
+ openedByAServer: this.connectionInfo?.openedByAServer,
25721
25722
  pendingRequests: this.pendingRequests.size,
25722
25723
  queuedResources: this.resourceQueues.size
25723
25724
  };
@@ -25858,6 +25859,7 @@ class GodotBridge extends EventEmitter {
25858
25859
  this.connectionInfo.lspPort = servedPort(message.lsp_port);
25859
25860
  this.connectionInfo.dapPort = servedPort(message.dap_port);
25860
25861
  this.connectionInfo.debugPort = servedPort(message.debug_port);
25862
+ this.connectionInfo.openedByAServer = message.opened_by_a_server === true;
25861
25863
  this.log("info", `Godot ready: ${message.project_path}`);
25862
25864
  this.emitBridgeEvent("godot_connected", { projectPath: message.project_path });
25863
25865
  }
@@ -26148,6 +26150,7 @@ function runArguments(options) {
26148
26150
  function editorArguments(projectPath, ports) {
26149
26151
  return ["-e", "--path", projectPath, "--lsp-port", String(ports.lsp), "--dap-port", String(ports.dap)];
26150
26152
  }
26153
+ var OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER";
26151
26154
  function userDataIn(home, variables = process.env) {
26152
26155
  const moved = ["APPDATA", "XDG_DATA_HOME"];
26153
26156
  const carried = {};
@@ -28717,6 +28720,17 @@ var SLOWEST_FRAME_RATE = 20;
28717
28720
  function patienceForFrames(frames, atLeast) {
28718
28721
  return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
28719
28722
  }
28723
+ function alive(pid) {
28724
+ if (pid === undefined) {
28725
+ return false;
28726
+ }
28727
+ try {
28728
+ process.kill(pid, 0);
28729
+ return true;
28730
+ } catch {
28731
+ return false;
28732
+ }
28733
+ }
28720
28734
  function isOfType(value, kind) {
28721
28735
  switch (kind) {
28722
28736
  case "string":
@@ -29014,12 +29028,7 @@ class GodotServer {
29014
29028
  if (announced === null || announced.pid === process.pid) {
29015
29029
  return null;
29016
29030
  }
29017
- try {
29018
- process.kill(announced.pid, 0);
29019
- return announced.pid;
29020
- } catch {
29021
- return null;
29022
- }
29031
+ return alive(announced.pid) ? announced.pid : null;
29023
29032
  }
29024
29033
  stopWatchingForASuccessor() {
29025
29034
  if (this.successorWatch !== null) {
@@ -29986,7 +29995,8 @@ class GodotServer {
29986
29995
  "editor_status says whether the bridge is up and what has reached it"
29987
29996
  ]);
29988
29997
  }
29989
- const asked = await this.handleViaBridge("restart_editor", {});
29998
+ const mine = before.openedByAServer === true && before.projectPath !== undefined;
29999
+ const asked = mine ? await this.startItAgain(before.projectPath ?? "", before.editorPid) : await this.handleViaBridge("restart_editor", {});
29990
30000
  if (asked.isError === true) {
29991
30001
  return asked;
29992
30002
  }
@@ -30014,6 +30024,26 @@ class GodotServer {
30014
30024
  tookMs: Date.now() - began
30015
30025
  });
30016
30026
  }
30027
+ async startItAgain(projectPath, editorPid) {
30028
+ const engine = await this.engine();
30029
+ if (!engine.ok) {
30030
+ return engine.response;
30031
+ }
30032
+ const ports = {
30033
+ lsp: this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT),
30034
+ dap: this.editorServes("dapPort", "GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT)
30035
+ };
30036
+ const asked = await this.handleViaBridge("quit_editor", {});
30037
+ if (asked.isError === true) {
30038
+ return asked;
30039
+ }
30040
+ await this.waitForBridge(() => !alive(editorPid), Date.now() + EDITOR_RESTART_TIMEOUT_MS);
30041
+ const opened = await this.openAnEditor(engine.value, projectPath, ports);
30042
+ if (opened.error !== null) {
30043
+ return this.createErrorResponse(`The editor was asked to go and could not be started again: ${opened.error}`, [`editor_launch opens one on ${projectPath}`]);
30044
+ }
30045
+ return this.jsonTextResponse({ restarting: true, pid: opened.pid });
30046
+ }
30017
30047
  async waitForBridge(reached, deadline) {
30018
30048
  for (;; ) {
30019
30049
  if (reached()) {
@@ -30043,14 +30073,28 @@ class GodotServer {
30043
30073
  }
30044
30074
  this.logDebug(`Launching Godot editor for project: ${project.value.path}`);
30045
30075
  const ports = await this.portsForAnEditor();
30046
- const editor = spawn(engine.value, editorArguments(project.value.path, ports), {
30076
+ const opened = await this.openAnEditor(engine.value, project.value.path, ports);
30077
+ if (opened.error !== null) {
30078
+ return this.createErrorResponse(`Could not start the editor: ${opened.error}`);
30079
+ }
30080
+ return this.jsonTextResponse({
30081
+ launched: true,
30082
+ pid: opened.pid,
30083
+ projectPath: project.value.path,
30084
+ lspPort: ports.lsp,
30085
+ dapPort: ports.dap
30086
+ });
30087
+ }
30088
+ async openAnEditor(engine, projectPath, ports) {
30089
+ const editor = spawn(engine, editorArguments(projectPath, ports), {
30047
30090
  stdio: "ignore",
30048
30091
  detached: true,
30049
30092
  env: {
30050
30093
  ...process.env,
30051
30094
  GDHARNESS_RUNTIME_DIR: runtimeDirectory(),
30052
30095
  GDHARNESS_LSP_PORT: String(ports.lsp),
30053
- GDHARNESS_DAP_PORT: String(ports.dap)
30096
+ GDHARNESS_DAP_PORT: String(ports.dap),
30097
+ [OPENED_BY_A_SERVER]: "1"
30054
30098
  }
30055
30099
  });
30056
30100
  const started = await new Promise((resolve) => {
@@ -30062,16 +30106,10 @@ class GodotServer {
30062
30106
  });
30063
30107
  });
30064
30108
  if (started !== null) {
30065
- return this.createErrorResponse(`Could not start the editor: ${started}`);
30109
+ return { pid: null, error: started };
30066
30110
  }
30067
30111
  editor.unref();
30068
- return this.jsonTextResponse({
30069
- launched: true,
30070
- pid: editor.pid ?? null,
30071
- projectPath: project.value.path,
30072
- lspPort: ports.lsp,
30073
- dapPort: ports.dap
30074
- });
30112
+ return { pid: editor.pid ?? null, error: null };
30075
30113
  }
30076
30114
  async portsForAnEditor() {
30077
30115
  const lsp = portFromEnvOrNull("GDHARNESS_LSP_PORT") ?? await freePort(DEFAULT_LSP_PORT);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
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",