@trolleroof/tui 0.2.4 → 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 +21 -0
  3. package/README.md +32 -8
  4. package/dist/index.js +286 -19
  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 +28 -4
  9. package/scripts/install-adapters.sh +25 -14
  10. package/scripts/install-adapters.test.sh +12 -2
  11. package/scripts/materialize-agents-plugin.sh +8 -6
  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
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "tui-gamepigeon",
3
+ "version": "0.2.6",
4
+ "description": "Play in a lightweight HTML overlay while Codex works.",
5
+ "author": {
6
+ "name": "Trolleroof"
7
+ },
8
+ "repository": "https://github.com/Trolleroof/tui-gamepigeon",
9
+ "license": "MIT",
10
+ "keywords": ["arcade", "2048", "tetris", "terminal", "game"],
11
+ "skills": "./skills/",
12
+ "interface": {
13
+ "displayName": "Tui Arcade",
14
+ "shortDescription": "Play 2048 or Tetris while Codex works",
15
+ "longDescription": "Open or restore a lightweight 2048 HTML overlay without leaving the active Codex task.",
16
+ "developerName": "Trolleroof",
17
+ "category": "Developer Tools",
18
+ "capabilities": ["Interactive"],
19
+ "defaultPrompt": ["Open the Arcade."]
20
+ }
21
+ }
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
 
@@ -82,7 +82,9 @@ npx -y bun x @trolleroof/tui@latest install --codex
82
82
  ```
83
83
 
84
84
  Replace `--codex` with `--claude`, `--opencode`, `--hermes`, `--openclaw`, or
85
- `--all`. This uses npm to fetch Bun for the command, then writes the harness adapter.
85
+ `--antigravity`, or `--all`. For Antigravity, run it from the project directory;
86
+ it writes that project's `.agents/plugins/tui-gamepigeon` plugin. This uses npm to
87
+ fetch Bun for the command, then writes the harness adapter.
86
88
  Restart the harness when it finishes.
87
89
 
88
90
  Install the plugin from the GitHub marketplace so the normal `claude` command runs
@@ -123,7 +125,7 @@ Production defaults to `https://runretroarcade.com/login.html`, `https://auth.ru
123
125
 
124
126
  ## Coding harness adapters
125
127
 
126
- 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.
127
129
 
128
130
  Requirements:
129
131
 
@@ -255,7 +257,7 @@ export GAMEPIGEON_BACKEND_TOKEN=retro_...
255
257
  # export GAMEPIGEON_BACKEND_URL=http://127.0.0.1:8787
256
258
  ```
257
259
 
258
- 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.
259
261
 
260
262
  Export policy-visible human demonstrations into leakage-safe BC splits:
261
263
 
@@ -297,7 +299,7 @@ Product events use a CloudEvents 1.0 envelope with `specversion`, `id`, `source`
297
299
 
298
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.
299
301
 
300
- 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.
301
303
 
302
304
  ## Architecture
303
305
 
@@ -322,7 +324,7 @@ bun run test:launcher
322
324
  bun run typecheck
323
325
  bun run build # emits dist/, exposes the `gamepigeon` bin
324
326
  bun run build:overlay:web # overlay bundle only
325
- bun run preview:overlay # browser preview (470×650 / 720×780 frame)
327
+ bun run preview:overlay # browser preview (520×740 / 720×780 frame)
326
328
  bun run dev:overlay # debug Tauri shell + show overlay
327
329
  ```
328
330
 
@@ -344,6 +346,28 @@ bun run version:bump --no-commit
344
346
 
345
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.
346
348
 
347
- 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.
348
372
 
349
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);
@@ -8334,14 +8611,15 @@ function printUsage() {
8334
8611
  console.log("usage: gamepigeon [game]");
8335
8612
  console.log(" gamepigeon auth [signup|login|oauth|status|logout]");
8336
8613
  console.log(" gamepigeon auth oauth <google|github>");
8337
- console.log(" gamepigeon install [--codex|--claude|--opencode|--hermes|--openclaw|--all]");
8614
+ console.log(" gamepigeon install [--codex|--claude|--opencode|--hermes|--openclaw|--antigravity|--all]");
8338
8615
  console.log("");
8339
8616
  console.log("With no argument, opens the arcade overlay menu.");
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,
8622
+ env: { ...process.env, GAMEPIGEON_WORKSPACE_DIR: process.cwd() },
8345
8623
  stdin: "ignore",
8346
8624
  stdout: "inherit",
8347
8625
  stderr: "inherit"
@@ -8350,19 +8628,8 @@ async function installAdapters(target) {
8350
8628
  if (proc.exitCode !== 0)
8351
8629
  process.exit(proc.exitCode ?? 1);
8352
8630
  }
8353
- async function launchOverlay(gameId) {
8354
- const args = ["bun", join4(ROOT, "scripts/overlay.ts"), "--show"];
8355
- if (gameId)
8356
- args.push("--game", gameId);
8357
- const proc = Bun.spawn(args, {
8358
- cwd: ROOT,
8359
- stdin: "ignore",
8360
- stdout: "inherit",
8361
- stderr: "inherit"
8362
- });
8363
- await proc.exited;
8364
- if (proc.exitCode !== 0)
8365
- process.exit(proc.exitCode ?? 1);
8631
+ async function launchOverlay2(gameId) {
8632
+ await launchOverlay("show", ROOT, { gameId });
8366
8633
  }
8367
8634
  async function main() {
8368
8635
  let arg = process.argv[2];
@@ -8396,7 +8663,7 @@ async function main() {
8396
8663
  }
8397
8664
  gameId = entry.id;
8398
8665
  }
8399
- await launchOverlay(gameId);
8666
+ await launchOverlay2(gameId);
8400
8667
  }
8401
8668
  main().catch((error) => {
8402
8669
  console.error(error instanceof Error ? error.message : String(error));