@playcraft/cli 0.0.57 → 0.0.59

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 (225) hide show
  1. package/README.md +34 -16
  2. package/dist/cli-root-help.js +1 -0
  3. package/dist/commands/build-all.js +10 -9
  4. package/dist/commands/build.js +13 -12
  5. package/dist/commands/create.js +26 -33
  6. package/dist/commands/platform-skills.generated.js +19 -0
  7. package/dist/commands/skills.js +2 -0
  8. package/dist/commands/tools-generation.js +1 -1
  9. package/dist/commands/workspace-runtime.js +54 -0
  10. package/dist/index.js +3 -1
  11. package/dist/project-skills/commands.js +72 -0
  12. package/dist/project-skills/lifecycle.js +28 -0
  13. package/dist/project-skills/local-run.js +112 -0
  14. package/dist/project-skills/local-store.js +149 -0
  15. package/dist/project-skills/messages.js +72 -0
  16. package/dist/project-skills/reconcile.js +62 -0
  17. package/dist/project-skills/remote-cache.js +51 -0
  18. package/dist/project-skills/validation.js +19 -0
  19. package/dist/remix/clone.js +2 -0
  20. package/dist/remix/init-template.js +2 -0
  21. package/dist/remix/pull.js +2 -0
  22. package/dist/remix/push.js +21 -1
  23. package/dist/utils/agent-api-client.js +54 -17
  24. package/dist/utils/tool-operation-journal.js +54 -0
  25. package/dist/workspace-runtime/codex/app-server.js +395 -0
  26. package/dist/workspace-runtime/codex/config-toml.js +25 -0
  27. package/dist/workspace-runtime/codex/jsonrpc-stdio.js +106 -0
  28. package/dist/workspace-runtime/codex/loopback.js +59 -0
  29. package/dist/workspace-runtime/codex/native-adapter.js +3 -0
  30. package/dist/workspace-runtime/main.js +10 -0
  31. package/dist/workspace-runtime/persistence/journal.js +350 -0
  32. package/dist/workspace-runtime/processes/managed-writes.js +69 -0
  33. package/dist/workspace-runtime/serve.js +111 -0
  34. package/dist/workspace-runtime/server/auth.js +21 -0
  35. package/dist/workspace-runtime/server/dispatch.js +861 -0
  36. package/dist/workspace-runtime/server/execution-group.js +88 -0
  37. package/dist/workspace-runtime/server/http.js +561 -0
  38. package/dist/workspace-runtime/server/lock.js +53 -0
  39. package/dist/workspace-runtime/server/types.js +1 -0
  40. package/dist/workspace-runtime/workspaces/context-error.js +2 -0
  41. package/dist/workspace-runtime/workspaces/file-snapshot.js +213 -0
  42. package/dist/workspace-runtime/workspaces/files.js +143 -0
  43. package/dist/workspace-runtime/workspaces/git.js +242 -0
  44. package/dist/workspace-runtime/workspaces/json5-edit.js +170 -0
  45. package/dist/workspace-runtime/workspaces/parameters.js +272 -0
  46. package/dist/workspace-runtime/workspaces/prepare.js +364 -0
  47. package/dist/workspace-runtime/workspaces/registry.js +107 -0
  48. package/dist/workspace-runtime/workspaces/revisions.js +31 -0
  49. package/package.json +6 -3
  50. package/project-template-v2/.claude/agents/artist.md +82 -0
  51. package/project-template-v2/.claude/agents/developer.md +153 -0
  52. package/project-template-v2/.claude/agents/game-designer.md +264 -0
  53. package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +533 -0
  54. package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
  55. package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +27 -0
  56. package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
  57. package/project-template-v2/.claude/agents/refs/atom-skill-library.md +81 -0
  58. package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +432 -0
  59. package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +252 -0
  60. package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  61. package/project-template-v2/.claude/agents/refs/framework-mda.md +147 -0
  62. package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  63. package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
  64. package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
  65. package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
  66. package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +300 -0
  67. package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +495 -0
  68. package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
  69. package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +111 -0
  70. package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
  71. package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +412 -0
  72. package/project-template-v2/.claude/agents/reviewer.md +75 -0
  73. package/project-template-v2/.claude/agents/technical-artist.md +86 -0
  74. package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +243 -0
  75. package/project-template-v2/.claude/hooks/user-prompt.mjs +133 -0
  76. package/project-template-v2/.claude/settings.json +33 -0
  77. package/project-template-v2/.claude/skills/brainstorming/SKILL.md +161 -0
  78. package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  79. package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +177 -0
  80. package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  81. package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  82. package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  83. package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  84. package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  85. package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +309 -0
  86. package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
  87. package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
  88. package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
  89. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
  90. package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
  91. package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
  92. package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
  93. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
  94. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
  95. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
  96. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
  97. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
  98. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
  99. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
  100. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
  101. package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
  102. package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  103. package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +373 -0
  104. package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  105. package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +215 -0
  106. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +382 -0
  107. package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
  108. package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  109. package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  110. package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
  111. package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
  112. package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
  113. package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
  114. package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +390 -0
  115. package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
  116. package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
  117. package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
  118. package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +240 -0
  119. package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +226 -0
  120. package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
  121. package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +275 -0
  122. package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +134 -0
  123. package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +142 -0
  124. package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +153 -0
  125. package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +386 -0
  126. package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  127. package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  128. package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  129. package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +102 -0
  130. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  131. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  132. package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  133. package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  134. package/project-template-v2/.claude/snapshot.mjs +263 -0
  135. package/project-template-v2/.playcraft/skills.lock.json +152 -0
  136. package/project-template-v2/CLAUDE.md +146 -0
  137. package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
  138. package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
  139. package/project-template-v2/assets/bundles/.gitkeep +0 -0
  140. package/project-template-v2/assets/images/bg/.gitkeep +0 -0
  141. package/project-template-v2/assets/images/reference/.gitkeep +0 -0
  142. package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
  143. package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
  144. package/project-template-v2/assets/images/ui/.gitkeep +0 -0
  145. package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
  146. package/project-template-v2/assets/models/.gitkeep +0 -0
  147. package/project-template-v2/docs/harness/iteration-1/context-flow.md +254 -0
  148. package/project-template-v2/docs/harness/iteration-1/generate-flow.md +91 -0
  149. package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +214 -0
  150. package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +75 -0
  151. package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +63 -0
  152. package/project-template-v2/docs/harness/iteration-2/context-flow.md +223 -0
  153. package/project-template-v2/docs/harness/iteration-2/generate-flow.md +129 -0
  154. package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +267 -0
  155. package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +164 -0
  156. package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +115 -0
  157. package/project-template-v2/docs/harness/orchestrator-flow.md +364 -0
  158. package/project-template-v2/docs/project-state.json +60 -0
  159. package/project-template-v2/docs/project-state.md +72 -0
  160. package/project-template-v2/docs/standards/README.md +225 -0
  161. package/project-template-v2/docs/standards/agent-behavior-standards.md +174 -0
  162. package/project-template-v2/docs/standards/artifacts/design-brief.md +19 -0
  163. package/project-template-v2/docs/standards/artifacts/design.md +22 -0
  164. package/project-template-v2/docs/standards/artifacts/game-code.md +41 -0
  165. package/project-template-v2/docs/standards/artifacts/todo-list.md +41 -0
  166. package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +343 -0
  167. package/project-template-v2/game/index.ts +18 -0
  168. package/project-template-v2/globals.d.ts +51 -0
  169. package/project-template-v2/index.css +34 -0
  170. package/project-template-v2/index.html +18 -0
  171. package/project-template-v2/main.ts +9 -0
  172. package/project-template-v2/package.json +46 -0
  173. package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +31 -0
  174. package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +86 -0
  175. package/project-template-v2/skills/_shared/scripts/dod-check.mjs +153 -0
  176. package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +70 -0
  177. package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +91 -0
  178. package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +131 -0
  179. package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +80 -0
  180. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +90 -0
  181. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +93 -0
  182. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +23 -0
  183. package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +230 -0
  184. package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +58 -0
  185. package/project-template-v2/skills/_shared/scripts/state-get.mjs +339 -0
  186. package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +39 -0
  187. package/project-template-v2/skills/_shared/scripts/state-set.mjs +94 -0
  188. package/project-template-v2/skills/_shared/scripts/state-store.mjs +783 -0
  189. package/project-template-v2/skills/_shared/scripts/todo-add.mjs +88 -0
  190. package/project-template-v2/skills/_shared/scripts/todo-get.mjs +130 -0
  191. package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +33 -0
  192. package/project-template-v2/skills/_shared/scripts/todo-set.mjs +47 -0
  193. package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +268 -0
  194. package/project-template-v2/skills/_shared/scripts/verify-env.mjs +285 -0
  195. package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  196. package/project-template-v2/skills/playable-autoplay/SKILL.md +176 -0
  197. package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +4 -0
  198. package/project-template-v2/skills/playable-debug/SKILL.md +116 -0
  199. package/project-template-v2/skills/playable-debug/agents/openai.yaml +4 -0
  200. package/project-template-v2/skills/playable-debug/references/debug-config.md +40 -0
  201. package/project-template-v2/skills/playable-record/SKILL.md +140 -0
  202. package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  203. package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  204. package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +425 -0
  205. package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +261 -0
  206. package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  207. package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +322 -0
  208. package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +105 -0
  209. package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +230 -0
  210. package/project-template-v2/skills/playable-validate/SKILL.md +61 -0
  211. package/project-template-v2/skills/playable-validate/validation-rules.md +14 -0
  212. package/project-template-v2/skills/playable-verify-ui/SKILL.md +63 -0
  213. package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  214. package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  215. package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  216. package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  217. package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  218. package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  219. package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  220. package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  221. package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  222. package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  223. package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
  224. package/project-template-v2/tsconfig.json +20 -0
  225. package/project-template-v2/vite.config.ts +27 -0
package/README.md CHANGED
@@ -5,7 +5,13 @@
5
5
  [![npm version](https://img.shields.io/npm/v/@playcraft/cli.svg)](https://www.npmjs.com/package/@playcraft/cli)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@playcraft/cli.svg)](https://www.npmjs.com/package/@playcraft/cli)
7
7
 
8
- PlayCraft CLI 是一个功能强大的命令行工具,支持本地代码与云端编辑器实时同步,以及将 PlayCanvas/PlayCraft 项目打包为多平台 Playable Ads。
8
+ PlayCraft CLI(Command-Line Interface,命令行工具)支持本地代码与云端编辑器实时同步,以及将 PlayCanvas/PlayCraft 项目打包为多平台 Playable Ads。
9
+
10
+ ## 云端工作台 Runtime
11
+
12
+ `playcraft workspace-runtime serve --config <json>` 已注册。它前台常驻、跳过升级检查,读取 Runtime Server 下发的引导配置,只绑定回环并校验 control token。`runtime.ensure` 会执行到终态;设置 `PLAYCRAFT_NATIVE_ADAPTER=loopback` 或测试注入 loopback 适配器后,可跑会话 create/turn/interrupt/审批,以及本地 journal 检查点/恢复/销毁。工作空间相对路径的 `file.write` 与文件检查点不依赖 loopback。相同幂等键上的 `uncertain` 操作不会新开 turn。这不是 Codex App Server。现有 `playcraft start` 仍用于单项目本地开发,二者不要混用。
13
+
14
+ 实现与模块边界见 [Sandbox Agent Server](./src/workspace-runtime/README.md);契约见 [云端工作台后端架构方案](../../docs/architecture/backend/cloud-workspace/backend-architecture.md#cli-runtime)。
9
15
 
10
16
  ## ✨ 特性
11
17
 
@@ -75,7 +81,7 @@ npx @playcraft/cli start --project <project-id> --daemon
75
81
  npx @playcraft/cli build
76
82
 
77
83
  # 命令行打包
78
- npx @playcraft/cli build --platform facebook --format html --output ./dist
84
+ npx @playcraft/cli build --platform facebook --format zip --output ./dist
79
85
  ```
80
86
 
81
87
  ### 5. 创建并继续编辑 Remix
@@ -182,6 +188,18 @@ echo '{"token":"<sandbox-jwt>","apiUrl":"https://..."}' > .playcraft.json
182
188
  | `recommend improvements` | Tag 改进建议 | `playcraft recommend improvements --channel Facebook` |
183
189
  | `recommend random` | 随机 Tag 组合推荐 | `playcraft recommend random --channel Google` |
184
190
 
191
+ ### 业务工具请求恢复
192
+
193
+ 业务工具写请求要求服务支持持久操作协议。命令行先保存请求编号、路径和输入摘要到 `~/.playcraft/tool-requests/`,再发送请求;记录不包含凭证或请求正文。可用绝对路径环境变量 `PLAYCRAFT_TOOL_REQUEST_DIR` 指定可持久保留的目录。
194
+
195
+ 如果请求超时或连接中断,错误会显示请求编号。使用相同参数重跑命令,并设置该编号,只查询原操作的确定结果:
196
+
197
+ ```bash
198
+ PLAYCRAFT_TOOL_REQUEST_ID=<错误中显示的请求编号> playcraft tools build-project --project-id <原项目编号>
199
+ ```
200
+
201
+ 恢复不会重新构建。操作尚未完成、结果未知或没有回执时会明确失败;不要换编号或删除记录来强行重试。只有错误明确说明能力检查失败且尚未发送操作时,才可在服务恢复后发起新的正常命令。服务不兼容时命令会在发送业务请求前停止;沙箱命令行与 Business 须排空旧调用后同步升级。
202
+
185
203
  ### 构建相关
186
204
 
187
205
  | 命令 | 说明 | 示例 |
@@ -195,18 +213,18 @@ echo '{"token":"<sandbox-jwt>","apiUrl":"https://..."}' > .playcraft.json
195
213
 
196
214
  现已支持 **10 个主流广告平台**:
197
215
 
198
- | 平台 | 格式 | 大小限制 | SDK/API |
199
- | ----------------- | -------- | ---------------------------------- | ------------ |
200
- | **Facebook** | HTML/ZIP | HTML: 5MB / ZIP: 5MB(HTML < 2MB) | FbPlayableAd |
201
- | **Snapchat** | ZIP | 5MB | MRAID 2.0 |
202
- | **ironSource** | HTML | 5MB | MRAID + dapi |
203
- | **AppLovin** | HTML | 5MB | MRAID 2.0 |
204
- | **Google Ads** | ZIP | 5MB | ExitApi |
205
- | **TikTok/Pangle** | ZIP | 5MB | Pangle SDK |
206
- | **Unity Ads** | HTML | 5MB | MRAID 2.0 |
207
- | **Liftoff** | ZIP | 5MB | MRAID |
208
- | **Moloco** | HTML | 5MB | FbPlayableAd |
209
- | **BIGO Ads** | ZIP | 5MB | BIGO SDK |
216
+ | 平台 | 格式 | 大小限制 | SDK/API |
217
+ | ----------------- | -------- | -------------------- | ------------ |
218
+ | **Facebook** | HTML/ZIP | HTML: 5MB / ZIP: 5MB | FbPlayableAd |
219
+ | **Snapchat** | ZIP | 5MB | MRAID 2.0 |
220
+ | **ironSource** | HTML | 5MB | MRAID + dapi |
221
+ | **AppLovin** | HTML | 5MB | MRAID 2.0 |
222
+ | **Google Ads** | ZIP | 5MB | ExitApi |
223
+ | **TikTok/Pangle** | ZIP | 5MB | Pangle SDK |
224
+ | **Unity Ads** | HTML | 5MB | MRAID 2.0 |
225
+ | **Liftoff** | ZIP | 5MB | MRAID |
226
+ | **Moloco** | HTML | 5MB | FbPlayableAd |
227
+ | **BIGO Ads** | ZIP | 5MB | BIGO SDK |
210
228
 
211
229
  ## 💡 使用示例
212
230
 
@@ -225,8 +243,8 @@ playcraft start --project your-project-id
225
243
  ### 打包 Playable Ads
226
244
 
227
245
  ```bash
228
- # Facebook 平台(单 HTML)
229
- playcraft build --platform facebook --format html --output ./dist/facebook
246
+ # Facebook 平台(ZIP,Phaser 产物格式)
247
+ playcraft build --platform facebook --format zip --output ./dist/facebook
230
248
 
231
249
  # Snapchat 平台(ZIP)
232
250
  playcraft build --platform snapchat --format zip --output ./dist/snapchat
@@ -7,6 +7,7 @@ export function getCliTopicsHelpText() {
7
7
  ' 素材 tools generate-* | image <子命令> | audio <子命令> | prefab <子命令>',
8
8
  ' 平台 build, build-base, build-playable, build-all, analyze, inspect, upgrade',
9
9
  ' tools 除 generate-* 外(Git / 沙箱构建 / 发布 / 列表 / Prefab 等,需后端 API)',
10
+ ' 沙箱常驻 workspace-runtime serve(Cloud Workspace,由 Runtime 引导配置启动)',
10
11
  '',
11
12
  '也可运行: playcraft topics',
12
13
  '',
@@ -33,12 +33,12 @@ const PLATFORM_GROUPS = {
33
33
  platforms: [...ALL_PLATFORMS],
34
34
  },
35
35
  html: {
36
- name: '仅 HTML 渠道 (applovin, ironsource, unity, moloco, remerge, adikteev)',
37
- platforms: ['applovin', 'ironsource', 'unity', 'moloco', 'remerge', 'adikteev'],
36
+ name: '仅 HTML 渠道 (applovin, ironsource, unity, moloco, adikteev)',
37
+ platforms: ['applovin', 'ironsource', 'unity', 'moloco', 'adikteev'],
38
38
  },
39
39
  zip: {
40
- name: '仅 ZIP 渠道 (google, tiktok, liftoff, bigo, snapchat, inmobi, mintegral)',
41
- platforms: ['google', 'tiktok', 'liftoff', 'bigo', 'snapchat', 'inmobi', 'mintegral'],
40
+ name: '仅 ZIP 渠道 (google, tiktok, liftoff, bigo, snapchat, inmobi, mintegral, remerge)',
41
+ platforms: ['google', 'tiktok', 'liftoff', 'bigo', 'snapchat', 'inmobi', 'mintegral', 'remerge'],
42
42
  },
43
43
  mainstream: {
44
44
  name: '主流渠道 (facebook, google, tiktok, applovin, ironsource, unity)',
@@ -55,7 +55,7 @@ const platformFormatConfig = {
55
55
  unity: { formats: ['html'], default: 'html' },
56
56
  moloco: { formats: ['html'], default: 'html' },
57
57
  adikteev: { formats: ['html', 'zip'], default: 'html', description: 'PlayCanvas/PlayCraft uses HTML, others use ZIP' },
58
- remerge: { formats: ['html'], default: 'html' },
58
+ remerge: { formats: ['zip'], default: 'zip' },
59
59
  google: { formats: ['html', 'zip'], default: 'html' },
60
60
  tiktok: { formats: ['zip'], default: 'zip' },
61
61
  liftoff: { formats: ['zip'], default: 'zip' },
@@ -164,10 +164,11 @@ export async function buildAllCommand(projectPath, options) {
164
164
  name: 'selection',
165
165
  message: '选择要批量构建的渠道:',
166
166
  choices: [
167
- { name: '🌐 全部渠道(13个)', value: 'all' },
168
- { name: '⭐ 主流渠道(facebook, google, tiktok, applovin, ironsource, unity)', value: 'mainstream' },
169
- { name: '📄 仅 HTML 渠道(applovin, ironsource, unity, moloco, remerge)', value: 'html' },
170
- { name: '📦 仅 ZIP 渠道(tiktok, liftoff, bigo, snapchat, inmobi)', value: 'zip' },
167
+ { name: `🌐 全部渠道(${ALL_PLATFORMS.length}个)`, value: 'all' },
168
+ // 分组文案直接取自 PLATFORM_GROUPS,避免两处渠道清单各写一份导致漂移
169
+ { name: `⭐ ${PLATFORM_GROUPS.mainstream.name}`, value: 'mainstream' },
170
+ { name: `📄 ${PLATFORM_GROUPS.html.name}`, value: 'html' },
171
+ { name: `📦 ${PLATFORM_GROUPS.zip.name}`, value: 'zip' },
171
172
  { name: '🔧 自定义选择', value: 'custom' },
172
173
  ],
173
174
  },
@@ -466,21 +466,20 @@ export async function buildCommand(projectPath, options) {
466
466
  'mintegral',
467
467
  ];
468
468
  // 渠道输出格式配置(根据各渠道Playable规格对照表)
469
- // 只支持 HTML: applovin, ironsource, unity, moloco, adikteev, remerge
470
- // 只支持 ZIP: bigo, snapchat
471
- // 支持两种: facebook, google, tiktok, liftoff, inmobi, mintegral
469
+ // 单格式(ZIP): bigo, snapchat, remerge
470
+ // 单格式(HTML): applovin, ironsource, unity, moloco
471
+ // 双格式: facebook, google, tiktok, liftoff, inmobi, mintegral
472
+ // adikteev: 由引擎决定(PlayCanvas/PlayCraft 用 HTML,其他引擎用 ZIP)
473
+ // 注:Facebook 在 Phaser(playable-scripts devkit)下的产物格式由其 devkit 输入配置决定(恒为 ZIP),
474
+ // 平台侧不在此处做引擎判断。
472
475
  const platformFormatConfig = {
473
- facebook: {
474
- formats: ['html', 'zip'],
475
- default: 'html',
476
- description: 'Facebook 支持两种格式'
477
- },
476
+ facebook: { formats: ['html', 'zip'], default: 'html' },
478
477
  applovin: { formats: ['html'], default: 'html' },
479
478
  ironsource: { formats: ['html'], default: 'html' },
480
479
  unity: { formats: ['html'], default: 'html' },
481
480
  moloco: { formats: ['html'], default: 'html' },
482
481
  adikteev: { formats: ['html', 'zip'], default: 'html', description: 'PlayCanvas/PlayCraft uses HTML, others use ZIP' },
483
- remerge: { formats: ['html'], default: 'html' },
482
+ remerge: { formats: ['zip'], default: 'zip' },
484
483
  google: {
485
484
  formats: ['html', 'zip'],
486
485
  default: 'html',
@@ -532,9 +531,11 @@ export async function buildCommand(projectPath, options) {
532
531
  ? (selectedPlatform === 'facebook' || selectedPlatform === 'google'
533
532
  ? 'HTML(单文件,最大 5MB,所有资源内联)'
534
533
  : 'HTML(单文件)')
535
- : (selectedPlatform === 'facebook' || selectedPlatform === 'google'
536
- ? 'ZIP(多文件,最大 5MB,HTML 文件需 < 2MB)'
537
- : 'ZIP(多文件)'),
534
+ : (selectedPlatform === 'google'
535
+ ? 'ZIP(多文件,最大 5MB,ZIP 内文件数 ≤ 512)'
536
+ : selectedPlatform === 'facebook'
537
+ ? 'ZIP(多文件,最大 5MB)'
538
+ : 'ZIP(多文件)'),
538
539
  value: f,
539
540
  })),
540
541
  default: formatConfig.default,
@@ -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) {
@@ -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
+ ];
@@ -18,6 +18,7 @@
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';
21
22
  import * as fs from 'node:fs';
22
23
  import * as path from 'node:path';
23
24
  import { getFusionHintsForGameplayAtoms, } from '../atom-tree/fusion-hints.js';
@@ -1430,6 +1431,7 @@ export function registerSkillsCommands(program) {
1430
1431
  const skills = program
1431
1432
  .command('skills')
1432
1433
  .description('开发时 Skill 发现与脚手架工具(按 engine/intent 推荐 skill 集合,或将 ref 文件复制到项目)');
1434
+ registerProjectSkillsCommands(skills);
1433
1435
  // 公共选项:skills 目录
1434
1436
  const addSkillsDirOption = (cmd) => cmd.option('--skills-dir <path>', '指定 skills 目录路径(默认自动发现)');
1435
1437
  // ── list ──────────────────────────────────────────────────────────────────
@@ -125,7 +125,7 @@ export function registerGenerationCommands(tools) {
125
125
  }
126
126
  console.log('');
127
127
  console.log('提示:传入模型名(如 gpt-image-2),后端按优先级依次 fallback 各 provider(gpt-image-2:iegg-litellm → mulerouter → 302)。');
128
- console.log(' 也可传入 provider/model(如 mulerouter/gpt-image-2)直连指定 provider。');
128
+ console.log(' 也可传入 provider/model(如 mulerouter/gpt-image-2、gamepilot/dreamina/dola-seedream-5-0-pro-260628)直连指定 provider。');
129
129
  console.log('');
130
130
  console.log('CAPABILITY 说明:');
131
131
  console.log(' text→image 仅支持文生图(不可传 --reference-image)');
@@ -0,0 +1,54 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { INCOMPATIBLE_EXIT_CODE, parseSandboxRuntimeConfig, } from '@playcraft/common/cloud-workspace/runtime';
3
+ import { startWorkspaceRuntime } from '../workspace-runtime/serve.js';
4
+ export async function runWorkspaceRuntimeServe(configPath) {
5
+ process.env.PLAYCRAFT_SKIP_UPDATE_CHECK = '1';
6
+ let config;
7
+ try {
8
+ config = parseSandboxRuntimeConfig(JSON.parse(await readFile(configPath, 'utf8')));
9
+ }
10
+ catch (error) {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ console.error(`workspace-runtime config invalid: ${message}`);
13
+ process.exitCode = INCOMPATIBLE_EXIT_CODE;
14
+ return;
15
+ }
16
+ try {
17
+ const runtime = await startWorkspaceRuntime(config);
18
+ console.log(JSON.stringify({
19
+ status: 'listening',
20
+ url: runtime.url,
21
+ phase: 'awaiting_native',
22
+ protocolMajor: runtime.config.protocolMajor,
23
+ protocolMinor: runtime.config.protocolMinor,
24
+ }));
25
+ await new Promise((resolve) => {
26
+ const stop = () => {
27
+ void runtime.close().finally(resolve);
28
+ };
29
+ process.once('SIGINT', stop);
30
+ process.once('SIGTERM', stop);
31
+ });
32
+ }
33
+ catch (error) {
34
+ const code = error.code;
35
+ if (code === 'protocol_incompatible' || code === 'duplicate_instance') {
36
+ console.error(error instanceof Error ? error.message : code);
37
+ process.exitCode = INCOMPATIBLE_EXIT_CODE;
38
+ return;
39
+ }
40
+ throw error;
41
+ }
42
+ }
43
+ export function registerWorkspaceRuntimeCommands(program) {
44
+ const workspaceRuntime = program
45
+ .command('workspace-runtime')
46
+ .description('沙箱内 Sandbox Agent Server(Cloud Workspace 常驻运行入口)');
47
+ workspaceRuntime
48
+ .command('serve')
49
+ .description('前台常驻:读取 Runtime Server 下发的配置,不启动本地 backend 或旧同步')
50
+ .requiredOption('--config <path>', 'Runtime 引导配置 JSON')
51
+ .action(async (options) => {
52
+ await runWorkspaceRuntimeServe(options.config);
53
+ });
54
+ }
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ import { registerLevelsCommands } from './commands/levels.js';
27
27
  import { registerPatternsCommands } from './commands/patterns.js';
28
28
  import { registerPradCommands } from './commands/prad.js';
29
29
  import { register3DCommands } from './commands/3d.js';
30
+ import { registerWorkspaceRuntimeCommands } from './commands/workspace-runtime.js';
30
31
  import { CLI_ROOT_DESCRIPTION, getCliTopicsHelpText, registerRootProgramHelp, } from './cli-root-help.js';
31
32
  const __filename = fileURLToPath(import.meta.url);
32
33
  const __dirname = dirname(__filename);
@@ -36,7 +37,7 @@ program.name('playcraft').description(CLI_ROOT_DESCRIPTION).version(packageJson.
36
37
  registerRootProgramHelp(program);
37
38
  // 版本检查(非阻塞,后台执行)
38
39
  // 只在非 upgrade 命令时检查,避免重复提示
39
- if (!process.argv.includes('upgrade')) {
40
+ if (!process.argv.includes('upgrade') && !process.argv.includes('workspace-runtime')) {
40
41
  checkForUpdates(packageJson);
41
42
  }
42
43
  // create 命令
@@ -368,4 +369,5 @@ registerPatternsCommands(program);
368
369
  registerPradCommands(program);
369
370
  // 3D model utilities (playcraft 3d <command>)
370
371
  register3DCommands(program);
372
+ registerWorkspaceRuntimeCommands(program);
371
373
  program.parse(process.argv);
@@ -0,0 +1,72 @@
1
+ import { verifyLocalProjectSkills } from './validation.js';
2
+ export { verifyLocalProjectSkills } from './validation.js';
3
+ import { downloadLocalProjectSkillArtifacts } from './remote-cache.js';
4
+ import { resolve } from 'node:path';
5
+ import { inspectProjectSkills, } from '@playcraft/common/project-skills';
6
+ import { syncLocalProjectSkills, enableLocalProjectSkill, installFrozenProjectSkills, explainLocalProjectSkills } from './local-store.js';
7
+ import { runLocalProjectSkills, localProjectSkillUsage } from './local-run.js';
8
+ import { projectSkillsMessage as t } from './messages.js';
9
+ import { reconcileLocalProjectSkills } from './reconcile.js';
10
+ export async function inspectLocalProjectSkills(directory) {
11
+ const inventory = await inspectProjectSkills(directory);
12
+ // Report metadata only; never print complete skill bytes or project test data.
13
+ return { declaration: inventory.declaration, discovered: inventory.discovered, issues: inventory.issues,
14
+ sources: inventory.sources.map((source) => ({ entryKey: source.entryKey, root: source.root,
15
+ sourceTreeHash: source.sourceTreeHash, files: source.files.map((file) => file.path) })) };
16
+ }
17
+ export function registerProjectSkillsCommands(skills) {
18
+ const project = skills.command('project').description(t('description'));
19
+ const output = async (action) => {
20
+ try {
21
+ process.stdout.write(`${JSON.stringify(await action(), null, 2)}\n`);
22
+ }
23
+ catch (error) {
24
+ process.stderr.write(`${t('failed')}: ${error instanceof Error ? error.message : String(error)}\n`);
25
+ process.exitCode = 1;
26
+ }
27
+ };
28
+ project.command('run <prompt>').description(t('run')).requiredOption('--model <provider/model>', t('model'))
29
+ .option('--dir <path>', t('directory'), '.').action((prompt, options) => output(async () => {
30
+ const result = await runLocalProjectSkills(resolve(options.dir), { prompt, model: options.model });
31
+ if (result.exitCode !== 0)
32
+ process.exitCode = result.exitCode;
33
+ return result;
34
+ }));
35
+ project.command('usage').description(t('usage')).option('--dir <path>', t('directory'), '.')
36
+ .action((options) => output(() => localProjectSkillUsage(resolve(options.dir))));
37
+ project.command('sync').description(t('sync')).option('--dir <path>', t('directory'), '.')
38
+ .action((options) => output(() => syncLocalProjectSkills(resolve(options.dir))));
39
+ project.command('reconcile').description(t('reconcile')).option('--dir <path>', t('directory'), '.')
40
+ .action((options) => output(() => reconcileLocalProjectSkills(resolve(options.dir), { write: true })));
41
+ project.command('install').description(t('install')).requiredOption('--frozen', t('frozen')).option('--dir <path>', t('directory'), '.')
42
+ .option('--online', t('online')).action((options) => output(async () => {
43
+ const directory = resolve(options.dir);
44
+ if (options.online)
45
+ await downloadLocalProjectSkillArtifacts(directory);
46
+ return installFrozenProjectSkills(directory);
47
+ }));
48
+ for (const enabled of [true, false])
49
+ project.command(enabled ? 'enable <entryKey>' : 'disable <entryKey>')
50
+ .description(t(enabled ? 'enable' : 'disable')).option('--dir <path>', t('directory'), '.')
51
+ .action((entryKey, options) => output(() => enableLocalProjectSkill(resolve(options.dir), entryKey, enabled)));
52
+ project.command('explain').description(t('explain')).option('--dir <path>', t('directory'), '.')
53
+ .action((options) => output(() => explainLocalProjectSkills(resolve(options.dir))));
54
+ project.command('inspect').description(t('inspect')).option('--dir <path>', t('directory'), '.')
55
+ .action(async (options) => {
56
+ const result = await inspectLocalProjectSkills(resolve(options.dir));
57
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
58
+ if (result.issues.length)
59
+ process.exitCode = 1;
60
+ });
61
+ project.command('verify').description(t('verify')).option('--dir <path>', t('directory'), '.')
62
+ .option('--json', t('json')).action(async (options) => {
63
+ try {
64
+ const result = await verifyLocalProjectSkills(resolve(options.dir));
65
+ process.stdout.write(`${options.json ? JSON.stringify(result) : t('verified')}\n`);
66
+ }
67
+ catch (error) {
68
+ process.stderr.write(`${t('failed')}: ${error instanceof Error ? error.message : String(error)}\n`);
69
+ process.exitCode = 1;
70
+ }
71
+ });
72
+ }
@@ -0,0 +1,28 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readProjectSkillsLockFile } from '@playcraft/common/project-skills';
3
+ import { syncLocalProjectSkills, readLocalProjectSkillSnapshot, installFrozenProjectSkills } from './local-store.js';
4
+ import { verifyLocalProjectSkills } from './validation.js';
5
+ import { projectSkillsMessage as t } from './messages.js';
6
+ /** Existing formats keep their legacy consumer; format 3 never silently downgrades. */
7
+ export async function prepareLocalSkillLifecycle(directory, action, cache) {
8
+ const raw = await readProjectSkillsLockFile(directory);
9
+ const managed = raw !== null && JSON.parse(raw).lockVersion === 3;
10
+ if (!managed && process.env.PROJECT_SKILLS_ADOPTION_ENABLED !== 'true')
11
+ return { managed: false };
12
+ if (raw !== null && !managed)
13
+ return { managed: false, legacy: true };
14
+ if (action === 'create' || action === 'push' || !managed)
15
+ await syncLocalProjectSkills(directory, cache);
16
+ await verifyLocalProjectSkills(directory);
17
+ const snapshot = await readLocalProjectSkillSnapshot(directory);
18
+ if (action === 'push') {
19
+ const paths = ['.playcraft/skills.json', '.playcraft/skills.lock.json', ...snapshot.declaration.entries.flatMap(entry => entry.source.kind === 'repository' ? [entry.source.root, ...(entry.projectFiles ?? [])] : [])];
20
+ const status = spawnSync('git', ['status', '--porcelain', '--', ...paths], { cwd: directory, encoding: 'utf8' });
21
+ if (status.status !== 0 || status.stdout.trim())
22
+ throw new Error(t('commitRequired'));
23
+ }
24
+ // Source-only checkouts work offline. Registry packages remain exact and require an authorized cache fill.
25
+ if (!snapshot.lock.entries.some(entry => entry.revision.kind === 'release'))
26
+ await installFrozenProjectSkills(directory, cache);
27
+ return { managed: true, declarationHash: snapshot.lock.declarationHash };
28
+ }