@playcraft/cli 0.1.35-prod → 0.1.37-beta

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 (366) hide show
  1. package/README.md +58 -3
  2. package/SKILLS_REGISTRY.md +149 -0
  3. package/dist/agent/local-backend.js +1 -1
  4. package/dist/auth/account-activation.js +157 -0
  5. package/dist/auth/messages.js +30 -0
  6. package/dist/cli-root-help.js +2 -0
  7. package/dist/commands/build-all.js +5 -19
  8. package/dist/commands/build-temp.js +26 -0
  9. package/dist/commands/build.js +15 -28
  10. package/dist/commands/create.js +26 -33
  11. package/dist/commands/image.js +2 -6
  12. package/dist/commands/login.js +84 -42
  13. package/dist/commands/platform-skills.generated.js +19 -0
  14. package/dist/commands/playable.js +53 -0
  15. package/dist/commands/skills.js +4 -0
  16. package/dist/commands/tools-3d.js +11 -0
  17. package/dist/commands/tools-batch.js +87 -0
  18. package/dist/commands/tools-generation.js +68 -109
  19. package/dist/commands/tools.js +2 -2
  20. package/dist/commands/video.js +111 -0
  21. package/dist/commands/workspace-runtime.js +54 -0
  22. package/dist/commands/workspace.js +35 -0
  23. package/dist/config.js +59 -4
  24. package/dist/index.js +40 -3
  25. package/dist/project-skills/builtin-import.js +225 -0
  26. package/dist/project-skills/commands.js +77 -0
  27. package/dist/project-skills/lifecycle.js +28 -0
  28. package/dist/project-skills/local-run.js +112 -0
  29. package/dist/project-skills/local-store.js +221 -0
  30. package/dist/project-skills/messages.js +80 -0
  31. package/dist/project-skills/reconcile.js +62 -0
  32. package/dist/project-skills/remote-cache.js +51 -0
  33. package/dist/project-skills/validation.js +19 -0
  34. package/dist/recording/messages.js +40 -0
  35. package/dist/recording/project-digest.js +85 -0
  36. package/dist/recording/recorder.js +293 -0
  37. package/dist/recording/scenario.js +50 -0
  38. package/dist/remix/clone.js +2 -0
  39. package/dist/remix/init-template.js +2 -0
  40. package/dist/remix/pull.js +2 -0
  41. package/dist/remix/push.js +21 -1
  42. package/dist/skill-registry/client.js +345 -0
  43. package/dist/skill-registry/commands.js +180 -0
  44. package/dist/skill-registry/evaluation-client.js +156 -0
  45. package/dist/skill-registry/evaluation-commands.js +174 -0
  46. package/dist/skill-registry/messages.js +177 -0
  47. package/dist/skill-registry/package.js +275 -0
  48. package/dist/skill-registry/platform-package.js +100 -0
  49. package/dist/utils/agent-api-client.js +185 -30
  50. package/dist/utils/environment-endpoints.js +17 -0
  51. package/dist/utils/playcraft-http-client.js +6 -4
  52. package/dist/utils/tool-operation-journal.js +124 -0
  53. package/dist/utils/video-evidence-git.js +81 -0
  54. package/dist/video-replication/client.js +222 -0
  55. package/dist/video-replication/comparison-evidence.js +149 -0
  56. package/dist/video-replication/comparison-files.js +57 -0
  57. package/dist/video-replication/comparison-log.js +72 -0
  58. package/dist/video-replication/comparison-messages.js +104 -0
  59. package/dist/video-replication/comparison-output.js +63 -0
  60. package/dist/video-replication/comparison-preflight.js +66 -0
  61. package/dist/video-replication/comparison.js +216 -0
  62. package/dist/video-replication/evidence.js +165 -0
  63. package/dist/video-replication/media.js +111 -0
  64. package/dist/video-replication/messages.js +89 -0
  65. package/dist/workspace/messages.js +35 -0
  66. package/dist/workspace/transfer-client.js +174 -0
  67. package/dist/workspace-runtime/codex/app-server.js +1040 -0
  68. package/dist/workspace-runtime/codex/compact.js +46 -0
  69. package/dist/workspace-runtime/codex/config-toml.js +107 -0
  70. package/dist/workspace-runtime/codex/jsonrpc-stdio.js +145 -0
  71. package/dist/workspace-runtime/codex/launcher-client.js +177 -0
  72. package/dist/workspace-runtime/codex/loopback.js +146 -0
  73. package/dist/workspace-runtime/codex/mcp-config.js +39 -0
  74. package/dist/workspace-runtime/codex/model-broker.js +263 -0
  75. package/dist/workspace-runtime/codex/native-adapter.js +9 -0
  76. package/dist/workspace-runtime/codex/permissions.js +70 -0
  77. package/dist/workspace-runtime/codex/thread-image.js +38 -0
  78. package/dist/workspace-runtime/codex/ui-instructions.js +4 -0
  79. package/dist/workspace-runtime/codex/ui-tools.js +183 -0
  80. package/dist/workspace-runtime/codex/work-mode.js +15 -0
  81. package/dist/workspace-runtime/main.js +10 -0
  82. package/dist/workspace-runtime/persistence/journal.js +358 -0
  83. package/dist/workspace-runtime/persistence/native-subagents.js +162 -0
  84. package/dist/workspace-runtime/persistence/sqlite-client.js +77 -0
  85. package/dist/workspace-runtime/persistence/sqlite-worker.js +1161 -0
  86. package/dist/workspace-runtime/persistence/stopped-execution.js +77 -0
  87. package/dist/workspace-runtime/previews/output.js +36 -0
  88. package/dist/workspace-runtime/previews/publish.js +262 -0
  89. package/dist/workspace-runtime/previews/single-file.js +43 -0
  90. package/dist/workspace-runtime/processes/managed-writes.js +69 -0
  91. package/dist/workspace-runtime/production/contracts.js +19 -0
  92. package/dist/workspace-runtime/production/controller.js +218 -0
  93. package/dist/workspace-runtime/production/dependencies.js +60 -0
  94. package/dist/workspace-runtime/production/native.js +212 -0
  95. package/dist/workspace-runtime/production/processes.js +102 -0
  96. package/dist/workspace-runtime/production/save-command.js +35 -0
  97. package/dist/workspace-runtime/production/source-archive.js +137 -0
  98. package/dist/workspace-runtime/production/source-copy.js +91 -0
  99. package/dist/workspace-runtime/production/stages.js +303 -0
  100. package/dist/workspace-runtime/production/storage.js +60 -0
  101. package/dist/workspace-runtime/serve.js +214 -0
  102. package/dist/workspace-runtime/server/auth.js +21 -0
  103. package/dist/workspace-runtime/server/connectivity.js +201 -0
  104. package/dist/workspace-runtime/server/desktop-host.js +429 -0
  105. package/dist/workspace-runtime/server/desktop-preview-config.js +81 -0
  106. package/dist/workspace-runtime/server/desktop-preview-service.js +157 -0
  107. package/dist/workspace-runtime/server/desktop-preview-static.js +53 -0
  108. package/dist/workspace-runtime/server/desktop-preview.js +189 -0
  109. package/dist/workspace-runtime/server/dispatch.js +1217 -0
  110. package/dist/workspace-runtime/server/endpoints.js +21 -0
  111. package/dist/workspace-runtime/server/execution-group.js +90 -0
  112. package/dist/workspace-runtime/server/http.js +1243 -0
  113. package/dist/workspace-runtime/server/lock.js +53 -0
  114. package/dist/workspace-runtime/server/rfb-watch-gate.js +168 -0
  115. package/dist/workspace-runtime/server/source-uploader.js +138 -0
  116. package/dist/workspace-runtime/server/terminal-host.js +366 -0
  117. package/dist/workspace-runtime/server/types.js +1 -0
  118. package/dist/workspace-runtime/server/workspace-transfer.js +99 -0
  119. package/dist/workspace-runtime/workspaces/archive.js +207 -0
  120. package/dist/workspace-runtime/workspaces/artifact-events.js +55 -0
  121. package/dist/workspace-runtime/workspaces/artifact-files.js +197 -0
  122. package/dist/workspace-runtime/workspaces/artifact-receipts.js +89 -0
  123. package/dist/workspace-runtime/workspaces/artifacts.js +282 -0
  124. package/dist/workspace-runtime/workspaces/attachment-content.js +123 -0
  125. package/dist/workspace-runtime/workspaces/attachments.js +185 -0
  126. package/dist/workspace-runtime/workspaces/batch-commit.js +142 -0
  127. package/dist/workspace-runtime/workspaces/cli-context.js +23 -0
  128. package/dist/workspace-runtime/workspaces/cli-credentials.js +111 -0
  129. package/dist/workspace-runtime/workspaces/composer-context.js +147 -0
  130. package/dist/workspace-runtime/workspaces/context-error.js +2 -0
  131. package/dist/workspace-runtime/workspaces/dependencies.js +264 -0
  132. package/dist/workspace-runtime/workspaces/developer-command.js +64 -0
  133. package/dist/workspace-runtime/workspaces/directories.js +133 -0
  134. package/dist/workspace-runtime/workspaces/directory-access.js +37 -0
  135. package/dist/workspace-runtime/workspaces/directory-attachments.js +245 -0
  136. package/dist/workspace-runtime/workspaces/directory-maintenance.js +111 -0
  137. package/dist/workspace-runtime/workspaces/directory-stats.js +49 -0
  138. package/dist/workspace-runtime/workspaces/disk-snapshot.js +31 -0
  139. package/dist/workspace-runtime/workspaces/empty-template.js +42 -0
  140. package/dist/workspace-runtime/workspaces/files-search.js +116 -0
  141. package/dist/workspace-runtime/workspaces/files.js +120 -0
  142. package/dist/workspace-runtime/workspaces/git-credential-broker.js +312 -0
  143. package/dist/workspace-runtime/workspaces/git-credential-helper.js +68 -0
  144. package/dist/workspace-runtime/workspaces/git-direct-store.js +155 -0
  145. package/dist/workspace-runtime/workspaces/git.js +670 -0
  146. package/dist/workspace-runtime/workspaces/instructions.js +56 -0
  147. package/dist/workspace-runtime/workspaces/json5-edit.js +170 -0
  148. package/dist/workspace-runtime/workspaces/library-attachments.js +268 -0
  149. package/dist/workspace-runtime/workspaces/optional-platform-skills.js +14 -0
  150. package/dist/workspace-runtime/workspaces/parameters.js +272 -0
  151. package/dist/workspace-runtime/workspaces/prepare.js +278 -0
  152. package/dist/workspace-runtime/workspaces/registry.js +251 -0
  153. package/dist/workspace-runtime/workspaces/remix-references.js +301 -0
  154. package/dist/workspace-runtime/workspaces/revisions.js +31 -0
  155. package/dist/workspace-runtime/workspaces/session-image.js +104 -0
  156. package/dist/workspace-runtime/workspaces/shared-git.js +40 -0
  157. package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +95 -0
  158. package/dist/workspace-runtime/workspaces/skills.js +191 -0
  159. package/dist/workspace-runtime/workspaces/transfer.js +288 -0
  160. package/dist/workspace-runtime/workspaces/turn-changes.js +75 -0
  161. package/dist/workspace-runtime/workspaces/video-attachments.js +49 -0
  162. package/package.json +15 -5
  163. package/project-template/.claude/agents/refs/ta-image-generation-detail.md +123 -67
  164. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +44 -105
  165. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +33 -42
  166. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +20 -18
  167. package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +7 -6
  168. package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -36
  169. package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +25 -0
  170. package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +79 -0
  171. package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +29 -0
  172. package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +37 -0
  173. package/project-template-v2/.claude/agents/artist.md +82 -0
  174. package/project-template-v2/.claude/agents/developer.md +153 -0
  175. package/project-template-v2/.claude/agents/game-designer.md +264 -0
  176. package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +533 -0
  177. package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
  178. package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +27 -0
  179. package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
  180. package/project-template-v2/.claude/agents/refs/atom-skill-library.md +81 -0
  181. package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +432 -0
  182. package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +252 -0
  183. package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  184. package/project-template-v2/.claude/agents/refs/framework-mda.md +147 -0
  185. package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  186. package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
  187. package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
  188. package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
  189. package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +300 -0
  190. package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +495 -0
  191. package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
  192. package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +111 -0
  193. package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
  194. package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +412 -0
  195. package/project-template-v2/.claude/agents/reviewer.md +75 -0
  196. package/project-template-v2/.claude/agents/technical-artist.md +86 -0
  197. package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +243 -0
  198. package/project-template-v2/.claude/hooks/user-prompt.mjs +133 -0
  199. package/project-template-v2/.claude/settings.json +33 -0
  200. package/project-template-v2/.claude/skills/brainstorming/SKILL.md +161 -0
  201. package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  202. package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +177 -0
  203. package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  204. package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  205. package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  206. package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  207. package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  208. package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +309 -0
  209. package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
  210. package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
  211. package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
  212. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
  213. package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
  214. package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
  215. package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
  216. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
  217. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
  218. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
  219. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
  220. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
  221. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
  222. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
  223. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
  224. package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
  225. package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  226. package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +373 -0
  227. package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  228. package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +215 -0
  229. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +382 -0
  230. package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
  231. package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  232. package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  233. package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
  234. package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
  235. package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
  236. package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
  237. package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +390 -0
  238. package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
  239. package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
  240. package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
  241. package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +240 -0
  242. package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +226 -0
  243. package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
  244. package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +275 -0
  245. package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +134 -0
  246. package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +142 -0
  247. package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +153 -0
  248. package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +386 -0
  249. package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  250. package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  251. package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  252. package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +102 -0
  253. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  254. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  255. package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  256. package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  257. package/project-template-v2/.claude/snapshot.mjs +263 -0
  258. package/project-template-v2/.playcraft/skills.lock.json +152 -0
  259. package/project-template-v2/CLAUDE.md +146 -0
  260. package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
  261. package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
  262. package/project-template-v2/assets/bundles/.gitkeep +0 -0
  263. package/project-template-v2/assets/images/bg/.gitkeep +0 -0
  264. package/project-template-v2/assets/images/reference/.gitkeep +0 -0
  265. package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
  266. package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
  267. package/project-template-v2/assets/images/ui/.gitkeep +0 -0
  268. package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
  269. package/project-template-v2/assets/models/.gitkeep +0 -0
  270. package/project-template-v2/docs/harness/iteration-1/context-flow.md +254 -0
  271. package/project-template-v2/docs/harness/iteration-1/generate-flow.md +91 -0
  272. package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +214 -0
  273. package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +75 -0
  274. package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +63 -0
  275. package/project-template-v2/docs/harness/iteration-2/context-flow.md +223 -0
  276. package/project-template-v2/docs/harness/iteration-2/generate-flow.md +129 -0
  277. package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +267 -0
  278. package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +164 -0
  279. package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +115 -0
  280. package/project-template-v2/docs/harness/orchestrator-flow.md +364 -0
  281. package/project-template-v2/docs/project-state.json +60 -0
  282. package/project-template-v2/docs/project-state.md +72 -0
  283. package/project-template-v2/docs/standards/README.md +225 -0
  284. package/project-template-v2/docs/standards/agent-behavior-standards.md +174 -0
  285. package/project-template-v2/docs/standards/artifacts/design-brief.md +19 -0
  286. package/project-template-v2/docs/standards/artifacts/design.md +22 -0
  287. package/project-template-v2/docs/standards/artifacts/game-code.md +41 -0
  288. package/project-template-v2/docs/standards/artifacts/todo-list.md +41 -0
  289. package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +343 -0
  290. package/project-template-v2/game/index.ts +18 -0
  291. package/project-template-v2/globals.d.ts +51 -0
  292. package/project-template-v2/index.css +34 -0
  293. package/project-template-v2/index.html +18 -0
  294. package/project-template-v2/main.ts +9 -0
  295. package/project-template-v2/package.json +46 -0
  296. package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +31 -0
  297. package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +86 -0
  298. package/project-template-v2/skills/_shared/scripts/dod-check.mjs +153 -0
  299. package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +70 -0
  300. package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +91 -0
  301. package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +131 -0
  302. package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +80 -0
  303. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +90 -0
  304. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +93 -0
  305. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +23 -0
  306. package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +230 -0
  307. package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +58 -0
  308. package/project-template-v2/skills/_shared/scripts/state-get.mjs +339 -0
  309. package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +39 -0
  310. package/project-template-v2/skills/_shared/scripts/state-set.mjs +94 -0
  311. package/project-template-v2/skills/_shared/scripts/state-store.mjs +783 -0
  312. package/project-template-v2/skills/_shared/scripts/todo-add.mjs +88 -0
  313. package/project-template-v2/skills/_shared/scripts/todo-get.mjs +130 -0
  314. package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +33 -0
  315. package/project-template-v2/skills/_shared/scripts/todo-set.mjs +47 -0
  316. package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +268 -0
  317. package/project-template-v2/skills/_shared/scripts/verify-env.mjs +285 -0
  318. package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  319. package/project-template-v2/skills/playable-autoplay/SKILL.md +176 -0
  320. package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +4 -0
  321. package/project-template-v2/skills/playable-debug/SKILL.md +116 -0
  322. package/project-template-v2/skills/playable-debug/agents/openai.yaml +4 -0
  323. package/project-template-v2/skills/playable-debug/references/debug-config.md +40 -0
  324. package/project-template-v2/skills/playable-record/SKILL.md +140 -0
  325. package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  326. package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  327. package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +425 -0
  328. package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +261 -0
  329. package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  330. package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +322 -0
  331. package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +105 -0
  332. package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +230 -0
  333. package/project-template-v2/skills/playable-validate/SKILL.md +61 -0
  334. package/project-template-v2/skills/playable-validate/validation-rules.md +14 -0
  335. package/project-template-v2/skills/playable-verify-ui/SKILL.md +63 -0
  336. package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  337. package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  338. package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  339. package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  340. package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  341. package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  342. package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  343. package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  344. package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  345. package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  346. package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
  347. package/project-template-v2/tsconfig.json +20 -0
  348. package/project-template-v2/vite.config.ts +27 -0
  349. package/project-templates/batch-locks/phaser.yaml +922 -0
  350. package/project-templates/batch-locks/threejs.yaml +978 -0
  351. package/project-templates/empty/AGENTS.md +41 -0
  352. package/project-templates/empty/gitignore +33 -0
  353. package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  354. package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +44 -105
  355. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +33 -42
  356. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +20 -18
  357. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +7 -6
  358. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -36
  359. package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  360. package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +44 -105
  361. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +33 -42
  362. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +20 -18
  363. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +7 -6
  364. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -36
  365. package/dist/commands/tools-image-context.js +0 -23
  366. package/dist/utils/image-asset-context.js +0 -105
@@ -0,0 +1,288 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { constants } from 'node:fs';
4
+ import { chmod, copyFile, lstat, mkdir, mkdtemp, open, readdir, realpath, rename, rm, rmdir, writeFile } from 'node:fs/promises';
5
+ import { tmpdir } from 'node:os';
6
+ import { dirname, join, resolve, sep } from 'node:path';
7
+ import { isCredentialFile } from './git.js';
8
+ export const WORKSPACE_TRANSFER_MAX_BYTES = 256 * 1024 * 1024;
9
+ const MAX_FILES = 20_000;
10
+ const EXCLUDED = new Set(['.git', 'node_modules', '.playcraft-workspace.json', '.playcraft.json']);
11
+ export class WorkspaceTransferError extends Error {
12
+ code;
13
+ constructor(code) {
14
+ super(code);
15
+ this.code = code;
16
+ }
17
+ }
18
+ function excluded(path) {
19
+ const parts = path.split('/');
20
+ return parts.some(part => EXCLUDED.has(part.toLowerCase()) || /^\.playcraft-(?:credential|transfer)-.*\.tmp$/i.test(part)) ||
21
+ parts.some((part, index) => part === '.playcraft' && /^(?:workspace-context(?:\..*)?|video-replication)$/.test(parts[index + 1] ?? '')) ||
22
+ isCredentialFile(path);
23
+ }
24
+ export function transferGit(root, args) {
25
+ const env = { PATH: process.env.PATH, HOME: tmpdir(),
26
+ GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null', GIT_TERMINAL_PROMPT: '0', GIT_ALLOW_PROTOCOL: 'file' };
27
+ const result = spawnSync('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'commit.gpgsign=false',
28
+ '-c', 'core.autocrlf=false', '-c', 'user.name=PlayCraft Workspace', '-c', 'user.email=workspace@playcraft.local', ...args], { cwd: root, encoding: 'utf8', env, timeout: 120_000, maxBuffer: 8 * 1024 * 1024 });
29
+ if (result.status !== 0)
30
+ throw new WorkspaceTransferError('workspace_transfer_git_failed');
31
+ return result.stdout.trim();
32
+ }
33
+ async function copySnapshot(source, target) {
34
+ let bytes = 0, files = 0;
35
+ async function copy(directory, prefix) {
36
+ const directoryInfo = await lstat(directory);
37
+ const directoryReal = await realpath(directory);
38
+ if (!directoryInfo.isDirectory() || directoryInfo.isSymbolicLink() ||
39
+ (directoryReal !== source && !directoryReal.startsWith(`${source}${sep}`)))
40
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
41
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
42
+ const path = prefix ? `${prefix}/${entry.name}` : entry.name;
43
+ if (excluded(path))
44
+ continue;
45
+ const from = join(directory, entry.name), to = join(target, path);
46
+ const info = await lstat(from);
47
+ if (info.isSymbolicLink())
48
+ continue;
49
+ if (info.isDirectory()) {
50
+ await mkdir(to, { recursive: true });
51
+ await copy(from, path);
52
+ }
53
+ else if (info.isFile()) {
54
+ bytes += info.size;
55
+ files++;
56
+ if (bytes > WORKSPACE_TRANSFER_MAX_BYTES || files > MAX_FILES)
57
+ throw new WorkspaceTransferError('workspace_transfer_too_large');
58
+ if (!(await realpath(from)).startsWith(`${source}${sep}`))
59
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
60
+ const handle = await open(from, constants.O_RDONLY | constants.O_NOFOLLOW);
61
+ try {
62
+ const before = await handle.stat();
63
+ if (!before.isFile() || before.dev !== info.dev || before.ino !== info.ino || before.size !== info.size ||
64
+ before.mtimeMs !== info.mtimeMs || before.ctimeMs !== info.ctimeMs)
65
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
66
+ const chunks = [];
67
+ let size = 0;
68
+ for await (const chunk of handle.createReadStream({ autoClose: false })) {
69
+ size += chunk.length;
70
+ if (size > before.size)
71
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
72
+ chunks.push(chunk);
73
+ }
74
+ const after = await handle.stat();
75
+ if (size !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs)
76
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
77
+ await writeFile(to, Buffer.concat(chunks));
78
+ }
79
+ finally {
80
+ await handle.close();
81
+ }
82
+ await chmod(to, info.mode & 0o111 ? 0o755 : 0o644);
83
+ }
84
+ }
85
+ }
86
+ await copy(source, '');
87
+ }
88
+ async function matchesFile(root, path, expected) {
89
+ const target = await safeTarget(root, path);
90
+ const info = await lstat(target).catch(error => { if (['ENOENT', 'ENOTDIR'].includes(error.code))
91
+ return undefined; throw error; });
92
+ if (!expected)
93
+ return !info || info.isDirectory();
94
+ if (!info?.isFile() || Boolean(info.mode & 0o111) !== expected.executable)
95
+ return false;
96
+ const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
97
+ try {
98
+ const stat = await handle.stat();
99
+ const hash = createHash(expected.sha.length === 64 ? 'sha256' : 'sha1');
100
+ hash.update(`blob ${stat.size}\0`);
101
+ for await (const chunk of handle.createReadStream({ autoClose: false }))
102
+ hash.update(chunk);
103
+ return hash.digest('hex') === expected.sha;
104
+ }
105
+ finally {
106
+ await handle.close();
107
+ }
108
+ }
109
+ async function removeEmptyParents(root, target) {
110
+ let parent = dirname(target);
111
+ while (parent !== root) {
112
+ if ((await readdir(parent)).length > 0)
113
+ break;
114
+ await rmdir(parent);
115
+ parent = dirname(parent);
116
+ }
117
+ }
118
+ async function installFile(root, path, source, executable) {
119
+ const target = await safeTarget(root, path);
120
+ const existing = await lstat(target).catch(error => { if (error.code === 'ENOENT')
121
+ return undefined; throw error; });
122
+ if (existing?.isDirectory())
123
+ await rmdir(target);
124
+ await mkdir(dirname(target), { recursive: true });
125
+ const stage = join(dirname(target), `.playcraft-transfer-${randomUUID()}.tmp`);
126
+ try {
127
+ await copyFile(source, stage);
128
+ await chmod(stage, executable ? 0o755 : 0o644);
129
+ await rename(stage, target);
130
+ }
131
+ catch (error) {
132
+ await rm(stage, { force: true }).catch(() => undefined);
133
+ await removeEmptyParents(root, target).catch(() => undefined);
134
+ throw error;
135
+ }
136
+ finally {
137
+ await rm(stage, { force: true }).catch(() => undefined);
138
+ }
139
+ }
140
+ /** A fresh history contains only the current portable files, including uncommitted changes. */
141
+ export async function createWorkspaceTransfer(root) {
142
+ const temporary = await mkdtemp(join(tmpdir(), 'playcraft-transfer-'));
143
+ const cleanup = () => rm(temporary, { recursive: true, force: true });
144
+ try {
145
+ const checkout = join(temporary, 'checkout');
146
+ await mkdir(checkout);
147
+ await copySnapshot(await realpath(root), checkout);
148
+ transferGit(checkout, ['init', '--initial-branch=main']);
149
+ transferGit(checkout, ['add', '--force', '--all']);
150
+ transferGit(checkout, ['commit', '--allow-empty', '-m', 'Workspace snapshot']);
151
+ const tree = transferGit(checkout, ['rev-parse', 'HEAD^{tree}']);
152
+ const head = transferGit(checkout, ['rev-parse', 'HEAD']);
153
+ const bundlePath = join(temporary, 'workspace.bundle');
154
+ transferGit(checkout, ['bundle', 'create', bundlePath, 'HEAD']);
155
+ return { bundlePath, tree, head, cleanup };
156
+ }
157
+ catch (error) {
158
+ await cleanup();
159
+ throw error;
160
+ }
161
+ }
162
+ function treeEntries(root) {
163
+ const entries = transferGit(root, ['ls-tree', '-r', '-z', 'HEAD']).split('\0').filter(Boolean).map(line => {
164
+ const match = /^(100644|100755) blob ([a-f0-9]{40,64})\t(.+)$/s.exec(line);
165
+ if (!match)
166
+ throw new WorkspaceTransferError('invalid_workspace_transfer');
167
+ const path = match[3];
168
+ if (path.length > 1024 || path.includes('\\') || path.startsWith('/') || /[\x00-\x1f\x7f]/.test(path) ||
169
+ path.split('/').some(part => !part || part === '.' || part === '..') || excluded(path))
170
+ throw new WorkspaceTransferError('invalid_workspace_transfer');
171
+ return { path, sha: match[2], executable: match[1] === '100755' };
172
+ });
173
+ if (entries.length > MAX_FILES)
174
+ throw new WorkspaceTransferError('workspace_transfer_too_large');
175
+ const normalized = entries.map(entry => entry.path.toLowerCase());
176
+ if (new Set(normalized).size !== entries.length)
177
+ throw new WorkspaceTransferError('invalid_workspace_transfer');
178
+ return entries;
179
+ }
180
+ async function safeTarget(root, path) {
181
+ const target = resolve(root, path);
182
+ if (!target.startsWith(`${root}${sep}`))
183
+ throw new WorkspaceTransferError('invalid_workspace_transfer');
184
+ let current = root;
185
+ for (const segment of path.split('/')) {
186
+ current = join(current, segment);
187
+ const info = await lstat(current).catch(error => { if (['ENOENT', 'ENOTDIR'].includes(error.code))
188
+ return undefined; throw error; });
189
+ if (info?.isSymbolicLink())
190
+ throw new WorkspaceTransferError('workspace_transfer_symlink');
191
+ }
192
+ return target;
193
+ }
194
+ /** Compare the entire checkout before applying local commits, so cloud edits are never overwritten. */
195
+ export async function applyWorkspaceTransfer(root, bundle, expectedTree) {
196
+ if (!/^[a-f0-9]{40,64}$/.test(expectedTree))
197
+ throw new WorkspaceTransferError('invalid_workspace_transfer');
198
+ if (!bundle.byteLength || bundle.byteLength > WORKSPACE_TRANSFER_MAX_BYTES)
199
+ throw new WorkspaceTransferError('workspace_transfer_too_large');
200
+ const rootReal = await realpath(root);
201
+ const temporary = await mkdtemp(join(tmpdir(), 'playcraft-import-'));
202
+ let current;
203
+ try {
204
+ const bundlePath = join(temporary, 'incoming.bundle'), incoming = join(temporary, 'checkout');
205
+ await writeFile(bundlePath, bundle, { mode: 0o600 });
206
+ transferGit(temporary, ['clone', '--no-checkout', '--', bundlePath, incoming]);
207
+ const entries = treeEntries(incoming);
208
+ const tree = transferGit(incoming, ['rev-parse', 'HEAD^{tree}']);
209
+ const head = transferGit(incoming, ['rev-parse', 'HEAD']);
210
+ let bytes = 0;
211
+ for (const entry of entries) {
212
+ bytes += Number(transferGit(incoming, ['cat-file', '-s', entry.sha]));
213
+ if (bytes > WORKSPACE_TRANSFER_MAX_BYTES)
214
+ throw new WorkspaceTransferError('workspace_transfer_too_large');
215
+ }
216
+ current = await createWorkspaceTransfer(rootReal);
217
+ if (current.tree === tree)
218
+ return { tree, head, changedPaths: [] };
219
+ if (current.tree !== expectedTree)
220
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
221
+ const baseline = join(dirname(current.bundlePath), 'checkout');
222
+ const previous = new Map(treeEntries(baseline).map(entry => [entry.path, entry]));
223
+ const next = new Map(entries.map(entry => [entry.path, entry]));
224
+ const changedPaths = [...new Set([...previous.keys(), ...next.keys()])]
225
+ .filter(path => previous.get(path)?.sha !== next.get(path)?.sha || previous.get(path)?.executable !== next.get(path)?.executable);
226
+ for (const path of changedPaths) {
227
+ const target = await safeTarget(rootReal, path);
228
+ const info = await lstat(target).catch(error => { if (['ENOENT', 'ENOTDIR'].includes(error.code))
229
+ return undefined; throw error; });
230
+ if (next.has(path) && info?.isDirectory()) {
231
+ const children = await readdir(target, { recursive: true });
232
+ for (const child of children) {
233
+ const childPath = `${path}/${child.split(sep).join('/')}`;
234
+ const childInfo = await lstat(join(target, child));
235
+ if (!childInfo.isDirectory() && (!previous.has(childPath) || next.has(childPath)))
236
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
237
+ }
238
+ }
239
+ }
240
+ transferGit(incoming, ['checkout', '--force']);
241
+ const recheck = await createWorkspaceTransfer(rootReal);
242
+ try {
243
+ if (recheck.tree !== expectedTree)
244
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
245
+ }
246
+ finally {
247
+ await recheck.cleanup();
248
+ }
249
+ const applied = [];
250
+ try {
251
+ // Deleting files first also permits a file to become a directory, or the reverse.
252
+ for (const path of changedPaths.filter(path => !next.has(path)).sort((a, b) => b.length - a.length)) {
253
+ if (!await matchesFile(rootReal, path, previous.get(path)))
254
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
255
+ await rm(await safeTarget(rootReal, path), { force: true });
256
+ applied.push(path);
257
+ await removeEmptyParents(rootReal, join(rootReal, path));
258
+ }
259
+ for (const path of changedPaths.filter(path => next.has(path))) {
260
+ if (!await matchesFile(rootReal, path, previous.get(path)))
261
+ throw new WorkspaceTransferError('workspace_transfer_conflict');
262
+ const entry = next.get(path);
263
+ await installFile(rootReal, path, join(incoming, path), entry.executable);
264
+ applied.push(path);
265
+ }
266
+ }
267
+ catch (error) {
268
+ // Roll back only our writes; never replace another writer's intervening content.
269
+ for (const path of applied.reverse()) {
270
+ if (!await matchesFile(rootReal, path, next.get(path)))
271
+ throw new WorkspaceTransferError('workspace_transfer_rollback_conflict');
272
+ const old = previous.get(path);
273
+ if (old)
274
+ await installFile(rootReal, path, join(baseline, path), old.executable);
275
+ else {
276
+ await rm(await safeTarget(rootReal, path), { force: true });
277
+ await removeEmptyParents(rootReal, join(rootReal, path));
278
+ }
279
+ }
280
+ throw error;
281
+ }
282
+ return { tree, head, changedPaths };
283
+ }
284
+ finally {
285
+ await current?.cleanup();
286
+ await rm(temporary, { recursive: true, force: true });
287
+ }
288
+ }
@@ -0,0 +1,75 @@
1
+ import { TURN_FILES_CHANGED_EVENT, } from '@playcraft/common/cloud-workspace/runtime';
2
+ import { diffWorkspaceTrees, retainTurnSnapshots, snapshotWorkspaceTree } from './git.js';
3
+ import { shareWorkspaceGit } from './shared-git.js';
4
+ const TURN_ENDS = new Set(['turn/completed', 'turn.completed', 'turn.interrupted', 'turn.failed']);
5
+ /**
6
+ * Snapshots taken when a session's turn starts, until that turn ends. Kept in memory only: a
7
+ * restart ends the native turn as well, and that turn then simply reports no changes.
8
+ */
9
+ const started = new WeakMap();
10
+ function turnsOf(state) {
11
+ let turns = started.get(state);
12
+ if (!turns)
13
+ started.set(state, turns = new Map());
14
+ return turns;
15
+ }
16
+ /**
17
+ * Records the work tree before a turn is sent. A directory that cannot be snapshotted (no
18
+ * repository, Git failure) gets no change summary; the turn itself is never held back by it.
19
+ */
20
+ export async function beginTurnChanges(state, sessionId, input) {
21
+ turnsOf(state).delete(sessionId);
22
+ if (input.directoryName === 'root')
23
+ return;
24
+ await shareWorkspaceGit(state, input.root).catch(() => undefined);
25
+ const before = await snapshotWorkspaceTree(input.root).catch(() => undefined);
26
+ if (before)
27
+ turnsOf(state).set(sessionId, { ...input, before });
28
+ }
29
+ /** A turn that never started has nothing to compare. */
30
+ export function abandonTurnChanges(state, sessionId) {
31
+ turnsOf(state).delete(sessionId);
32
+ }
33
+ /**
34
+ * When a session's turn ends, compares the work tree with its start and records the files that
35
+ * changed for that session's conversation. Returns no event when nothing changed.
36
+ */
37
+ export async function finishTurnChanges(state, event) {
38
+ const { threadId, turnId } = event;
39
+ if (!TURN_ENDS.has(event.method) || !threadId || !turnId)
40
+ return [];
41
+ const session = state.journal.listSessions().find((item) => item.threadId === threadId);
42
+ const start = session && turnsOf(state).get(session.sessionId);
43
+ if (!session || !start)
44
+ return [];
45
+ turnsOf(state).delete(session.sessionId);
46
+ try {
47
+ await shareWorkspaceGit(state, start.root);
48
+ const after = await snapshotWorkspaceTree(start.root);
49
+ const { files, truncated } = await diffWorkspaceTrees(start.root, start.before, after);
50
+ if (!files.length)
51
+ return [];
52
+ await retainTurnSnapshots(start.root, turnId, start.before, after);
53
+ const changes = { threadId, turnId, before: start.before, after, files, truncated };
54
+ return [await state.journal.allocateSourceEvent({
55
+ runtimeId: state.config.runtimeId,
56
+ generation: state.config.generation,
57
+ runtimeEpoch: state.runtimeEpoch,
58
+ type: TURN_FILES_CHANGED_EVENT,
59
+ payload: {
60
+ ...changes,
61
+ sessionId: session.sessionId,
62
+ ...(start.workspaceId ? { workspaceId: start.workspaceId } : {}),
63
+ ...(start.directoryName ? { directoryName: start.directoryName } : {}),
64
+ },
65
+ occurredAt: new Date().toISOString(),
66
+ sessionId: session.sessionId,
67
+ workspaceId: start.workspaceId,
68
+ threadId,
69
+ turnId,
70
+ })];
71
+ }
72
+ catch {
73
+ return [];
74
+ }
75
+ }
@@ -0,0 +1,49 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { WORKSPACE_VIDEO_ATTACHMENT_MAX_DURATION_MS, WORKSPACE_VIDEO_ATTACHMENT_MAX_BYTES } from '@playcraft/common/cloud-workspace/runtime';
4
+ /** Probe the opened descriptor so replacing a pathname cannot redirect the media reader. */
5
+ export async function probeAttachmentVideo(handle) {
6
+ const output = await new Promise((resolve, reject) => {
7
+ const child = spawn(process.env.PLAYCRAFT_FFPROBE_PATH ?? 'ffprobe', [
8
+ '-v', 'error', '-protocol_whitelist', 'file,pipe', '-show_entries',
9
+ 'format=format_name,duration:stream=codec_type,width,height', '-of', 'json',
10
+ process.platform === 'linux' ? '/proc/self/fd/3' : '/dev/fd/3',
11
+ ], { stdio: ['ignore', 'pipe', 'ignore', handle.fd] });
12
+ let text = '';
13
+ const timer = setTimeout(() => { child.kill('SIGKILL'); reject(new Error('invalid_attachment')); }, 15_000);
14
+ timer.unref();
15
+ child.stdout.on('data', (chunk) => {
16
+ text += chunk.toString('utf8');
17
+ if (text.length > 64 * 1024) {
18
+ child.kill('SIGKILL');
19
+ reject(new Error('invalid_attachment'));
20
+ }
21
+ });
22
+ child.once('error', () => { clearTimeout(timer); reject(new Error('video_probe_unavailable')); });
23
+ child.once('close', code => { clearTimeout(timer); code === 0 ? resolve(text) : reject(new Error('invalid_attachment')); });
24
+ });
25
+ const result = JSON.parse(output);
26
+ const stream = result.streams?.find(item => item.codec_type === 'video');
27
+ const durationMs = Math.round(Number(result.format?.duration) * 1000);
28
+ const formats = result.format?.format_name?.split(',') ?? [];
29
+ const mimeType = formats.includes('webm') ? 'video/webm' : formats.includes('mp4') ? 'video/mp4' : formats.includes('mov') ? 'video/quicktime' : undefined;
30
+ if (!stream || !mimeType || !Number.isFinite(durationMs) || durationMs <= 0 || durationMs > WORKSPACE_VIDEO_ATTACHMENT_MAX_DURATION_MS
31
+ || !Number.isSafeInteger(stream.width) || !Number.isSafeInteger(stream.height) || stream.width <= 0 || stream.height <= 0
32
+ || stream.width > 16384 || stream.height > 16384)
33
+ throw new Error('invalid_attachment');
34
+ return { mimeType, durationMs, width: stream.width, height: stream.height };
35
+ }
36
+ export async function hashAttachment(handle) {
37
+ const digest = createHash('sha256');
38
+ const chunk = Buffer.allocUnsafe(64 * 1024);
39
+ let position = 0;
40
+ for (;;) {
41
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, position);
42
+ if (!bytesRead)
43
+ return digest.digest('hex');
44
+ digest.update(chunk.subarray(0, bytesRead));
45
+ position += bytesRead;
46
+ if (position > WORKSPACE_VIDEO_ATTACHMENT_MAX_BYTES)
47
+ throw new Error('invalid_attachment');
48
+ }
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/cli",
3
- "version": "0.1.35-prod",
3
+ "version": "0.1.37-beta",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -14,16 +14,18 @@
14
14
  "dist",
15
15
  "templates",
16
16
  "project-template",
17
+ "project-template-v2",
17
18
  "engine-packs",
18
19
  "project-templates",
19
20
  "REMIX.md",
21
+ "SKILLS_REGISTRY.md",
20
22
  "README.md"
21
23
  ],
22
24
  "scripts": {
23
25
  "dev": "tsc -w",
24
- "build": "tsc",
26
+ "build": "node scripts/generate-video-skills.mjs && tsc",
25
27
  "start": "node dist/index.js",
26
- "test": "vitest run",
28
+ "test": "pnpm build && vitest run",
27
29
  "test:agent": "node scripts/run-agent-tests.mjs --list",
28
30
  "test:agent:designer": "node scripts/run-agent-tests.mjs game-designer",
29
31
  "test:agent:developer": "node scripts/run-agent-tests.mjs developer",
@@ -35,6 +37,10 @@
35
37
  "test:agent:gate": "node scripts/run-agent-tests.mjs gate",
36
38
  "test:agent:vlm": "node scripts/run-agent-tests.mjs vlm",
37
39
  "test:agent:stop": "node scripts/run-agent-tests.mjs stop",
40
+ "generate:platform-skills": "node scripts/generate-platform-skills.mjs",
41
+ "verify:platform-skills": "node scripts/generate-platform-skills.mjs --check",
42
+ "generate:video-skills": "node scripts/generate-video-skills.mjs",
43
+ "verify:video-skills": "node scripts/generate-video-skills.mjs --check && node --test scripts/generate-video-skills.test.mjs",
38
44
  "verify:agent-stop": "node scripts/verify-agent-stop.mjs",
39
45
  "verify-sprite-overlay": "node scripts/verify-sprite-overlay.mjs",
40
46
  "link": "node scripts/bump-local.js && pnpm build && npm link",
@@ -45,8 +51,9 @@
45
51
  "@gltf-transform/core": "^4.3.0",
46
52
  "@gltf-transform/extensions": "^4.3.0",
47
53
  "@gltf-transform/functions": "^4.3.0",
48
- "@playcraft/build": "^0.1.34",
49
- "@playcraft/common": "^0.1.34",
54
+ "@playcraft/build": "^0.1.37-beta",
55
+ "@playcraft/common": "^0.1.37-beta",
56
+ "better-sqlite3-pinned": "npm:better-sqlite3@13.0.3",
50
57
  "chokidar": "^4.0.3",
51
58
  "commander": "^13.1.0",
52
59
  "cors": "^2.8.6",
@@ -62,8 +69,10 @@
62
69
  "json5": "^2.2.3",
63
70
  "latest-version": "^7.0.0",
64
71
  "music-metadata": "^11.12.3",
72
+ "node-pty": "1.0.0",
65
73
  "ora": "^8.2.0",
66
74
  "picocolors": "^1.1.1",
75
+ "playwright-core": "1.52.0",
67
76
  "sharp": "^0.34.5",
68
77
  "update-notifier": "^7.3.1",
69
78
  "ws": "^8.19.0",
@@ -79,6 +88,7 @@
79
88
  "@types/sharp": "^0.32.0",
80
89
  "@types/update-notifier": "^6.0.8",
81
90
  "@types/ws": "^8.18.1",
91
+ "esbuild": "^0.28.1",
82
92
  "typescript": "^5.9.3",
83
93
  "vitest": "^3.2.4"
84
94
  }