claude-tetris 0.1.0

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.
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ // scripts/launch.mjs — Startet die Split-Pane: Claude Code links, Tetris rechts.
3
+ //
4
+ // Nutzt Windows Terminal (`wt.exe`), das auf diesem Windows-Setup vorhanden ist.
5
+ // Claude läuft nativ (npm global), kein WSL nötig.
6
+ //
7
+ // Aufruf: node scripts/launch.mjs [projekt-pfad]
8
+ // ohne Pfad: aktuelles Verzeichnis
9
+
10
+ import { spawnSync } from "node:child_process";
11
+ import fs from "node:fs";
12
+ import os from "node:os";
13
+ import path from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ const PLUGIN_DIR = path.resolve(__dirname, "..");
18
+
19
+ const projectDir = process.argv.slice(2).find((a) => !a.startsWith("--")) || process.cwd();
20
+
21
+ // Windows Terminal existiert?
22
+ function findWT() {
23
+ // wt.exe ist meist im PATH
24
+ const r = spawnSync("wt.exe", ["--version"], { stdio: "ignore" });
25
+ return r.status === 0;
26
+ }
27
+
28
+ function main() {
29
+ if (process.argv.includes("--dry-run")) {
30
+ const tetrisCmd = `node "${path.join(PLUGIN_DIR, "bin", "tetris.mjs")}"`;
31
+ const wtArgs = [
32
+ "new-tab",
33
+ "--title", "Claude Code",
34
+ "cmd", "/k", `cd /d "${projectDir}" && claude`,
35
+ ";",
36
+ "split-pane", "--size", "0.38",
37
+ "--title", "claude-tetris",
38
+ "cmd", "/k", `cd /d "${PLUGIN_DIR}" && ${tetrisCmd}`,
39
+ ];
40
+ console.log("DRY-RUN wt.exe " + wtArgs.map((a) => JSON.stringify(a)).join(" "));
41
+ return;
42
+ }
43
+
44
+ if (!findWT()) {
45
+ console.error("❌ Windows Terminal (wt.exe) nicht gefunden.");
46
+ console.error(" Installiere es aus dem Microsoft Store oder starte das Spiel direkt mit `claude-tetris`.");
47
+ process.exit(1);
48
+ }
49
+
50
+ const tetrisCmd = `node "${path.join(PLUGIN_DIR, "bin", "tetris.mjs")}"`;
51
+
52
+ const wtArgs = [
53
+ "new-tab",
54
+ "--title", "Claude Code",
55
+ "cmd", "/k", `cd /d "${projectDir}" && claude`,
56
+ ";",
57
+ "split-pane", "--size", "0.38",
58
+ "--title", "claude-tetris",
59
+ "cmd", "/k", `cd /d "${PLUGIN_DIR}" && ${tetrisCmd}`,
60
+ ];
61
+
62
+ console.log("🚀 Starte Split-Pane…");
63
+ console.log(` Links: Claude Code (${projectDir})`);
64
+ console.log(` Rechts: claude-tetris (${PLUGIN_DIR})`);
65
+ console.log("");
66
+
67
+ // WICHTIG: shell:false — sonst jagt Node die Args durch eine weitere
68
+ // cmd.exe-Ebene, die das ';' zum Separator macht und die Quotes zerschießt.
69
+ // So erhält wt.exe exakt das argv, das wir hier bauen.
70
+ const r = spawnSync("wt.exe", wtArgs, { stdio: "inherit", shell: false });
71
+ if (r.error) {
72
+ console.error(`❌ Konnte Windows Terminal nicht starten: ${r.error.message}`);
73
+ process.exit(1);
74
+ }
75
+ console.log("✅ Fenster geöffnet. Tetris pausiert automatisch, wenn Claude fertig ist.");
76
+ console.log(" (Strg+C in der Tetris-Pane beendet das Spiel.)");
77
+ }
78
+
79
+ main();
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ // scripts/tetris-signal.mjs — Die Bridge zwischen Claude Code Hooks und dem Tetris.
3
+ //
4
+ // Claude Codes Hooks rufen dieses Script auf:
5
+ // tetris-signal.mjs play → Claude fängt an zu arbeiten → Tetris läuft
6
+ // tetris-signal.mjs pause → Claude ist fertig → Tetris pausiert
7
+ // tetris-signal.mjs status → gibt aktuellen Zustand als JSON aus
8
+ //
9
+ // Es schreibt nur die state.json (lib/signal.mjs) — kein Terminal-Zugriff nötig,
10
+ // deshalb funktioniert es auch aus Hooks heraus (die haben kein /dev/tty).
11
+
12
+ import { setPlay, setPause, readState, STATES } from "../lib/signal.mjs";
13
+
14
+ const cmd = process.argv[2] || "status";
15
+
16
+ switch (cmd) {
17
+ case "play":
18
+ setPlay("claude-code");
19
+ console.log("▶ Tetris: PLAY (Claude arbeitet)");
20
+ break;
21
+ case "pause":
22
+ setPause("claude-code");
23
+ console.log("⏸ Tetris: PAUSE (Claude fertig)");
24
+ break;
25
+ case "status": {
26
+ const { state, at } = readState();
27
+ console.log(JSON.stringify({ state, at, playing: state === STATES.PLAY }));
28
+ break;
29
+ }
30
+ default:
31
+ console.error(`Unbekannt: ${cmd}. Nutzung: tetris-signal.mjs [play|pause|status]`);
32
+ process.exit(1);
33
+ }
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ // scripts/uninstall.mjs — Entfernt claude-tetris Hooks aus ~/.claude/settings.json.
3
+ // Lässt alle anderen Hooks (z.B. rune-kit) unberührt.
4
+
5
+ import fs from "node:fs";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const PLUGIN_DIR = path.resolve(__dirname, "..");
12
+ const SIGNAL = path.join(PLUGIN_DIR, "scripts", "tetris-signal.mjs");
13
+ const CLAUDE_HOME =
14
+ (process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude")).replace(/[/\\]$/, "");
15
+ const SETTINGS = path.join(CLAUDE_HOME, "settings.json");
16
+
17
+ const PLAY_CMD = `node "${SIGNAL}" play`;
18
+ const PAUSE_CMD = `node "${SIGNAL}" pause`;
19
+
20
+ function main() {
21
+ console.log(`\n🗑 claude-tetris deinstallieren…\n`);
22
+ if (!fs.existsSync(SETTINGS)) {
23
+ console.log(` Keine settings.json gefunden — nichts zu tun.`);
24
+ return;
25
+ }
26
+ const settings = JSON.parse(fs.readFileSync(SETTINGS, "utf8"));
27
+ const hooks = settings.hooks || {};
28
+
29
+ for (const event of ["UserPromptSubmit", "Stop"]) {
30
+ if (!hooks[event]) continue;
31
+ const before = hooks[event].length;
32
+ hooks[event] = hooks[event].filter(
33
+ (entry) =>
34
+ !(entry.hooks || []).some(
35
+ (h) => h.command === PLAY_CMD || h.command === PAUSE_CMD
36
+ )
37
+ );
38
+ const removed = before - hooks[event].length;
39
+ if (removed > 0) console.log(` ✓ ${event}: ${removed} Hook(s) entfernt`);
40
+ else console.log(` • ${event}: keine claude-tetris Hooks gefunden`);
41
+ if (hooks[event].length === 0) delete hooks[event];
42
+ }
43
+
44
+ if (settings.enabledPlugins) delete settings.enabledPlugins["claude-tetris@local"];
45
+
46
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
47
+ fs.copyFileSync(SETTINGS, `${SETTINGS}.claude-tetris-backup-${ts}`);
48
+ fs.writeFileSync(SETTINGS, JSON.stringify(settings, null, 2) + "\n");
49
+ console.log(`\n✅ claude-tetris entfernt. Backup angelegt.`);
50
+ }
51
+
52
+ main();
@@ -0,0 +1,54 @@
1
+ // scripts/web-server.mjs — tiny zero-dependency static server for the
2
+ // claude-tetris showcase site (web/). Run with `npm run dev`.
3
+ //
4
+ // Serves the ./web directory on http://localhost:8137 and, on Windows,
5
+ // opens it in your default browser (and in headless Chromium for a snapshot).
6
+
7
+ import http from "node:http";
8
+ import { readFile } from "node:fs/promises";
9
+ import { existsSync } from "node:fs";
10
+ import path from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { spawn } from "node:child_process";
13
+
14
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "web");
15
+ const PORT = process.env.PORT || 8137;
16
+
17
+ const TYPES = {
18
+ ".html": "text/html; charset=utf-8",
19
+ ".css": "text/css; charset=utf-8",
20
+ ".js": "text/javascript; charset=utf-8",
21
+ ".mjs": "text/javascript; charset=utf-8",
22
+ ".json": "application/json; charset=utf-8",
23
+ ".svg": "image/svg+xml",
24
+ ".png": "image/png",
25
+ ".ico": "image/x-icon",
26
+ };
27
+
28
+ const server = http.createServer(async (req, res) => {
29
+ try {
30
+ let urlPath = decodeURIComponent(req.url.split("?")[0]);
31
+ if (urlPath === "/") urlPath = "/index.html";
32
+ const filePath = path.join(ROOT, path.normalize(urlPath));
33
+ if (!filePath.startsWith(ROOT) || !existsSync(filePath)) {
34
+ res.writeHead(404, { "Content-Type": "text/plain" });
35
+ res.end("404 Not Found");
36
+ return;
37
+ }
38
+ const data = await readFile(filePath);
39
+ res.writeHead(200, { "Content-Type": TYPES[path.extname(filePath)] || "application/octet-stream" });
40
+ res.end(data);
41
+ } catch (err) {
42
+ res.writeHead(500, { "Content-Type": "text/plain" });
43
+ res.end("500 " + err.message);
44
+ }
45
+ });
46
+
47
+ server.listen(PORT, () => {
48
+ const url = `http://localhost:${PORT}`;
49
+ console.log(`\n claude-tetris showcase → ${url}\n (serving ${ROOT})\n`);
50
+ // Open in default browser on Windows
51
+ if (process.platform === "win32") {
52
+ spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
53
+ }
54
+ });
package/uninstall.bat ADDED
@@ -0,0 +1,33 @@
1
+ @echo off
2
+ REM uninstall.bat — claude-tetris sauber entfernen (per Doppelklick).
3
+ REM Entfernt nur unsere Hooks, laesst alle anderen (z.B. rune-kit) intakt.
4
+
5
+ set "PLUGIN_DIR=%~dp0"
6
+ if "%PLUGIN_DIR:~-1%"=="\" set "PLUGIN_DIR=%PLUGIN_DIR:~0,-1%"
7
+
8
+ cls
9
+ echo.
10
+ echo claude-tetris Deinstaller
11
+ echo =========================
12
+ echo.
13
+
14
+ where node >nul 2>&1
15
+ if errorlevel 1 (
16
+ echo [X] Node.js nicht gefunden — kann nicht deinstallieren.
17
+ echo.
18
+ pause
19
+ exit /b 1
20
+ )
21
+
22
+ echo [+] Entferne claude-tetris Hooks...
23
+ echo.
24
+ node "%PLUGIN_DIR%\scripts\uninstall.mjs"
25
+
26
+ echo.
27
+ echo ============================================
28
+ echo Fertig! claude-tetris wurde entfernt.
29
+ echo ============================================
30
+ echo.
31
+ echo (Deine anderen Hooks sind unveraendert.)
32
+ echo.
33
+ pause