@trolleroof/tui 0.2.5 → 0.2.6

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.
Files changed (91) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/.codex-plugin/plugin.json +2 -2
  3. package/README.md +29 -7
  4. package/dist/index.js +284 -18
  5. package/overlay/dist/app.js +74 -0
  6. package/overlay/dist/index.html +1589 -0
  7. package/overlay-release.json +2 -2
  8. package/package.json +27 -4
  9. package/scripts/install-adapters.sh +6 -6
  10. package/scripts/install-adapters.test.sh +2 -2
  11. package/scripts/materialize-agents-plugin.sh +3 -3
  12. package/scripts/npm-pack.test.ts +51 -0
  13. package/scripts/overlay.ts +1 -1
  14. package/src/analytics/events.test.ts +77 -0
  15. package/src/analytics/events.ts +85 -0
  16. package/src/analytics/posthog.test.ts +85 -0
  17. package/src/analytics/posthog.ts +88 -0
  18. package/src/auth-pages.ts +108 -0
  19. package/src/auth.test.ts +255 -0
  20. package/src/auth.ts +511 -0
  21. package/src/core/game.ts +35 -0
  22. package/src/core/key.ts +12 -0
  23. package/src/core/rng.ts +41 -0
  24. package/src/data/bc-export.test.ts +153 -0
  25. package/src/data/bc-export.ts +125 -0
  26. package/src/data/trajectory.ts +103 -0
  27. package/src/games/connect4/connect4.ts +81 -0
  28. package/src/games/connect4/engine.test.ts +77 -0
  29. package/src/games/connect4/engine.ts +89 -0
  30. package/src/games/g2048/engine.test.ts +138 -0
  31. package/src/games/g2048/engine.ts +109 -0
  32. package/src/games/g2048/game2048.ts +104 -0
  33. package/src/games/minesweeper/engine.test.ts +83 -0
  34. package/src/games/minesweeper/engine.ts +112 -0
  35. package/src/games/minesweeper/minesweeper.ts +107 -0
  36. package/src/games/registry.test.ts +16 -0
  37. package/src/games/registry.ts +130 -0
  38. package/src/games/snake/engine.test.ts +89 -0
  39. package/src/games/snake/engine.ts +95 -0
  40. package/src/games/snake/snake.ts +77 -0
  41. package/src/games/sokoban/engine.test.ts +49 -0
  42. package/src/games/sokoban/engine.ts +152 -0
  43. package/src/games/sokoban/sokoban.ts +103 -0
  44. package/src/games/sudoku/engine.test.ts +88 -0
  45. package/src/games/sudoku/engine.ts +99 -0
  46. package/src/games/sudoku/sudoku.ts +100 -0
  47. package/src/games/tetris/engine.test.ts +378 -0
  48. package/src/games/tetris/engine.ts +236 -0
  49. package/src/games/tetris/tetris.ts +220 -0
  50. package/src/index.ts +82 -0
  51. package/src/recording/chunks.ts +93 -0
  52. package/src/recording/config.test.ts +31 -0
  53. package/src/recording/config.ts +37 -0
  54. package/src/recording/contracts.test.ts +71 -0
  55. package/src/recording/gameplay.test.ts +70 -0
  56. package/src/recording/gameplay.ts +18 -0
  57. package/src/recording/http-transport.test.ts +197 -0
  58. package/src/recording/identity.test.ts +26 -0
  59. package/src/recording/identity.ts +38 -0
  60. package/src/recording/ids.ts +27 -0
  61. package/src/recording/permissions.ts +10 -0
  62. package/src/recording/recorder.test.ts +388 -0
  63. package/src/recording/recorder.ts +464 -0
  64. package/src/recording/store.test.ts +231 -0
  65. package/src/recording/store.ts +305 -0
  66. package/src/recording/terminal-grid.test.ts +88 -0
  67. package/src/recording/terminal-grid.ts +248 -0
  68. package/src/recording/trace-recorder.test.ts +287 -0
  69. package/src/recording/trace-recorder.ts +560 -0
  70. package/src/recording/transport.ts +170 -0
  71. package/src/recording/types.test.ts +18 -0
  72. package/src/recording/types.ts +326 -0
  73. package/src/recording/uploader.test.ts +193 -0
  74. package/src/recording/uploader.ts +103 -0
  75. package/src/results/completion.ts +100 -0
  76. package/src/results/http-result-transport.test.ts +75 -0
  77. package/src/results/local-score.test.ts +32 -0
  78. package/src/results/local-score.ts +39 -0
  79. package/src/results/results.test.ts +383 -0
  80. package/src/results/store.ts +149 -0
  81. package/src/results/transport.ts +79 -0
  82. package/src/results/types.ts +20 -0
  83. package/src/results/uploader.ts +84 -0
  84. package/src/sync/config.test.ts +108 -0
  85. package/src/sync/config.ts +115 -0
  86. package/src/sync/coordinator.test.ts +423 -0
  87. package/src/sync/coordinator.ts +277 -0
  88. package/src/sync/game-start-queue.test.ts +93 -0
  89. package/src/sync/game-start-queue.ts +45 -0
  90. package/src/sync/identity-client.test.ts +34 -0
  91. package/src/sync/identity-client.ts +43 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "tui-gamepigeon",
3
+ "version": "0.2.6",
4
+ "description": "Play in a lightweight HTML overlay while Claude Code works.",
5
+ "author": {
6
+ "name": "Trolleroof"
7
+ },
8
+ "repository": "https://github.com/Trolleroof/tui-gamepigeon",
9
+ "license": "MIT"
10
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tui-gamepigeon",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Play in a lightweight HTML overlay while Codex works.",
5
5
  "author": {
6
6
  "name": "Trolleroof"
@@ -10,7 +10,7 @@
10
10
  "keywords": ["arcade", "2048", "tetris", "terminal", "game"],
11
11
  "skills": "./skills/",
12
12
  "interface": {
13
- "displayName": "GamePigeon Arcade",
13
+ "displayName": "Tui Arcade",
14
14
  "shortDescription": "Play 2048 or Tetris while Codex works",
15
15
  "longDescription": "Open or restore a lightweight 2048 HTML overlay without leaving the active Codex task.",
16
16
  "developerName": "Trolleroof",
package/README.md CHANGED
@@ -57,7 +57,7 @@ To open the overlay directly without a harness:
57
57
  ```bash
58
58
  bun run start # menu
59
59
  bun run start 2048 # a specific game
60
- bun run preview:overlay # browser preview at 470×650 (no Tauri)
60
+ bun run preview:overlay # browser preview at 520×740 (no Tauri)
61
61
  bun run dev:overlay # rebuild web bundle + debug shell, then show
62
62
  ```
63
63
 
@@ -71,7 +71,7 @@ bun run preview:overlay
71
71
  bun run preview:overlay -- --game=connect4
72
72
  ```
73
73
 
74
- The preview frames the real overlay UI at the production sizes (**470×650** compact, **720×780** expanded). Titlebar **↗ / ↙** toggles the frame; Hide and Shutdown are no-ops in the browser. Open a game from the menu, or pass `--game=<id>` / use `?game=<id>` (optional `&variation=classic`).
74
+ The preview frames the real overlay UI at the production sizes (**520×740** compact, **720×780** expanded). Titlebar **↗ / ↙** toggles the frame; Hide and Shutdown are no-ops in the browser. Open a game from the menu, or pass `--game=<id>` / use `?game=<id>` (optional `&variation=classic`).
75
75
 
76
76
  ### Global (installed once, works everywhere)
77
77
 
@@ -125,7 +125,7 @@ Production defaults to `https://runretroarcade.com/login.html`, `https://auth.ru
125
125
 
126
126
  ## Coding harness adapters
127
127
 
128
- The bundled adapters give Codex CLI, Claude Code, and OpenCode the same Arcade launcher. GamePigeon opens a centered, borderless HTML overlay above the active desktop. It uses the operating system's installed webview through a small Tauri shell, not Chrome or Electron.
128
+ The bundled adapters give Codex CLI, Claude Code, and OpenCode the same Arcade launcher. Tui opens a centered, borderless HTML overlay above the active desktop. It uses the operating system's installed webview through a small Tauri shell, not Chrome or Electron.
129
129
 
130
130
  Requirements:
131
131
 
@@ -257,7 +257,7 @@ export GAMEPIGEON_BACKEND_TOKEN=retro_...
257
257
  # export GAMEPIGEON_BACKEND_URL=http://127.0.0.1:8787
258
258
  ```
259
259
 
260
- The HTML overlay journals each seeded action sequence in local storage, rebuilds replay-safe schema-v2 engine-state traces at episode end, and hands immutable chunks plus completed-game results to Tauri. Tauri validates and durably queues them before any network call, then resolves the canonical `/v1/me` user and drains both outboxes in the background. Chunks live under `~/.tui-gamepigeon/recordings/overlay-pending/` until `/v1/game-event-chunks` accepts them; results live under `~/.tui-gamepigeon/recordings/results/overlay-pending/` until `/v1/game-results` accepts them. Interrupted and offline sessions recover on the next launch, while cloud-acknowledged payloads move to the matching `overlay-completed/` directory.
260
+ The HTML overlay journals each seeded action sequence in local storage, rebuilds replay-safe schema-v2 engine-state traces at episode end, and hands immutable chunks plus completed-game results to Tauri. Tauri validates and durably queues them before any network call, then resolves the canonical `/v1/me` user and drains both outboxes in the background. Chunks live under `~/.tui-gamepigeon/recordings/overlay-pending/` until `/v1/game-event-chunks` accepts them; results live under `~/.tui-gamepigeon/recordings/results/overlay-pending/` until `/v1/game-results` accepts them. Interrupted and offline sessions recover on the next launch, while cloud-acknowledged payloads move to the matching `overlay-completed/` directory. A payload the backend refuses outright — a non-retryable HTTP status, or an acknowledgment that does not match what was sent — moves to the matching `overlay-failed/` directory with a reason logged to stderr and a sync error raised, so one rejected chunk never blocks the rest of the queue. Sign-in and backend-URL failures are not rejections: those keep the payload queued for the next launch. Nothing deletes those files; pass them to the exporter with `--input` if you want them back.
261
261
 
262
262
  Export policy-visible human demonstrations into leakage-safe BC splits:
263
263
 
@@ -299,7 +299,7 @@ Product events use a CloudEvents 1.0 envelope with `specversion`, `id`, `source`
299
299
 
300
300
  The website owns `site.visited`; invite and payment services own their corresponding events. Until those producers exist, the names above reserve the shared contract without fabricating client events. Analytics must never include terminal frames, trajectories, prompts, repository contents, credentials, email addresses, or raw provider payloads.
301
301
 
302
- The live [GamePigeon AARRR client dashboard](https://us.posthog.com/project/434929/dashboard/1866662) covers the client-owned acquisition, activation, and retention signals. See [`docs/analytics.md`](docs/analytics.md) for event ownership, saved insights, and the CLI verification runbook.
302
+ The live [Tui AARRR client dashboard](https://us.posthog.com/project/434929/dashboard/1866662) covers the client-owned acquisition, activation, and retention signals. See [`docs/analytics.md`](docs/analytics.md) for event ownership, saved insights, and the CLI verification runbook.
303
303
 
304
304
  ## Architecture
305
305
 
@@ -324,7 +324,7 @@ bun run test:launcher
324
324
  bun run typecheck
325
325
  bun run build # emits dist/, exposes the `gamepigeon` bin
326
326
  bun run build:overlay:web # overlay bundle only
327
- bun run preview:overlay # browser preview (470×650 / 720×780 frame)
327
+ bun run preview:overlay # browser preview (520×740 / 720×780 frame)
328
328
  bun run dev:overlay # debug Tauri shell + show overlay
329
329
  ```
330
330
 
@@ -346,6 +346,28 @@ bun run version:bump --no-commit
346
346
 
347
347
  The version lives in four manifests — `package.json`, `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, and `.claude-plugin/marketplace.json` — and the script rewrites all of them together. Bump by hand and they drift, which is why `scripts/version-bump.test.ts` asserts they agree.
348
348
 
349
- The script commits but never pushes; publishing stays an explicit action. On the next push to `main`, CI builds the binaries, publishes them as `overlay-v<version>`, and commits an `overlay-release.json` pointing at that tag, which is what installed plugins download on their next session.
349
+ The script commits but never pushes; publishing stays an explicit action. Push `main`, then tag:
350
+
351
+ ```bash
352
+ git push origin main
353
+ git tag overlay-v<version> && git push origin overlay-v<version>
354
+ ```
355
+
356
+ That tag triggers the Overlay workflow: it builds macOS/Windows binaries, creates the GitHub release `overlay-v<version>`, commits `overlay-release.json` on `main`, and publishes **`tui-gamepigeon@<version>` to npm** when `NPM_TOKEN` is configured (skips gracefully if the version already exists or the secret is missing). Installed plugins and npm installs both use the same manifest to download the native overlay on the next launch.
357
+
358
+ ### npm install (launcher only)
359
+
360
+ Requires [Bun](https://bun.sh) 1.3+. The CLI opens the same HTML overlay and games as the Claude marketplace plugin; the native shell is downloaded on first run from the matching GitHub release.
361
+
362
+ ```bash
363
+ npm i -g tui-gamepigeon
364
+ gamepigeon # arcade menu
365
+ gamepigeon 2048 # jump into a game
366
+ gamepigeon auth status
367
+ ```
368
+
369
+ Harness hooks and auto-open on prompt still come from the [GitHub marketplace plugin](#coding-harness-adapters); npm is an alternate way to install the launcher without cloning the repo.
370
+
371
+ Maintainers: add an npm automation token as the repository secret **`NPM_TOKEN`** (write publish). Rotate it in npm account settings if compromised; never commit tokens.
350
372
 
351
373
  Adding a new file that carries the version means adding it to `VERSION_FILES` in `scripts/version-bump.ts`, or it silently stops being bumped.
package/dist/index.js CHANGED
@@ -16,8 +16,8 @@ var __export = (target, all) => {
16
16
  };
17
17
 
18
18
  // src/index.ts
19
- import { join as join4, dirname as dirname3 } from "path";
20
- import { fileURLToPath } from "url";
19
+ import { join as join5, dirname as dirname4 } from "path";
20
+ import { fileURLToPath as fileURLToPath2 } from "url";
21
21
 
22
22
  // src/recording/ids.ts
23
23
  import { randomUUID } from "crypto";
@@ -6835,6 +6835,20 @@ async function signInWithOAuth(provider, options = {}) {
6835
6835
  callback.close();
6836
6836
  }
6837
6837
  }
6838
+ async function refreshSessionIfPresent() {
6839
+ const session = readSession();
6840
+ if (!session)
6841
+ return null;
6842
+ try {
6843
+ const user = await resolveUser(session.sessionToken);
6844
+ saveSession(session.sessionToken, user);
6845
+ return user;
6846
+ } catch {
6847
+ rmSync2(sessionFile(), { force: true });
6848
+ clearBackendSession();
6849
+ return null;
6850
+ }
6851
+ }
6838
6852
  async function signOutLocal() {
6839
6853
  const session = readSession();
6840
6854
  try {
@@ -8322,8 +8336,271 @@ function findGame(id) {
8322
8336
  return REGISTRY.find((e) => e.id === norm);
8323
8337
  }
8324
8338
 
8339
+ // scripts/overlay.ts
8340
+ import {
8341
+ accessSync,
8342
+ chmodSync as chmodSync3,
8343
+ constants,
8344
+ existsSync as existsSync3,
8345
+ mkdirSync as mkdirSync4,
8346
+ readFileSync as readFileSync4,
8347
+ readdirSync,
8348
+ renameSync as renameSync2,
8349
+ rmSync as rmSync3,
8350
+ writeFileSync as writeFileSync4
8351
+ } from "fs";
8352
+ import { homedir as homedir4, tmpdir } from "os";
8353
+ import { dirname as dirname3, join as join4 } from "path";
8354
+ import { fileURLToPath } from "url";
8355
+ var ROOT_DIR = join4(dirname3(fileURLToPath(import.meta.url)), "..");
8356
+ var DEFAULT_REPO = "Trolleroof/tui-gamepigeon";
8357
+ var SHUTDOWN_FLAG = join4(tmpdir(), ".gamepigeon-overlay.off");
8358
+ var OVERLAY_LEASE_FILE = join4(tmpdir(), ".gamepigeon-overlay.alive");
8359
+ var PAUSED_FLAG = join4(tmpdir(), ".gamepigeon-overlay.paused");
8360
+ var ANALYTICS_QUEUE = join4(homedir4(), ".tui-gamepigeon", "analytics-events.jsonl");
8361
+ async function flushAnalyticsQueue() {
8362
+ if (!process.env.POSTHOG_API_KEY || !existsSync3(ANALYTICS_QUEUE))
8363
+ return;
8364
+ try {
8365
+ const events = readFileSync4(ANALYTICS_QUEUE, "utf8").trim().split(`
8366
+ `).filter(Boolean).map((line) => JSON.parse(line));
8367
+ for (const event of events)
8368
+ analytics.capture(event);
8369
+ const flushed = await Promise.race([
8370
+ analytics.shutdown().then(() => true),
8371
+ Bun.sleep(2000).then(() => false)
8372
+ ]);
8373
+ if (flushed)
8374
+ rmSync3(ANALYTICS_QUEUE, { force: true });
8375
+ } catch {}
8376
+ }
8377
+ function isShutDown() {
8378
+ return existsSync3(SHUTDOWN_FLAG);
8379
+ }
8380
+ function clearShutdown() {
8381
+ try {
8382
+ rmSync3(SHUTDOWN_FLAG, { force: true });
8383
+ } catch {}
8384
+ }
8385
+ function setOverlayLease(active, path = OVERLAY_LEASE_FILE) {
8386
+ if (active)
8387
+ writeFileSync4(path, "", { mode: 384 });
8388
+ else
8389
+ rmSync3(path, { force: true });
8390
+ }
8391
+ function overlayExecutableName() {
8392
+ return process.platform === "win32" ? "gamepigeon-overlay.exe" : "gamepigeon-overlay";
8393
+ }
8394
+ function overlayReleaseAsset() {
8395
+ const platform = process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : process.platform;
8396
+ const extension = process.platform === "win32" ? ".exe" : "";
8397
+ return `gamepigeon-overlay-${platform}-${process.arch}${extension}`;
8398
+ }
8399
+ function managedBinDir() {
8400
+ return process.env.GAMEPIGEON_OVERLAY_HOME ?? join4(homedir4(), ".gamepigeon", "bin");
8401
+ }
8402
+ function managedBinaryPath(version2) {
8403
+ return join4(managedBinDir(), version2, overlayExecutableName());
8404
+ }
8405
+ function readReleaseManifest(root = ROOT_DIR) {
8406
+ try {
8407
+ const parsed = JSON.parse(readFileSync4(join4(root, "overlay-release.json"), "utf8"));
8408
+ if (typeof parsed.tag === "string" && typeof parsed.version === "string") {
8409
+ return { tag: parsed.tag, version: parsed.version };
8410
+ }
8411
+ } catch {}
8412
+ return null;
8413
+ }
8414
+ function repoSlug(root = ROOT_DIR) {
8415
+ try {
8416
+ const parsed = JSON.parse(readFileSync4(join4(root, ".claude-plugin", "plugin.json"), "utf8"));
8417
+ const match = /github\.com\/([^/]+\/[^/.]+)/.exec(parsed.repository ?? "");
8418
+ if (match?.[1])
8419
+ return match[1];
8420
+ } catch {}
8421
+ return DEFAULT_REPO;
8422
+ }
8423
+ function overlayBinaryCandidates(root = ROOT_DIR, manifest = readReleaseManifest(root)) {
8424
+ const executable = overlayExecutableName();
8425
+ const platform = process.platform === "darwin" ? "macos" : process.platform;
8426
+ const current = manifest ? readCurrentVersion() : null;
8427
+ return [
8428
+ process.env.GAMEPIGEON_OVERLAY_BIN,
8429
+ manifest ? managedBinaryPath(manifest.version) : undefined,
8430
+ join4(root, "bin", `${platform}-${process.arch}`, executable),
8431
+ join4(root, "tauri", "target", "release", executable),
8432
+ join4(root, "tauri", "target", "debug", executable),
8433
+ current && current !== manifest?.version ? managedBinaryPath(current) : undefined
8434
+ ].filter((candidate) => Boolean(candidate));
8435
+ }
8436
+ function isLaunchable(candidate) {
8437
+ try {
8438
+ accessSync(candidate, process.platform === "win32" ? constants.F_OK : constants.X_OK);
8439
+ return true;
8440
+ } catch {
8441
+ return false;
8442
+ }
8443
+ }
8444
+ function resolveOverlayBinary(root = ROOT_DIR, manifest = readReleaseManifest(root)) {
8445
+ for (const candidate of overlayBinaryCandidates(root, manifest)) {
8446
+ if (isLaunchable(candidate))
8447
+ return candidate;
8448
+ }
8449
+ throw new Error("Tui overlay binary was not found. Run `bun run build:overlay` or install a packaged release.");
8450
+ }
8451
+ async function downloadReleaseBinary(manifest, root) {
8452
+ const destination = managedBinaryPath(manifest.version);
8453
+ const staging = `${destination}.download`;
8454
+ mkdirSync4(dirname3(destination), { recursive: true });
8455
+ const asset = overlayReleaseAsset();
8456
+ const slug = repoSlug(root);
8457
+ let downloaded = false;
8458
+ try {
8459
+ const gh = Bun.spawn(["gh", "release", "download", manifest.tag, "--repo", slug, "--pattern", asset, "--output", staging, "--clobber"], { stdin: "ignore", stdout: "ignore", stderr: "ignore" });
8460
+ downloaded = await gh.exited === 0;
8461
+ } catch {
8462
+ downloaded = false;
8463
+ }
8464
+ if (!downloaded) {
8465
+ const response = await fetch(`https://github.com/${slug}/releases/download/${manifest.tag}/${asset}`);
8466
+ if (!response.ok) {
8467
+ throw new Error(`Overlay download failed (${response.status}) for ${manifest.tag}/${asset}`);
8468
+ }
8469
+ writeFileSync4(staging, new Uint8Array(await response.arrayBuffer()));
8470
+ }
8471
+ if (process.platform !== "win32")
8472
+ chmodSync3(staging, 493);
8473
+ renameSync2(staging, destination);
8474
+ }
8475
+ function pruneManagedBinaries(keepVersion) {
8476
+ try {
8477
+ for (const entry of readdirSync(managedBinDir())) {
8478
+ if (entry !== keepVersion && entry !== "current") {
8479
+ rmSync3(join4(managedBinDir(), entry), { recursive: true, force: true });
8480
+ }
8481
+ }
8482
+ } catch {}
8483
+ }
8484
+ function readCurrentVersion() {
8485
+ try {
8486
+ return readFileSync4(join4(managedBinDir(), "current"), "utf8").trim() || null;
8487
+ } catch {
8488
+ return null;
8489
+ }
8490
+ }
8491
+ function quitOverlay(binary, wait = true) {
8492
+ const child = Bun.spawn([binary, "--quit"], {
8493
+ detached: true,
8494
+ stdin: "ignore",
8495
+ stdout: "ignore",
8496
+ stderr: "ignore"
8497
+ });
8498
+ child.unref();
8499
+ if (!wait)
8500
+ return Promise.resolve();
8501
+ return Promise.race([child.exited, Bun.sleep(2000)]);
8502
+ }
8503
+ async function ensureOverlayBinary(root = ROOT_DIR) {
8504
+ const manifest = readReleaseManifest(root);
8505
+ if (!manifest)
8506
+ return;
8507
+ const destination = managedBinaryPath(manifest.version);
8508
+ if (!isLaunchable(destination)) {
8509
+ try {
8510
+ await downloadReleaseBinary(manifest, root);
8511
+ } catch {
8512
+ return;
8513
+ }
8514
+ }
8515
+ const previous = readCurrentVersion();
8516
+ if (previous && previous !== manifest.version) {
8517
+ const stale = managedBinaryPath(previous);
8518
+ if (isLaunchable(stale))
8519
+ await quitOverlay(stale);
8520
+ }
8521
+ writeFileSync4(join4(managedBinDir(), "current"), manifest.version);
8522
+ pruneManagedBinaries(manifest.version);
8523
+ }
8524
+ function applyOverlayLease(action, leasePath = OVERLAY_LEASE_FILE) {
8525
+ if (action === "quit")
8526
+ setOverlayLease(false, leasePath);
8527
+ else if (action === "show" || action === "enable")
8528
+ setOverlayLease(true, leasePath);
8529
+ }
8530
+ function overlaySpawnArgs(action, options = {}) {
8531
+ const args = [`--${action}`];
8532
+ if (options.gameId)
8533
+ args.push("--game", options.gameId);
8534
+ if (options.variationId)
8535
+ args.push("--variation", options.variationId);
8536
+ return args;
8537
+ }
8538
+ async function launchOverlay(action, root = ROOT_DIR, options = {}) {
8539
+ if (action !== "quit")
8540
+ await flushAnalyticsQueue();
8541
+ if ((action === "show" || action === "enable") && process.env.GAMEPIGEON_OVERLAY_DRY_RUN !== "1") {
8542
+ await refreshSessionIfPresent();
8543
+ }
8544
+ applyOverlayLease(action);
8545
+ if (action === "quit") {
8546
+ try {
8547
+ rmSync3(PAUSED_FLAG, { force: true });
8548
+ } catch {}
8549
+ }
8550
+ if (action === "reset" || action === "enable")
8551
+ clearShutdown();
8552
+ else if ((action === "show" || action === "pause") && isShutDown())
8553
+ return;
8554
+ if (action === "pause") {
8555
+ if (!existsSync3(OVERLAY_LEASE_FILE) || existsSync3(PAUSED_FLAG))
8556
+ return;
8557
+ try {
8558
+ writeFileSync4(PAUSED_FLAG, "", { mode: 384 });
8559
+ } catch {}
8560
+ } else if (action === "show" || action === "enable") {
8561
+ try {
8562
+ rmSync3(PAUSED_FLAG, { force: true });
8563
+ } catch {}
8564
+ }
8565
+ if (action !== "quit" && process.env.GAMEPIGEON_OVERLAY_DRY_RUN !== "1" && !process.env.GAMEPIGEON_OVERLAY_BIN) {
8566
+ await ensureOverlayBinary(root);
8567
+ }
8568
+ let binary;
8569
+ try {
8570
+ binary = resolveOverlayBinary(root);
8571
+ } catch (error) {
8572
+ if (action === "quit")
8573
+ return;
8574
+ throw error;
8575
+ }
8576
+ const spawnArgs = overlaySpawnArgs(action, options);
8577
+ if (process.env.GAMEPIGEON_OVERLAY_DRY_RUN === "1") {
8578
+ console.log(JSON.stringify([binary, ...spawnArgs]));
8579
+ return;
8580
+ }
8581
+ if (action === "quit") {
8582
+ await quitOverlay(binary, false);
8583
+ return;
8584
+ }
8585
+ const child = Bun.spawn([binary, ...spawnArgs], {
8586
+ cwd: root,
8587
+ detached: true,
8588
+ env: {
8589
+ ...process.env,
8590
+ GAMEPIGEON_OVERLAY_LEASE: OVERLAY_LEASE_FILE,
8591
+ ...options.gameId ? { GAMEPIGEON_GAME: options.gameId } : {},
8592
+ ...options.variationId ? { GAMEPIGEON_VARIATION: options.variationId } : {}
8593
+ },
8594
+ stdin: "ignore",
8595
+ stdout: "ignore",
8596
+ stderr: "ignore"
8597
+ });
8598
+ child.unref();
8599
+ }
8600
+ if (false) {}
8601
+
8325
8602
  // src/index.ts
8326
- var ROOT = join4(dirname3(fileURLToPath(import.meta.url)), "..");
8603
+ var ROOT = join5(dirname4(fileURLToPath2(import.meta.url)), "..");
8327
8604
  function captureAuthSuccess(result) {
8328
8605
  if (result.userId)
8329
8606
  analytics.setDistinctId(result.userId);
@@ -8340,7 +8617,7 @@ function printUsage() {
8340
8617
  console.log(`Available games: ${ids}`);
8341
8618
  }
8342
8619
  async function installAdapters(target) {
8343
- const proc = Bun.spawn(["bash", join4(ROOT, "scripts/install-adapters.sh"), target ?? "--all"], {
8620
+ const proc = Bun.spawn(["bash", join5(ROOT, "scripts/install-adapters.sh"), target ?? "--all"], {
8344
8621
  cwd: ROOT,
8345
8622
  env: { ...process.env, GAMEPIGEON_WORKSPACE_DIR: process.cwd() },
8346
8623
  stdin: "ignore",
@@ -8351,19 +8628,8 @@ async function installAdapters(target) {
8351
8628
  if (proc.exitCode !== 0)
8352
8629
  process.exit(proc.exitCode ?? 1);
8353
8630
  }
8354
- async function launchOverlay(gameId) {
8355
- const args = ["bun", join4(ROOT, "scripts/overlay.ts"), "--show"];
8356
- if (gameId)
8357
- args.push("--game", gameId);
8358
- const proc = Bun.spawn(args, {
8359
- cwd: ROOT,
8360
- stdin: "ignore",
8361
- stdout: "inherit",
8362
- stderr: "inherit"
8363
- });
8364
- await proc.exited;
8365
- if (proc.exitCode !== 0)
8366
- process.exit(proc.exitCode ?? 1);
8631
+ async function launchOverlay2(gameId) {
8632
+ await launchOverlay("show", ROOT, { gameId });
8367
8633
  }
8368
8634
  async function main() {
8369
8635
  let arg = process.argv[2];
@@ -8397,7 +8663,7 @@ async function main() {
8397
8663
  }
8398
8664
  gameId = entry.id;
8399
8665
  }
8400
- await launchOverlay(gameId);
8666
+ await launchOverlay2(gameId);
8401
8667
  }
8402
8668
  main().catch((error) => {
8403
8669
  console.error(error instanceof Error ? error.message : String(error));