@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.
- package/.claude-plugin/plugin.json +10 -0
- package/.codex-plugin/plugin.json +21 -0
- package/README.md +32 -8
- package/dist/index.js +286 -19
- package/overlay/dist/app.js +74 -0
- package/overlay/dist/index.html +1589 -0
- package/overlay-release.json +2 -2
- package/package.json +28 -4
- package/scripts/install-adapters.sh +25 -14
- package/scripts/install-adapters.test.sh +12 -2
- package/scripts/materialize-agents-plugin.sh +8 -6
- 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
|
+
}
|
|
@@ -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
|
|
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
|
|
|
@@ -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`.
|
|
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.
|
|
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 [
|
|
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 (
|
|
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.
|
|
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
|
|
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);
|
|
@@ -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",
|
|
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
|
|
8354
|
-
|
|
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
|
|
8666
|
+
await launchOverlay2(gameId);
|
|
8400
8667
|
}
|
|
8401
8668
|
main().catch((error) => {
|
|
8402
8669
|
console.error(error instanceof Error ? error.message : String(error));
|