@trolleroof/tui 0.2.3
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/README.md +349 -0
- package/dist/index.js +8404 -0
- package/overlay-release.json +4 -0
- package/package.json +48 -0
- package/scripts/build-overlay-web.ts +34 -0
- package/scripts/dev-harness.sh +81 -0
- package/scripts/dev-harness.test.sh +33 -0
- package/scripts/export-bc.ts +124 -0
- package/scripts/install-adapters.sh +192 -0
- package/scripts/install-adapters.test.sh +69 -0
- package/scripts/manage-hermes-hooks.py +39 -0
- package/scripts/materialize-agents-plugin.sh +85 -0
- package/scripts/overlay.test.ts +106 -0
- package/scripts/overlay.ts +385 -0
- package/scripts/plugin-update.test.ts +318 -0
- package/scripts/plugin-update.ts +378 -0
- package/scripts/preview-overlay.ts +153 -0
- package/scripts/run-plugin-update.test.ts +31 -0
- package/scripts/run-plugin-update.ts +65 -0
- package/scripts/version-bump.test.ts +76 -0
- package/scripts/version-bump.ts +89 -0
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trolleroof/tui",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Play familiar single-player games in a lightweight HTML overlay while your agents work.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gamepigeon": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"scripts",
|
|
12
|
+
"overlay-release.json"
|
|
13
|
+
],
|
|
14
|
+
"packageManager": "bun@1.3.11",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "bun src/index.ts",
|
|
17
|
+
"build": "bun build src/index.ts --outdir dist --target bun",
|
|
18
|
+
"build:overlay:web": "bun scripts/build-overlay-web.ts",
|
|
19
|
+
"build:overlay": "bun run build:overlay:web && cargo build --release --manifest-path tauri/Cargo.toml",
|
|
20
|
+
"claude:dev": "bash scripts/dev-harness.sh claude",
|
|
21
|
+
"codex:dev": "bash scripts/dev-harness.sh codex",
|
|
22
|
+
"antigravity:dev": "bash scripts/dev-harness.sh antigravity",
|
|
23
|
+
"dev:overlay": "bun run build:overlay:web && cargo build --manifest-path tauri/Cargo.toml && GAMEPIGEON_OVERLAY_BIN=\"$PWD/tauri/target/debug/gamepigeon-overlay\" bun scripts/overlay.ts --show",
|
|
24
|
+
"preview:overlay": "bun scripts/preview-overlay.ts",
|
|
25
|
+
"install:adapters": "bash scripts/install-adapters.sh",
|
|
26
|
+
"test": "bun test ./src/",
|
|
27
|
+
"test:adapters": "bun run build && bash scripts/install-adapters.test.sh && bash scripts/dev-harness.test.sh",
|
|
28
|
+
"test:overlay": "bun test scripts/overlay.test.ts scripts/plugin-update.test.ts scripts/version-bump.test.ts overlay/session.test.ts overlay/persistence.test.ts overlay/recording.test.ts overlay/renderers.test.ts overlay/motion.test.ts overlay/connect4-layout.test.ts overlay/analytics.test.ts overlay/tauri-bridge.test.ts",
|
|
29
|
+
"test:plugin-update": "bun test scripts/plugin-update.test.ts scripts/run-plugin-update.test.ts",
|
|
30
|
+
"test:launcher": "bun run test:overlay",
|
|
31
|
+
"typecheck": "tsc --noEmit && tsc -p overlay/tsconfig.json",
|
|
32
|
+
"export:bc": "bun scripts/export-bc.ts",
|
|
33
|
+
"version:bump": "bun scripts/version-bump.ts",
|
|
34
|
+
"prepack": "bun run build"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"bun": ">=1.3"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/bun": "^1.3.5",
|
|
41
|
+
"typescript": "^5.6.0",
|
|
42
|
+
"vitest": "^4.1.10"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@tauri-apps/api": "^2.11.1",
|
|
46
|
+
"posthog-node": "^5.44.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
const root = join(import.meta.dir, "..");
|
|
6
|
+
|
|
7
|
+
/** Worktrees and fresh clones often have no node_modules; overlay bundle needs @tauri-apps/api. */
|
|
8
|
+
function ensureNodeModules(): void {
|
|
9
|
+
const tauriApi = join(root, "node_modules/@tauri-apps/api/package.json");
|
|
10
|
+
if (existsSync(tauriApi)) return;
|
|
11
|
+
console.error("node_modules missing — running bun install…");
|
|
12
|
+
const install = spawnSync("bun", ["install"], { cwd: root, stdio: "inherit" });
|
|
13
|
+
if (install.status !== 0) process.exit(install.status ?? 1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
ensureNodeModules();
|
|
17
|
+
|
|
18
|
+
const pkg = JSON.parse(await Bun.file("package.json").text()) as { version?: string };
|
|
19
|
+
|
|
20
|
+
const result = await Bun.build({
|
|
21
|
+
entrypoints: ["overlay/app.ts"],
|
|
22
|
+
outdir: "overlay/dist",
|
|
23
|
+
target: "browser",
|
|
24
|
+
minify: true,
|
|
25
|
+
define: {
|
|
26
|
+
__POSTHOG_CAPTURE_KEY__: JSON.stringify(process.env.POSTHOG_CAPTURE_KEY ?? ""),
|
|
27
|
+
__APP_VERSION__: JSON.stringify(pkg.version ?? "0.0.0"),
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!result.success) {
|
|
32
|
+
for (const log of result.logs) console.error(log);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
HARNESS="${1:-}"
|
|
6
|
+
|
|
7
|
+
usage() {
|
|
8
|
+
cat <<'EOF' >&2
|
|
9
|
+
Usage: scripts/dev-harness.sh {claude|codex|antigravity|prepare}
|
|
10
|
+
|
|
11
|
+
Shared local overlay development launcher:
|
|
12
|
+
prepare Build overlay web bundle + debug Tauri shell and export dev env
|
|
13
|
+
claude prepare + claude --plugin-dir .
|
|
14
|
+
codex prepare + workspace plugin + Codex adapter + codex
|
|
15
|
+
antigravity prepare + workspace plugin + agy
|
|
16
|
+
EOF
|
|
17
|
+
exit 2
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
require_command() {
|
|
21
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
22
|
+
echo "Missing required command: $1" >&2
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
prepare_overlay() {
|
|
28
|
+
require_command bun
|
|
29
|
+
require_command cargo
|
|
30
|
+
if [[ ! -f "$ROOT_DIR/node_modules/@tauri-apps/api/package.json" ]]; then
|
|
31
|
+
(cd "$ROOT_DIR" && bun install)
|
|
32
|
+
fi
|
|
33
|
+
(cd "$ROOT_DIR" && bun run build:overlay:web)
|
|
34
|
+
cargo build --manifest-path "$ROOT_DIR/tauri/Cargo.toml"
|
|
35
|
+
export GAMEPIGEON_OVERLAY_BIN="$ROOT_DIR/tauri/target/debug/gamepigeon-overlay"
|
|
36
|
+
export GAMEPIGEON_SKIP_UPDATE_CHECK=1
|
|
37
|
+
bun "$ROOT_DIR/scripts/overlay.ts" --quit
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
ensure_codex_plugin() {
|
|
41
|
+
bash "$ROOT_DIR/scripts/materialize-agents-plugin.sh"
|
|
42
|
+
bash "$ROOT_DIR/scripts/install-adapters.sh" --codex
|
|
43
|
+
require_command codex
|
|
44
|
+
|
|
45
|
+
if ! codex plugin marketplace list 2>/dev/null | grep -Fq "$ROOT_DIR"; then
|
|
46
|
+
codex plugin marketplace add "$ROOT_DIR" >/dev/null
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if ! codex plugin list 2>/dev/null | grep -Fq 'tui-gamepigeon@tui-gamepigeon-dev'; then
|
|
50
|
+
codex plugin add tui-gamepigeon@tui-gamepigeon-dev >/dev/null
|
|
51
|
+
fi
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
ensure_antigravity_plugin() {
|
|
55
|
+
bash "$ROOT_DIR/scripts/materialize-agents-plugin.sh"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case "$HARNESS" in
|
|
59
|
+
prepare)
|
|
60
|
+
prepare_overlay
|
|
61
|
+
;;
|
|
62
|
+
claude)
|
|
63
|
+
prepare_overlay
|
|
64
|
+
require_command claude
|
|
65
|
+
exec claude --plugin-dir "$ROOT_DIR"
|
|
66
|
+
;;
|
|
67
|
+
codex)
|
|
68
|
+
prepare_overlay
|
|
69
|
+
ensure_codex_plugin
|
|
70
|
+
exec codex
|
|
71
|
+
;;
|
|
72
|
+
antigravity)
|
|
73
|
+
prepare_overlay
|
|
74
|
+
ensure_antigravity_plugin
|
|
75
|
+
require_command agy
|
|
76
|
+
exec agy
|
|
77
|
+
;;
|
|
78
|
+
*)
|
|
79
|
+
usage
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
TEMP_DIR="$(mktemp -d)"
|
|
6
|
+
trap 'rm -rf "$TEMP_DIR"' EXIT
|
|
7
|
+
|
|
8
|
+
WORKTREE="$TEMP_DIR/worktree"
|
|
9
|
+
mkdir -p "$WORKTREE/.codex-plugin" "$WORKTREE/scripts"
|
|
10
|
+
cp "$ROOT_DIR/.codex-plugin/plugin.json" "$WORKTREE/.codex-plugin/plugin.json"
|
|
11
|
+
cp "$ROOT_DIR/scripts/materialize-agents-plugin.sh" "$WORKTREE/scripts/materialize-agents-plugin.sh"
|
|
12
|
+
chmod +x "$WORKTREE/scripts/materialize-agents-plugin.sh"
|
|
13
|
+
|
|
14
|
+
(
|
|
15
|
+
cd "$WORKTREE"
|
|
16
|
+
bash scripts/materialize-agents-plugin.sh
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
test -f "$WORKTREE/.agents/plugins/marketplace.json"
|
|
20
|
+
test -f "$WORKTREE/.agents/plugins/tui-gamepigeon/plugin.json"
|
|
21
|
+
test -f "$WORKTREE/.agents/plugins/tui-gamepigeon/hooks.json"
|
|
22
|
+
test -f "$WORKTREE/.agents/plugins/tui-gamepigeon/skills/arcade/SKILL.md"
|
|
23
|
+
|
|
24
|
+
grep -Fq "bun $WORKTREE/scripts/overlay.ts --enable" \
|
|
25
|
+
"$WORKTREE/.agents/plugins/tui-gamepigeon/skills/arcade/SKILL.md"
|
|
26
|
+
grep -Fq "bun $WORKTREE/scripts/overlay.ts --reset" \
|
|
27
|
+
"$WORKTREE/.agents/plugins/tui-gamepigeon/hooks.json"
|
|
28
|
+
grep -Fq '"name": "tui-gamepigeon-dev"' \
|
|
29
|
+
"$WORKTREE/.agents/plugins/marketplace.json"
|
|
30
|
+
|
|
31
|
+
bash "$ROOT_DIR/scripts/dev-harness.sh" 2>/dev/null && exit 1 || true
|
|
32
|
+
|
|
33
|
+
echo "dev-harness tests passed"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
statSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import {
|
|
12
|
+
extractBehaviorCloningSamples,
|
|
13
|
+
splitForGame,
|
|
14
|
+
type BehaviorCloningSample,
|
|
15
|
+
type DatasetSplit,
|
|
16
|
+
} from "../src/data/bc-export.js";
|
|
17
|
+
import type { TraceEventChunk } from "../src/recording/types.js";
|
|
18
|
+
|
|
19
|
+
interface CliOptions {
|
|
20
|
+
inputs: string[];
|
|
21
|
+
output: string;
|
|
22
|
+
includeNoops: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function usage(): never {
|
|
26
|
+
console.error(
|
|
27
|
+
"Usage: bun run export:bc -- [--input <file-or-directory>]... [--output <directory>] [--include-noops]",
|
|
28
|
+
);
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseArgs(args: string[]): CliOptions {
|
|
33
|
+
const inputs: string[] = [];
|
|
34
|
+
let output = resolve("bc-dataset");
|
|
35
|
+
let includeNoops = false;
|
|
36
|
+
for (let index = 0; index < args.length; index++) {
|
|
37
|
+
const argument = args[index]!;
|
|
38
|
+
if (argument === "--input") {
|
|
39
|
+
const value = args[++index];
|
|
40
|
+
if (!value) usage();
|
|
41
|
+
inputs.push(resolve(value));
|
|
42
|
+
} else if (argument === "--output") {
|
|
43
|
+
const value = args[++index];
|
|
44
|
+
if (!value) usage();
|
|
45
|
+
output = resolve(value);
|
|
46
|
+
} else if (argument === "--include-noops") {
|
|
47
|
+
includeNoops = true;
|
|
48
|
+
} else {
|
|
49
|
+
usage();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (inputs.length === 0) {
|
|
53
|
+
const recordings = resolve(homedir(), ".tui-gamepigeon", "recordings");
|
|
54
|
+
inputs.push(resolve(recordings, "completed", "chunks"));
|
|
55
|
+
inputs.push(resolve(recordings, "overlay-completed"));
|
|
56
|
+
}
|
|
57
|
+
return { inputs, output, includeNoops };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function jsonFiles(input: string): string[] {
|
|
61
|
+
let stats;
|
|
62
|
+
try {
|
|
63
|
+
stats = statSync(input);
|
|
64
|
+
} catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
if (stats.isFile()) return input.endsWith(".json") ? [input] : [];
|
|
68
|
+
if (!stats.isDirectory()) return [];
|
|
69
|
+
return readdirSync(input, { withFileTypes: true })
|
|
70
|
+
.flatMap((entry) => jsonFiles(resolve(input, entry.name)))
|
|
71
|
+
.sort();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function loadChunks(inputs: readonly string[]): TraceEventChunk[] {
|
|
75
|
+
return inputs.flatMap(jsonFiles).flatMap((path) => {
|
|
76
|
+
try {
|
|
77
|
+
const value = JSON.parse(readFileSync(path, "utf8")) as TraceEventChunk;
|
|
78
|
+
return value.schema_version === 2 && Array.isArray(value.events) ? [value] : [];
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error(`Could not parse ${path}: ${String(error)}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function writeDataset(
|
|
86
|
+
output: string,
|
|
87
|
+
samples: readonly BehaviorCloningSample[],
|
|
88
|
+
includeNoops: boolean,
|
|
89
|
+
): void {
|
|
90
|
+
mkdirSync(output, { recursive: true, mode: 0o700 });
|
|
91
|
+
chmodSync(output, 0o700);
|
|
92
|
+
const splits: Record<DatasetSplit, BehaviorCloningSample[]> = {
|
|
93
|
+
train: [],
|
|
94
|
+
validation: [],
|
|
95
|
+
test: [],
|
|
96
|
+
};
|
|
97
|
+
for (const sample of samples) splits[splitForGame(sample.game_id)].push(sample);
|
|
98
|
+
for (const [name, rows] of Object.entries(splits)) {
|
|
99
|
+
const body = rows.map((row) => JSON.stringify(row)).join("\n");
|
|
100
|
+
writeFileSync(resolve(output, `${name}.jsonl`), body ? `${body}\n` : "", { mode: 0o600 });
|
|
101
|
+
}
|
|
102
|
+
const manifest = {
|
|
103
|
+
schema_version: 1,
|
|
104
|
+
format: "behavior_cloning_jsonl",
|
|
105
|
+
split_unit: "game_id",
|
|
106
|
+
noops_included: includeNoops,
|
|
107
|
+
samples: {
|
|
108
|
+
total: samples.length,
|
|
109
|
+
train: splits.train.length,
|
|
110
|
+
validation: splits.validation.length,
|
|
111
|
+
test: splits.test.length,
|
|
112
|
+
},
|
|
113
|
+
games: [...new Set(samples.map((sample) => sample.game_type))].sort(),
|
|
114
|
+
};
|
|
115
|
+
writeFileSync(resolve(output, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, {
|
|
116
|
+
mode: 0o600,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const options = parseArgs(process.argv.slice(2));
|
|
121
|
+
const chunks = loadChunks(options.inputs);
|
|
122
|
+
const samples = extractBehaviorCloningSamples(chunks, { includeNoops: options.includeNoops });
|
|
123
|
+
writeDataset(options.output, samples, options.includeNoops);
|
|
124
|
+
console.log(`Exported ${samples.length} BC samples from ${chunks.length} chunks to ${options.output}`);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
6
|
+
CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
7
|
+
OPENCODE_CONFIG_DIR="${OPENCODE_CONFIG_DIR:-$HOME/.config/opencode}"
|
|
8
|
+
HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
|
|
9
|
+
OPENCLAW_HOME="${OPENCLAW_HOME:-$HOME/.openclaw}"
|
|
10
|
+
MARKER="Generated by tui-gamepigeon scripts/install-adapters.sh"
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<'EOF'
|
|
14
|
+
Usage: scripts/install-adapters.sh [--codex|--claude|--opencode|--hermes|--openclaw|--all]
|
|
15
|
+
|
|
16
|
+
Install the Arcade command for Codex, Claude Code, OpenCode, Hermes, OpenClaw, or all of them (the default).
|
|
17
|
+
EOF
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
install_codex=false
|
|
21
|
+
install_claude=false
|
|
22
|
+
install_opencode=false
|
|
23
|
+
install_hermes=false
|
|
24
|
+
install_openclaw=false
|
|
25
|
+
|
|
26
|
+
case "${1:---all}" in
|
|
27
|
+
--all)
|
|
28
|
+
install_codex=true
|
|
29
|
+
install_claude=true
|
|
30
|
+
install_opencode=true
|
|
31
|
+
install_hermes=true
|
|
32
|
+
install_openclaw=true
|
|
33
|
+
;;
|
|
34
|
+
--codex)
|
|
35
|
+
install_codex=true
|
|
36
|
+
;;
|
|
37
|
+
--claude)
|
|
38
|
+
install_claude=true
|
|
39
|
+
;;
|
|
40
|
+
--opencode)
|
|
41
|
+
install_opencode=true
|
|
42
|
+
;;
|
|
43
|
+
--hermes)
|
|
44
|
+
install_hermes=true
|
|
45
|
+
;;
|
|
46
|
+
--openclaw)
|
|
47
|
+
install_openclaw=true
|
|
48
|
+
;;
|
|
49
|
+
-h|--help)
|
|
50
|
+
usage
|
|
51
|
+
exit 0
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
usage >&2
|
|
55
|
+
exit 2
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
|
|
59
|
+
if [[ $# -gt 1 ]]; then
|
|
60
|
+
usage >&2
|
|
61
|
+
exit 2
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
skill_targets=()
|
|
65
|
+
if [[ "$install_codex" == true ]]; then
|
|
66
|
+
skill_targets+=("$CODEX_HOME/skills/arcade")
|
|
67
|
+
fi
|
|
68
|
+
if [[ "$install_claude" == true ]]; then
|
|
69
|
+
skill_targets+=("$CLAUDE_CONFIG_DIR/skills/arcade")
|
|
70
|
+
fi
|
|
71
|
+
if [[ "$install_hermes" == true ]]; then
|
|
72
|
+
skill_targets+=("$HERMES_HOME/skills/arcade")
|
|
73
|
+
fi
|
|
74
|
+
if [[ "$install_openclaw" == true ]]; then
|
|
75
|
+
skill_targets+=("$OPENCLAW_HOME/skills/arcade")
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
opencode_command="$OPENCODE_CONFIG_DIR/commands/arcade.md"
|
|
79
|
+
files_to_check=()
|
|
80
|
+
if [[ "$install_codex" == true || "$install_claude" == true ]]; then
|
|
81
|
+
for target in "${skill_targets[@]}"; do
|
|
82
|
+
files_to_check+=("$target/SKILL.md")
|
|
83
|
+
done
|
|
84
|
+
fi
|
|
85
|
+
if [[ "$install_opencode" == true ]]; then
|
|
86
|
+
files_to_check+=("$opencode_command")
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
for generated_file in "${files_to_check[@]}"; do
|
|
90
|
+
if [[ -f "$generated_file" ]] && ! grep -Fq "$MARKER" "$generated_file"; then
|
|
91
|
+
echo "Refusing to replace an existing adapter: $generated_file" >&2
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
printf -v launcher_command 'bun %q --enable' "$ROOT_DIR/scripts/overlay.ts"
|
|
97
|
+
|
|
98
|
+
if [[ "$install_codex" == true || "$install_claude" == true ]]; then
|
|
99
|
+
for target in "${skill_targets[@]}"; do
|
|
100
|
+
mkdir -p "$target"
|
|
101
|
+
cat > "$target/SKILL.md" <<EOF
|
|
102
|
+
---
|
|
103
|
+
name: arcade
|
|
104
|
+
description: Open or restore the native GamePigeon HTML overlay while the coding harness works.
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
# Arcade
|
|
108
|
+
|
|
109
|
+
<!-- $MARKER -->
|
|
110
|
+
|
|
111
|
+
Execute this command exactly once:
|
|
112
|
+
|
|
113
|
+
\`$launcher_command\`
|
|
114
|
+
|
|
115
|
+
Do not inspect the user's repository. Do not spawn another agent or task. Do not run any other command.
|
|
116
|
+
|
|
117
|
+
GamePigeon opens as a borderless HTML overlay without a Dock or taskbar entry. Running Arcade again focuses the existing overlay.
|
|
118
|
+
|
|
119
|
+
On success, respond only with \`Arcade opened.\`. On failure, relay the launcher's error without attempting another launch method.
|
|
120
|
+
EOF
|
|
121
|
+
echo "Installed Arcade skill at $target"
|
|
122
|
+
done
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [[ "$install_opencode" == true ]]; then
|
|
126
|
+
mkdir -p "$(dirname "$opencode_command")"
|
|
127
|
+
cat > "$opencode_command" <<EOF
|
|
128
|
+
---
|
|
129
|
+
description: Open or restore the GamePigeon Arcade overlay
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
<!-- $MARKER -->
|
|
133
|
+
|
|
134
|
+
!\`$launcher_command\`
|
|
135
|
+
|
|
136
|
+
The command opens GamePigeon as a borderless HTML overlay without a Dock or taskbar entry. Running \`/arcade\` again focuses the existing overlay.
|
|
137
|
+
|
|
138
|
+
Respond only with \`Arcade opened.\` when the launcher succeeds. If its output reports an error, relay that error without attempting another launch method.
|
|
139
|
+
EOF
|
|
140
|
+
echo "Installed Arcade command at $opencode_command"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
if [[ "$install_hermes" == true ]]; then
|
|
144
|
+
if [[ -f "$HERMES_HOME/config.yaml" ]]; then
|
|
145
|
+
if [[ -f "$HERMES_HOME/hermes-agent/venv/bin/python" ]]; then
|
|
146
|
+
"$HERMES_HOME/hermes-agent/venv/bin/python" "$ROOT_DIR/scripts/manage-hermes-hooks.py" "$ROOT_DIR"
|
|
147
|
+
else
|
|
148
|
+
python3 "$ROOT_DIR/scripts/manage-hermes-hooks.py" "$ROOT_DIR" || python "$ROOT_DIR/scripts/manage-hermes-hooks.py" "$ROOT_DIR"
|
|
149
|
+
fi
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
if [[ "$install_openclaw" == true ]]; then
|
|
154
|
+
mkdir -p "$OPENCLAW_HOME/hooks/arcade"
|
|
155
|
+
cat > "$OPENCLAW_HOME/hooks/arcade/HOOK.md" <<EOF
|
|
156
|
+
---
|
|
157
|
+
name: arcade
|
|
158
|
+
description: "Open or restore the GamePigeon Arcade overlay"
|
|
159
|
+
metadata:
|
|
160
|
+
{
|
|
161
|
+
"openclaw":
|
|
162
|
+
{
|
|
163
|
+
"emoji": "🎮",
|
|
164
|
+
"events": ["command:new", "command:reset"],
|
|
165
|
+
"install": [{ "id": "arcade", "kind": "local", "label": "Local Arcade" }],
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
---
|
|
169
|
+
# Arcade Hook
|
|
170
|
+
This hook manages the GamePigeon Arcade overlay lifecycle.
|
|
171
|
+
EOF
|
|
172
|
+
|
|
173
|
+
cat > "$OPENCLAW_HOME/hooks/arcade/handler.js" <<EOF
|
|
174
|
+
import { exec } from "node:child_process";
|
|
175
|
+
|
|
176
|
+
export default async function handle(event) {
|
|
177
|
+
const root = "$ROOT_DIR";
|
|
178
|
+
let action = "";
|
|
179
|
+
if (event.action === "new" || event.action === "reset") {
|
|
180
|
+
action = "--reset && bun " + root + "/scripts/overlay.ts --show";
|
|
181
|
+
}
|
|
182
|
+
if (action) {
|
|
183
|
+
exec("bun " + root + "/scripts/overlay.ts " + action);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
EOF
|
|
187
|
+
|
|
188
|
+
if command -v openclaw &> /dev/null; then
|
|
189
|
+
openclaw hooks enable arcade || true
|
|
190
|
+
fi
|
|
191
|
+
echo "Installed and enabled OpenClaw hook at $OPENCLAW_HOME/hooks/arcade"
|
|
192
|
+
fi
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
TEMP_DIR="$(mktemp -d)"
|
|
6
|
+
trap 'rm -rf "$TEMP_DIR"' EXIT
|
|
7
|
+
|
|
8
|
+
CODEX_HOME="$TEMP_DIR/codex" \
|
|
9
|
+
CLAUDE_CONFIG_DIR="$TEMP_DIR/claude" \
|
|
10
|
+
OPENCODE_CONFIG_DIR="$TEMP_DIR/opencode" \
|
|
11
|
+
HERMES_HOME="$TEMP_DIR/hermes" \
|
|
12
|
+
OPENCLAW_HOME="$TEMP_DIR/openclaw" \
|
|
13
|
+
bash "$ROOT_DIR/scripts/install-adapters.sh"
|
|
14
|
+
|
|
15
|
+
for skill_file in \
|
|
16
|
+
"$TEMP_DIR/codex/skills/arcade/SKILL.md" \
|
|
17
|
+
"$TEMP_DIR/claude/skills/arcade/SKILL.md" \
|
|
18
|
+
"$TEMP_DIR/hermes/skills/arcade/SKILL.md" \
|
|
19
|
+
"$TEMP_DIR/openclaw/skills/arcade/SKILL.md"; do
|
|
20
|
+
test -f "$skill_file"
|
|
21
|
+
grep -q '^name: arcade$' "$skill_file"
|
|
22
|
+
grep -Fq "bun $ROOT_DIR/scripts/overlay.ts --enable" "$skill_file"
|
|
23
|
+
grep -Fq 'Do not spawn another agent or task.' "$skill_file"
|
|
24
|
+
grep -Fq 'GamePigeon opens as a borderless HTML overlay' "$skill_file"
|
|
25
|
+
done
|
|
26
|
+
|
|
27
|
+
test -f "$TEMP_DIR/openclaw/hooks/arcade/HOOK.md"
|
|
28
|
+
test -f "$TEMP_DIR/openclaw/hooks/arcade/handler.js"
|
|
29
|
+
|
|
30
|
+
opencode_command="$TEMP_DIR/opencode/commands/arcade.md"
|
|
31
|
+
test -f "$opencode_command"
|
|
32
|
+
grep -q '^description: Open or restore the GamePigeon Arcade overlay$' "$opencode_command"
|
|
33
|
+
grep -Fq "!\`bun $ROOT_DIR/scripts/overlay.ts --enable\`" "$opencode_command"
|
|
34
|
+
grep -Fq 'Running `/arcade` again focuses the existing overlay.' "$opencode_command"
|
|
35
|
+
|
|
36
|
+
CODEX_ONLY="$TEMP_DIR/codex-only"
|
|
37
|
+
CLAUDE_UNUSED="$TEMP_DIR/claude-unused"
|
|
38
|
+
OPENCODE_UNUSED="$TEMP_DIR/opencode-unused"
|
|
39
|
+
CODEX_HOME="$CODEX_ONLY" \
|
|
40
|
+
CLAUDE_CONFIG_DIR="$CLAUDE_UNUSED" \
|
|
41
|
+
OPENCODE_CONFIG_DIR="$OPENCODE_UNUSED" \
|
|
42
|
+
bash "$ROOT_DIR/scripts/install-adapters.sh" --codex
|
|
43
|
+
|
|
44
|
+
test -f "$CODEX_ONLY/skills/arcade/SKILL.md"
|
|
45
|
+
test ! -e "$CLAUDE_UNUSED"
|
|
46
|
+
test ! -e "$OPENCODE_UNUSED"
|
|
47
|
+
|
|
48
|
+
CLI_CODEX_HOME="$TEMP_DIR/cli-codex"
|
|
49
|
+
CODEX_HOME="$CLI_CODEX_HOME" \
|
|
50
|
+
CLAUDE_CONFIG_DIR="$TEMP_DIR/cli-claude" \
|
|
51
|
+
OPENCODE_CONFIG_DIR="$TEMP_DIR/cli-opencode" \
|
|
52
|
+
HERMES_HOME="$TEMP_DIR/cli-hermes" \
|
|
53
|
+
OPENCLAW_HOME="$TEMP_DIR/cli-openclaw" \
|
|
54
|
+
bun "$ROOT_DIR/dist/index.js" install --codex
|
|
55
|
+
test -f "$CLI_CODEX_HOME/skills/arcade/SKILL.md"
|
|
56
|
+
|
|
57
|
+
OPENCODE_ONLY="$TEMP_DIR/opencode-only"
|
|
58
|
+
CODEX_UNUSED="$TEMP_DIR/codex-unused"
|
|
59
|
+
CLAUDE_UNUSED_2="$TEMP_DIR/claude-unused-2"
|
|
60
|
+
CODEX_HOME="$CODEX_UNUSED" \
|
|
61
|
+
CLAUDE_CONFIG_DIR="$CLAUDE_UNUSED_2" \
|
|
62
|
+
OPENCODE_CONFIG_DIR="$OPENCODE_ONLY" \
|
|
63
|
+
bash "$ROOT_DIR/scripts/install-adapters.sh" --opencode
|
|
64
|
+
|
|
65
|
+
test -f "$OPENCODE_ONLY/commands/arcade.md"
|
|
66
|
+
test ! -e "$CODEX_UNUSED"
|
|
67
|
+
test ! -e "$CLAUDE_UNUSED_2"
|
|
68
|
+
|
|
69
|
+
echo "adapter installer tests passed"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
config_path = os.path.expanduser('~/.hermes/config.yaml')
|
|
6
|
+
if not os.path.exists(config_path):
|
|
7
|
+
print("Hermes config.yaml not found.")
|
|
8
|
+
sys.exit(0)
|
|
9
|
+
|
|
10
|
+
with open(config_path, 'r') as f:
|
|
11
|
+
config = yaml.safe_load(f) or {}
|
|
12
|
+
|
|
13
|
+
# Define our hooks under the proper event-name keys mapping to arrays of command objects
|
|
14
|
+
root_dir = sys.argv[1]
|
|
15
|
+
root_dir = sys.argv[1]
|
|
16
|
+
new_hooks = {
|
|
17
|
+
'on_session_start': [
|
|
18
|
+
{'command': f"bun {root_dir}/scripts/overlay.ts --reset"},
|
|
19
|
+
{'command': f"bun {root_dir}/scripts/overlay.ts --show"}
|
|
20
|
+
],
|
|
21
|
+
'post_tool_call': [
|
|
22
|
+
{'command': f"bun {root_dir}/scripts/overlay.ts --pause"}
|
|
23
|
+
],
|
|
24
|
+
'on_session_end': [
|
|
25
|
+
{'command': f"bun {root_dir}/scripts/overlay.ts --quit"}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
existing_hooks = config.get('hooks', {})
|
|
29
|
+
for event, cmds in new_hooks.items():
|
|
30
|
+
existing = existing_hooks.get(event, [])
|
|
31
|
+
existing_cmds = {c['command'] for c in existing}
|
|
32
|
+
existing_hooks[event] = existing + [c for c in cmds if c['command'] not in existing_cmds]
|
|
33
|
+
config['hooks'] = existing_hooks
|
|
34
|
+
config['hooks_auto_accept'] = True
|
|
35
|
+
|
|
36
|
+
with open(config_path, 'w') as f:
|
|
37
|
+
yaml.safe_dump(config, f, default_flow_style=False)
|
|
38
|
+
|
|
39
|
+
print("Hermes hooks updated successfully.")
|