@playcraft/cli 0.1.36-beta1 → 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
@@ -4,6 +4,7 @@ import pc from 'picocolors';
4
4
  import ora from 'ora';
5
5
  import { BaseBuilder, ViteBuilder, PlayableBuilder, PlayableAnalyzer, EngineDetector, detectAmmoUsage } from '@playcraft/build';
6
6
  import { loadBuildConfig } from '../build-config.js';
7
+ import { cleanupBuildTempDirectory, createBuildTempDirectory } from './build-temp.js';
7
8
  import inquirer from 'inquirer';
8
9
  /**
9
10
  * 类型守卫:检查对象是否符合 PlayableScriptsConfig 类型
@@ -250,6 +251,7 @@ export async function buildCommand(projectPath, options) {
250
251
  const spinner = ora(pc.cyan('🔨 开始打包 Playable Ad...')).start();
251
252
  // 清理选项(默认为 false,使用覆盖模式)
252
253
  const shouldClean = options.clean === true;
254
+ let tempBuildDir;
253
255
  try {
254
256
  // ====== 引擎 + 构建工具检测 ======
255
257
  spinner.text = pc.cyan('检测项目引擎类型...');
@@ -450,6 +452,7 @@ export async function buildCommand(projectPath, options) {
450
452
  const shouldRunPlayableOnly = options.mode === 'playable';
451
453
  // 验证平台
452
454
  const validPlatforms = [
455
+ 'playcraft',
453
456
  'facebook',
454
457
  'snapchat',
455
458
  'ironsource',
@@ -473,6 +476,7 @@ export async function buildCommand(projectPath, options) {
473
476
  // 注:Facebook 在 Phaser(playable-scripts devkit)下的产物格式由其 devkit 输入配置决定(恒为 ZIP),
474
477
  // 平台侧不在此处做引擎判断。
475
478
  const platformFormatConfig = {
479
+ playcraft: { formats: ['html'], default: 'html' },
476
480
  facebook: { formats: ['html', 'zip'], default: 'html' },
477
481
  applovin: { formats: ['html'], default: 'html' },
478
482
  ironsource: { formats: ['html'], default: 'html' },
@@ -584,8 +588,11 @@ export async function buildCommand(projectPath, options) {
584
588
  console.log(pc.dim(' 无主题目录,跳过主题选择'));
585
589
  }
586
590
  }
587
- // 输入商店跳转地址(CTA)- 如果配置文件中没有则必填
588
- if (!options.storeUrls) {
591
+ // 输入商店跳转地址(CTA)- 如果配置文件中没有则必填;通用预览(playcraft)不面向渠道,可不填
592
+ if (selectedPlatform === 'playcraft' && !options.storeUrls) {
593
+ console.log(pc.dim('\nℹ️ 通用预览不需要商店地址'));
594
+ }
595
+ else if (!options.storeUrls) {
589
596
  console.log(pc.cyan('\n🔗 商店跳转地址(CTA 按钮目标)'));
590
597
  console.log(pc.dim(' iOS 和 Android 地址均为必填'));
591
598
  const storeUrlAnswer = await inquirer.prompt([
@@ -636,8 +643,8 @@ export async function buildCommand(projectPath, options) {
636
643
  // 未指定格式,使用默认值
637
644
  options.format = formatConfig.default;
638
645
  }
639
- // 如果命令行未传入 storeUrls,交互式输入(必填)
640
- if (!options.storeUrls) {
646
+ // 如果命令行未传入 storeUrls,交互式输入(必填);通用预览(playcraft)不面向渠道,可不填
647
+ if (!options.storeUrls && options.platform !== 'playcraft') {
641
648
  spinner.stop();
642
649
  console.log(pc.cyan('\n🔗 商店跳转地址(CTA 按钮目标)'));
643
650
  console.log(pc.dim(' iOS 和 Android 地址均为必填'));
@@ -742,7 +749,7 @@ export async function buildCommand(projectPath, options) {
742
749
  // 输入是源代码或官方构建,先执行阶段1
743
750
  spinner.text = pc.cyan('执行阶段1: 基础构建...');
744
751
  // 将临时目录放在项目所在目录下,避免跨盘符路径问题(Windows 上 C:\Temp 和 D:\project 会导致 Vite 路径计算错误)
745
- const tempDir = path.join(resolvedProjectPath, '.playcraft-temp', `base-build-${Date.now()}`);
752
+ tempBuildDir = await createBuildTempDirectory(resolvedProjectPath);
746
753
  // 构建 playableScriptsConfig,合并 CLI 选择的参数
747
754
  const playableScriptsConfig = buildPlayableScriptsConfig(fileConfig?.playableScripts, {
748
755
  platform: options.platform,
@@ -750,7 +757,7 @@ export async function buildCommand(projectPath, options) {
750
757
  selectedThemes,
751
758
  });
752
759
  const baseBuilder = new BaseBuilder(resolvedProjectPath, {
753
- outputDir: tempDir,
760
+ outputDir: tempBuildDir,
754
761
  selectedScenes,
755
762
  analyze: buildOptions.analyze,
756
763
  analyzeReportPath: buildOptions.analyze ? 'base-bundle-report.html' : undefined,
@@ -795,16 +802,7 @@ export async function buildCommand(projectPath, options) {
795
802
  }
796
803
  console.log('\n' + pc.green(`输出目录: ${finalOutputDir}`));
797
804
  console.log(pc.dim('💡 产物已包含渠道适配、MRAID 注入、代码混淆和 fflate 压缩'));
798
- // 清理临时目录
799
- try {
800
- const tempBaseDir = path.join(resolvedProjectPath, '.playcraft-temp');
801
- await fs.rm(tempBaseDir, { recursive: true, force: true });
802
- console.log(pc.dim(`\n🗑️ 已清理临时目录`));
803
- }
804
- catch (error) {
805
- // 记录清理错误信息
806
- console.log(pc.dim(`\n⚠️ 清理临时目录失败: ${error instanceof Error ? error.message : String(error)}`));
807
- }
805
+ await cleanupBuildTempDirectory(tempBuildDir);
808
806
  return;
809
807
  }
810
808
  spinner.text = pc.cyan('✅ 阶段1完成,开始阶段2...');
@@ -976,18 +974,7 @@ export async function buildCommand(projectPath, options) {
976
974
  console.log(pc.yellow(`\n⚠️ 检测到 Ammo.js,建议替换为 ${analysis.suggestedEngine}`));
977
975
  }
978
976
  }
979
- // 清理临时目录(调试时注释掉)
980
- if (!isBaseBuild && baseBuildDir.includes('.playcraft-temp')) {
981
- // 调试:暂时保留临时目录
982
- console.log(pc.yellow(`[DEBUG] 临时目录保留在: ${baseBuildDir}`));
983
- // try {
984
- // // 清理整个 .playcraft-temp 目录
985
- // const tempBaseDir = path.join(resolvedProjectPath, '.playcraft-temp');
986
- // await fs.rm(tempBaseDir, { recursive: true, force: true });
987
- // } catch (error) {
988
- // // 忽略清理错误
989
- // }
990
- }
977
+ await cleanupBuildTempDirectory(tempBuildDir);
991
978
  }
992
979
  catch (error) {
993
980
  spinner.fail(pc.red('打包失败'));
@@ -1,3 +1,4 @@
1
+ import { prepareLocalSkillLifecycle } from '../project-skills/lifecycle.js';
1
2
  import { execFileSync, execSync } from 'child_process';
2
3
  import inquirer from 'inquirer';
3
4
  import fs from 'fs/promises';
@@ -6,8 +7,11 @@ import { fileURLToPath } from 'url';
6
7
  import pc from 'picocolors';
7
8
  import ora from 'ora';
8
9
  import { resolveCreateTemplate, resolveCreateTemplateDir, } from '../scaffold/templates.js';
10
+ import { PLATFORM_SKILL_DIRS } from './platform-skills.generated.js';
9
11
  const __filename = fileURLToPath(import.meta.url);
10
12
  const __dirname = path.dirname(__filename);
13
+ /** `@playcraft/skills` 包内 Platform Skill 的目录名(与 Atom Skill 的 `skills/` 区分)。 */
14
+ const PLATFORM_SKILLS_DIRNAME = 'playcraft-skills';
11
15
  /** 生成一个本地占位 Project ID(99xxxxxx),后续可通过 playcraft config set 更新为真实 ID */
12
16
  function generateMockProjectId() {
13
17
  const suffix = Math.floor(Math.random() * 90_000_000) + 10_000_000;
@@ -849,7 +853,7 @@ function extractSkillBodyForCursor(content) {
849
853
  * @param cliCommandsDir CLI 命令所在目录(用于解析 skills 包位置)
850
854
  * @param configDir 配置目录名(如 '.claude' 或 '.codebuddy')
851
855
  */
852
- async function setupPlatformSkillLinksInner(targetDir, cliCommandsDir, configDir) {
856
+ export async function setupPlatformSkillLinksInner(targetDir, cliCommandsDir, configDir) {
853
857
  const skillsLinkDir = path.join(targetDir, configDir, 'skills');
854
858
  await fs.mkdir(skillsLinkDir, { recursive: true });
855
859
  // 以下 Skill 在项目模板中以静态目录形式存在,不创建软连接覆盖它们。
@@ -868,11 +872,12 @@ async function setupPlatformSkillLinksInner(targetDir, cliCommandsDir, configDir
868
872
  'playcraft-image-generation',
869
873
  'playcraft-audio-generation',
870
874
  ]);
871
- // 尝试从 CLI 安装包的位置定位 @playcraft/skills/skills 目录
875
+ // Platform Skill 住在 playcraft-skills/,与 Atom Skill 的 skills/ 是两个目录。
876
+ // 尝试从 CLI 安装包的位置定位它(monorepo 内或与 CLI 平级安装时可解析)。
872
877
  const candidateDirs = [
873
- path.resolve(cliCommandsDir, '..', '..', '..', '..', 'skills', 'skills'),
874
- path.resolve(cliCommandsDir, '..', '..', '..', 'skills', 'skills'),
875
- path.resolve(cliCommandsDir, '..', '..', 'skills', 'skills'),
878
+ path.resolve(cliCommandsDir, '..', '..', '..', '..', 'skills', PLATFORM_SKILLS_DIRNAME),
879
+ path.resolve(cliCommandsDir, '..', '..', '..', 'skills', PLATFORM_SKILLS_DIRNAME),
880
+ path.resolve(cliCommandsDir, '..', '..', 'skills', PLATFORM_SKILLS_DIRNAME),
876
881
  ];
877
882
  let resolvedSkillsDir = null;
878
883
  for (const dir of candidateDirs) {
@@ -885,18 +890,24 @@ async function setupPlatformSkillLinksInner(targetDir, cliCommandsDir, configDir
885
890
  // 继续下一个候选
886
891
  }
887
892
  }
888
- // 相对路径(指向 npm install 后的 node_modules)始终创建,作为安装后激活的备用
889
- const relativeSkillsBase = path.join('..', '..', 'node_modules', '@playcraft', 'skills', 'skills');
893
+ // 相对路径(指向 npm install 后的 node_modules)作为安装后激活的备用
894
+ const relativeSkillsBase = path.join('..', '..', 'node_modules', '@playcraft', 'skills', PLATFORM_SKILLS_DIRNAME);
890
895
  if (resolvedSkillsDir) {
891
- // 能立即解析 → 创建指向绝对路径的软连接,当下即可使用
896
+ // 能立即解析 → 以真实目录为准创建绝对路径软连接,当下即可使用。
897
+ // 按「是否含 SKILL.md」判定而非 playcraft- 前缀:sound-effects 无前缀但同样是平台技能。
892
898
  const entries = await fs.readdir(resolvedSkillsDir, { withFileTypes: true });
893
899
  for (const entry of entries) {
894
- if (!entry.name.startsWith('playcraft-') || staticSkills.has(entry.name))
900
+ if (!entry.isDirectory() || staticSkills.has(entry.name))
895
901
  continue;
896
- const linkPath = path.join(skillsLinkDir, entry.name);
897
902
  const targetPath = path.join(resolvedSkillsDir, entry.name);
898
903
  try {
899
- await fs.symlink(targetPath, linkPath, 'dir');
904
+ await fs.access(path.join(targetPath, 'SKILL.md'));
905
+ }
906
+ catch {
907
+ continue;
908
+ }
909
+ try {
910
+ await fs.symlink(targetPath, path.join(skillsLinkDir, entry.name), 'dir');
900
911
  }
901
912
  catch {
902
913
  // 已存在则跳过
@@ -904,28 +915,9 @@ async function setupPlatformSkillLinksInner(targetDir, cliCommandsDir, configDir
904
915
  }
905
916
  }
906
917
  else {
907
- // 无法立即解析 → 创建指向 node_modules 的相对路径软连接,npm install 后自动激活
908
- const knownPlatformTools = [
909
- 'playcraft-asset-management',
910
- 'playcraft-audio-generation',
911
- 'playcraft-build',
912
- 'playcraft-code-editor',
913
- 'playcraft-create-remix',
914
- 'playcraft-deploy',
915
- 'playcraft-image-generation',
916
- 'playcraft-image-processing',
917
- 'playcraft-platform-intro',
918
- 'playcraft-prefab',
919
- 'playcraft-project-management',
920
- 'playcraft-remix-workflow',
921
- 'playcraft-save',
922
- 'playcraft-skill-recommender',
923
- 'playcraft-sprite-generation',
924
- 'playcraft-sprite-remix',
925
- 'playcraft-sprite-sheet',
926
- 'playcraft-3d-flip-sprite',
927
- ];
928
- for (const name of knownPlatformTools) {
918
+ // 无法立即解析 → 指向 node_modules 的相对路径软连接,npm install 后自动激活。
919
+ // 清单由 scripts/generate-platform-skills.mjs 从真实目录生成并由 CI 校验,不要手写。
920
+ for (const name of PLATFORM_SKILL_DIRS) {
929
921
  if (staticSkills.has(name))
930
922
  continue;
931
923
  const linkPath = path.join(skillsLinkDir, name);
@@ -1289,6 +1281,7 @@ export async function createCommand(projectName, options) {
1289
1281
  smokeSpinner.warn(`Harness 自检跳过: ${message}`);
1290
1282
  }
1291
1283
  }
1284
+ await prepareLocalSkillLifecycle(targetDir, 'create');
1292
1285
  const gitSpinner = ora(isWorktreeMode ? '提交项目分支...' : '初始化 Git 仓库...').start();
1293
1286
  try {
1294
1287
  if (isWorktreeMode) {
@@ -677,9 +677,7 @@ async function runBatchStep(cmd, args, inputPath, outputPath) {
677
677
  const meta = await sharp(inputPath).metadata();
678
678
  const targetW = scale ? Math.round((meta.width ?? 64) * scale) : w;
679
679
  const targetH = scale ? Math.round((meta.height ?? 64) * scale) : h;
680
- await safeToFile(sharp(inputPath).resize(targetW, targetH, {
681
- fit, background: { r: 0, g: 0, b: 0, alpha: 0 },
682
- }), outputPath, inputPath);
680
+ await safeToFile(sharp(inputPath).resize(targetW, targetH, { fit }), outputPath, inputPath);
683
681
  break;
684
682
  }
685
683
  case 'crop': {
@@ -838,9 +836,7 @@ export function registerImageCommands(program) {
838
836
  process.exit(1);
839
837
  }
840
838
  const fit = parseResizeFitOption(opts.fit);
841
- await safeToFile(sharp(opts.input).resize(targetW, targetH, {
842
- fit, background: { r: 0, g: 0, b: 0, alpha: 0 },
843
- }), opts.output, opts.input);
839
+ await safeToFile(sharp(opts.input).resize(targetW, targetH, { fit }), opts.output, opts.input);
844
840
  const outSz = statSync(resolveSandboxPath(opts.output)).size;
845
841
  console.log(`Resized ${w}x${h} → ${targetW ?? '?'}x${targetH ?? '?'} — ${opts.output} (${sizeLabelBytes(outSz)})`);
846
842
  }
@@ -1,7 +1,8 @@
1
1
  import http from 'http';
2
- import { exec } from 'child_process';
2
+ import { execFile } from 'child_process';
3
3
  import crypto from 'crypto';
4
- import { saveGlobalConfig, loadGlobalConfig } from '../config.js';
4
+ import { loadGlobalConfig, normalizeEnvironmentBaseUrl, readEnvironmentProfile, saveEnvironmentProfile } from '../config.js';
5
+ import { activateAccount } from '../auth/account-activation.js';
5
6
  /** 默认云端 Portal(Next.js `/cli-auth` 与 API 同源时的基址;可通过 --url 或 PLAYCRAFT_URL 覆盖) */
6
7
  export const DEFAULT_PLAYCRAFT_CLOUD_BASE = 'https://playcraft.aix.intlgame.com';
7
8
  const CALLBACK_PORT = 3456;
@@ -18,10 +19,11 @@ function openBrowser(url) {
18
19
  const cmd = process.platform === 'darwin'
19
20
  ? 'open'
20
21
  : process.platform === 'win32'
21
- ? 'start'
22
+ ? 'rundll32'
22
23
  : 'xdg-open';
23
24
  try {
24
- exec(`${cmd} "${url}"`, (err, _stdout, stderr) => {
25
+ const arguments_ = process.platform === 'win32' ? ['url.dll,FileProtocolHandler', url] : [url];
26
+ execFile(cmd, arguments_, (err, _stdout, stderr) => {
25
27
  if (err) {
26
28
  console.error('Failed to open browser automatically:', err.message);
27
29
  if (stderr?.trim()) {
@@ -44,21 +46,18 @@ function resolveLoginBaseUrl(options) {
44
46
  globalConfig.url ||
45
47
  globalConfig.backendUrl ||
46
48
  DEFAULT_PLAYCRAFT_CLOUD_BASE;
47
- return raw.replace(/\/+$/, '');
49
+ const base = normalizeEnvironmentBaseUrl(raw);
50
+ if (!base)
51
+ throw new Error('Invalid environment address');
52
+ return base;
48
53
  }
49
54
  function normalizeLoginEnvironmentUrl(raw) {
50
- try {
51
- const url = new URL(raw);
52
- url.hash = '';
53
- url.search = '';
54
- url.pathname = url.pathname.replace(/\/+$/, '');
55
- return url.toString().replace(/\/+$/, '');
56
- }
57
- catch {
58
- return undefined;
59
- }
55
+ return normalizeEnvironmentBaseUrl(raw);
60
56
  }
61
57
  export function resolveExistingTokenForLoginTarget(globalConfig, backendUrl) {
58
+ const profile = readEnvironmentProfile(globalConfig, backendUrl);
59
+ if (profile)
60
+ return profile.token;
62
61
  const token = typeof globalConfig.token === 'string' ? globalConfig.token : undefined;
63
62
  const storedBackendUrl = typeof globalConfig.backendUrl === 'string'
64
63
  ? globalConfig.backendUrl
@@ -74,9 +73,17 @@ export function resolveExistingTokenForLoginTarget(globalConfig, backendUrl) {
74
73
  : undefined;
75
74
  }
76
75
  export function buildCliAuthUrl(options) {
77
- const url = new URL('/cli-auth', options.backendUrl);
76
+ const base = normalizeEnvironmentBaseUrl(options.backendUrl);
77
+ if (!base)
78
+ throw new Error('Invalid environment address');
79
+ const url = new URL(`${base}/cli-auth`);
80
+ url.searchParams.set('callbackUrl', options.callbackUrl);
78
81
  url.searchParams.set('callback', options.callbackUrl);
79
82
  url.searchParams.set('state', options.state);
83
+ if (options.codeChallenge) {
84
+ url.searchParams.set('codeChallenge', options.codeChallenge);
85
+ url.searchParams.set('codeChallengeMethod', 'S256');
86
+ }
80
87
  return url.toString();
81
88
  }
82
89
  export async function renewExistingToken(options) {
@@ -87,12 +94,20 @@ export async function renewExistingToken(options) {
87
94
  try {
88
95
  const res = await fetchFn(`${options.backendUrl}/api/settings/personal-access-tokens/current/renew`, {
89
96
  method: 'PATCH',
97
+ redirect: 'error',
98
+ signal: AbortSignal.timeout(10_000),
90
99
  headers: {
91
100
  Authorization: `Bearer ${options.token}`,
92
101
  'Content-Type': 'application/json',
93
102
  },
94
103
  body: JSON.stringify({ expiresIn: '365d' }),
95
104
  });
105
+ if (res.ok && res.json && options.onRenewed) {
106
+ const result = await res.json();
107
+ if (typeof result.expiresAt === 'string' && Date.parse(result.expiresAt) > Date.now()) {
108
+ options.onRenewed(result.expiresAt);
109
+ }
110
+ }
96
111
  return res.ok;
97
112
  }
98
113
  catch {
@@ -100,29 +115,43 @@ export async function renewExistingToken(options) {
100
115
  }
101
116
  }
102
117
  export async function loginCommand(options) {
118
+ if (options.activate) {
119
+ await activateAccount(options, DEFAULT_PLAYCRAFT_CLOUD_BASE);
120
+ return;
121
+ }
103
122
  const backendUrl = resolveLoginBaseUrl(options);
104
123
  const callbackPort = options.callbackPort ?? CALLBACK_PORT;
105
124
  const globalConfig = loadGlobalConfig();
106
125
  const existingToken = resolveExistingTokenForLoginTarget(globalConfig, backendUrl);
107
126
  if (existingToken) {
108
- const renewed = await renewExistingToken({ backendUrl, token: existingToken });
127
+ let expiresAt;
128
+ const renewed = await renewExistingToken({ backendUrl, token: existingToken,
129
+ onRenewed: (value) => { expiresAt = value; } });
109
130
  if (renewed) {
110
- saveGlobalConfig({
111
- token: existingToken,
112
- url: backendUrl,
113
- backendUrl,
114
- });
131
+ const previous = readEnvironmentProfile(globalConfig, backendUrl);
132
+ saveEnvironmentProfile(backendUrl, { token: existingToken,
133
+ ...(previous?.identity ? { identity: previous.identity } : {}),
134
+ ...(expiresAt ? { expiresAt } : {}) });
115
135
  console.log('Existing CLI token renewed. Token saved to ~/.playcraft/config.json');
116
136
  return;
117
137
  }
118
138
  }
119
139
  const state = crypto.randomBytes(16).toString('hex');
140
+ const codeVerifier = crypto.randomBytes(32).toString('base64url');
141
+ const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
142
+ let callbackUrl = '';
143
+ let exchanging = false;
120
144
  return new Promise((resolve, reject) => {
121
145
  let timeout;
122
146
  const server = http.createServer((req, res) => {
123
- res.setHeader('Access-Control-Allow-Origin', '*');
147
+ res.setHeader('Access-Control-Allow-Origin', new URL(backendUrl).origin);
124
148
  res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
125
149
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
150
+ if (req.headers.origin && req.headers.origin !== new URL(backendUrl).origin) {
151
+ res.writeHead(403);
152
+ res.end();
153
+ return;
154
+ }
126
155
  if (req.method === 'OPTIONS') {
127
156
  res.writeHead(204);
128
157
  res.end();
@@ -130,8 +159,9 @@ export async function loginCommand(options) {
130
159
  }
131
160
  if (req.url === '/callback' && req.method === 'POST') {
132
161
  let body = '';
133
- req.on('data', (chunk) => (body += chunk));
134
- req.on('end', () => {
162
+ req.on('data', (chunk) => { body += chunk; if (body.length > 16_384)
163
+ req.destroy(); });
164
+ req.on('end', async () => {
135
165
  try {
136
166
  const data = JSON.parse(body);
137
167
  if (data.state !== state) {
@@ -139,22 +169,36 @@ export async function loginCommand(options) {
139
169
  'Content-Type': 'text/html',
140
170
  Connection: 'close',
141
171
  });
142
- res.end('<html><body><h1>Invalid state</h1></body></html>', () => {
143
- closeServer(() => {
144
- reject(new Error('State mismatch - possible CSRF attack'));
145
- });
146
- });
172
+ res.end('<html><body><h1>Invalid state</h1></body></html>');
147
173
  return;
148
174
  }
149
- const callbackToken = typeof data.token === 'string' ? data.token : undefined;
150
- if (!callbackToken) {
151
- throw new Error('Login callback did not include a token');
175
+ if (exchanging) {
176
+ res.writeHead(409);
177
+ res.end();
178
+ return;
179
+ }
180
+ if (typeof data.code !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(data.code)) {
181
+ throw new Error('Login callback did not include an authorization code');
152
182
  }
153
- saveGlobalConfig({
154
- token: callbackToken,
155
- url: backendUrl,
156
- backendUrl,
183
+ exchanging = true;
184
+ const base = normalizeEnvironmentBaseUrl(backendUrl);
185
+ if (!base)
186
+ throw new Error('Invalid environment address');
187
+ const exchange = await fetch(`${base}/api/cloud-environments/exchange`, {
188
+ method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10_000),
189
+ headers: { 'Content-Type': 'application/json' },
190
+ body: JSON.stringify({ code: data.code, state, callbackUrl, codeVerifier }),
157
191
  });
192
+ if (!exchange.ok)
193
+ throw new Error('Authorization code exchange failed');
194
+ const credential = await exchange.json();
195
+ if (!credential.token || !/^playcraft_pat_[A-Za-z0-9_-]{32}$/.test(credential.token)
196
+ || !credential.identity?.userId || !Array.isArray(credential.identity.identities)
197
+ || !credential.expiresAt || Date.parse(credential.expiresAt) <= Date.now()) {
198
+ throw new Error('Invalid environment credential response');
199
+ }
200
+ saveEnvironmentProfile(base, { token: credential.token,
201
+ identity: credential.identity, expiresAt: credential.expiresAt });
158
202
  res.writeHead(200, {
159
203
  'Content-Type': 'text/html',
160
204
  Connection: 'close',
@@ -174,9 +218,6 @@ export async function loginCommand(options) {
174
218
  catch (err) {
175
219
  const cause = err instanceof Error ? err : new Error(String(err));
176
220
  console.error('[playcraft login] Callback handling failed:', cause.message);
177
- if (cause.stack) {
178
- console.error(cause.stack);
179
- }
180
221
  const userFacing = cause instanceof SyntaxError
181
222
  ? 'Invalid JSON in login callback.'
182
223
  : `Login callback failed: ${cause.message}`;
@@ -221,14 +262,15 @@ export async function loginCommand(options) {
221
262
  });
222
263
  });
223
264
  try {
224
- server.listen(callbackPort, () => {
265
+ server.listen(callbackPort, '127.0.0.1', () => {
225
266
  const address = server.address();
226
267
  const listeningPort = typeof address === 'object' && address ? address.port : callbackPort;
227
- const callbackUrl = `http://localhost:${listeningPort}/callback`;
268
+ callbackUrl = `http://127.0.0.1:${listeningPort}/callback`;
228
269
  const authUrl = buildCliAuthUrl({
229
270
  backendUrl,
230
271
  callbackUrl,
231
272
  state,
273
+ codeChallenge,
232
274
  });
233
275
  console.log('Opening browser for login...');
234
276
  console.log(`If the browser does not open, visit this URL manually:\n ${authUrl}\n`);
@@ -0,0 +1,19 @@
1
+ // 此文件由 scripts/generate-platform-skills.mjs 生成,请勿手改。
2
+ // 重新生成:pnpm --filter @playcraft/cli generate:platform-skills
3
+ /** `@playcraft/skills/playcraft-skills/` 下的 Platform Skill 目录名。 */
4
+ export const PLATFORM_SKILL_DIRS = [
5
+ 'playcraft-3d-flip-sprite',
6
+ 'playcraft-asset-management',
7
+ 'playcraft-build',
8
+ 'playcraft-code-editor',
9
+ 'playcraft-create-remix',
10
+ 'playcraft-deploy',
11
+ 'playcraft-iteration-methodology',
12
+ 'playcraft-platform-intro',
13
+ 'playcraft-prefab',
14
+ 'playcraft-project-management',
15
+ 'playcraft-remix-workflow',
16
+ 'playcraft-save',
17
+ 'playcraft-template-upgrade-merge',
18
+ 'sound-effects',
19
+ ];
@@ -0,0 +1,53 @@
1
+ import { recordPlayable } from '../recording/recorder.js';
2
+ import { RecordingError, recordingMessage as t } from '../recording/messages.js';
3
+ import { dirname, join } from 'node:path';
4
+ import { captureGeneratedArtifact } from '../workspace-runtime/workspaces/artifact-receipts.js';
5
+ export async function runPlayableRecord(options) {
6
+ const interrupted = new AbortController();
7
+ const deadline = new AbortController();
8
+ const onInterrupt = () => interrupted.abort(new DOMException('Interrupted', 'AbortError'));
9
+ process.once('SIGINT', onInterrupt);
10
+ let timer;
11
+ try {
12
+ const seconds = options.timeout === undefined ? 1200 : Number(options.timeout);
13
+ if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 1800)
14
+ throw new RecordingError('invalidArguments', 64);
15
+ timer = setTimeout(() => deadline.abort(new DOMException('Timed out', 'TimeoutError')), seconds * 1000);
16
+ const result = await recordPlayable(options, AbortSignal.any([interrupted.signal, deadline.signal]));
17
+ const metadata = result.envelope.result;
18
+ const output = result.envelope.artifacts;
19
+ if (result.envelope.ok && metadata && output?.candidate) {
20
+ const artifacts = [metadata.candidate, ...metadata.controls];
21
+ const paths = [output.candidate, ...(output.controls ?? [])];
22
+ for (let index = 0; index < artifacts.length; index++) {
23
+ const artifact = artifacts[index], path = paths[index];
24
+ if (!path)
25
+ continue;
26
+ const thumbnail = artifact.screenshots?.[0]?.path;
27
+ await captureGeneratedArtifact(path, 'playable record', {
28
+ width: artifact.video.source.width, height: artifact.video.source.height,
29
+ duration: artifact.video.source.durationMs / 1000,
30
+ ...(thumbnail ? { thumbnailPath: join(dirname(output.metadata), thumbnail) } : {}),
31
+ });
32
+ }
33
+ }
34
+ process.stdout.write(`${JSON.stringify(result.envelope)}\n`);
35
+ process.exitCode = result.exitCode;
36
+ }
37
+ catch (error) {
38
+ const code = error instanceof RecordingError ? error.code : 'recordingFailed';
39
+ process.stdout.write(`${JSON.stringify({ schemaVersion: 'playcraft.video-cli.v1', command: 'playable record', state: 'failed', ok: false, error: { code, message: t(code) } })}\n`);
40
+ process.exitCode = error instanceof RecordingError ? error.exitCode : 4;
41
+ }
42
+ finally {
43
+ if (timer)
44
+ clearTimeout(timer);
45
+ process.removeListener('SIGINT', onInterrupt);
46
+ }
47
+ }
48
+ export function registerPlayableCommands(program) {
49
+ program.command('playable').description(t('topic')).command('record').description(t('record'))
50
+ .option('--url <url>', t('url')).option('--scenario <file>', t('scenario')).option('--out <directory>', t('out'))
51
+ .option('--browser-path <path>', t('browser')).option('--timeout <seconds>', t('timeout'))
52
+ .action(runPlayableRecord);
53
+ }
@@ -18,6 +18,8 @@
18
18
  * 3. <cwd>/node_modules/@playcraft/skills/playcraft-skills(兼容旧路径 skills)
19
19
  * 4. <CLI 自身>/../../skills/playcraft-skills(monorepo 开发时,兼容旧路径 skills)
20
20
  */
21
+ import { registerProjectSkillsCommands } from '../project-skills/commands.js';
22
+ import { registerRegistryCommands } from '../skill-registry/commands.js';
21
23
  import * as fs from 'node:fs';
22
24
  import * as path from 'node:path';
23
25
  import { getFusionHintsForGameplayAtoms, } from '../atom-tree/fusion-hints.js';
@@ -1430,6 +1432,8 @@ export function registerSkillsCommands(program) {
1430
1432
  const skills = program
1431
1433
  .command('skills')
1432
1434
  .description('开发时 Skill 发现与脚手架工具(按 engine/intent 推荐 skill 集合,或将 ref 文件复制到项目)');
1435
+ registerProjectSkillsCommands(skills);
1436
+ registerRegistryCommands(skills);
1433
1437
  // 公共选项:skills 目录
1434
1438
  const addSkillsDirOption = (cmd) => cmd.option('--skills-dir <path>', '指定 skills 目录路径(默认自动发现)');
1435
1439
  // ── list ──────────────────────────────────────────────────────────────────
@@ -2,6 +2,7 @@ import { writeFileSync, mkdirSync, readFileSync } from 'fs';
2
2
  import { dirname } from 'path';
3
3
  import { AgentApiClient } from '../utils/agent-api-client.js';
4
4
  import { mimeTypeForImagePath, handleError } from './tools-utils.js';
5
+ import { captureGeneratedArtifact } from '../workspace-runtime/workspaces/artifact-receipts.js';
5
6
  const AVATAR_TEMPLATES = [
6
7
  'basketball', 'badminton', 'pingpong', 'gymnastics', 'pilidance',
7
8
  'tennis', 'athletics', 'footballboykicking1', 'footballboykicking2',
@@ -148,6 +149,9 @@ export function register3DCommands(tools) {
148
149
  writeFileSync(opts.savePreview, previewBuf);
149
150
  console.log(` Preview saved: ${opts.savePreview}`);
150
151
  }
152
+ await captureGeneratedArtifact(opts.output, 'generate-3d', {
153
+ ...(opts.savePreview && result.previewImageBase64 ? { thumbnailPath: opts.savePreview } : {}),
154
+ });
151
155
  console.log(`\nNext steps:`);
152
156
  console.log(` Inspect: playcraft 3d info ${opts.output}`);
153
157
  console.log(` Apply texture: playcraft tools generate-texture --model-url <url> --reference-image <img> --output out.glb`);
@@ -209,6 +213,7 @@ export function register3DCommands(tools) {
209
213
  const outDir = dirname(opts.output);
210
214
  mkdirSync(outDir, { recursive: true });
211
215
  writeFileSync(opts.output, buf);
216
+ await captureGeneratedArtifact(opts.output, 'generate-texture');
212
217
  const sizeMB = (buf.length / 1024 / 1024).toFixed(2);
213
218
  console.log(`\nTextured model saved: ${opts.output} (${sizeMB}MB, ${elapsed}s)`);
214
219
  if (result.resultUrl)
@@ -253,6 +258,7 @@ export function register3DCommands(tools) {
253
258
  const outDir = dirname(opts.output);
254
259
  mkdirSync(outDir, { recursive: true });
255
260
  writeFileSync(opts.output, buf);
261
+ await captureGeneratedArtifact(opts.output, 'retopology');
256
262
  const sizeMB = (buf.length / 1024 / 1024).toFixed(2);
257
263
  console.log(`\nRetopologized model saved: ${opts.output} (${sizeMB}MB, ${elapsed}s)`);
258
264
  }
@@ -289,6 +295,7 @@ export function register3DCommands(tools) {
289
295
  const outDir = dirname(opts.output);
290
296
  mkdirSync(outDir, { recursive: true });
291
297
  writeFileSync(opts.output, buf);
298
+ await captureGeneratedArtifact(opts.output, 'uv-unwrap');
292
299
  const sizeMB = (buf.length / 1024 / 1024).toFixed(2);
293
300
  console.log(`\nUV-unwrapped model saved: ${opts.output} (${sizeMB}MB, ${elapsed}s)`);
294
301
  }
@@ -324,6 +331,7 @@ export function register3DCommands(tools) {
324
331
  const outDir = dirname(opts.output);
325
332
  mkdirSync(outDir, { recursive: true });
326
333
  writeFileSync(opts.output, buf);
334
+ await captureGeneratedArtifact(opts.output, 'convert-3d');
327
335
  const sizeMB = (buf.length / 1024 / 1024).toFixed(2);
328
336
  console.log(`\nConverted model saved: ${opts.output} (${sizeMB}MB, ${elapsed}s)`);
329
337
  console.log(` Format: ${result.format.toUpperCase()}`);
@@ -359,6 +367,7 @@ export function register3DCommands(tools) {
359
367
  const outDir = dirname(opts.output);
360
368
  mkdirSync(outDir, { recursive: true });
361
369
  writeFileSync(opts.output, buf);
370
+ await captureGeneratedArtifact(opts.output, 'generate-3d-parts');
362
371
  const sizeMB = (buf.length / 1024 / 1024).toFixed(2);
363
372
  console.log(`\n3D parts model saved: ${opts.output} (${sizeMB}MB, ${elapsed}s)`);
364
373
  }
@@ -402,6 +411,7 @@ export function register3DCommands(tools) {
402
411
  const outDir = dirname(opts.output);
403
412
  mkdirSync(outDir, { recursive: true });
404
413
  writeFileSync(opts.output, buf);
414
+ await captureGeneratedArtifact(opts.output, 'generate-motion');
405
415
  const sizeMB = (buf.length / 1024 / 1024).toFixed(2);
406
416
  console.log(`\nMotion FBX saved: ${opts.output} (${sizeMB}MB, ${elapsed}s)`);
407
417
  }
@@ -463,6 +473,7 @@ export function register3DCommands(tools) {
463
473
  const outDir = dirname(opts.output);
464
474
  mkdirSync(outDir, { recursive: true });
465
475
  writeFileSync(opts.output, buf);
476
+ await captureGeneratedArtifact(opts.output, 'generate-avatar');
466
477
  const sizeMB = (buf.length / 1024 / 1024).toFixed(2);
467
478
  console.log(`\n3D avatar saved: ${opts.output} (${sizeMB}MB, ${elapsed}s)`);
468
479
  }