playholos 0.0.0-stage → 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.
Files changed (3) hide show
  1. package/package.json +21 -4
  2. package/play.mjs +91 -0
  3. package/README.md +0 -3
package/package.json CHANGED
@@ -1,6 +1,23 @@
1
1
  {
2
2
  "name": "playholos",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Play Holos on your own computer, with the Mind answering on your own Claude plan.",
5
+ "license": "MIT",
6
+ "author": "John Polacek",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/johnpolacek/playholos.git",
10
+ "directory": "npx"
11
+ },
12
+ "homepage": "https://playholos.com",
13
+ "type": "module",
14
+ "bin": {
15
+ "playholos": "play.mjs"
16
+ },
17
+ "files": [
18
+ "play.mjs"
19
+ ],
20
+ "engines": {
21
+ "node": ">=20.9"
22
+ }
23
+ }
package/play.mjs ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ import { spawn, spawnSync } from "node:child_process";
3
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import path from "node:path";
6
+ import { Readable } from "node:stream";
7
+
8
+ // npx playholos: the game on your own machine, with no git and no clone. Downloads the repo from GitHub once, to
9
+ // ~/playholos or the folder named, then runs its npm run play, which installs what it needs and opens the game with
10
+ // the Mind answering on your Claude login. --update swaps in the newest copy. 2026-11-04.
11
+
12
+ const REPO = "johnpolacek/playholos";
13
+ /** The commit a downloaded copy came from, kept beside it so a later run can tell it is behind. */
14
+ const STAMP = ".playholos-version";
15
+ const win = process.platform === "win32";
16
+
17
+ const args = process.argv.slice(2);
18
+ const update = args.includes("--update");
19
+ const dir = path.resolve(args.find((a) => !a.startsWith("-")) ?? path.join(homedir(), "playholos"));
20
+
21
+ const [major, minor] = process.versions.node.split(".").map(Number);
22
+ if (major < 20 || (major === 20 && minor < 9)) {
23
+ console.error(`Holos needs Node 20.9 or later. This is ${process.versions.node}.`);
24
+ process.exit(1);
25
+ }
26
+
27
+ // Checked before the download, so a missing Claude Code doesn't cost the download first.
28
+ if (spawnSync("claude", ["--version"], { shell: win }).status !== 0) {
29
+ console.error("Holos plays on your Claude login through Claude Code, and it isn't installed.");
30
+ console.error("Install it from https://claude.com/claude-code, run claude once to log in, then npx playholos again.");
31
+ process.exit(1);
32
+ }
33
+
34
+ /** The newest commit on main, or null when GitHub can't be reached. */
35
+ async function latest() {
36
+ try {
37
+ const res = await fetch(`https://api.github.com/repos/${REPO}/commits/main`, { headers: { accept: "application/vnd.github.sha" } });
38
+ return res.ok ? (await res.text()).trim() : null;
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ /** Unpacks the repo at a commit into a folder, streamed through tar, which macOS, Linux and Windows 10 all carry. */
45
+ async function download(sha, into) {
46
+ console.log(`Downloading Holos to ${into}, about 40 MB…`);
47
+ const res = await fetch(`https://codeload.github.com/${REPO}/tar.gz/${sha}`);
48
+ if (!res.ok || !res.body) throw new Error(`GitHub answered ${res.status}.`);
49
+ mkdirSync(into, { recursive: true });
50
+ const tar = spawn("tar", ["-xzf", "-", "--strip-components=1", "-C", into], { stdio: ["pipe", "inherit", "inherit"] });
51
+ Readable.fromWeb(res.body).pipe(tar.stdin);
52
+ await new Promise((done, fail) => tar.on("close", (code) => (code === 0 ? done() : fail(new Error(`tar stopped with ${code}.`)))));
53
+ writeFileSync(path.join(into, STAMP), sha);
54
+ }
55
+
56
+ const ours = existsSync(path.join(dir, "scripts", "play.mjs"));
57
+ const git = existsSync(path.join(dir, ".git"));
58
+
59
+ try {
60
+ if (!ours) {
61
+ if (existsSync(dir) && readdirSync(dir).length) {
62
+ console.error(`${dir} already holds something else. Name an empty folder: npx playholos <folder>`);
63
+ process.exit(1);
64
+ }
65
+ await download((await latest()) ?? "main", dir);
66
+ } else if (git) {
67
+ // A clone, as a contributor keeps. git updates it, not us.
68
+ if (update) console.log("This copy is a git clone. Run git pull in it to update.");
69
+ } else {
70
+ const sha = await latest();
71
+ const have = existsSync(path.join(dir, STAMP)) ? readFileSync(path.join(dir, STAMP), "utf8").trim() : null;
72
+ if (sha && sha !== have) {
73
+ if (update) {
74
+ // Into a folder beside it first, so a failed download leaves the old copy playable. Saves live in the browser.
75
+ const next = `${dir}.next`;
76
+ rmSync(next, { recursive: true, force: true });
77
+ await download(sha, next);
78
+ rmSync(dir, { recursive: true, force: true });
79
+ renameSync(next, dir);
80
+ } else {
81
+ console.log("A newer Holos is out. npx playholos --update fetches it.");
82
+ }
83
+ }
84
+ }
85
+ } catch (e) {
86
+ console.error(`We couldn't download Holos. ${e.message}`);
87
+ process.exit(1);
88
+ }
89
+
90
+ const play = spawn(process.execPath, ["scripts/play.mjs"], { cwd: dir, stdio: "inherit" });
91
+ play.on("close", (code) => process.exit(code ?? 0));
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.