@playcraft/cli 0.1.42 → 0.1.44-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 (390) hide show
  1. package/README.md +62 -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 +105 -0
  18. package/dist/commands/tools-generation.js +69 -137
  19. package/dist/commands/tools-utils.js +0 -12
  20. package/dist/commands/tools.js +2 -2
  21. package/dist/commands/video.js +122 -0
  22. package/dist/commands/workspace-runtime.js +54 -0
  23. package/dist/commands/workspace.js +40 -0
  24. package/dist/config.js +59 -4
  25. package/dist/index.js +40 -3
  26. package/dist/project-skills/builtin-import.js +225 -0
  27. package/dist/project-skills/commands.js +77 -0
  28. package/dist/project-skills/lifecycle.js +28 -0
  29. package/dist/project-skills/local-run.js +112 -0
  30. package/dist/project-skills/local-store.js +221 -0
  31. package/dist/project-skills/messages.js +80 -0
  32. package/dist/project-skills/reconcile.js +62 -0
  33. package/dist/project-skills/remote-cache.js +51 -0
  34. package/dist/project-skills/validation.js +19 -0
  35. package/dist/recording/messages.js +40 -0
  36. package/dist/recording/project-digest.js +85 -0
  37. package/dist/recording/recorder.js +293 -0
  38. package/dist/recording/scenario.js +50 -0
  39. package/dist/remix/clone.js +2 -0
  40. package/dist/remix/init-template.js +2 -0
  41. package/dist/remix/pull.js +2 -0
  42. package/dist/remix/push.js +21 -1
  43. package/dist/skill-registry/client.js +345 -0
  44. package/dist/skill-registry/commands.js +180 -0
  45. package/dist/skill-registry/evaluation-client.js +156 -0
  46. package/dist/skill-registry/evaluation-commands.js +174 -0
  47. package/dist/skill-registry/messages.js +177 -0
  48. package/dist/skill-registry/package.js +275 -0
  49. package/dist/skill-registry/platform-package.js +100 -0
  50. package/dist/utils/agent-api-client.js +210 -30
  51. package/dist/utils/environment-endpoints.js +17 -0
  52. package/dist/utils/error-diagnostics.js +20 -0
  53. package/dist/utils/playcraft-http-client.js +6 -4
  54. package/dist/utils/tool-operation-journal.js +124 -0
  55. package/dist/utils/video-evidence-git.js +81 -0
  56. package/dist/video-replication/client.js +234 -0
  57. package/dist/video-replication/comparison-evidence.js +149 -0
  58. package/dist/video-replication/comparison-files.js +57 -0
  59. package/dist/video-replication/comparison-log.js +72 -0
  60. package/dist/video-replication/comparison-messages.js +104 -0
  61. package/dist/video-replication/comparison-output.js +63 -0
  62. package/dist/video-replication/comparison-preflight.js +66 -0
  63. package/dist/video-replication/comparison.js +230 -0
  64. package/dist/video-replication/diagnostics.js +41 -0
  65. package/dist/video-replication/evidence.js +165 -0
  66. package/dist/video-replication/media.js +112 -0
  67. package/dist/video-replication/messages.js +97 -0
  68. package/dist/workspace/messages.js +67 -0
  69. package/dist/workspace/transfer-client.js +233 -0
  70. package/dist/workspace/transfer-errors.js +48 -0
  71. package/dist/workspace-runtime/batch/control.js +80 -0
  72. package/dist/workspace-runtime/batch/deliver.js +150 -0
  73. package/dist/workspace-runtime/batch/freeze.js +102 -0
  74. package/dist/workspace-runtime/batch/generate.js +205 -0
  75. package/dist/workspace-runtime/batch/git.js +40 -0
  76. package/dist/workspace-runtime/batch/occupancy.js +8 -0
  77. package/dist/workspace-runtime/batch/policy.js +70 -0
  78. package/dist/workspace-runtime/batch/prepare.js +202 -0
  79. package/dist/workspace-runtime/batch/processes.js +72 -0
  80. package/dist/workspace-runtime/batch/records.js +113 -0
  81. package/dist/workspace-runtime/batch/routes.js +58 -0
  82. package/dist/workspace-runtime/batch/source-capture.js +159 -0
  83. package/dist/workspace-runtime/codex/app-server.js +1040 -0
  84. package/dist/workspace-runtime/codex/compact.js +46 -0
  85. package/dist/workspace-runtime/codex/config-toml.js +107 -0
  86. package/dist/workspace-runtime/codex/jsonrpc-stdio.js +145 -0
  87. package/dist/workspace-runtime/codex/launcher-client.js +177 -0
  88. package/dist/workspace-runtime/codex/loopback.js +146 -0
  89. package/dist/workspace-runtime/codex/mcp-config.js +39 -0
  90. package/dist/workspace-runtime/codex/model-broker.js +263 -0
  91. package/dist/workspace-runtime/codex/native-adapter.js +9 -0
  92. package/dist/workspace-runtime/codex/permissions.js +79 -0
  93. package/dist/workspace-runtime/codex/thread-image.js +38 -0
  94. package/dist/workspace-runtime/codex/ui-instructions.js +4 -0
  95. package/dist/workspace-runtime/codex/ui-tools.js +186 -0
  96. package/dist/workspace-runtime/codex/work-mode.js +15 -0
  97. package/dist/workspace-runtime/main.js +10 -0
  98. package/dist/workspace-runtime/persistence/journal.js +365 -0
  99. package/dist/workspace-runtime/persistence/native-subagents.js +162 -0
  100. package/dist/workspace-runtime/persistence/sqlite-client.js +77 -0
  101. package/dist/workspace-runtime/persistence/sqlite-worker.js +1161 -0
  102. package/dist/workspace-runtime/persistence/stopped-execution.js +77 -0
  103. package/dist/workspace-runtime/previews/output.js +36 -0
  104. package/dist/workspace-runtime/previews/publish.js +262 -0
  105. package/dist/workspace-runtime/previews/single-file.js +43 -0
  106. package/dist/workspace-runtime/processes/managed-writes.js +69 -0
  107. package/dist/workspace-runtime/production/content-changes.js +36 -0
  108. package/dist/workspace-runtime/production/contracts.js +19 -0
  109. package/dist/workspace-runtime/production/controller.js +228 -0
  110. package/dist/workspace-runtime/production/dependencies.js +60 -0
  111. package/dist/workspace-runtime/production/native.js +239 -0
  112. package/dist/workspace-runtime/production/processes.js +102 -0
  113. package/dist/workspace-runtime/production/save-command.js +35 -0
  114. package/dist/workspace-runtime/production/source-archive.js +137 -0
  115. package/dist/workspace-runtime/production/source-copy.js +92 -0
  116. package/dist/workspace-runtime/production/stages.js +354 -0
  117. package/dist/workspace-runtime/production/storage.js +75 -0
  118. package/dist/workspace-runtime/serve.js +283 -0
  119. package/dist/workspace-runtime/server/auth.js +21 -0
  120. package/dist/workspace-runtime/server/connectivity.js +209 -0
  121. package/dist/workspace-runtime/server/desktop-host.js +429 -0
  122. package/dist/workspace-runtime/server/desktop-preview-config.js +81 -0
  123. package/dist/workspace-runtime/server/desktop-preview-service.js +157 -0
  124. package/dist/workspace-runtime/server/desktop-preview-static.js +53 -0
  125. package/dist/workspace-runtime/server/desktop-preview.js +189 -0
  126. package/dist/workspace-runtime/server/dispatch.js +1339 -0
  127. package/dist/workspace-runtime/server/endpoints.js +21 -0
  128. package/dist/workspace-runtime/server/execution-group.js +90 -0
  129. package/dist/workspace-runtime/server/http.js +1297 -0
  130. package/dist/workspace-runtime/server/lock.js +53 -0
  131. package/dist/workspace-runtime/server/rfb-watch-gate.js +168 -0
  132. package/dist/workspace-runtime/server/session-restore.js +84 -0
  133. package/dist/workspace-runtime/server/source-uploader.js +138 -0
  134. package/dist/workspace-runtime/server/terminal-host.js +366 -0
  135. package/dist/workspace-runtime/server/types.js +1 -0
  136. package/dist/workspace-runtime/server/workspace-transfer.js +168 -0
  137. package/dist/workspace-runtime/workspaces/archive.js +207 -0
  138. package/dist/workspace-runtime/workspaces/artifact-events.js +55 -0
  139. package/dist/workspace-runtime/workspaces/artifact-files.js +199 -0
  140. package/dist/workspace-runtime/workspaces/artifact-receipts.js +89 -0
  141. package/dist/workspace-runtime/workspaces/artifacts.js +282 -0
  142. package/dist/workspace-runtime/workspaces/asset-library-import.js +366 -0
  143. package/dist/workspace-runtime/workspaces/attachment-content.js +73 -0
  144. package/dist/workspace-runtime/workspaces/attachments.js +185 -0
  145. package/dist/workspace-runtime/workspaces/batch-commit.js +142 -0
  146. package/dist/workspace-runtime/workspaces/binary-content.js +65 -0
  147. package/dist/workspace-runtime/workspaces/binary-write.js +112 -0
  148. package/dist/workspace-runtime/workspaces/cli-context.js +23 -0
  149. package/dist/workspace-runtime/workspaces/cli-credentials.js +111 -0
  150. package/dist/workspace-runtime/workspaces/composer-context.js +147 -0
  151. package/dist/workspace-runtime/workspaces/context-error.js +2 -0
  152. package/dist/workspace-runtime/workspaces/dependencies.js +264 -0
  153. package/dist/workspace-runtime/workspaces/developer-command.js +64 -0
  154. package/dist/workspace-runtime/workspaces/directories.js +137 -0
  155. package/dist/workspace-runtime/workspaces/directory-access.js +37 -0
  156. package/dist/workspace-runtime/workspaces/directory-attachments.js +245 -0
  157. package/dist/workspace-runtime/workspaces/directory-create.js +57 -0
  158. package/dist/workspace-runtime/workspaces/directory-maintenance.js +111 -0
  159. package/dist/workspace-runtime/workspaces/directory-stats.js +49 -0
  160. package/dist/workspace-runtime/workspaces/disk-snapshot.js +31 -0
  161. package/dist/workspace-runtime/workspaces/empty-template.js +42 -0
  162. package/dist/workspace-runtime/workspaces/file-content.js +48 -0
  163. package/dist/workspace-runtime/workspaces/files-search.js +116 -0
  164. package/dist/workspace-runtime/workspaces/files.js +150 -0
  165. package/dist/workspace-runtime/workspaces/git-credential-broker.js +312 -0
  166. package/dist/workspace-runtime/workspaces/git-credential-helper.js +68 -0
  167. package/dist/workspace-runtime/workspaces/git-direct-store.js +155 -0
  168. package/dist/workspace-runtime/workspaces/git.js +670 -0
  169. package/dist/workspace-runtime/workspaces/instructions.js +56 -0
  170. package/dist/workspace-runtime/workspaces/json5-edit.js +170 -0
  171. package/dist/workspace-runtime/workspaces/library-attachments.js +268 -0
  172. package/dist/workspace-runtime/workspaces/optional-platform-skills.js +14 -0
  173. package/dist/workspace-runtime/workspaces/parameters.js +272 -0
  174. package/dist/workspace-runtime/workspaces/prepare.js +278 -0
  175. package/dist/workspace-runtime/workspaces/registry.js +251 -0
  176. package/dist/workspace-runtime/workspaces/remix-references.js +301 -0
  177. package/dist/workspace-runtime/workspaces/revisions.js +31 -0
  178. package/dist/workspace-runtime/workspaces/session-image.js +104 -0
  179. package/dist/workspace-runtime/workspaces/shared-git.js +40 -0
  180. package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +95 -0
  181. package/dist/workspace-runtime/workspaces/skills.js +191 -0
  182. package/dist/workspace-runtime/workspaces/transfer-quota.js +69 -0
  183. package/dist/workspace-runtime/workspaces/transfer.js +471 -0
  184. package/dist/workspace-runtime/workspaces/turn-changes.js +75 -0
  185. package/dist/workspace-runtime/workspaces/video-attachments.js +49 -0
  186. package/package.json +15 -5
  187. package/project-template/.claude/agents/refs/ta-image-generation-detail.md +123 -67
  188. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +63 -135
  189. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +33 -42
  190. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +20 -18
  191. package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +7 -6
  192. package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
  193. package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +25 -0
  194. package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +79 -0
  195. package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +29 -0
  196. package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +37 -0
  197. package/project-template-v2/.claude/agents/artist.md +82 -0
  198. package/project-template-v2/.claude/agents/developer.md +153 -0
  199. package/project-template-v2/.claude/agents/game-designer.md +264 -0
  200. package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +533 -0
  201. package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
  202. package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +27 -0
  203. package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
  204. package/project-template-v2/.claude/agents/refs/atom-skill-library.md +81 -0
  205. package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +432 -0
  206. package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +252 -0
  207. package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  208. package/project-template-v2/.claude/agents/refs/framework-mda.md +147 -0
  209. package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  210. package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
  211. package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
  212. package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
  213. package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +300 -0
  214. package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +495 -0
  215. package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
  216. package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +111 -0
  217. package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
  218. package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +412 -0
  219. package/project-template-v2/.claude/agents/reviewer.md +75 -0
  220. package/project-template-v2/.claude/agents/technical-artist.md +86 -0
  221. package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +243 -0
  222. package/project-template-v2/.claude/hooks/user-prompt.mjs +133 -0
  223. package/project-template-v2/.claude/settings.json +33 -0
  224. package/project-template-v2/.claude/skills/brainstorming/SKILL.md +161 -0
  225. package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  226. package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +177 -0
  227. package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  228. package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  229. package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  230. package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  231. package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  232. package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +309 -0
  233. package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
  234. package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
  235. package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
  236. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
  237. package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
  238. package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
  239. package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
  240. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
  241. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
  242. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
  243. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
  244. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
  245. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
  246. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
  247. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
  248. package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
  249. package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  250. package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +373 -0
  251. package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  252. package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +215 -0
  253. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +382 -0
  254. package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
  255. package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  256. package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  257. package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
  258. package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
  259. package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
  260. package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
  261. package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +390 -0
  262. package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
  263. package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
  264. package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
  265. package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +240 -0
  266. package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +226 -0
  267. package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
  268. package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +275 -0
  269. package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +134 -0
  270. package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +142 -0
  271. package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +153 -0
  272. package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +386 -0
  273. package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  274. package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  275. package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  276. package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +102 -0
  277. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  278. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  279. package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  280. package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  281. package/project-template-v2/.claude/snapshot.mjs +263 -0
  282. package/project-template-v2/.playcraft/skills.lock.json +152 -0
  283. package/project-template-v2/CLAUDE.md +146 -0
  284. package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
  285. package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
  286. package/project-template-v2/assets/bundles/.gitkeep +0 -0
  287. package/project-template-v2/assets/images/bg/.gitkeep +0 -0
  288. package/project-template-v2/assets/images/reference/.gitkeep +0 -0
  289. package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
  290. package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
  291. package/project-template-v2/assets/images/ui/.gitkeep +0 -0
  292. package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
  293. package/project-template-v2/assets/models/.gitkeep +0 -0
  294. package/project-template-v2/docs/harness/iteration-1/context-flow.md +254 -0
  295. package/project-template-v2/docs/harness/iteration-1/generate-flow.md +91 -0
  296. package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +214 -0
  297. package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +75 -0
  298. package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +63 -0
  299. package/project-template-v2/docs/harness/iteration-2/context-flow.md +223 -0
  300. package/project-template-v2/docs/harness/iteration-2/generate-flow.md +129 -0
  301. package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +267 -0
  302. package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +164 -0
  303. package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +115 -0
  304. package/project-template-v2/docs/harness/orchestrator-flow.md +364 -0
  305. package/project-template-v2/docs/project-state.json +60 -0
  306. package/project-template-v2/docs/project-state.md +72 -0
  307. package/project-template-v2/docs/standards/README.md +225 -0
  308. package/project-template-v2/docs/standards/agent-behavior-standards.md +174 -0
  309. package/project-template-v2/docs/standards/artifacts/design-brief.md +19 -0
  310. package/project-template-v2/docs/standards/artifacts/design.md +22 -0
  311. package/project-template-v2/docs/standards/artifacts/game-code.md +41 -0
  312. package/project-template-v2/docs/standards/artifacts/todo-list.md +41 -0
  313. package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +343 -0
  314. package/project-template-v2/game/index.ts +18 -0
  315. package/project-template-v2/globals.d.ts +51 -0
  316. package/project-template-v2/index.css +34 -0
  317. package/project-template-v2/index.html +18 -0
  318. package/project-template-v2/main.ts +9 -0
  319. package/project-template-v2/package.json +46 -0
  320. package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +31 -0
  321. package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +86 -0
  322. package/project-template-v2/skills/_shared/scripts/dod-check.mjs +153 -0
  323. package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +70 -0
  324. package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +91 -0
  325. package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +131 -0
  326. package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +80 -0
  327. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +90 -0
  328. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +93 -0
  329. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +23 -0
  330. package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +230 -0
  331. package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +58 -0
  332. package/project-template-v2/skills/_shared/scripts/state-get.mjs +339 -0
  333. package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +39 -0
  334. package/project-template-v2/skills/_shared/scripts/state-set.mjs +94 -0
  335. package/project-template-v2/skills/_shared/scripts/state-store.mjs +783 -0
  336. package/project-template-v2/skills/_shared/scripts/todo-add.mjs +88 -0
  337. package/project-template-v2/skills/_shared/scripts/todo-get.mjs +130 -0
  338. package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +33 -0
  339. package/project-template-v2/skills/_shared/scripts/todo-set.mjs +47 -0
  340. package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +268 -0
  341. package/project-template-v2/skills/_shared/scripts/verify-env.mjs +285 -0
  342. package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  343. package/project-template-v2/skills/playable-autoplay/SKILL.md +176 -0
  344. package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +4 -0
  345. package/project-template-v2/skills/playable-debug/SKILL.md +116 -0
  346. package/project-template-v2/skills/playable-debug/agents/openai.yaml +4 -0
  347. package/project-template-v2/skills/playable-debug/references/debug-config.md +40 -0
  348. package/project-template-v2/skills/playable-record/SKILL.md +140 -0
  349. package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  350. package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  351. package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +425 -0
  352. package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +261 -0
  353. package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  354. package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +322 -0
  355. package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +105 -0
  356. package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +230 -0
  357. package/project-template-v2/skills/playable-validate/SKILL.md +61 -0
  358. package/project-template-v2/skills/playable-validate/validation-rules.md +14 -0
  359. package/project-template-v2/skills/playable-verify-ui/SKILL.md +63 -0
  360. package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  361. package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  362. package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  363. package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  364. package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  365. package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  366. package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  367. package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  368. package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  369. package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  370. package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
  371. package/project-template-v2/tsconfig.json +20 -0
  372. package/project-template-v2/vite.config.ts +27 -0
  373. package/project-templates/batch-locks/phaser.yaml +922 -0
  374. package/project-templates/batch-locks/threejs.yaml +978 -0
  375. package/project-templates/empty/AGENTS.md +41 -0
  376. package/project-templates/empty/gitignore +33 -0
  377. package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  378. package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +63 -135
  379. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +33 -42
  380. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +20 -18
  381. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +7 -6
  382. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
  383. package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  384. package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +63 -135
  385. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +33 -42
  386. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +20 -18
  387. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +7 -6
  388. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
  389. package/dist/commands/tools-image-context.js +0 -29
  390. package/dist/utils/image-asset-context.js +0 -228
@@ -1,23 +1,21 @@
1
1
  # Image Generation Prompt Cookbook
2
2
 
3
- > 适用于 PlayCraft CLI (Command-Line Interface, 命令行工具) `playcraft tools generate-image` 及沙箱生图工具。
3
+ > 适用于 PlayCraft CLI `playcraft tools generate-image`。
4
4
  > 所有模型均适用;gpt-image-2 类模型对英文 prompt 理解最准确,优先写英文。
5
5
 
6
- 透明交付通过 `background: transparent` / `--background transparent` 或目标字段配置表达。用户未明确要求背景且字段没有明确不透明约束时,默认透明,包括卡牌、棋子和面板。下方常规前景模板描述主体、文字、风格、构图与材质;源图背景遵循主文第 2 节:Image 2.5 由后端组装,不追加背景后缀;明确选用其他模型时,首次请求需追加单一 `on a uniform solid pure white background for subsequent background removal`。不要同时要求透明、白底与色幕。用户或字段明确要求完整场景时可以描述背景内容;矩形本体的白色牌面属于前景,应在透明画布上保留。独立色幕流程见主文第 3 节,中间图必须关闭自动去背。
7
-
8
6
  ---
9
7
 
10
8
  ## 一、Prompt 万能公式
11
9
 
12
10
  ```
13
- [主体描述] + [风格/介质] + [构图/视角] + [场景内容(如需要)] + [质量修饰词]
11
+ [主体描述] + [风格/介质] + [构图/视角] + [背景] + [质量修饰词]
14
12
  ```
15
13
 
16
14
  **示例**:
17
15
 
18
16
  ```
19
17
  A golden sword icon, flat 2D vector style, centered composition,
20
- isolated subject, clean crisp edges, game UI asset, high quality
18
+ white background, clean crisp edges, game UI asset, high quality
21
19
  ```
22
20
 
23
21
  ---
@@ -28,13 +26,12 @@ isolated subject, clean crisp edges, game UI asset, high quality
28
26
 
29
27
  ```
30
28
  A [物品名] icon, flat 2D vector illustration, bold outlines,
31
- vibrant colors, centered composition, isolated subject,
29
+ vibrant colors, centered composition, white background,
32
30
  no shadow, no gradient, game UI style, sharp crisp edges
33
31
  ```
34
32
 
35
33
  - 关键词:`flat 2D` / `bold outlines` / `centered` / `no shadow` / `game UI style`
36
34
  - 避免:`3D` / `realistic` / `photographic` / `drop shadow`
37
- - 需要透明交付时传 `background: transparent` / `--background transparent`。
38
35
 
39
36
  ### 🃏 卡牌 / 棋子 / 方块(Rectangular Tiles)
40
37
 
@@ -53,11 +50,12 @@ sharp clean edges, centered on white background
53
50
  ```
54
51
  A [角色描述] character, full body visible, game sprite style,
55
52
  [艺术风格: pixel art / cartoon / chibi / realistic],
56
- isolated subject,
53
+ isolated on solid bright green #00FF00 background,
57
54
  centered, facing forward / side view, clean silhouette
58
55
  ```
59
56
 
60
- - 需要透明交付 → `background: transparent` / `--background transparent`,按主文第 2 节准备源图背景,由后端去背并完成验收。
57
+ - 需要透明背景 → **绿幕策略**,prompt 加 `"on solid bright green #00FF00 background, isolated"`
58
+ - 后处理:`remove-background --tolerance 25`
61
59
 
62
60
  ### 🌄 场景背景(Backgrounds)
63
61
 
@@ -122,7 +120,7 @@ Grid layout, equal cell size, no borders between cells. Bold black ink for spade
122
120
  **图标集示例**(一批 9 个图标):
123
121
 
124
122
  ```
125
- Sprite sheet grid, 3 columns × 3 rows. Flat 2D game icon style, isolated subjects.
123
+ Sprite sheet grid, 3 columns × 3 rows. Flat 2D game icon style, white background.
126
124
  [Row1,Col1]: sword icon; [Row1,Col2]: shield icon; [Row1,Col3]: bow and arrow icon;
127
125
  [Row2,Col1]: magic staff icon; [Row2,Col2]: potion bottle icon; [Row2,Col3]: scroll icon;
128
126
  [Row3,Col1]: coin gold icon; [Row3,Col2]: gem diamond icon; [Row3,Col3]: key icon.
@@ -131,18 +129,17 @@ Each icon centered in its cell, bold outlines, vibrant colors, no shadow.
131
129
 
132
130
  **背景选择(重要!)**:
133
131
 
134
- | 素材类型 | 交付要求 | 切分后处理 |
135
- | ----------------------------------- | ----------------------------------------------------- | --------------------------------- |
136
- | 独立元素(角色/车/道具/图标)需透明 | `--background transparent`,按主文第 2 节准备源图背景 | 保留透明通道,验证每格前景与边缘 |
137
- | 卡牌/棋子/UI 面板(矩形本体) | 默认 `--background transparent`,保留牌面与边框 | 验证本体外透明,按目标尺寸 resize |
138
- | 用户/字段明确要求保留背景的最终素材 | `--background opaque`,整张图片不透明 | 保留背景,按目标尺寸 resize |
132
+ | 素材类型 | prompt 背景写法 | 切分后处理 |
133
+ | ----------------------------------- | --------------------------------------------------- | ---------------------------------- |
134
+ | 独立元素(角色/车/道具/图标)需透明 | `isolated on solid bright green #00FF00 background` | `remove-background --tolerance 25` |
135
+ | 卡牌/棋子/UI 面板(矩形本体) | `white background, no shadow` | 直接 resize,不去背景 |
139
136
 
140
137
  **注意事项**:
141
138
 
142
139
  - 每批最多 **4×4 = 16 个元素**,超过会出现混乱
143
140
  - 每个格的描述要精确,包含特征性视觉元素
144
141
  - 切分后用 `sprite-split --rows N --columns M` 按网格切分
145
- - 模板:[gen-batch-sprite.template.mjs](gen-batch-sprite.template.mjs):`backgroundMode='automatic'` 为常规流程,`removeBg` 表示最终是否透明。独立色幕需显式设 `backgroundMode='chroma'`、所选模型和色幕颜色;脚本关闭整板自动去背,切分后逐格去背。
142
+ - 模板:[gen-batch-sprite.template.mjs](gen-batch-sprite.template.mjs)
146
143
 
147
144
  ---
148
145
 
@@ -186,7 +183,7 @@ gpt-image-2 是目前质量最高的文生图模型,prompt 套路略有不同
186
183
  - **英文 prompt 效果最佳**,中文理解也不错但英文更稳定
187
184
  - 对**复杂场景**、**细节描述**处理能力远超其他模型
188
185
  - 支持**图生图 edit 模式**(传 `--reference-image`),局部编辑能力强
189
- - 不支持原生透明 PNG (Portable Network Graphics, 便携式网络图形) 输出(ALPHA = no);透明交付仍由工具后处理。明确选择独立色幕制作时再按主文第 3 节处理。
186
+ - 不支持透明 PNG 输出(ALPHA = no),需绿幕策略
190
187
 
191
188
  ### prompt 格式建议
192
189
 
@@ -234,7 +231,6 @@ elements centered in each cell, no borders between cells.
234
231
  ```bash
235
232
  playcraft tools generate-image \
236
233
  --prompt "A sword icon, flat 2D..." \
237
- --background transparent \
238
234
  --output assets/gen/sword \
239
235
  --count 4 \
240
236
  --image-model iegg-litellm/gpt-image-2
@@ -252,7 +248,6 @@ playcraft tools generate-image \
252
248
  # 一次 API 调用生成 4 个候选,选最好的(比 4 次独立调用更高效)
253
249
  playcraft tools generate-image \
254
250
  --prompt "..." \
255
- --background "$BACKGROUND_MODE" \
256
251
  --output "test/concept" \
257
252
  --count 4 \
258
253
  --image-model iegg-litellm/gpt-image-2
@@ -275,8 +270,6 @@ playcraft tools generate-image \
275
270
  >
276
271
  > 注意:`figer_icon.aiimage`、`win_result_panel.aiimage` 等 atom Skills 提供了带参考图的生成配方,Game Designer `playcraft skills match` 能匹配到时优先使用。本节模板用于无对应 atom Skill 的场景。
277
272
 
278
- 下方独立界面前景通过 `background: transparent` / `--background transparent` 要求最终透明;模板描述主体,调用前按主文第 2 节补充适用于所选模型的源图背景。
279
-
280
273
  ### 🟠 CTA 按钮(Call-to-Action Button)
281
274
 
282
275
  ```
@@ -287,13 +280,13 @@ Important details: [bright orange / gold / green] gradient fill,
287
280
  subtle drop shadow below button, glossy highlight on top edge,
288
281
  3px white inner border, corners rounded radius 24px
289
282
  Use case: Game ad install button, isolated asset, HTML5 playable ad
290
- Constraints: No extra elements, centered, clean crisp edges,
283
+ Constraints: Transparent background, no extra elements, centered, clean crisp edges,
291
284
  no watermark
292
285
  ```
293
286
 
294
287
  - CTA 颜色必须是游戏画面中**最少使用**的颜色(对比稀缺性原则)
295
288
  - 触控目标区域 ≥ 48×48px,生成建议尺寸 360×120px(9:3 比例)
296
- - 后处理由工具完成,验证最终透明背景、文字与边缘。
289
+ - 后处理:不需要去背景(直接生成透明背景或用绿幕)
297
290
 
298
291
  ---
299
292
 
@@ -306,7 +299,7 @@ Important details: [color] filled portion [80% of total width], unfilled portion
306
299
  rounded end caps, thin outer frame in [darker shade],
307
300
  optional inner glow on filled section
308
301
  Use case: Game HUD progress indicator, isolated UI element
309
- Constraints: Horizontal orientation, no text labels,
302
+ Constraints: Transparent background, horizontal orientation, no text labels,
310
303
  clean edges, no watermark
311
304
  ```
312
305
 
@@ -324,7 +317,8 @@ Important details: warm skin tone, white sleeve cuff visible at wrist,
324
317
  soft drop shadow beneath finger tip, friendly approachable style,
325
318
  [optional: subtle animated glow ring around fingertip]
326
319
  Use case: Tutorial overlay tap gesture for playable ad, points toward target element
327
- Constraints: No extra limbs or elements, finger pointing at bottom-center
320
+ Constraints: Transparent background, PNG with alpha channel,
321
+ no extra limbs or elements, finger pointing at bottom-center
328
322
  ```
329
323
 
330
324
  **箭头变体**(更简洁):
@@ -334,7 +328,7 @@ Subject: Bold cartoon arrow pointing [down / right], game tutorial UI
334
328
  Style: flat 2D, thick outline 4px, rounded arrowhead
335
329
  Important details: [bright yellow / white] fill, [dark outline],
336
330
  slight perspective tilt for depth, attention-grabbing
337
- Constraints: Single clean arrow shape, no text
331
+ Constraints: Transparent background, single clean arrow shape, no text
338
332
  ```
339
333
 
340
334
  ---
@@ -348,7 +342,7 @@ Important details: white or [light color] fill, [2–3px] dark outline,
348
342
  tail pointing [down-left / down-center], interior empty for text overlay,
349
343
  subtle inner shadow or gradient
350
344
  Use case: Game tutorial hint tooltip, isolated UI asset
351
- Constraints: No text inside, clean edges,
345
+ Constraints: Transparent background, no text inside, clean edges,
352
346
  no watermark, no extra decorations
353
347
  ```
354
348
 
@@ -368,7 +362,7 @@ Important details: [treasure chest]: lid slightly ajar, golden glow escaping fro
368
362
  [gold coins]: scattered pile, gleaming highlights, not neatly stacked (implies uncollected);
369
363
  [gem crystal]: large, brilliant facets, floating above ground, clearly valuable
370
364
  Use case: EndCard reward visual, creates FOMO and drives install
371
- Constraints: Centered, isolated subject,
365
+ Constraints: Transparent background, centered, isolated subject,
372
366
  no CTA text (added separately), warm celebratory lighting
373
367
  ```
374
368
 
@@ -379,19 +373,25 @@ Constraints: Centered, isolated subject,
379
373
 
380
374
  ### 📊 UI 元素背景处理规则速查
381
375
 
382
- CTA 按钮、进度条、引导手势、对话气泡与 EndCard 奖励图标都通过 `background: transparent` / `--background transparent` 要求最终透明;主体内容使用上述模板,源图背景按主文第 2 节准备。交付后验证文字、完整前景及深浅背景上的边缘。独立色幕制作沿用主文第 3 节的单一背景与独立去背流程。
376
+ | UI 元素类型 | prompt 背景写法 | 后处理 |
377
+ | ---------------- | ----------------------------------------------- | ------------------------------ |
378
+ | CTA 按钮 | `transparent background` 或绿幕 | floodfill 去背 |
379
+ | 进度条 | `transparent background` 或绿幕 | floodfill 去背 |
380
+ | 引导手势 | `transparent background, PNG with alpha` | floodfill 去背(tolerance 25) |
381
+ | 对话气泡 | `transparent background` 或绿幕 | floodfill 去背 |
382
+ | EndCard 奖励图标 | 绿幕(`solid bright green #00FF00 background`) | floodfill 去背(tolerance 25) |
383
383
 
384
384
  ---
385
385
 
386
386
  ## 六、常见问题 & 修正方案
387
387
 
388
- | 问题 | 原因 | 修正 |
389
- | ------------------ | -------------------- | ------------------------------------------------------------------------ |
390
- | 图标边缘有阴影 | 模型默认加立体效果 | 加 `no shadow, no drop shadow, flat 2D` |
391
- | 构图不居中 | 没有明确指定 | 加 `centered composition, subject centered` |
392
- | 背景不干净 | 交付要求或处理不匹配 | 核对背景开关、字段配置及模型对应的源图规则;检查最终验收,不混加背景要求 |
393
- | 多个物体出现 | 主体不够明确 | 加 `a single [物体]`, `only one [物体]` |
394
- | 文字拼写错误 | 所有模型通病 | 尝试 gpt-image-2(最好),或多生成几张 |
395
- | 风格不一致(批量) | 没用 reference image | Phase 2 必须加 `--reference-image` |
396
- | 细节过于复杂 | 素材尺寸小但细节太多 | 加 `simple design, minimal details, bold shapes` |
397
- | 生成极慢/超时 | 模型负载高 | 换另一个 provider 或换模型 |
388
+ | 问题 | 原因 | 修正 |
389
+ | ------------------ | -------------------- | ------------------------------------------------ |
390
+ | 图标边缘有阴影 | 模型默认加立体效果 | 加 `no shadow, no drop shadow, flat 2D` |
391
+ | 构图不居中 | 没有明确指定 | 加 `centered composition, subject centered` |
392
+ | 背景不干净 | 背景描述不够明确 | 加 `solid white background, isolated` 或绿幕策略 |
393
+ | 多个物体出现 | 主体不够明确 | 加 `a single [物体]`, `only one [物体]` |
394
+ | 文字拼写错误 | 所有模型通病 | 尝试 gpt-image-2(最好),或多生成几张 |
395
+ | 风格不一致(批量) | 没用 reference image | Phase 2 必须加 `--reference-image` |
396
+ | 细节过于复杂 | 素材尺寸小但细节太多 | 加 `simple design, minimal details, bold shapes` |
397
+ | 生成极慢/超时 | 模型负载高 | 换另一个 provider 或换模型 |
@@ -1,29 +0,0 @@
1
- import { readImageAssetContext } from '../utils/image-asset-context.js';
2
- import { collectReferenceImagePaths, handleError, parseImageTargetFieldPath } from './tools-utils.js';
3
- export function registerImageContextCommand(tools) {
4
- tools.command('image-context')
5
- .description('Read image asset reference and delivery constraints locally; does not generate an image')
6
- .requiredOption('--asset-path <path>', 'Existing image asset path relative to the project')
7
- .option('--target-field-path <path>', 'Target image field as an array of keys, e.g. ["endUI","playButton"]', parseImageTargetFieldPath)
8
- .option('--output-path <path>', 'Intended output path; checks shared asset protection before generation', collectReferenceImagePaths, [])
9
- .option('--allowed-output-root <path>', 'Additional output directory authorized by the caller; does not allow external references', collectReferenceImagePaths, [])
10
- .option('--reference-image <path>', 'Explicit local reference; may be repeated', collectReferenceImagePaths, [])
11
- .option('--no-target-reference', 'Do not use the target schema or current asset as a reference')
12
- .option('--prompt <text>', 'Generation request used to resolve background intent')
13
- .option('--remove-background', 'Explicitly request verified transparency')
14
- .option('--no-remove-background', 'Explicitly keep an opaque background')
15
- .action(async (opts) => {
16
- try {
17
- console.log(JSON.stringify(await readImageAssetContext(opts.assetPath, process.cwd(), {
18
- referenceImagePaths: opts.referenceImage, useTargetReference: opts.targetReference,
19
- ...(opts.targetFieldPath ? { targetFieldPath: opts.targetFieldPath } : {}),
20
- outputPaths: opts.outputPath,
21
- allowedOutputRoots: opts.allowedOutputRoot,
22
- prompt: opts.prompt, removeBackground: opts.removeBackground,
23
- })));
24
- }
25
- catch (error) {
26
- handleError(error);
27
- }
28
- });
29
- }
@@ -1,228 +0,0 @@
1
- import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
2
- import path from 'node:path';
3
- import JSON5 from 'json5';
4
- import sharp from 'sharp';
5
- import { extractAssetFields, getByPath, resolveAiImageTargetConstraints, resolveImageBackgroundRequirement, THEME_SCHEMA_PATH, themeDataPath, } from '@playcraft/common/prefab';
6
- function isWithinDirectory(root, file) {
7
- const relative = path.relative(root, file);
8
- return relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
9
- }
10
- /** Resolve existing ancestors so a new workspace need not have been created yet. */
11
- function realPathWithParents(file) {
12
- let ancestor = file;
13
- while (!existsSync(ancestor)) {
14
- const parent = path.dirname(ancestor);
15
- if (parent === ancestor)
16
- throw new Error(`Image path has no existing ancestor: ${file}`);
17
- ancestor = parent;
18
- }
19
- return path.resolve(realpathSync(ancestor), path.relative(ancestor, file));
20
- }
21
- /** Resolve aliases and existing parent symlinks, including for a new output file. */
22
- function canonicalProjectPath(root, input) {
23
- const mapped = input.startsWith('/project/') ? input.slice('/project/'.length) : input;
24
- const file = path.resolve(root, mapped);
25
- if (!isWithinDirectory(root, file)) {
26
- throw new Error(`Image asset path is outside the project: ${input}`);
27
- }
28
- const real = realPathWithParents(file);
29
- if (!isWithinDirectory(realPathWithParents(root), real)) {
30
- throw new Error(`Image asset symlink is outside the project: ${input}`);
31
- }
32
- return real;
33
- }
34
- /** Check every extension a writer may choose before sending a generation request. */
35
- export function assertImageAssetOutputPaths(context, outputPaths, projectDir = process.cwd(), allowedOutputRoots = []) {
36
- if (!context.requiresSeparateOutput)
37
- return;
38
- const root = path.resolve(projectDir);
39
- const target = canonicalProjectPath(root, context.targetAssetPath);
40
- const targetStat = statSync(target);
41
- const outputRoots = [root, ...allowedOutputRoots.map(outputRoot => path.resolve(root, outputRoot))];
42
- for (const output of outputPaths) {
43
- const mapped = output.startsWith('/project/') ? output.slice('/project/'.length) : output;
44
- const { dir, name } = path.parse(mapped);
45
- // The response encoding can change the requested extension.
46
- const candidates = [mapped, ...['.png', '.webp', '.jpg', '.gif', '.bmp'].map(ext => path.join(dir, `${name}${ext}`))];
47
- for (const candidate of candidates) {
48
- const candidatePath = path.resolve(root, candidate);
49
- const outputRoot = outputRoots.find(allowed => isWithinDirectory(allowed, candidatePath));
50
- if (!outputRoot)
51
- throw new Error(`Image output path is outside the project and allowed output roots: ${output}`);
52
- const file = canonicalProjectPath(outputRoot, candidatePath);
53
- const fileStat = existsSync(file) ? statSync(file) : undefined;
54
- if (!path.relative(target, file) || (targetStat.ino !== 0 && fileStat?.ino === targetStat.ino && fileStat.dev === targetStat.dev)) {
55
- throw new Error(`Cannot overwrite shared image asset ${context.targetAssetPath}: other fields have different requirements. Use a separate outputPath, then explicitly update the selected field reference; no shared asset was changed.`);
56
- }
57
- }
58
- }
59
- }
60
- /** Resolve only real files in this project, including legacy /project aliases. */
61
- function projectFile(root, input) {
62
- const mapped = input.startsWith('/project/') ? input.slice('/project/'.length) : input;
63
- const file = path.resolve(root, mapped);
64
- if (!isWithinDirectory(root, file))
65
- throw new Error(`Image asset path is outside the project: ${input}`);
66
- const real = realpathSync(file);
67
- if (!isWithinDirectory(realpathSync(root), real))
68
- throw new Error(`Image asset symlink is outside the project: ${input}`);
69
- return file;
70
- }
71
- /** Read the same per-field constraints used by the asset image dialog. */
72
- export async function readImageAssetContext(assetPath, projectDir = process.cwd(), options = {}) {
73
- const targetFieldPath = options.targetFieldPath;
74
- if (targetFieldPath !== undefined && (!Array.isArray(targetFieldPath)
75
- || !targetFieldPath.length || targetFieldPath.some(key => typeof key !== 'string' || !key.length))) {
76
- throw new Error('targetFieldPath must be a non-empty array of non-empty field names.');
77
- }
78
- const root = path.resolve(projectDir);
79
- const target = projectFile(root, assetPath);
80
- const realTarget = realpathSync(target);
81
- const targetStat = statSync(target);
82
- const relativeTarget = path.relative(root, target).replace(/\\/g, '/');
83
- const themeMatch = relativeTarget.match(/^src\/theme\/([^/]+)\/assets\//);
84
- let fields = [];
85
- const schemaFile = path.join(root, THEME_SCHEMA_PATH);
86
- if (themeMatch && existsSync(schemaFile)) {
87
- const schema = JSON5.parse(readFileSync(projectFile(root, THEME_SCHEMA_PATH), 'utf8'));
88
- const dataPath = themeDataPath(themeMatch[1]);
89
- const data = existsSync(path.join(root, dataPath))
90
- ? JSON5.parse(readFileSync(projectFile(root, dataPath), 'utf8')) : {};
91
- const themeRoot = path.dirname(path.join(root, dataPath));
92
- const matches = extractAssetFields(schema, data).filter(candidate => {
93
- let configured = getByPath(data, candidate.path);
94
- if (configured === undefined && ['gameplay', 'gamePlay'].includes(candidate.path[0])) {
95
- configured = getByPath(data, [candidate.path[0] === 'gameplay' ? 'gamePlay' : 'gameplay', ...candidate.path.slice(1)]);
96
- }
97
- const value = typeof configured === 'string' ? configured : candidate.schema.default;
98
- if (typeof value !== 'string' || /^https?:\/\//i.test(value))
99
- return false;
100
- const configuredPath = value.startsWith('/project/') ? path.resolve(root, value.slice('/project/'.length))
101
- : path.resolve(themeRoot, value);
102
- if (configuredPath === target)
103
- return true;
104
- try {
105
- const candidateFile = projectFile(root, configuredPath);
106
- if (!path.relative(realTarget, realpathSync(candidateFile)))
107
- return true;
108
- const candidateStat = statSync(candidateFile);
109
- return targetStat.ino !== 0 && candidateStat.ino === targetStat.ino && candidateStat.dev === targetStat.dev;
110
- }
111
- catch {
112
- // Other fields may reference missing files or paths outside this project.
113
- return false;
114
- }
115
- });
116
- if (!matches.length)
117
- throw new Error(`No image schema field matches ${relativeTarget}; check theme.data.json5 before generating.`);
118
- fields = matches;
119
- if (targetFieldPath && !fields.some(candidate => candidate.path.length === targetFieldPath.length
120
- && candidate.path.every((key, index) => key === targetFieldPath[index]))) {
121
- throw new Error(`targetFieldPath ${JSON.stringify(targetFieldPath)} does not reference ${relativeTarget}. Matching fields: ${matches.map(candidate => JSON.stringify(candidate.path)).join(', ')}.`);
122
- }
123
- }
124
- else if (targetFieldPath) {
125
- throw new Error('targetFieldPath requires an asset with a matching theme schema.');
126
- }
127
- const targetMeta = await sharp(target).metadata();
128
- let targetHasTransparency;
129
- const contextForField = async (field) => {
130
- const xp = field?.schema.xplatform;
131
- const ext = xp?.ext;
132
- // Schema references follow the dialog's root/theme conventions; explicit tool paths do not change.
133
- const schemaReferences = Array.isArray(ext?.reference_image)
134
- ? ext.reference_image.filter(ref => typeof ref === 'string' && ref.trim()).map(ref => {
135
- const raw = ref.trim().replace(/\\/g, '/');
136
- const normalized = raw.replace(/^\.\/+/, '').replace(/^\/+/, '').replace(/\/{2,}/g, '/');
137
- if (/^\/?(?:assets|src|public)\//.test(raw) || normalized.startsWith('src/'))
138
- return normalized;
139
- if (!themeMatch || path.isAbsolute(raw) || /^https?:\/\//i.test(raw))
140
- return raw;
141
- return path.posix.join('src/theme', themeMatch[1], normalized);
142
- }) : [];
143
- const references = options.referenceImagePaths?.length ? options.referenceImagePaths
144
- : options.useTargetReference === false ? [] : schemaReferences.length ? schemaReferences : [relativeTarget];
145
- const referenceImagePaths = references.map(ref => /^https?:\/\//i.test(ref) ? ref
146
- : path.relative(realpathSync(root), canonicalProjectPath(root, ref)).replace(/\\/g, '/'));
147
- const hasSchemaBackground = typeof ext?.remove_background === 'boolean';
148
- if (!hasSchemaBackground && targetHasTransparency === undefined) {
149
- targetHasTransparency = !!targetMeta.hasAlpha && (await sharp(target).stats()).channels.at(-1).min < 250;
150
- }
151
- const removeBackground = resolveImageBackgroundRequirement({
152
- prompt: options.prompt ?? '',
153
- schemaRemoveBackground: hasSchemaBackground ? ext.remove_background : undefined,
154
- removeBackground: options.removeBackground,
155
- fallbackRemoveBackground: targetHasTransparency ?? false,
156
- });
157
- const delivery = resolveAiImageTargetConstraints({
158
- schemaResolution: ext?.resolution,
159
- schemaMimeType: xp?.mime_type,
160
- schemaFitMode: xp?.fit_mode ?? ext?.fit_mode,
161
- schemaRemoveBackground: removeBackground,
162
- schemaPaddingColor: ext?.padding_color,
163
- assetPath: relativeTarget,
164
- referenceWidth: targetMeta.width,
165
- referenceHeight: targetMeta.height,
166
- referenceFormat: targetMeta.format,
167
- });
168
- if (!['png', 'webp', 'jpeg'].includes(delivery.format))
169
- throw new Error('Unsupported image delivery format; use a dedicated animation or vector workflow.');
170
- return {
171
- targetAssetPath: relativeTarget,
172
- fieldPath: field?.path,
173
- referenceImagePaths,
174
- width: delivery.width, height: delivery.height,
175
- aspectRatio: typeof ext?.ratio === 'string' ? ext.ratio : `${delivery.width}:${delivery.height}`,
176
- outputFormat: (removeBackground && delivery.format === 'jpeg' && !delivery.fromSchemaMimeType
177
- ? 'png' : delivery.format),
178
- outputFormatFromSchema: delivery.fromSchemaMimeType,
179
- fitMode: delivery.fitMode, removeBackground: delivery.removeBackground, paddingColor: delivery.paddingColor,
180
- removeBackgroundFromSchema: hasSchemaBackground,
181
- imageModelRef: typeof ext?.imageModelRef === 'string' ? ext.imageModelRef : undefined,
182
- };
183
- };
184
- const contexts = [];
185
- let unresolvedSharedField = false;
186
- for (const field of fields.length ? fields : [undefined]) {
187
- try {
188
- contexts.push(await contextForField(field));
189
- }
190
- catch (error) {
191
- const selected = field?.path.length === targetFieldPath?.length
192
- && field?.path.every((key, index) => key === targetFieldPath?.[index]);
193
- if (!targetFieldPath || selected)
194
- throw error;
195
- // Invalid sibling requirements still protect the original shared file.
196
- unresolvedSharedField = true;
197
- }
198
- }
199
- const context = targetFieldPath ? contexts.find(candidate => candidate.fieldPath?.length === targetFieldPath.length
200
- && candidate.fieldPath.every((key, index) => key === targetFieldPath[index])) : contexts[0];
201
- const deliveryKey = ({ fieldPath: _fieldPath, outputFormatFromSchema: _formatSource, removeBackgroundFromSchema: _backgroundSource, aspectRatio, ...delivery }) => {
202
- const ratio = aspectRatio.match(/^\s*(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)\s*$/);
203
- return JSON.stringify({ ...delivery, aspectRatio: ratio && Number(ratio[2]) > 0 ? Number(ratio[1]) / Number(ratio[2]) : aspectRatio });
204
- };
205
- const conflicting = unresolvedSharedField || contexts.some(candidate => deliveryKey(candidate) !== deliveryKey(context));
206
- if (conflicting && !targetFieldPath) {
207
- throw new Error(`Multiple image fields use ${relativeTarget} with different references or delivery constraints. Set targetFieldPath (or --target-field-path) to one of: ${fields.map(field => JSON.stringify(field.path)).join(', ')}.`);
208
- }
209
- if (targetFieldPath) {
210
- // A separate output retains exactly the selected field's requirements.
211
- if (conflicting || contexts.some(candidate => (candidate.outputFormatFromSchema && !context.outputFormatFromSchema)
212
- || (candidate.removeBackgroundFromSchema && !context.removeBackgroundFromSchema)))
213
- context.requiresSeparateOutput = true;
214
- }
215
- else {
216
- context.outputFormatFromSchema = contexts.some(candidate => candidate.outputFormatFromSchema);
217
- context.removeBackgroundFromSchema = contexts.some(candidate => candidate.removeBackgroundFromSchema);
218
- }
219
- assertImageAssetOutputPaths(context, options.outputPaths ?? [], root, options.allowedOutputRoots);
220
- if (context.referenceImagePaths.length > 8)
221
- throw new Error('At most 8 reference images are allowed.');
222
- for (const ref of context.referenceImagePaths) {
223
- if (/^https?:\/\//i.test(ref))
224
- throw new Error('Download the schema reference into the project before generating.');
225
- await sharp(projectFile(root, ref)).metadata();
226
- }
227
- return context;
228
- }