cozyclay 3.0.3 → 3.1.1

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 (246) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/INSTALL_FOR_AGENTS.md +146 -0
  3. package/README.md +74 -192
  4. package/THIRD_PARTY_NOTICES.md +0 -21
  5. package/bin/agent/agent-routes.mjs +29 -62
  6. package/bin/agent/agent-runner.mjs +5 -3
  7. package/bin/agent/main-agent.mjs +70 -0
  8. package/bin/agent/main-prompt.mjs +17 -0
  9. package/bin/agent/plan-tool.mjs +114 -0
  10. package/bin/agent/script-tool.mjs +2 -0
  11. package/bin/agent/sculpt-sketch.mjs +190 -0
  12. package/bin/agent/session-store.mjs +4 -3
  13. package/bin/agent/sketch-code.mjs +151 -0
  14. package/bin/agent/sketch-sandbox.mjs +102 -0
  15. package/bin/agent/studio-prompt.mjs +1 -1
  16. package/bin/agent/studio-task-routes.mjs +185 -0
  17. package/bin/agent/studio-tasks.mjs +156 -0
  18. package/bin/agent/studio-tools.mjs +3 -2
  19. package/bin/agent/studio-turn-transaction.mjs +76 -0
  20. package/bin/agent/turn-frames.mjs +4 -0
  21. package/bin/cozyclay.mjs +5 -7
  22. package/dist/app/index.html +7 -6
  23. package/dist/assets/TaskBoard-ngdqiu7u.js +1 -0
  24. package/dist/assets/app-BlwOpest.css +1 -0
  25. package/dist/assets/app-DmR7jBkz.js +5144 -0
  26. package/dist/assets/compile-worker-D0pKioa0.js +5 -0
  27. package/dist/assets/landing-y3EqDgSZ.js +1 -0
  28. package/dist/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
  29. package/dist/assets/motion3-compile-BB3_1-EB.js +1 -0
  30. package/dist/assets/scene-asset-sync-CBx7XRti.css +1 -0
  31. package/dist/assets/scene-asset-sync-Cgdtr1Yw.js +13 -0
  32. package/dist/assets/task-board-ErQvfoaz.js +14 -0
  33. package/dist/assets/workflow-zQM-EKW1.js +179 -0
  34. package/dist/assets/x-bot-tpose-body-BusyR3eg.json +1 -0
  35. package/dist/assets/y-bot-tpose-body-Dy1VKChZ.json +1 -0
  36. package/dist/cozyclay-package.json +1 -1
  37. package/dist/greybox-to-video/index.html +2 -3
  38. package/dist/index.html +7 -57
  39. package/dist/llms.txt +2 -1
  40. package/dist/previs-software/index.html +2 -2
  41. package/dist/privacy/index.html +3 -7
  42. package/dist/seedance-camera-control/index.html +2 -2
  43. package/dist/sitemap.xml +5 -5
  44. package/dist/sw.js +5 -1
  45. package/dist/thumbs/blank-stage-dark.webp +0 -0
  46. package/dist/thumbs/blank-stage-light.webp +0 -0
  47. package/dist/workflow/index.html +5 -4
  48. package/mcp/mesh-file.mjs +3 -1
  49. package/mcp/server.mjs +3 -0
  50. package/mcp/tool-handlers.mjs +2 -2
  51. package/package.json +5 -3
  52. package/src/App.jsx +51 -282
  53. package/src/analytics.js +3 -148
  54. package/src/app-stage.jsx +1 -1
  55. package/src/asset-pane.jsx +5 -10
  56. package/src/auto-flat.js +10 -0
  57. package/src/bus-tasks.js +11 -5
  58. package/src/command-bus.js +1 -1
  59. package/src/commands/elements.js +4 -3
  60. package/src/commands/motion3-compile.js +51 -27
  61. package/src/commands/motion3.js +3 -3
  62. package/src/commands/objects.js +1 -1
  63. package/src/commands/project.js +1 -1
  64. package/src/commands/shot.js +16 -4
  65. package/src/controls.jsx +0 -11
  66. package/src/cuts.js +11 -0
  67. package/src/domains/cast.js +3 -2
  68. package/src/domains/motion.js +3 -10
  69. package/src/domains/playback.js +32 -0
  70. package/src/domains/scenes.js +3 -22
  71. package/src/domains/shots.js +5 -5
  72. package/src/dualview.jsx +11 -15
  73. package/src/hierarchy-panel.css +18 -4
  74. package/src/hierarchy-panel.jsx +12 -11
  75. package/src/kits/held.js +3 -2
  76. package/src/light-blockers.js +2 -2
  77. package/src/main.jsx +5 -0
  78. package/src/material-look.js +189 -0
  79. package/src/material-presets.js +78 -0
  80. package/src/material-textures.js +157 -0
  81. package/src/motion/ground.js +5 -14
  82. package/src/motion/playback-frame-store.js +79 -0
  83. package/src/motion/playback-probe.js +16 -0
  84. package/src/motion/playback-subject.js +7 -0
  85. package/src/motion/timeline.css +43 -5
  86. package/src/motion/timeline.jsx +4 -3
  87. package/src/motion3/body.js +37 -5
  88. package/src/motion3/carried.js +11 -3
  89. package/src/motion3/compile-job.js +1 -1
  90. package/src/motion3/compiler/compile.js +7 -1
  91. package/src/motion3/compiler/finger-close.js +137 -0
  92. package/src/motion3/compiler/limbs.js +18 -3
  93. package/src/motion3/compiler/pose.js +6 -0
  94. package/src/motion3/compiler/validate-objects.js +17 -2
  95. package/src/motion3/compiler/validate-parts.js +2 -1
  96. package/src/motion3/compiler/vocab.js +4 -4
  97. package/src/motion3/describe.js +2 -2
  98. package/src/motion3/fix/index.js +5 -3
  99. package/src/motion3/flight.js +38 -0
  100. package/src/motion3/free-motion.js +10 -7
  101. package/src/motion3/load.js +125 -0
  102. package/src/motion3/mass.js +23 -0
  103. package/src/motion3/object-keys.js +11 -6
  104. package/src/motion3/object-pose.js +11 -3
  105. package/src/motion3/pipeline.js +3 -0
  106. package/src/motion3/qa/explain.js +3 -1
  107. package/src/motion3/qa/grasp-checks.js +123 -0
  108. package/src/motion3/qa/hints-grasp.js +71 -0
  109. package/src/motion3/qa/index.js +3 -1
  110. package/src/motion3/qa/object-checks.js +7 -2
  111. package/src/motion3/qa/patch.js +2 -1
  112. package/src/motion3/qa/spec-checks.js +10 -1
  113. package/src/motion3/schema/fields.js +3 -3
  114. package/src/motion3/schema/topics.js +9 -8
  115. package/src/motion3/schema/types.js +4 -1
  116. package/src/motion3/seat.js +84 -0
  117. package/src/motion3/settle.js +11 -6
  118. package/src/motion3/shapes.js +70 -0
  119. package/src/motion3/skills/catch.js +71 -0
  120. package/src/motion3/skills/index.js +10 -3
  121. package/src/motion3/skills/info.js +13 -0
  122. package/src/motion3/skills/throw.js +72 -0
  123. package/src/object-catalog.jsx +0 -4
  124. package/src/object-gizmo.jsx +2 -1
  125. package/src/object-look.js +9 -5
  126. package/src/object-path.js +1 -1
  127. package/src/panels/ProjectPanel.jsx +42 -21
  128. package/src/panels/details.css +15 -4
  129. package/src/popover.js +37 -0
  130. package/src/posestudio.jsx +15 -10
  131. package/src/primitive-look.js +38 -0
  132. package/src/project-browser-art.jsx +19 -0
  133. package/src/project-browser.css +74 -121
  134. package/src/project-browser.jsx +36 -60
  135. package/src/props.jsx +24 -35
  136. package/src/render-passes.js +7 -1
  137. package/src/retired-kinds.js +37 -0
  138. package/src/scene-objects.js +5 -9
  139. package/src/scenes.js +4 -5
  140. package/src/sculpt-colours.js +65 -0
  141. package/src/sculpt-compare.js +13 -50
  142. package/src/sculpt-csg.js +112 -0
  143. package/src/sculpt-curve.js +123 -0
  144. package/src/sculpt-cutters.js +43 -0
  145. package/src/sculpt-detail.js +207 -0
  146. package/src/sculpt-geometry-loft-convex.js +100 -0
  147. package/src/sculpt-geometry-shapes.js +250 -0
  148. package/src/sculpt-geometry.js +33 -15
  149. package/src/sculpt-loft-rings.js +225 -0
  150. package/src/sculpt-noise.js +65 -0
  151. package/src/sculpt-part-fields.js +27 -0
  152. package/src/sculpt-recipe-read.js +65 -0
  153. package/src/sculpt-recipe.js +33 -75
  154. package/src/sculpt-run.js +1 -1
  155. package/src/sculpt-scatter-items.js +183 -0
  156. package/src/sculpt-scatter-shape.js +57 -0
  157. package/src/sculpt-scatter.js +124 -0
  158. package/src/sculpt-shapes-curved.js +211 -0
  159. package/src/sculpt-shapes-loft-convex.js +218 -0
  160. package/src/sculpt-shapes.js +138 -0
  161. package/src/sculpt-sketch-state.js +76 -0
  162. package/src/sculpt-sketch.jsx +124 -0
  163. package/src/sculpt-surface-fields.js +65 -0
  164. package/src/settings-menu.jsx +1 -20
  165. package/src/shell/BottomDock.jsx +9 -9
  166. package/src/shell/MenuBar.jsx +1 -9
  167. package/src/shell/PreferencesDialog.jsx +3 -4
  168. package/src/shell/ViewportToolbar.jsx +3 -3
  169. package/src/shell/add-camera-shot.js +11 -0
  170. package/src/shell/glass-regions.css +3 -2
  171. package/src/shell/glass.css +0 -5
  172. package/src/shell/lens-scrub-math.js +51 -0
  173. package/src/shell/lens-scrub.css +73 -0
  174. package/src/shell/lens-scrub.jsx +98 -0
  175. package/src/shell/mode.css +6 -5
  176. package/src/shell/preferences.css +1 -32
  177. package/src/shell/shell.css +18 -1
  178. package/src/shell/studio-shell-context.js +5 -0
  179. package/src/shot-frame.js +26 -0
  180. package/src/stage-canvas-rigs.jsx +7 -8
  181. package/src/stage-characters.jsx +26 -0
  182. package/src/stage-gizmo.js +43 -0
  183. package/src/stage-labels-ko.js +0 -6
  184. package/src/stage-startup-defaults.js +3 -9
  185. package/src/starter-scenes.js +5 -11
  186. package/src/studio-agent-commands.js +2 -2
  187. package/src/studio-agent-protocol.js +2 -7
  188. package/src/studio-app-binding.js +1 -1
  189. package/src/studio-elements.js +6 -2
  190. package/src/styles/themes.css +13 -0
  191. package/src/styles.css +6 -674
  192. package/src/text/text-object.jsx +1 -1
  193. package/src/ui.jsx +8 -16
  194. package/src/use-embed-bridge.js +3 -6
  195. package/src/use-render-activity.js +3 -1
  196. package/src/view-ghosts.jsx +5 -3
  197. package/src/viewport-visible-rect.js +37 -0
  198. package/src/workflow/AgentPanel.jsx +79 -46
  199. package/src/workflow/AgentPlanCard.jsx +41 -0
  200. package/src/workflow/AgentProgressCard.jsx +1 -1
  201. package/src/workflow/TaskBoard.jsx +102 -0
  202. package/src/workflow/TaskInline.jsx +114 -0
  203. package/src/workflow/agent-client.js +25 -36
  204. package/src/workflow/agent-panel.css +14 -1
  205. package/src/workflow/agent-plan.css +93 -0
  206. package/src/workflow/agent-plan.js +59 -0
  207. package/src/workflow/attachment-image.js +10 -3
  208. package/src/workflow/attachment-video.js +91 -0
  209. package/src/workflow/task-board.css +98 -0
  210. package/src/workflow/task-board.js +169 -0
  211. package/tools/check-eslint.mjs +2 -1
  212. package/tools/check-knip.mjs +2 -1
  213. package/tools/dev/pages/greybox-to-video.html +2 -3
  214. package/tools/dev/pages/previs-software.html +2 -2
  215. package/tools/dev/pages/privacy.html +3 -7
  216. package/tools/dev/pages/seedance-camera-control.html +2 -2
  217. package/tools/dev-full.mjs +11 -2
  218. package/tools/eslint-baseline.json +6 -6
  219. package/tools/knip-baseline.json +0 -11
  220. package/tools/loc-baseline.json +30 -31
  221. package/tools/metrics-snapshot.mjs +43 -5
  222. package/tools/render-start-thumbs.mjs +175 -0
  223. package/tools/run-tests.mjs +10 -17
  224. package/tools/sitemap.mjs +1 -1
  225. package/dist/assets/analytics-BibyE5ar.js +0 -6
  226. package/dist/assets/app-DhRJ-PBz.js +0 -5088
  227. package/dist/assets/app-f-fkV5uP.css +0 -1
  228. package/dist/assets/compile-worker-DqiA4AaN.js +0 -5
  229. package/dist/assets/first-shot-handoff-Qx7SKl38.js +0 -1
  230. package/dist/assets/landing-DYg87MDY.js +0 -1
  231. package/dist/assets/scene-asset-sync-DBYQBR0T.js +0 -20
  232. package/dist/assets/scene-asset-sync-_S8NQl5B.css +0 -1
  233. package/dist/assets/workflow-BSA22Ofr.js +0 -179
  234. package/dist/scenes/city-block.cclayproject +0 -1
  235. package/src/burn-in.js +0 -91
  236. package/src/camera-tutorial.jsx +0 -520
  237. package/src/facing-marks.js +0 -88
  238. package/src/first-shot-handoff.js +0 -44
  239. package/src/panels/motion.css +0 -197
  240. package/src/set-pieces.jsx +0 -183
  241. package/src/tutorial-analytics.js +0 -86
  242. package/src/tutorial-walk.js +0 -17
  243. package/src/use-camera-tutorial.js +0 -50
  244. package/src/use-semantic-state.js +0 -12
  245. package/tools/metrics/archive-traffic.mjs +0 -138
  246. package/tools/perf/ik-camera-drag.mjs +0 -138
@@ -1,11 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Snapshot GitHub traffic + repo stats + npm downloads into JSONL ledgers.
3
+ * Snapshot GitHub traffic, stars, referrers, repo stats and npm downloads
4
+ * into JSONL ledgers on the `metrics` branch.
4
5
  *
5
- * GitHub's traffic API only keeps 14 days, so this must run on a schedule
6
+ * GitHub's traffic API only keeps 14 days, so this runs daily
6
7
  * (.github/workflows/metrics.yml) to build a permanent time series. Rows are
7
8
  * deduped by date so overlapping 14-day windows never double-count.
8
9
  *
10
+ * Files written to <output-dir>:
11
+ * traffic-daily.jsonl one row per UTC day: views, view_uniques, clones, clone_uniques
12
+ * stars-daily.jsonl one row per UTC day: new_stars, total (rebuilt from starred_at)
13
+ * referrers.jsonl one row per run date: 14-day top referrers and paths
14
+ * repo-stats.jsonl one row per run date: stars, forks, watchers, issues, npm weekly
15
+ *
9
16
  * Usage: GITHUB_TOKEN=... node tools/metrics-snapshot.mjs <output-dir>
10
17
  */
11
18
  import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
@@ -26,9 +33,9 @@ if (!token) {
26
33
  process.exit(2);
27
34
  }
28
35
 
29
- async function github(path) {
36
+ async function github(path, accept = "application/vnd.github+json") {
30
37
  const res = await fetch(`https://api.github.com/repos/${REPO}${path}`, {
31
- headers: { authorization: `Bearer ${token}`, accept: "application/vnd.github+json" },
38
+ headers: { authorization: `Bearer ${token}`, accept },
32
39
  });
33
40
  if (!res.ok) throw new Error(`GET ${path} -> ${res.status}`);
34
41
  return res.json();
@@ -95,4 +102,35 @@ stats.set(today, {
95
102
  });
96
103
  const statsTotal = saveRows(statsFile, stats);
97
104
 
98
- console.log(`traffic-daily: +${added} new day(s), ${trafficTotal} total; repo-stats: ${statsTotal} snapshot(s)`);
105
+ // 3) Stars per UTC day, rebuilt from every stargazer's starred_at. Someone who
106
+ // later unstars drops out of the history, so totals can trail repo.stargazers_count
107
+ // by a few. Rebuilt in full each run, so it never drifts.
108
+ const starDays = new Map();
109
+ for (let page = 1; page <= 400; page++) {
110
+ const batch = await github(`/stargazers?per_page=100&page=${page}`, "application/vnd.github.star+json");
111
+ if (!Array.isArray(batch) || batch.length === 0) break;
112
+ for (const s of batch) {
113
+ const day = String(s.starred_at).slice(0, 10);
114
+ starDays.set(day, (starDays.get(day) ?? 0) + 1);
115
+ }
116
+ if (batch.length < 100) break;
117
+ }
118
+ const starRows = new Map();
119
+ let running = 0;
120
+ for (const day of [...starDays.keys()].sort()) {
121
+ running += starDays.get(day);
122
+ starRows.set(day, { date: day, new_stars: starDays.get(day), total: running });
123
+ }
124
+ const starsTotal = saveRows(join(outDir, "stars-daily.jsonl"), starRows);
125
+
126
+ // 4) 14-day referrers and paths, one snapshot per run date.
127
+ const [referrers, paths] = await Promise.all([github("/traffic/popular/referrers"), github("/traffic/popular/paths")]);
128
+ const refFile = join(outDir, "referrers.jsonl");
129
+ const refs = loadRows(refFile);
130
+ refs.set(today, { date: today, referrers, paths });
131
+ const refTotal = saveRows(refFile, refs);
132
+
133
+ console.log(
134
+ `traffic-daily: +${added} new day(s), ${trafficTotal} total; repo-stats: ${statsTotal} snapshot(s); ` +
135
+ `stars-daily: ${starsTotal} day(s), ${running} stars; referrers: ${refTotal} snapshot(s)`,
136
+ );
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Render the start-screen template thumbnails from the real studio.
4
+ *
5
+ * npm run dev -- --port 5180 # any running dev server
6
+ * node tools/render-start-thumbs.mjs [--url http://127.0.0.1:5180/app/] [--out public/thumbs]
7
+ *
8
+ * For each template (Blank Stage plus every entry in STARTER_SCENES) and each
9
+ * theme, this opens the studio in a fresh headless Chrome profile, creates the
10
+ * project the way a user does (Blank Stage: the chooser's Create button; a
11
+ * starter: `?scene=<id>`), renders one offscreen frame through the studio's own
12
+ * capture path (`window.__cozyclay.captureFraming`), and writes
13
+ * `<out>/<template-id>-<theme>.webp` at 680x383 (2x the 340px preview).
14
+ * The chooser picks the file by id and theme and falls back to a plain tile
15
+ * when it is missing, so a new starter only needs this script re-run. A
16
+ * starter with no entry in FRAMINGS is shot through its own shot camera.
17
+ *
18
+ * Options: --url, --out, --cdp-port (default 9472), --only <template-id>.
19
+ * CHROME_PATH picks the browser; THUMBS_CHROME_FLAGS replaces the GPU flags
20
+ * (default: Metal on macOS, SwiftShader elsewhere).
21
+ */
22
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
23
+ import { tmpdir } from "node:os";
24
+ import { join, resolve } from "node:path";
25
+ import { fileURLToPath } from "node:url";
26
+ import { spawnOwned, terminateOwned } from "./process-supervisor.mjs";
27
+ import { STARTER_SCENES } from "../src/starter-scenes.js";
28
+ import { THEMES, THEME_KEY } from "../src/theme.js";
29
+
30
+ const ROOT = resolve(fileURLToPath(new URL("..", import.meta.url)));
31
+ const WIDTH = 680;
32
+ const HEIGHT = 383;
33
+ const MAX_BYTES = 60 * 1024;
34
+
35
+ function arg(name, fallback) {
36
+ const argv = process.argv.slice(2);
37
+ const index = argv.indexOf(name);
38
+ return index >= 0 ? argv[index + 1] : fallback;
39
+ }
40
+ const appUrl = new URL(arg("--url", "http://127.0.0.1:5180/app/"));
41
+ const outDir = resolve(ROOT, arg("--out", "public/thumbs"));
42
+ const cdpPort = Number(arg("--cdp-port", 9472));
43
+ const only = arg("--only", null);
44
+ const chromePath = process.env.CHROME_PATH || "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
45
+ const gpuFlags = (process.env.THUMBS_CHROME_FLAGS
46
+ ?? (process.platform === "darwin" ? "--use-angle=metal --ignore-gpu-blocklist" : "--use-angle=swiftshader --enable-unsafe-swiftshader"))
47
+ .split(/\s+/).filter(Boolean);
48
+
49
+ /** A studio framing ({ pos, yaw, pitch, fovDeg }) from an eye point and a target. */
50
+ function lookAt(eye, target, fovDeg) {
51
+ const [dx, dy, dz] = target.map((value, index) => value - eye[index]);
52
+ return { pos: { x: eye[0], y: eye[1], z: eye[2] }, yaw: Math.atan2(-dx, -dz), pitch: Math.atan2(dy, Math.hypot(dx, dz)), fovDeg };
53
+ }
54
+
55
+ // Eye-level 3/4 view. Blank Stage: the default character on the floor grid.
56
+ const FRAMINGS = {
57
+ "blank-stage": lookAt([1.6, 1.55, 3.7], [0, 0.88, 0], 40),
58
+ };
59
+
60
+ const templates = [
61
+ { id: "blank-stage", url: appUrl.href, create: true },
62
+ ...STARTER_SCENES.map((scene) => ({ id: scene.id, url: `${appUrl.href}?scene=${encodeURIComponent(scene.id)}`, create: false })),
63
+ ].filter((template) => !only || template.id === only);
64
+
65
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
66
+
67
+ async function connect() {
68
+ const deadline = Date.now() + 30000;
69
+ while (Date.now() < deadline) {
70
+ try {
71
+ const targets = await (await fetch(`http://127.0.0.1:${cdpPort}/json`)).json();
72
+ const page = targets.find((target) => target.type === "page" && target.webSocketDebuggerUrl);
73
+ if (page) {
74
+ const ws = new WebSocket(page.webSocketDebuggerUrl);
75
+ await new Promise((done, fail) => { ws.onopen = done; ws.onerror = fail; });
76
+ return ws;
77
+ }
78
+ } catch {
79
+ // Chrome is still starting.
80
+ }
81
+ await sleep(150);
82
+ }
83
+ throw new Error("headless Chrome did not expose a page target");
84
+ }
85
+
86
+ function client(ws) {
87
+ let nextId = 1;
88
+ const pending = new Map();
89
+ ws.onmessage = (event) => {
90
+ const message = JSON.parse(event.data);
91
+ const entry = pending.get(message.id);
92
+ if (!entry) return;
93
+ pending.delete(message.id);
94
+ if (message.error) entry.reject(new Error(JSON.stringify(message.error)));
95
+ else entry.resolve(message.result);
96
+ };
97
+ const send = (method, params = {}) => new Promise((resolveSend, reject) => {
98
+ const id = nextId++;
99
+ pending.set(id, { resolve: resolveSend, reject });
100
+ ws.send(JSON.stringify({ id, method, params }));
101
+ });
102
+ const evaluate = async (expression) => {
103
+ const result = await send("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true });
104
+ if (result.exceptionDetails) throw new Error(result.exceptionDetails.exception?.description || "evaluate failed");
105
+ return result.result.value;
106
+ };
107
+ const waitFor = async (label, expression, timeoutMs = 30000) => {
108
+ const deadline = Date.now() + timeoutMs;
109
+ while (Date.now() < deadline) {
110
+ if (await evaluate(`(() => { try { return Boolean(${expression}); } catch { return false; } })()`)) return;
111
+ await sleep(200);
112
+ }
113
+ throw new Error(`timed out waiting for ${label}`);
114
+ };
115
+ return { send, evaluate, waitFor };
116
+ }
117
+
118
+ // Runs in the page: render the framing at 2x, downscale, and encode WebP under the size cap.
119
+ const encodeInPage = (framing) => `(async () => {
120
+ const studio = window.__cozyclay;
121
+ const framing = ${JSON.stringify(framing)};
122
+ const output = { width: ${WIDTH * 2}, height: ${HEIGHT * 2} };
123
+ const png = framing ? studio.captureFraming(framing, output) : studio.capturePlate();
124
+ if (!png) throw new Error("studio capture returned nothing");
125
+ const image = new Image();
126
+ image.src = png;
127
+ await image.decode();
128
+ const canvas = document.createElement("canvas");
129
+ canvas.width = ${WIDTH};
130
+ canvas.height = ${HEIGHT};
131
+ const ctx = canvas.getContext("2d");
132
+ ctx.imageSmoothingQuality = "high";
133
+ const scale = Math.max(canvas.width / image.width, canvas.height / image.height);
134
+ const w = image.width * scale, h = image.height * scale;
135
+ ctx.drawImage(image, (canvas.width - w) / 2, (canvas.height - h) / 2, w, h);
136
+ for (let quality = 0.92; quality > 0.4; quality -= 0.06) {
137
+ const url = canvas.toDataURL("image/webp", quality);
138
+ if (url.length * 0.75 <= ${MAX_BYTES} || quality <= 0.46) return url;
139
+ }
140
+ })()`;
141
+
142
+ async function renderOne(template, theme) {
143
+ const profile = mkdtempSync(join(tmpdir(), "cozyclay-thumbs-"));
144
+ const chrome = spawnOwned(chromePath, [
145
+ "--headless=new", `--remote-debugging-port=${cdpPort}`, `--user-data-dir=${profile}`,
146
+ "--window-size=1440,900", "--no-first-run", "--no-default-browser-check", ...gpuFlags, "about:blank",
147
+ ], { stdio: "ignore" });
148
+ try {
149
+ const cdp = client(await connect());
150
+ await cdp.send("Page.enable");
151
+ await cdp.send("Page.addScriptToEvaluateOnNewDocument", { source: `try { localStorage.setItem(${JSON.stringify(THEME_KEY)}, ${JSON.stringify(theme)}); } catch {}` });
152
+ await cdp.send("Page.navigate", { url: template.url });
153
+ if (template.create) {
154
+ await cdp.waitFor("the start screen", `document.querySelector('[data-template-id="${template.id}"]')`);
155
+ await cdp.evaluate(`document.querySelector('[data-template-id="${template.id}"]').click()`);
156
+ await sleep(200);
157
+ await cdp.evaluate("document.querySelector('.v2-start-project-form').requestSubmit()");
158
+ }
159
+ await cdp.waitFor("the studio", "window.__cozyclay?.charA && !document.querySelector('.v2-start-screen')");
160
+ await sleep(3000); // character model, textures and shadows settle
161
+ const dataUrl = await cdp.evaluate(encodeInPage(FRAMINGS[template.id] ?? null));
162
+ const bytes = Buffer.from(dataUrl.split(",")[1], "base64");
163
+ const file = join(outDir, `${template.id}-${theme}.webp`);
164
+ writeFileSync(file, bytes);
165
+ console.log(`${file.slice(ROOT.length + 1)} ${(bytes.length / 1024).toFixed(1)} KB`);
166
+ } finally {
167
+ await terminateOwned(chrome);
168
+ rmSync(profile, { recursive: true, force: true });
169
+ }
170
+ }
171
+
172
+ mkdirSync(outDir, { recursive: true });
173
+ for (const template of templates) {
174
+ for (const theme of THEMES) await renderOne(template, theme);
175
+ }
@@ -14,7 +14,7 @@ if (!process.env.COZYCLAY_AGENT_SESSIONS_DIR) {
14
14
  }
15
15
 
16
16
  const NODE_FILES = [
17
- "test/motion3/verify-motion3-core.mjs", "test/motion3/verify-motion3-idle.mjs", "test/motion3/verify-motion3-flip.mjs", "test/motion3/verify-motion3-timing.mjs", "test/motion3/verify-motion3-self.mjs", "test/motion3/verify-motion3-commands.mjs", "test/motion3/verify-motion3-exitcar.mjs", "test/motion3/verify-motion3-agentloop.mjs", "test/motion3/verify-motion3-search.mjs", "test/motion3/verify-motion3-skills.mjs", "test/motion3/verify-motion3-carried.mjs", "test/motion3/verify-motion3-people.mjs", "test/motion3/verify-motion3-hit.mjs", "test/motion3/verify-motion3-socket.mjs", "test/motion3/verify-motion3-reach-human.mjs", "test/motion3/verify-motion3-stance-grasp.mjs", "test/motion3/verify-motion3-grasp-friction.mjs", "test/motion3/verify-motion3-grasp-measure.mjs", "test/motion3/verify-motion3-contracts.mjs", "test/motion3/verify-motion3-describe.mjs", "test/motion3/verify-motion3-timeline.mjs", "test/motion3/verify-motion3-probes.mjs", "test/motion3/verify-motion3-grab.mjs", "test/motion3/verify-motion3-grab-blend.mjs", "test/motion3/verify-motion3-patch.mjs", "test/motion3/verify-motion3-repair.mjs", "test/motion3/verify-motion3-specstore.mjs", "test/motion3/verify-motion3-visual.mjs", "test/motion3/verify-motion3-goals.mjs", "test/motion3/verify-motion3-prompt.mjs", "test/motion3/verify-motion3-cup.mjs", "test/verify-proxy-figure.mjs", "test/verify-text-object.mjs",
17
+ "test/motion3/verify-motion3-core.mjs", "test/motion3/verify-playback-frame-store.mjs", "test/verify-playback-render-isolation.mjs", "test/motion3/verify-motion3-idle.mjs", "test/motion3/verify-motion3-flip.mjs", "test/motion3/verify-motion3-timing.mjs", "test/motion3/verify-motion3-self.mjs", "test/motion3/verify-motion3-commands.mjs", "test/motion3/verify-motion3-exitcar.mjs", "test/motion3/verify-motion3-agentloop.mjs", "test/motion3/verify-motion3-search.mjs", "test/motion3/verify-motion3-skills.mjs", "test/motion3/verify-motion3-carried.mjs", "test/motion3/verify-motion3-people.mjs", "test/motion3/verify-motion3-hit.mjs", "test/motion3/verify-motion3-socket.mjs", "test/motion3/verify-motion3-shapes.mjs", "test/motion3/verify-motion3-grasp-fields.mjs", "test/motion3/verify-motion3-finger-contact.mjs", "test/motion3/verify-motion3-grasp-qa.mjs", "test/motion3/verify-motion3-load.mjs", "test/motion3/verify-motion3-throw.mjs", "test/motion3/verify-motion3-catch.mjs", "test/motion3/verify-motion3-grasp-hints.mjs", "test/motion3/verify-motion3-reach-human.mjs", "test/motion3/verify-motion3-stance-grasp.mjs", "test/motion3/verify-motion3-grasp-friction.mjs", "test/motion3/verify-motion3-grasp-measure.mjs", "test/motion3/verify-motion3-contracts.mjs", "test/motion3/verify-motion3-describe.mjs", "test/motion3/verify-motion3-timeline.mjs", "test/motion3/verify-motion3-probes.mjs", "test/motion3/verify-motion3-grab.mjs", "test/motion3/verify-motion3-grab-blend.mjs", "test/motion3/verify-motion3-patch.mjs", "test/motion3/verify-motion3-repair.mjs", "test/motion3/verify-motion3-specstore.mjs", "test/motion3/verify-motion3-visual.mjs", "test/motion3/verify-motion3-goals.mjs", "test/motion3/verify-motion3-prompt.mjs", "test/motion3/verify-motion3-cup.mjs", "test/verify-proxy-figure.mjs", "test/verify-text-object.mjs",
18
18
  "test/verify-proxy-figure-bounds.mjs",
19
19
  "test/verify-character-capabilities.mjs",
20
20
  "test/verify-root-path.mjs",
@@ -22,13 +22,12 @@ const NODE_FILES = [
22
22
  "test/bus/verify-view-mode-pose.mjs",
23
23
  "test/verify-studio-actions.mjs",
24
24
  "test/ardy/verify-collision-blockers.mjs",
25
- "test/verify-facing-marks.mjs",
26
25
  "test/verify-pose-fade.mjs",
27
26
  "test/verify-pose-gizmo.mjs",
28
27
  "test/ardy/verify-key-runs.mjs",
29
28
  "test/ardy/verify-playback-clock.mjs",
30
29
  "test/ardy/verify-timeline-coordinates.mjs",
31
- "test/verify-tokens.mjs",
30
+ "test/verify-tokens.mjs", "test/verify-lens-scrub.mjs",
32
31
  "test/demo/verify-api-routes.mjs",
33
32
  "test/ik/verify-collision-capsules.mjs",
34
33
  "test/ik/verify-ground.mjs",
@@ -44,13 +43,13 @@ const NODE_FILES = [
44
43
  "test/ik/verify-range-pin-ui.mjs",
45
44
  "test/ik/verify-range-pin-object-transform.mjs",
46
45
  "test/verify-ik-camera-performance.mjs",
47
- "test/verify-ik-entry-camera.mjs",
46
+ "test/verify-ik-entry-camera.mjs", "test/verify-viewport-visible-rect.mjs",
48
47
  "test/process/verify-lifecycle.mjs",
49
48
  "test/process/verify-mcp-package-isolation.mjs",
50
49
  "test/process/verify-package-telemetry.mjs",
51
50
  "test/process/verify-agent-deps.mjs",
52
51
  "test/verify-agent-image-references.mjs",
53
- "test/verify-agent-panel.mjs", "test/verify-agent-turn-view.mjs", "test/verify-agent-reply-text.mjs",
52
+ "test/verify-agent-panel.mjs", "test/verify-agent-turn-view.mjs", "test/verify-agent-plan.mjs", "test/verify-agent-reply-text.mjs", "test/verify-agent-sculpt-sketch.mjs",
54
53
  "test/verify-agent-host.mjs",
55
54
  "test/verify-agent-routes.mjs",
56
55
  "test/verify-studio-owner-restart.mjs",
@@ -65,7 +64,7 @@ const NODE_FILES = [
65
64
  "test/verify-schema-to-typebox.mjs",
66
65
  "test/verify-agent-sse-golden.mjs",
67
66
  "test/verify-agent-context-budget.mjs", "test/verify-agent-context-trim.mjs",
68
- "test/verify-agent-supervisor.mjs",
67
+ "test/verify-agent-supervisor.mjs", "test/verify-studio-tasks.mjs", "test/verify-task-board.mjs",
69
68
  "test/verify-studio-agent-protocol.mjs",
70
69
  "test/verify-studio-agent-sampled-framing.mjs",
71
70
  "test/verify-studio-agent-tokens.mjs",
@@ -82,30 +81,27 @@ const NODE_FILES = [
82
81
  "test/verify-auto-color.mjs",
83
82
  "test/verify-coplanar-depth.mjs",
84
83
  "test/verify-empty-object.mjs",
85
- "test/verify-sculpt-recipe.mjs",
84
+ "test/verify-sculpt-recipe.mjs", "test/verify-sculpt-shapes.mjs", "test/verify-sculpt-loft-convex.mjs", "test/verify-sculpt-detail.mjs",
86
85
  "test/verify-sculpt-object.mjs",
87
86
  "test/verify-sculpt-compare.mjs",
88
87
  "test/verify-sculpt-run.mjs",
89
88
  "test/verify-sculpt-compare-command.mjs",
90
89
  "test/verify-sculpt-panel.mjs",
91
90
  "test/verify-sculpt-blueprint.mjs",
92
- "test/verify-sculpt-mode.mjs",
91
+ "test/verify-sculpt-mode.mjs", "test/verify-sculpt-csg.mjs", "test/verify-material-presets.mjs",
93
92
  "test/verify-route-owner.mjs",
94
93
  "test/verify-part-colours.mjs",
95
94
  "test/verify-asset-shelf.mjs",
96
95
  "test/verify-blocking-depth.mjs",
97
96
  "test/verify-render-passes-video.mjs",
98
- "test/verify-burn-in.mjs",
99
97
  "test/verify-camera-block.mjs", "test/verify-camera-direction.mjs",
100
98
  "test/verify-camera-follow.mjs",
101
99
  "test/verify-camera-move.mjs",
102
100
  "test/verify-camera-rail-schedule.mjs",
103
101
  "test/verify-camera-pointer-lock.mjs",
104
- "test/verify-camera-tutorial.mjs",
105
- "test/verify-first-shot-handoff.mjs",
106
102
  "test/verify-codex-client.mjs",
107
103
  "test/verify-cuts.mjs",
108
- "test/verify-shot-guides.mjs",
104
+ "test/verify-shot-guides.mjs", "test/verify-shot-frame.mjs",
109
105
  "test/verify-error-boundary.mjs",
110
106
  "test/verify-g006-css.mjs",
111
107
  "test/verify-gizmo-claim.mjs",
@@ -238,9 +234,6 @@ const BROWSER_FILES = [
238
234
  "test/verify-static-shot-export-browser.mjs",
239
235
  "test/qa-agent-view-toggle-browser.mjs",
240
236
  "test/qa-agent-scenarios-browser.mjs",
241
- "test/qa-camera-tutorial-browser.mjs",
242
- "test/qa-first-shot-handoff-browser.mjs",
243
- "test/qa-tutorial-analytics-browser.mjs",
244
237
  "test/qa-camera-pointer-lock-browser.mjs",
245
238
  "test/qa-first-edit-browser.mjs",
246
239
  "test/qa-ia-tail-browser.mjs",
@@ -262,7 +255,7 @@ const BROWSER_FILES = [
262
255
  // The inventory sweep only picks up `verify*.mjs`; a browser suite named for
263
256
  // the QA runner it needs is listed here so it still shows up in the manifest
264
257
  // (as an EXCLUDE with its reason) instead of going unmentioned.
265
- const EXTRA_INVENTORY = ["test/ik/translation-step.mjs", "test/qa-scenes-v5-migration-browser.mjs", "test/qa-agent-scenarios-browser.mjs", "test/qa-execution-outcomes-browser.mjs", "test/qa-export-recovery-browser.mjs", "test/qa-agent-view-toggle-browser.mjs", "test/qa-camera-tutorial-browser.mjs", "test/qa-first-shot-handoff-browser.mjs", "test/qa-tutorial-analytics-browser.mjs", "test/qa-camera-pointer-lock-browser.mjs", "test/qa-first-edit-browser.mjs", "test/qa-ia-tail-browser.mjs", "test/qa-keyframe-pack-browser.mjs", "test/qa-preview-browser.mjs", "test/qa-reference-slots-browser.mjs", "test/qa-scene-playback-browser.mjs", "test/qa-scene-switcher-browser.mjs", "test/qa-send-to-ai-browser.mjs", "test/qa-view-menu-browser.mjs", "test/qa-live-reconnect-browser.mjs", "test/qa-clipboard-paste-browser.mjs", "test/qa-rail-lean-browser.mjs", "test/qa-rail-reach-browser.mjs", "test/qa-rail-ring-browser.mjs", "test/qa-speed-graph-browser.mjs"];
258
+ const EXTRA_INVENTORY = ["test/ik/translation-step.mjs", "test/qa-scenes-v5-migration-browser.mjs", "test/qa-agent-scenarios-browser.mjs", "test/qa-execution-outcomes-browser.mjs", "test/qa-export-recovery-browser.mjs", "test/qa-agent-view-toggle-browser.mjs", "test/qa-camera-pointer-lock-browser.mjs", "test/qa-first-edit-browser.mjs", "test/qa-ia-tail-browser.mjs", "test/qa-keyframe-pack-browser.mjs", "test/qa-preview-browser.mjs", "test/qa-reference-slots-browser.mjs", "test/qa-scene-playback-browser.mjs", "test/qa-scene-switcher-browser.mjs", "test/qa-send-to-ai-browser.mjs", "test/qa-view-menu-browser.mjs", "test/qa-live-reconnect-browser.mjs", "test/qa-clipboard-paste-browser.mjs", "test/qa-rail-lean-browser.mjs", "test/qa-rail-reach-browser.mjs", "test/qa-rail-ring-browser.mjs", "test/qa-speed-graph-browser.mjs"];
266
259
 
267
260
  EXTRA_INVENTORY.push("test/qa-proxy-figure-browser.mjs");
268
261
  EXTRA_INVENTORY.push("test/qa-proxy-entry-browser.mjs");
@@ -275,7 +268,7 @@ function verificationFiles(directory) {
275
268
  .flatMap((entry) => {
276
269
  const path = join(directory, entry.name);
277
270
  if (entry.isDirectory()) return entry.name === "node_modules" ? [] : verificationFiles(path);
278
- return entry.isFile() && /^verify(-.*)?\.mjs$/.test(entry.name) ? [relative(".", path)] : [];
271
+ return entry.isFile() && /^verify(-.*)?\.mjs$/.test(entry.name) ? [relative(".", path).replaceAll("\\", "/")] : [];
279
272
  })
280
273
  .sort();
281
274
  }
package/tools/sitemap.mjs CHANGED
@@ -8,7 +8,7 @@ import { resolve } from "node:path";
8
8
 
9
9
  const ORIGIN = "https://cozyclay.org";
10
10
  const PAGES = [
11
- { path: "/", sources: ["index.html", "public/media", "public/scenes"] },
11
+ { path: "/", sources: ["index.html", "public/media"] },
12
12
  { path: "/greybox-to-video/", sources: ["greybox-to-video/index.html"] },
13
13
  { path: "/previs-software/", sources: ["previs-software/index.html"] },
14
14
  { path: "/seedance-camera-control/", sources: ["seedance-camera-control/index.html"] },