cozyclay 1.0.0 → 1.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 (2) hide show
  1. package/bin/cozyclay.mjs +85 -5
  2. package/package.json +1 -1
package/bin/cozyclay.mjs CHANGED
@@ -15,9 +15,11 @@
15
15
  * before it can serve a prebuilt app is a launcher that will break.
16
16
  */
17
17
  import { spawn } from "node:child_process";
18
- import { createReadStream, existsSync, statSync } from "node:fs";
18
+ import { createReadStream, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
19
19
  import { createServer, request as httpRequest } from "node:http";
20
+ import { homedir } from "node:os";
20
21
  import { extname, join, normalize, resolve } from "node:path";
22
+ import { createInterface } from "node:readline";
21
23
  import { fileURLToPath } from "node:url";
22
24
 
23
25
  const PKG_ROOT = resolve(fileURLToPath(new URL("..", import.meta.url)));
@@ -43,7 +45,7 @@ const TYPES = {
43
45
  };
44
46
 
45
47
  function parseArgs(argv) {
46
- const opts = { port: 5180, host: "127.0.0.1", ardy: true, open: true };
48
+ const opts = { port: 5180, host: "127.0.0.1", ardy: true, open: true, star: true };
47
49
  for (let i = 0; i < argv.length; i += 1) {
48
50
  const arg = argv[i];
49
51
  if (arg === "--port" || arg === "-p") opts.port = Number(argv[++i]);
@@ -52,6 +54,7 @@ function parseArgs(argv) {
52
54
  else if (arg.startsWith("--host=")) opts.host = arg.slice(7);
53
55
  else if (arg === "--no-ardy") opts.ardy = false;
54
56
  else if (arg === "--no-open") opts.open = false;
57
+ else if (arg === "--no-star") opts.star = false;
55
58
  else if (arg === "--help" || arg === "-h") opts.help = true;
56
59
  else if (arg === "--version" || arg === "-v") opts.version = true;
57
60
  else {
@@ -66,12 +69,20 @@ function parseArgs(argv) {
66
69
  return opts;
67
70
  }
68
71
 
72
+ const REPO_URL = "https://github.com/HaD0Yun/CozyClay";
73
+ const STATE_DIR = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "cozyclay");
74
+ const STATE_FILE = join(STATE_DIR, "state.json");
75
+ // Only worth asking someone who actually used the thing. A prompt three
76
+ // seconds in is a popup; a prompt after a real session is a question.
77
+ const STAR_AFTER_MS = Number(process.env.COZYCLAY_STAR_AFTER_MS ?? 60_000);
78
+
69
79
  const HELP = `cozyclay - browser-based 3D staging studio
70
80
 
71
81
  npx cozyclay start the studio and open it
72
82
  npx cozyclay --port 5200 serve on another port
73
83
  npx cozyclay --no-ardy skip the optional motion-generation sidecar
74
84
  npx cozyclay --no-open do not open a browser
85
+ npx cozyclay --no-star never ask about starring the repo
75
86
 
76
87
  Motion generation needs an SSH-reachable NVIDIA machine running ARDY; point
77
88
  the sidecar at it with CCLAY_ARDY_HOST. Everything else - staging, posing,
@@ -108,6 +119,66 @@ function proxyToBridge(req, res) {
108
119
  req.pipe(upstream);
109
120
  }
110
121
 
122
+ function readState() {
123
+ try {
124
+ return JSON.parse(readFileSync(STATE_FILE, "utf8"));
125
+ } catch {
126
+ return {};
127
+ }
128
+ }
129
+
130
+ function writeState(patch) {
131
+ try {
132
+ mkdirSync(STATE_DIR, { recursive: true });
133
+ writeFileSync(STATE_FILE, JSON.stringify({ ...readState(), ...patch }, null, "\t"));
134
+ } catch {
135
+ /* a read-only home is not a reason to fail a local dev server */
136
+ }
137
+ }
138
+
139
+ async function starCount() {
140
+ try {
141
+ const res = await fetch("https://api.github.com/repos/HaD0Yun/CozyClay", {
142
+ headers: { accept: "application/vnd.github+json" },
143
+ signal: AbortSignal.timeout(2500),
144
+ });
145
+ if (!res.ok) return null;
146
+ const body = await res.json();
147
+ return Number.isInteger(body?.stargazers_count) ? body.stargazers_count : null;
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+
153
+ // Asked once, on the way out, and never again on this machine. A CLI that
154
+ // nags on every run is worse than one that never asks.
155
+ async function maybeAskForStar(startedAt, opts) {
156
+ if (!opts.star) return;
157
+ if (process.env.CI || process.env.COZYCLAY_NO_STAR) return;
158
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
159
+ if (Date.now() - startedAt < STAR_AFTER_MS) return;
160
+ if (readState().starPromptedAt) return;
161
+
162
+ writeState({ starPromptedAt: new Date().toISOString() });
163
+ const stars = await starCount();
164
+ const tally = stars === null ? "" : ` (${stars} so far)`;
165
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
166
+ const answer = await new Promise((done) => {
167
+ const timer = setTimeout(() => done(""), 12_000);
168
+ rl.question(`\nWas CozyClay any good? A star helps people find it${tally}. Open GitHub? [Y/n] `, (a) => {
169
+ clearTimeout(timer);
170
+ done(a);
171
+ });
172
+ });
173
+ rl.close();
174
+ if (/^(y|yes|)$/i.test(answer.trim())) {
175
+ openBrowser(REPO_URL);
176
+ console.log("Thanks. Opening the repo.");
177
+ } else {
178
+ console.log(`Fair enough. ${REPO_URL} if you change your mind.`);
179
+ }
180
+ }
181
+
111
182
  function openBrowser(url) {
112
183
  const cmd =
113
184
  process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -163,10 +234,19 @@ const server = createServer((req, res) => {
163
234
  serveFile(res, target);
164
235
  });
165
236
 
166
- function shutdown() {
237
+ const startedAt = Date.now();
238
+ let shuttingDown = false;
239
+ async function shutdown() {
240
+ if (shuttingDown) process.exit(0);
241
+ shuttingDown = true;
167
242
  if (bridge) bridge.kill("SIGTERM");
168
- server.close(() => process.exit(0));
169
- setTimeout(() => process.exit(0), 2000).unref();
243
+ server.close();
244
+ try {
245
+ await maybeAskForStar(startedAt, opts);
246
+ } catch {
247
+ /* never let the goodbye prompt hold the process hostage */
248
+ }
249
+ process.exit(0);
170
250
  }
171
251
  process.on("SIGINT", shutdown);
172
252
  process.on("SIGTERM", shutdown);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozyclay",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Browser-based 3D staging studio: block a scene, pose characters, author camera moves and cuts, and preview generated motion.",
5
5
  "scripts": {
6
6
  "dev": "node tools/dev-full.mjs --host 127.0.0.1 --port 5180",