@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.
- package/.claude-plugin/plugin.json +10 -0
- package/.codex-plugin/plugin.json +2 -2
- package/README.md +29 -7
- package/dist/index.js +284 -18
- package/overlay/dist/app.js +74 -0
- package/overlay/dist/index.html +1589 -0
- package/overlay-release.json +2 -2
- package/package.json +27 -4
- package/scripts/install-adapters.sh +6 -6
- package/scripts/install-adapters.test.sh +2 -2
- package/scripts/materialize-agents-plugin.sh +3 -3
- package/scripts/npm-pack.test.ts +51 -0
- package/scripts/overlay.ts +1 -1
- package/src/analytics/events.test.ts +77 -0
- package/src/analytics/events.ts +85 -0
- package/src/analytics/posthog.test.ts +85 -0
- package/src/analytics/posthog.ts +88 -0
- package/src/auth-pages.ts +108 -0
- package/src/auth.test.ts +255 -0
- package/src/auth.ts +511 -0
- package/src/core/game.ts +35 -0
- package/src/core/key.ts +12 -0
- package/src/core/rng.ts +41 -0
- package/src/data/bc-export.test.ts +153 -0
- package/src/data/bc-export.ts +125 -0
- package/src/data/trajectory.ts +103 -0
- package/src/games/connect4/connect4.ts +81 -0
- package/src/games/connect4/engine.test.ts +77 -0
- package/src/games/connect4/engine.ts +89 -0
- package/src/games/g2048/engine.test.ts +138 -0
- package/src/games/g2048/engine.ts +109 -0
- package/src/games/g2048/game2048.ts +104 -0
- package/src/games/minesweeper/engine.test.ts +83 -0
- package/src/games/minesweeper/engine.ts +112 -0
- package/src/games/minesweeper/minesweeper.ts +107 -0
- package/src/games/registry.test.ts +16 -0
- package/src/games/registry.ts +130 -0
- package/src/games/snake/engine.test.ts +89 -0
- package/src/games/snake/engine.ts +95 -0
- package/src/games/snake/snake.ts +77 -0
- package/src/games/sokoban/engine.test.ts +49 -0
- package/src/games/sokoban/engine.ts +152 -0
- package/src/games/sokoban/sokoban.ts +103 -0
- package/src/games/sudoku/engine.test.ts +88 -0
- package/src/games/sudoku/engine.ts +99 -0
- package/src/games/sudoku/sudoku.ts +100 -0
- package/src/games/tetris/engine.test.ts +378 -0
- package/src/games/tetris/engine.ts +236 -0
- package/src/games/tetris/tetris.ts +220 -0
- package/src/index.ts +82 -0
- package/src/recording/chunks.ts +93 -0
- package/src/recording/config.test.ts +31 -0
- package/src/recording/config.ts +37 -0
- package/src/recording/contracts.test.ts +71 -0
- package/src/recording/gameplay.test.ts +70 -0
- package/src/recording/gameplay.ts +18 -0
- package/src/recording/http-transport.test.ts +197 -0
- package/src/recording/identity.test.ts +26 -0
- package/src/recording/identity.ts +38 -0
- package/src/recording/ids.ts +27 -0
- package/src/recording/permissions.ts +10 -0
- package/src/recording/recorder.test.ts +388 -0
- package/src/recording/recorder.ts +464 -0
- package/src/recording/store.test.ts +231 -0
- package/src/recording/store.ts +305 -0
- package/src/recording/terminal-grid.test.ts +88 -0
- package/src/recording/terminal-grid.ts +248 -0
- package/src/recording/trace-recorder.test.ts +287 -0
- package/src/recording/trace-recorder.ts +560 -0
- package/src/recording/transport.ts +170 -0
- package/src/recording/types.test.ts +18 -0
- package/src/recording/types.ts +326 -0
- package/src/recording/uploader.test.ts +193 -0
- package/src/recording/uploader.ts +103 -0
- package/src/results/completion.ts +100 -0
- package/src/results/http-result-transport.test.ts +75 -0
- package/src/results/local-score.test.ts +32 -0
- package/src/results/local-score.ts +39 -0
- package/src/results/results.test.ts +383 -0
- package/src/results/store.ts +149 -0
- package/src/results/transport.ts +79 -0
- package/src/results/types.ts +20 -0
- package/src/results/uploader.ts +84 -0
- package/src/sync/config.test.ts +108 -0
- package/src/sync/config.ts +115 -0
- package/src/sync/coordinator.test.ts +423 -0
- package/src/sync/coordinator.ts +277 -0
- package/src/sync/game-start-queue.test.ts +93 -0
- package/src/sync/game-start-queue.ts +45 -0
- package/src/sync/identity-client.test.ts +34 -0
- 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.
|
|
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": "
|
|
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
|
|
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 (**
|
|
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.
|
|
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 [
|
|
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 (
|
|
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.
|
|
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
|
|
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 =
|
|
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",
|
|
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
|
|
8355
|
-
|
|
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
|
|
8666
|
+
await launchOverlay2(gameId);
|
|
8401
8667
|
}
|
|
8402
8668
|
main().catch((error) => {
|
|
8403
8669
|
console.error(error instanceof Error ? error.message : String(error));
|