cozyclay 3.1.3 → 3.1.4
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/CHANGELOG.md +12 -0
- package/bin/cozyclay.mjs +2 -48
- package/bin/star-prompt.mjs +123 -0
- package/dist/app/index.html +1 -1
- package/dist/assets/{app-DwETmoQZ.js → app-KXdgBC-S.js} +1 -1
- package/dist/cozyclay-package.json +1 -1
- package/package.json +1 -1
- package/tools/loc-baseline.json +1 -1
- package/tools/run-tests.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 3.1.4 - 2026-10-24
|
|
6
|
+
|
|
7
|
+
Starring CozyClay from the terminal takes one key when the GitHub CLI is signed in.
|
|
8
|
+
|
|
9
|
+
CLI
|
|
10
|
+
|
|
11
|
+
- The one-time star question on exit stars NomaDamas/CozyClay through `gh`
|
|
12
|
+
when `gh` is installed and signed in: answer `y` and it is done, with no
|
|
13
|
+
browser. An account that already starred the repo is not asked. Without
|
|
14
|
+
`gh` the question opens the repo page as before, and `--no-star`,
|
|
15
|
+
`COZYCLAY_NO_STAR` and CI still turn it off.
|
|
16
|
+
|
|
5
17
|
## 3.1.3 - 2026-10-24
|
|
6
18
|
|
|
7
19
|
More ways to connect a model (Claude Pro/Max, OpenAI-compatible servers, MiniMax M3.1 Flash), a Simplified Chinese UI, and easier project opening.
|
package/bin/cozyclay.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { createInterface } from "node:readline";
|
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
24
|
import { ensureMcpRuntime, runMcp } from "./mcp-runtime.mjs";
|
|
25
25
|
import { openBrowser } from "./open-browser.mjs";
|
|
26
|
+
import { maybeAskForStar } from "./star-prompt.mjs";
|
|
26
27
|
import { checkForUpdate, readUpdateStatus, runUpdate } from "./update-check.mjs";
|
|
27
28
|
import { handleOAuthRequest } from "./codex-auth.mjs";
|
|
28
29
|
import { createAgentHandler } from "./agent/agent-routes.mjs";
|
|
@@ -120,12 +121,8 @@ function parseArgs(argv) {
|
|
|
120
121
|
return opts;
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
const REPO_URL = "https://github.com/NomaDamas/CozyClay";
|
|
124
124
|
const STATE_DIR = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "cozyclay");
|
|
125
125
|
const STATE_FILE = join(STATE_DIR, "state.json");
|
|
126
|
-
// Only worth asking someone who actually used the thing. A prompt three
|
|
127
|
-
// seconds in is a popup; a prompt after a real session is a question.
|
|
128
|
-
const STAR_AFTER_MS = Number(process.env.COZYCLAY_STAR_AFTER_MS ?? 60_000);
|
|
129
126
|
|
|
130
127
|
async function askFirstLaunchSource() {
|
|
131
128
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
@@ -197,49 +194,6 @@ function writeState(patch) {
|
|
|
197
194
|
}
|
|
198
195
|
}
|
|
199
196
|
|
|
200
|
-
async function starCount() {
|
|
201
|
-
try {
|
|
202
|
-
const res = await fetch("https://api.github.com/repos/NomaDamas/CozyClay", {
|
|
203
|
-
headers: { accept: "application/vnd.github+json" },
|
|
204
|
-
signal: AbortSignal.timeout(2500),
|
|
205
|
-
});
|
|
206
|
-
if (!res.ok) return null;
|
|
207
|
-
const body = await res.json();
|
|
208
|
-
return Number.isInteger(body?.stargazers_count) ? body.stargazers_count : null;
|
|
209
|
-
} catch {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Asked once, on the way out, and never again on this machine. A CLI that
|
|
215
|
-
// nags on every run is worse than one that never asks.
|
|
216
|
-
async function maybeAskForStar(startedAt, opts) {
|
|
217
|
-
if (!opts.star) return;
|
|
218
|
-
if (process.env.CI || process.env.COZYCLAY_NO_STAR) return;
|
|
219
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return;
|
|
220
|
-
if (Date.now() - startedAt < STAR_AFTER_MS) return;
|
|
221
|
-
if (readState().starPromptedAt) return;
|
|
222
|
-
|
|
223
|
-
writeState({ starPromptedAt: new Date().toISOString() });
|
|
224
|
-
const stars = await starCount();
|
|
225
|
-
const tally = stars === null ? "" : ` (${stars} so far)`;
|
|
226
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
227
|
-
const answer = await new Promise((done) => {
|
|
228
|
-
const timer = setTimeout(() => done(""), 12_000);
|
|
229
|
-
rl.question(`\nWas CozyClay any good? A star helps people find it${tally}. Open GitHub? [Y/n] `, (a) => {
|
|
230
|
-
clearTimeout(timer);
|
|
231
|
-
done(a);
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
rl.close();
|
|
235
|
-
if (/^(y|yes|)$/i.test(answer.trim())) {
|
|
236
|
-
openBrowser(REPO_URL);
|
|
237
|
-
console.log("Thanks. Opening the repo.");
|
|
238
|
-
} else {
|
|
239
|
-
console.log(`Fair enough. ${REPO_URL} if you change your mind.`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
197
|
// Read on every launch now, so it must not be able to take the launcher down:
|
|
244
198
|
// the version is cosmetic here, and "0.0.0" just suppresses a bogus notice.
|
|
245
199
|
function readVersion() {
|
|
@@ -343,7 +297,7 @@ async function shutdown(exitCode = 0) {
|
|
|
343
297
|
shuttingDown = true;
|
|
344
298
|
if (server?.listening) await new Promise((resolvePromise) => server.close(() => resolvePromise()));
|
|
345
299
|
try {
|
|
346
|
-
await maybeAskForStar(startedAt, opts);
|
|
300
|
+
await maybeAskForStar(startedAt, opts, { readState, writeState });
|
|
347
301
|
} catch {
|
|
348
302
|
/* never let the goodbye prompt hold the process hostage */
|
|
349
303
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-time "star the repo" question on the way out of `npx cozyclay` (#900).
|
|
3
|
+
*
|
|
4
|
+
* With the GitHub CLI installed and signed in, a "y" stars the repo through `gh` on
|
|
5
|
+
* the spot: no browser hop, which is where most people who meant to star it got lost.
|
|
6
|
+
* Without gh, a "yes" opens the repo page as before. Asked once per machine, after a
|
|
7
|
+
* real session, never in CI or without a terminal, and never to an account that has
|
|
8
|
+
* already starred it. Every failure here is silent: this runs while the launcher exits.
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { createInterface } from "node:readline";
|
|
12
|
+
import { openBrowser } from "./open-browser.mjs";
|
|
13
|
+
|
|
14
|
+
const REPO = "NomaDamas/CozyClay";
|
|
15
|
+
const REPO_URL = `https://github.com/${REPO}`;
|
|
16
|
+
const STARRED_PATH = `/user/starred/${REPO}`;
|
|
17
|
+
const ANSWER_TIMEOUT_MS = 12_000;
|
|
18
|
+
// Only worth asking someone who actually used the thing. A prompt three
|
|
19
|
+
// seconds in is a popup; a prompt after a real session is a question.
|
|
20
|
+
const STAR_AFTER_MS = Number(process.env.COZYCLAY_STAR_AFTER_MS ?? 60_000);
|
|
21
|
+
|
|
22
|
+
/** Runs gh and resolves true on exit 0. No gh on PATH, a timeout or an error status is false. */
|
|
23
|
+
function runGh(args) {
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
try {
|
|
26
|
+
execFile("gh", args, {
|
|
27
|
+
timeout: 4000,
|
|
28
|
+
windowsHide: true,
|
|
29
|
+
env: { ...process.env, GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1" },
|
|
30
|
+
}, (error) => resolve(!error));
|
|
31
|
+
} catch {
|
|
32
|
+
resolve(false);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function starCount() {
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(`https://api.github.com/repos/${REPO}`, {
|
|
40
|
+
headers: { accept: "application/vnd.github+json" },
|
|
41
|
+
signal: AbortSignal.timeout(2500),
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) return null;
|
|
44
|
+
const body = await res.json();
|
|
45
|
+
return Number.isInteger(body?.stargazers_count) ? body.stargazers_count : null;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function askLine(question) {
|
|
52
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
53
|
+
return new Promise((done) => {
|
|
54
|
+
const timer = setTimeout(() => {
|
|
55
|
+
rl.close();
|
|
56
|
+
done("");
|
|
57
|
+
}, ANSWER_TIMEOUT_MS);
|
|
58
|
+
rl.question(question, (answer) => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
rl.close();
|
|
61
|
+
done(answer);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Asked once, on the way out, and never again on this machine. A CLI that nags on
|
|
68
|
+
* every run is worse than one that never asks. Everything after `state` is injectable
|
|
69
|
+
* so the test can drive it without a terminal, gh, or the network.
|
|
70
|
+
*/
|
|
71
|
+
export async function maybeAskForStar(startedAt, opts, {
|
|
72
|
+
readState,
|
|
73
|
+
writeState,
|
|
74
|
+
env = process.env,
|
|
75
|
+
interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
76
|
+
now = Date.now,
|
|
77
|
+
minSessionMs = STAR_AFTER_MS,
|
|
78
|
+
gh = runGh,
|
|
79
|
+
ask = askLine,
|
|
80
|
+
stars = starCount,
|
|
81
|
+
open = openBrowser,
|
|
82
|
+
log = console.log,
|
|
83
|
+
}) {
|
|
84
|
+
if (!opts.star) return;
|
|
85
|
+
if (env.CI || env.COZYCLAY_NO_STAR) return;
|
|
86
|
+
if (!interactive) return;
|
|
87
|
+
if (now() - startedAt < minSessionMs) return;
|
|
88
|
+
if (readState().starPromptedAt) return;
|
|
89
|
+
|
|
90
|
+
const promptedAt = new Date(now()).toISOString();
|
|
91
|
+
const viaGh = await gh(["auth", "status"]);
|
|
92
|
+
// GET /user/starred/<repo> answers 204 for a starred repo and 404 otherwise.
|
|
93
|
+
if (viaGh && await gh(["api", STARRED_PATH, "--silent"])) {
|
|
94
|
+
writeState({ starPromptedAt: promptedAt });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
writeState({ starPromptedAt: promptedAt });
|
|
98
|
+
const count = await stars();
|
|
99
|
+
const tally = count === null ? "" : ` (${count} so far)`;
|
|
100
|
+
|
|
101
|
+
if (viaGh) {
|
|
102
|
+
const answer = await ask(`\nWas CozyClay any good? A star helps people find it${tally}. Star ${REPO} with your gh account? [y/N] `);
|
|
103
|
+
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
104
|
+
log(`No problem. ${REPO_URL} if you change your mind.`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (await gh(["api", "--method", "PUT", STARRED_PATH, "--silent"])) {
|
|
108
|
+
log(`Starred ${REPO}. Thank you!`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
log("gh could not star it, so opening the repo instead.");
|
|
112
|
+
open(REPO_URL);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const answer = await ask(`\nWas CozyClay any good? A star helps people find it${tally}. Open GitHub? [Y/n] `);
|
|
117
|
+
if (/^(y|yes|)$/i.test(answer.trim())) {
|
|
118
|
+
open(REPO_URL);
|
|
119
|
+
log("Thanks. Opening the repo.");
|
|
120
|
+
} else {
|
|
121
|
+
log(`Fair enough. ${REPO_URL} if you change your mind.`);
|
|
122
|
+
}
|
|
123
|
+
}
|
package/dist/app/index.html
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
<title>CozyClay Studio — 3D staging and previs in your browser</title>
|
|
35
35
|
|
|
36
36
|
<script defer src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token": "6a4384d1375d4ffc8a30a6346a8ad1a0"}'></script>
|
|
37
|
-
<script type="module" crossorigin src="/assets/app-
|
|
37
|
+
<script type="module" crossorigin src="/assets/app-KXdgBC-S.js"></script>
|
|
38
38
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
39
39
|
<link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-P2Xu9kJm.js">
|
|
40
40
|
<link rel="modulepreload" crossorigin href="/assets/task-board-DFfNa0xg.js">
|