gdharness 0.5.7 → 0.5.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 CHANGED
@@ -15821,7 +15821,7 @@ var init_tool_definitions = __esm(() => {
15821
15821
  },
15822
15822
  {
15823
15823
  name: "runtime_input",
15824
- description: "Input to the running game: a whole click on a Control named by path, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it.",
15824
+ description: "Input to the running game: a whole click on a Control named by path, typing into whatever has the focus, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it.",
15825
15825
  parameters: {
15826
15826
  projectPath: RUNNING_PROJECT_PATH,
15827
15827
  nodePath: { type: "string", description: "click: the Control to click, at its centre." },
@@ -15829,6 +15829,10 @@ var init_tool_definitions = __esm(() => {
15829
15829
  pressed: { type: "boolean", description: "Press or release. Default true." },
15830
15830
  strength: { type: "number", description: "action: 0 to 1. Default 1." },
15831
15831
  keycode: { type: "string", description: 'key: the key name, such as "Space" or "A".' },
15832
+ text: {
15833
+ type: "string",
15834
+ description: "text: what to type. A newline is Enter and a tab is Tab."
15835
+ },
15832
15836
  shift: { type: "boolean" },
15833
15837
  ctrl: { type: "boolean" },
15834
15838
  alt: { type: "boolean" },
@@ -15851,6 +15855,10 @@ var init_tool_definitions = __esm(() => {
15851
15855
  },
15852
15856
  action: { summary: "press or release an action", requires: ["action"] },
15853
15857
  key: { summary: "press or release a key", requires: ["keycode"] },
15858
+ text: {
15859
+ summary: "type a string wherever the focus is, a character at a time",
15860
+ requires: ["text"]
15861
+ },
15854
15862
  mouse_click: { summary: "one mouse button event at a position", requires: ["x", "y"] },
15855
15863
  mouse_motion: { summary: "move the mouse to a position", requires: ["x", "y"] }
15856
15864
  }
@@ -30638,6 +30646,47 @@ var init_stdio2 = __esm(() => {
30638
30646
  init_stdio();
30639
30647
  });
30640
30648
 
30649
+ // src/bridge-announce.ts
30650
+ import { mkdirSync as mkdirSync5, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
30651
+ import { dirname as dirname6, join as join11 } from "node:path";
30652
+ function announcementPath(projectPath) {
30653
+ return join11(projectPath, ".godot", "gdharness-bridge.json");
30654
+ }
30655
+ function announceBridge(projectPath, bridge) {
30656
+ const path = announcementPath(projectPath);
30657
+ try {
30658
+ mkdirSync5(dirname6(path), { recursive: true });
30659
+ const announcement = {
30660
+ protocol: BRIDGE_ANNOUNCE_PROTOCOL,
30661
+ host: bridge.host,
30662
+ port: bridge.port,
30663
+ pid: process.pid,
30664
+ version: bridge.version,
30665
+ startedAt: new Date().toISOString()
30666
+ };
30667
+ writeFileSync7(path, `${JSON.stringify(announcement, null, 2)}
30668
+ `, "utf8");
30669
+ return path;
30670
+ } catch (error) {
30671
+ console.error(`[SERVER] Could not announce the editor bridge at ${path}: ${errorMessage(error)}`);
30672
+ return null;
30673
+ }
30674
+ }
30675
+ function withdrawBridge(path) {
30676
+ if (path === null) {
30677
+ return;
30678
+ }
30679
+ try {
30680
+ rmSync6(path, { force: true });
30681
+ } catch (error) {
30682
+ console.error(`[SERVER] Could not withdraw the editor bridge announcement: ${errorMessage(error)}`);
30683
+ }
30684
+ }
30685
+ var BRIDGE_ANNOUNCE_PROTOCOL = 1;
30686
+ var init_bridge_announce = __esm(() => {
30687
+ init_errors();
30688
+ });
30689
+
30641
30690
  // src/framing.ts
30642
30691
  function frame(message) {
30643
30692
  const body = Buffer.from(JSON.stringify(message), "utf8");
@@ -34105,8 +34154,8 @@ function resolveDefaultBridgeHost() {
34105
34154
  const host = process.env["GDHARNESS_BRIDGE_HOST"]?.trim();
34106
34155
  return host === undefined || host === "" ? DEFAULT_HOST : host;
34107
34156
  }
34108
- function getDefaultBridge() {
34109
- defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost());
34157
+ function getDefaultBridge(mayMove = false) {
34158
+ defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost(), DEFAULT_TIMEOUT_MS, mayMove);
34110
34159
  return defaultBridge;
34111
34160
  }
34112
34161
  var DEFAULT_PORT = 6505, DEFAULT_HOST = "127.0.0.1", DEFAULT_TIMEOUT_MS = 30000, KEEPALIVE_INTERVAL_MS = 1e4, SECOND_CONNECTION_CLOSE_CODE = 4000, GodotBridge, defaultBridge = null;
@@ -34122,19 +34171,37 @@ var init_godot_bridge = __esm(() => {
34122
34171
  connectionInfo = null;
34123
34172
  pendingRequests = new Map;
34124
34173
  resourceQueues = new Map;
34125
- port;
34174
+ wantedPort;
34175
+ boundPort = null;
34126
34176
  host;
34127
34177
  timeoutMs;
34128
- constructor(port = DEFAULT_PORT, host = DEFAULT_HOST, timeoutMs = DEFAULT_TIMEOUT_MS) {
34178
+ mayMove;
34179
+ constructor(port = DEFAULT_PORT, host = DEFAULT_HOST, timeoutMs = DEFAULT_TIMEOUT_MS, mayMove = false) {
34129
34180
  super();
34130
- this.port = port;
34181
+ this.wantedPort = port;
34131
34182
  this.host = host;
34132
34183
  this.timeoutMs = timeoutMs;
34184
+ this.mayMove = mayMove;
34133
34185
  }
34134
- start() {
34186
+ get port() {
34187
+ return this.boundPort ?? this.wantedPort;
34188
+ }
34189
+ async start() {
34135
34190
  if (this.httpServer) {
34136
- return Promise.resolve();
34191
+ return;
34192
+ }
34193
+ try {
34194
+ await this.listenOn(this.wantedPort);
34195
+ } catch (error) {
34196
+ const held = error instanceof Error && "code" in error && error.code === "EADDRINUSE";
34197
+ if (!held || !this.mayMove) {
34198
+ throw error;
34199
+ }
34200
+ this.log("warn", `Editor bridge port ${this.wantedPort} is held; taking another one.`);
34201
+ await this.listenOn(0);
34137
34202
  }
34203
+ }
34204
+ listenOn(port) {
34138
34205
  return new Promise((resolve, reject) => {
34139
34206
  const server = http.createServer((_req, res) => {
34140
34207
  res.writeHead(404);
@@ -34158,6 +34225,8 @@ var init_godot_bridge = __esm(() => {
34158
34225
  settled = true;
34159
34226
  this.httpServer = server;
34160
34227
  this.godotWss = godotWss;
34228
+ const bound = server.address();
34229
+ this.boundPort = typeof bound === "object" && bound !== null ? bound.port : port;
34161
34230
  this.log("info", `Editor bridge listening on ${this.host}:${this.port}`);
34162
34231
  resolve();
34163
34232
  });
@@ -34174,7 +34243,7 @@ var init_godot_bridge = __esm(() => {
34174
34243
  godotWss.on("error", (error) => {
34175
34244
  this.log("error", `Godot WebSocket server error: ${error.message}`);
34176
34245
  });
34177
- server.listen(this.port, this.host);
34246
+ server.listen(port, this.host);
34178
34247
  });
34179
34248
  }
34180
34249
  async stop() {
@@ -34208,6 +34277,7 @@ var init_godot_bridge = __esm(() => {
34208
34277
  this.httpServer = null;
34209
34278
  }
34210
34279
  await Promise.all(closeTasks);
34280
+ this.boundPort = null;
34211
34281
  this.connectionInfo = null;
34212
34282
  this.log("info", "WebSocket bridge stopped");
34213
34283
  }
@@ -34857,7 +34927,7 @@ var init_junit = __esm(() => {
34857
34927
  import { realpathSync } from "node:fs";
34858
34928
  import { readFile, realpath } from "node:fs/promises";
34859
34929
  import { createConnection as createConnection2 } from "node:net";
34860
- import { dirname as dirname6, resolve as resolve3 } from "node:path";
34930
+ import { dirname as dirname7, resolve as resolve3 } from "node:path";
34861
34931
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
34862
34932
  function diagnosticsKey(uri) {
34863
34933
  let decoded;
@@ -34983,7 +35053,7 @@ class GodotLSPClient {
34983
35053
  if (this.initialized) {
34984
35054
  return;
34985
35055
  }
34986
- const rootPath = this.rootPath ?? dirname6(resolve3(filePath));
35056
+ const rootPath = this.rootPath ?? dirname7(resolve3(filePath));
34987
35057
  await this.initialize(rootPath);
34988
35058
  }
34989
35059
  async sendRequest(method, params) {
@@ -35358,7 +35428,7 @@ var init_lsp_client = __esm(() => {
35358
35428
 
35359
35429
  // src/project-scan.ts
35360
35430
  import { readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
35361
- import { join as join11 } from "node:path";
35431
+ import { join as join12 } from "node:path";
35362
35432
  function projectStructure(projectPath) {
35363
35433
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
35364
35434
  const visit = (directory) => {
@@ -35367,7 +35437,7 @@ function projectStructure(projectPath) {
35367
35437
  continue;
35368
35438
  }
35369
35439
  if (entry.isDirectory()) {
35370
- visit(join11(directory, entry.name));
35440
+ visit(join12(directory, entry.name));
35371
35441
  } else if (entry.isFile()) {
35372
35442
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
35373
35443
  if (extension === "tscn") {
@@ -35409,7 +35479,7 @@ function searchProject(projectPath, options) {
35409
35479
  if (SKIPPED.has(entry.name)) {
35410
35480
  continue;
35411
35481
  }
35412
- const entryPath = join11(directory, entry.name);
35482
+ const entryPath = join12(directory, entry.name);
35413
35483
  if (entry.isDirectory()) {
35414
35484
  visit(entryPath);
35415
35485
  continue;
@@ -35451,20 +35521,20 @@ var init_project_scan = __esm(() => {
35451
35521
  import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync10, unlinkSync } from "node:fs";
35452
35522
  import { createConnection as createConnection3 } from "node:net";
35453
35523
  import { tmpdir as tmpdir4 } from "node:os";
35454
- import { join as join12, resolve as resolve4 } from "node:path";
35524
+ import { join as join13, resolve as resolve4 } from "node:path";
35455
35525
  function runtimeDirectory(variables = process.env) {
35456
35526
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
35457
35527
  if (explicit) {
35458
35528
  return explicit;
35459
35529
  }
35460
35530
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
35461
- return join12(perUser ?? tmpdir4(), "gdharness");
35531
+ return join13(perUser ?? tmpdir4(), "gdharness");
35462
35532
  }
35463
35533
  function runtimeDirectories(variables = process.env) {
35464
35534
  const candidates = [runtimeDirectory(variables)];
35465
35535
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
35466
35536
  for (const base of fallbacks) {
35467
- candidates.push(join12(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
35537
+ candidates.push(join13(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
35468
35538
  }
35469
35539
  return [...new Set(candidates.map((path) => resolve4(path)))];
35470
35540
  }
@@ -35527,7 +35597,7 @@ function announcedIn(directory) {
35527
35597
  if (!match) {
35528
35598
  continue;
35529
35599
  }
35530
- const file = join12(directory, entry);
35600
+ const file = join13(directory, entry);
35531
35601
  const pid = Number.parseInt(match[1] ?? "", 10);
35532
35602
  const announced = processAlive(pid) ? parseAnnouncement(file, pid) : { kind: "rubbish" };
35533
35603
  if (announced.kind === "rubbish") {
@@ -35673,9 +35743,9 @@ var init_runtime_client = __esm(() => {
35673
35743
 
35674
35744
  // src/server.ts
35675
35745
  import { execFile as execFile5, spawn } from "node:child_process";
35676
- import { existsSync as existsSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync11, realpathSync as realpathSync2, rmSync as rmSync6 } from "node:fs";
35746
+ import { existsSync as existsSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync11, realpathSync as realpathSync2, rmSync as rmSync7 } from "node:fs";
35677
35747
  import { tmpdir as tmpdir5 } from "node:os";
35678
- import { basename as basename2, dirname as dirname7, join as join13, normalize as normalize3 } from "node:path";
35748
+ import { basename as basename2, dirname as dirname8, join as join14, normalize as normalize3 } from "node:path";
35679
35749
  import { setTimeout as delay2 } from "node:timers/promises";
35680
35750
  import { fileURLToPath as fileURLToPath4 } from "node:url";
35681
35751
  import { promisify as promisify5 } from "node:util";
@@ -35721,7 +35791,7 @@ function camelCased(params) {
35721
35791
  class GodotServer {
35722
35792
  mcp;
35723
35793
  locator = new GodotLocator2;
35724
- operationsScript = join13(__dirname2, "godot", "operations", "godot_operations.gd");
35794
+ operationsScript = join14(__dirname2, "godot", "operations", "godot_operations.gd");
35725
35795
  godotBridge;
35726
35796
  tools = buildToolDefinitions();
35727
35797
  activeProcess = null;
@@ -35735,8 +35805,11 @@ class GodotServer {
35735
35805
  bridgeRetry = null;
35736
35806
  lastProjectPath = null;
35737
35807
  shutdownInitiated = false;
35808
+ ownProject;
35809
+ announcedAt = null;
35738
35810
  constructor() {
35739
- this.godotBridge = getDefaultBridge();
35811
+ this.ownProject = envValue("GDHARNESS_PROJECT") ?? null;
35812
+ this.godotBridge = getDefaultBridge(this.ownProject !== null);
35740
35813
  this.mcp = new McpServer({ name: "gdharness", version: SERVER_VERSION }, { capabilities: { tools: {}, resources: {} } });
35741
35814
  this.setupToolHandlers();
35742
35815
  setupResourceHandlers(this.mcp, () => this.lastProjectPath);
@@ -35784,6 +35857,7 @@ class GodotServer {
35784
35857
  this.bridgeStartupError = null;
35785
35858
  const bridgeStatus = this.godotBridge.getStatus();
35786
35859
  console.error(`[SERVER] Godot Editor Bridge started on ${bridgeStatus.host}:${bridgeStatus.port}`);
35860
+ this.announceTheBridge();
35787
35861
  } catch (bridgeError) {
35788
35862
  const code = bridgeError instanceof Error && "code" in bridgeError && typeof bridgeError.code === "string" ? bridgeError.code : null;
35789
35863
  const reason = errorMessage(bridgeError);
@@ -35816,6 +35890,18 @@ class GodotServer {
35816
35890
  this.stopTryingTheBridge();
35817
35891
  const bridgeStatus = this.godotBridge.getStatus();
35818
35892
  console.error(`[SERVER] Godot Editor Bridge came up on ${bridgeStatus.host}:${bridgeStatus.port}; editor tools are live.`);
35893
+ this.announceTheBridge();
35894
+ }
35895
+ announceTheBridge() {
35896
+ if (this.ownProject === null) {
35897
+ return;
35898
+ }
35899
+ const status = this.godotBridge.getStatus();
35900
+ this.announcedAt = announceBridge(this.ownProject, {
35901
+ host: status.host,
35902
+ port: status.port,
35903
+ version: SERVER_VERSION
35904
+ });
35819
35905
  }
35820
35906
  stopTryingTheBridge() {
35821
35907
  if (this.bridgeRetry !== null) {
@@ -35826,6 +35912,8 @@ class GodotServer {
35826
35912
  async cleanup() {
35827
35913
  this.logDebug("Cleaning up resources");
35828
35914
  this.stopTryingTheBridge();
35915
+ withdrawBridge(this.announcedAt);
35916
+ this.announcedAt = null;
35829
35917
  if (this.activeProcess) {
35830
35918
  this.activeProcess.process?.kill();
35831
35919
  this.activeProcess = null;
@@ -36208,7 +36296,7 @@ class GodotServer {
36208
36296
  if (path === undefined) {
36209
36297
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
36210
36298
  }
36211
- const file = join13(path, "project.godot");
36299
+ const file = join14(path, "project.godot");
36212
36300
  if (!existsSync11(file)) {
36213
36301
  return {
36214
36302
  ok: false,
@@ -36473,7 +36561,7 @@ class GodotServer {
36473
36561
  return contained.response;
36474
36562
  }
36475
36563
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
36476
- if (!existsSync11(join13(project.value.path, runner))) {
36564
+ if (!existsSync11(join14(project.value.path, runner))) {
36477
36565
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
36478
36566
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
36479
36567
  ]);
@@ -36506,7 +36594,7 @@ class GodotServer {
36506
36594
  ];
36507
36595
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
36508
36596
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
36509
- const userData = mkdtempSync3(join13(tmpdir5(), "gdharness-tests-"));
36597
+ const userData = mkdtempSync3(join14(tmpdir5(), "gdharness-tests-"));
36510
36598
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
36511
36599
  const hung = await new Promise((resolve) => {
36512
36600
  const timer = setTimeout(() => {
@@ -36522,20 +36610,20 @@ class GodotServer {
36522
36610
  resolve(false);
36523
36611
  });
36524
36612
  });
36525
- const reportsDir = join13(project.value.path, ".godot", "gdharness-reports");
36613
+ const reportsDir = join14(project.value.path, ".godot", "gdharness-reports");
36526
36614
  let report = null;
36527
36615
  let reportProblem = null;
36528
36616
  try {
36529
36617
  const written = existsSync11(reportsDir) ? readdirSync6(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
36530
36618
  const newest = written.at(-1);
36531
36619
  if (newest !== undefined) {
36532
- report = parseJUnit(readFileSync11(join13(reportsDir, newest, "results.xml"), "utf8"));
36620
+ report = parseJUnit(readFileSync11(join14(reportsDir, newest, "results.xml"), "utf8"));
36533
36621
  }
36534
36622
  } catch (error) {
36535
36623
  reportProblem = errorMessage(error);
36536
36624
  } finally {
36537
- rmSync6(reportsDir, { recursive: true, force: true });
36538
- rmSync6(userData, { recursive: true, force: true });
36625
+ rmSync7(reportsDir, { recursive: true, force: true });
36626
+ rmSync7(userData, { recursive: true, force: true });
36539
36627
  }
36540
36628
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
36541
36629
  const verdicts = {
@@ -36648,6 +36736,7 @@ class GodotServer {
36648
36736
  startupError: this.bridgeStartupError,
36649
36737
  staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
36650
36738
  retryingBridge: this.bridgeRetry === null ? undefined : true,
36739
+ announcedAt: this.announcedAt ?? undefined,
36651
36740
  note: isPortConflict ? "Bridge port is already in use. Another gdharness instance owns the editor bridge, so this server cannot reach the editor. Usually the server this one replaced, still on its way out." : undefined,
36652
36741
  suggestion: isPortConflict ? `This server is asking for the port again every ${BRIDGE_RETRY_MS / 1000}s and takes it the moment the other one lets go, so editor tools come back on their own. Ask again, or end the older gdharness process to have it now.` : undefined
36653
36742
  };
@@ -37043,8 +37132,8 @@ class GodotServer {
37043
37132
  return this.createErrorResponse(choice.problem);
37044
37133
  }
37045
37134
  const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
37046
- const screenshotDir = expectsScreenshot ? mkdtempSync3(join13(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
37047
- const screenshotPath = screenshotDir ? join13(screenshotDir, "capture.png") : null;
37135
+ const screenshotDir = expectsScreenshot ? mkdtempSync3(join14(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
37136
+ const screenshotPath = screenshotDir ? join14(screenshotDir, "capture.png") : null;
37048
37137
  try {
37049
37138
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
37050
37139
  if (!reply.ok) {
@@ -37070,7 +37159,7 @@ class GodotServer {
37070
37159
  };
37071
37160
  } finally {
37072
37161
  if (screenshotDir) {
37073
- rmSync6(screenshotDir, { recursive: true, force: true });
37162
+ rmSync7(screenshotDir, { recursive: true, force: true });
37074
37163
  }
37075
37164
  }
37076
37165
  }
@@ -37132,6 +37221,7 @@ var init_server3 = __esm(() => {
37132
37221
  init_mcp();
37133
37222
  init_stdio2();
37134
37223
  init_types();
37224
+ init_bridge_announce();
37135
37225
  init_class_cache();
37136
37226
  init_dap_client();
37137
37227
  init_errors();
@@ -37150,7 +37240,7 @@ var init_server3 = __esm(() => {
37150
37240
  init_tool_definitions();
37151
37241
  init_update_check();
37152
37242
  run5 = promisify5(execFile5);
37153
- __dirname2 = dirname7(fileURLToPath4(import.meta.url));
37243
+ __dirname2 = dirname8(fileURLToPath4(import.meta.url));
37154
37244
  PATH_SOLUTIONS = [
37155
37245
  'Give the path relative to the project, such as "scenes/main.tscn" or "res://scenes/main.tscn"',
37156
37246
  "Point projectPath at the project the file belongs to"
@@ -37208,7 +37298,7 @@ var init_server3 = __esm(() => {
37208
37298
 
37209
37299
  // src/cli.ts
37210
37300
  import { existsSync as existsSync12 } from "fs";
37211
- import { join as join14, resolve as resolve5 } from "path";
37301
+ import { join as join15, resolve as resolve5 } from "path";
37212
37302
 
37213
37303
  // src/errors.ts
37214
37304
  class Refusal extends Error {
@@ -37418,9 +37508,13 @@ function isEmptyCollection(node) {
37418
37508
  init_errors();
37419
37509
  init_runner();
37420
37510
  var SERVER_KEY = "gdharness";
37421
- function launchFor(version, godotPath, runner = currentRunner()) {
37511
+ function launchFor(version, godotPath, projectPath, runner = currentRunner()) {
37422
37512
  const spawn = spawnFor(runner, version);
37423
- return { command: spawn.command, args: spawn.args, env: { GODOT_PATH: godotPath } };
37513
+ return {
37514
+ command: spawn.command,
37515
+ args: spawn.args,
37516
+ env: { GODOT_PATH: godotPath, GDHARNESS_PROJECT: projectPath }
37517
+ };
37424
37518
  }
37425
37519
  function tomlArray(values) {
37426
37520
  return `[${values.map((value) => `"${value}"`).join(", ")}]`;
@@ -38469,6 +38563,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
38469
38563
  | Where a control is, and whether it is visible | \`runtime_inspect\` \`find\`, \`rect\` |
38470
38564
  | What a property reads right now | \`runtime_inspect\` \`property\`, \`runtime_invoke\` |
38471
38565
  | Press a button | \`runtime_input click\`, which says what was under the pointer |
38566
+ | Fill in a field | \`runtime_input click\` on it, then \`runtime_input text\` |
38472
38567
  | Wait for something | \`runtime_wait\`, never a sleep |
38473
38568
  | A picture, for a person who asked to see one | \`runtime_capture\` |
38474
38569
 
@@ -38605,7 +38700,7 @@ class UsageError extends Refusal {
38605
38700
  function projectArgument(at) {
38606
38701
  const named = args.slice(at).find((value) => !value.startsWith("--"));
38607
38702
  const projectPath = resolve5(named ?? ".");
38608
- if (!existsSync12(join14(projectPath, "project.godot"))) {
38703
+ if (!existsSync12(join15(projectPath, "project.godot"))) {
38609
38704
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
38610
38705
  }
38611
38706
  return projectPath;
@@ -38731,7 +38826,7 @@ async function setup() {
38731
38826
  console.log(`class list rebuilt: ${String(classes.payload["classes"])} classes`);
38732
38827
  }
38733
38828
  if (!args.includes("--no-connect")) {
38734
- const launch = launchFor(getLocalVersion(), godot.godotPath);
38829
+ const launch = launchFor(getLocalVersion(), godot.godotPath, projectPath);
38735
38830
  const ask = new Ask;
38736
38831
  let chosen;
38737
38832
  try {
@@ -38826,7 +38921,7 @@ async function upgrade() {
38826
38921
  said(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
38827
38922
  }
38828
38923
  said(await runOperation(godot, "refresh_class_cache", {}, projectPath), "rebuilding the class list");
38829
- const launch = launchFor(version, godot.godotPath);
38924
+ const launch = launchFor(version, godot.godotPath, projectPath);
38830
38925
  const already = HARNESSES.filter((harness) => registered(harness, projectPath));
38831
38926
  for (const group of groupByFile(already, projectPath)) {
38832
38927
  reportConnection(group, launch, projectPath);
@@ -0,0 +1 @@
1
+ uid://dfde0vm55ws7m