@playcraft/cli 0.1.37-beta → 0.1.39-prod

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 +3 -58
  2. package/dist/agent/local-backend.js +1 -1
  3. package/dist/cli-root-help.js +0 -2
  4. package/dist/commands/build-all.js +19 -5
  5. package/dist/commands/build.js +28 -15
  6. package/dist/commands/create.js +33 -26
  7. package/dist/commands/image.js +6 -2
  8. package/dist/commands/login.js +42 -84
  9. package/dist/commands/skills.js +0 -4
  10. package/dist/commands/tools-3d.js +0 -11
  11. package/dist/commands/tools-generation.js +126 -68
  12. package/dist/commands/tools-image-context.js +23 -0
  13. package/dist/commands/tools.js +2 -2
  14. package/dist/config.js +4 -59
  15. package/dist/index.js +3 -40
  16. package/dist/remix/clone.js +0 -2
  17. package/dist/remix/init-template.js +0 -2
  18. package/dist/remix/pull.js +0 -2
  19. package/dist/remix/push.js +1 -21
  20. package/dist/utils/agent-api-client.js +30 -185
  21. package/dist/utils/image-asset-context.js +106 -0
  22. package/dist/utils/playcraft-http-client.js +4 -6
  23. package/package.json +5 -15
  24. package/project-template/.claude/agents/refs/ta-image-generation-detail.md +67 -123
  25. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +135 -63
  26. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  27. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  28. package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  29. package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
  30. package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  31. package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +135 -63
  32. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  33. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  34. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  35. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
  36. package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  37. package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +135 -63
  38. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  39. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  40. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  41. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
  42. package/SKILLS_REGISTRY.md +0 -149
  43. package/dist/auth/account-activation.js +0 -157
  44. package/dist/auth/messages.js +0 -30
  45. package/dist/commands/build-temp.js +0 -26
  46. package/dist/commands/platform-skills.generated.js +0 -19
  47. package/dist/commands/playable.js +0 -53
  48. package/dist/commands/tools-batch.js +0 -87
  49. package/dist/commands/video.js +0 -111
  50. package/dist/commands/workspace-runtime.js +0 -54
  51. package/dist/commands/workspace.js +0 -35
  52. package/dist/project-skills/builtin-import.js +0 -225
  53. package/dist/project-skills/commands.js +0 -77
  54. package/dist/project-skills/lifecycle.js +0 -28
  55. package/dist/project-skills/local-run.js +0 -112
  56. package/dist/project-skills/local-store.js +0 -221
  57. package/dist/project-skills/messages.js +0 -80
  58. package/dist/project-skills/reconcile.js +0 -62
  59. package/dist/project-skills/remote-cache.js +0 -51
  60. package/dist/project-skills/validation.js +0 -19
  61. package/dist/recording/messages.js +0 -40
  62. package/dist/recording/project-digest.js +0 -85
  63. package/dist/recording/recorder.js +0 -293
  64. package/dist/recording/scenario.js +0 -50
  65. package/dist/skill-registry/client.js +0 -345
  66. package/dist/skill-registry/commands.js +0 -180
  67. package/dist/skill-registry/evaluation-client.js +0 -156
  68. package/dist/skill-registry/evaluation-commands.js +0 -174
  69. package/dist/skill-registry/messages.js +0 -177
  70. package/dist/skill-registry/package.js +0 -275
  71. package/dist/skill-registry/platform-package.js +0 -100
  72. package/dist/utils/environment-endpoints.js +0 -17
  73. package/dist/utils/tool-operation-journal.js +0 -124
  74. package/dist/utils/video-evidence-git.js +0 -81
  75. package/dist/video-replication/client.js +0 -222
  76. package/dist/video-replication/comparison-evidence.js +0 -149
  77. package/dist/video-replication/comparison-files.js +0 -57
  78. package/dist/video-replication/comparison-log.js +0 -72
  79. package/dist/video-replication/comparison-messages.js +0 -104
  80. package/dist/video-replication/comparison-output.js +0 -63
  81. package/dist/video-replication/comparison-preflight.js +0 -66
  82. package/dist/video-replication/comparison.js +0 -216
  83. package/dist/video-replication/evidence.js +0 -165
  84. package/dist/video-replication/media.js +0 -111
  85. package/dist/video-replication/messages.js +0 -89
  86. package/dist/workspace/messages.js +0 -35
  87. package/dist/workspace/transfer-client.js +0 -174
  88. package/dist/workspace-runtime/codex/app-server.js +0 -1040
  89. package/dist/workspace-runtime/codex/compact.js +0 -46
  90. package/dist/workspace-runtime/codex/config-toml.js +0 -107
  91. package/dist/workspace-runtime/codex/jsonrpc-stdio.js +0 -145
  92. package/dist/workspace-runtime/codex/launcher-client.js +0 -177
  93. package/dist/workspace-runtime/codex/loopback.js +0 -146
  94. package/dist/workspace-runtime/codex/mcp-config.js +0 -39
  95. package/dist/workspace-runtime/codex/model-broker.js +0 -263
  96. package/dist/workspace-runtime/codex/native-adapter.js +0 -9
  97. package/dist/workspace-runtime/codex/permissions.js +0 -70
  98. package/dist/workspace-runtime/codex/thread-image.js +0 -38
  99. package/dist/workspace-runtime/codex/ui-instructions.js +0 -4
  100. package/dist/workspace-runtime/codex/ui-tools.js +0 -183
  101. package/dist/workspace-runtime/codex/work-mode.js +0 -15
  102. package/dist/workspace-runtime/main.js +0 -10
  103. package/dist/workspace-runtime/persistence/journal.js +0 -358
  104. package/dist/workspace-runtime/persistence/native-subagents.js +0 -162
  105. package/dist/workspace-runtime/persistence/sqlite-client.js +0 -77
  106. package/dist/workspace-runtime/persistence/sqlite-worker.js +0 -1161
  107. package/dist/workspace-runtime/persistence/stopped-execution.js +0 -77
  108. package/dist/workspace-runtime/previews/output.js +0 -36
  109. package/dist/workspace-runtime/previews/publish.js +0 -262
  110. package/dist/workspace-runtime/previews/single-file.js +0 -43
  111. package/dist/workspace-runtime/processes/managed-writes.js +0 -69
  112. package/dist/workspace-runtime/production/contracts.js +0 -19
  113. package/dist/workspace-runtime/production/controller.js +0 -218
  114. package/dist/workspace-runtime/production/dependencies.js +0 -60
  115. package/dist/workspace-runtime/production/native.js +0 -212
  116. package/dist/workspace-runtime/production/processes.js +0 -102
  117. package/dist/workspace-runtime/production/save-command.js +0 -35
  118. package/dist/workspace-runtime/production/source-archive.js +0 -137
  119. package/dist/workspace-runtime/production/source-copy.js +0 -91
  120. package/dist/workspace-runtime/production/stages.js +0 -303
  121. package/dist/workspace-runtime/production/storage.js +0 -60
  122. package/dist/workspace-runtime/serve.js +0 -214
  123. package/dist/workspace-runtime/server/auth.js +0 -21
  124. package/dist/workspace-runtime/server/connectivity.js +0 -201
  125. package/dist/workspace-runtime/server/desktop-host.js +0 -429
  126. package/dist/workspace-runtime/server/desktop-preview-config.js +0 -81
  127. package/dist/workspace-runtime/server/desktop-preview-service.js +0 -157
  128. package/dist/workspace-runtime/server/desktop-preview-static.js +0 -53
  129. package/dist/workspace-runtime/server/desktop-preview.js +0 -189
  130. package/dist/workspace-runtime/server/dispatch.js +0 -1217
  131. package/dist/workspace-runtime/server/endpoints.js +0 -21
  132. package/dist/workspace-runtime/server/execution-group.js +0 -90
  133. package/dist/workspace-runtime/server/http.js +0 -1243
  134. package/dist/workspace-runtime/server/lock.js +0 -53
  135. package/dist/workspace-runtime/server/rfb-watch-gate.js +0 -168
  136. package/dist/workspace-runtime/server/source-uploader.js +0 -138
  137. package/dist/workspace-runtime/server/terminal-host.js +0 -366
  138. package/dist/workspace-runtime/server/types.js +0 -1
  139. package/dist/workspace-runtime/server/workspace-transfer.js +0 -99
  140. package/dist/workspace-runtime/workspaces/archive.js +0 -207
  141. package/dist/workspace-runtime/workspaces/artifact-events.js +0 -55
  142. package/dist/workspace-runtime/workspaces/artifact-files.js +0 -197
  143. package/dist/workspace-runtime/workspaces/artifact-receipts.js +0 -89
  144. package/dist/workspace-runtime/workspaces/artifacts.js +0 -282
  145. package/dist/workspace-runtime/workspaces/attachment-content.js +0 -123
  146. package/dist/workspace-runtime/workspaces/attachments.js +0 -185
  147. package/dist/workspace-runtime/workspaces/batch-commit.js +0 -142
  148. package/dist/workspace-runtime/workspaces/cli-context.js +0 -23
  149. package/dist/workspace-runtime/workspaces/cli-credentials.js +0 -111
  150. package/dist/workspace-runtime/workspaces/composer-context.js +0 -147
  151. package/dist/workspace-runtime/workspaces/context-error.js +0 -2
  152. package/dist/workspace-runtime/workspaces/dependencies.js +0 -264
  153. package/dist/workspace-runtime/workspaces/developer-command.js +0 -64
  154. package/dist/workspace-runtime/workspaces/directories.js +0 -133
  155. package/dist/workspace-runtime/workspaces/directory-access.js +0 -37
  156. package/dist/workspace-runtime/workspaces/directory-attachments.js +0 -245
  157. package/dist/workspace-runtime/workspaces/directory-maintenance.js +0 -111
  158. package/dist/workspace-runtime/workspaces/directory-stats.js +0 -49
  159. package/dist/workspace-runtime/workspaces/disk-snapshot.js +0 -31
  160. package/dist/workspace-runtime/workspaces/empty-template.js +0 -42
  161. package/dist/workspace-runtime/workspaces/files-search.js +0 -116
  162. package/dist/workspace-runtime/workspaces/files.js +0 -120
  163. package/dist/workspace-runtime/workspaces/git-credential-broker.js +0 -312
  164. package/dist/workspace-runtime/workspaces/git-credential-helper.js +0 -68
  165. package/dist/workspace-runtime/workspaces/git-direct-store.js +0 -155
  166. package/dist/workspace-runtime/workspaces/git.js +0 -670
  167. package/dist/workspace-runtime/workspaces/instructions.js +0 -56
  168. package/dist/workspace-runtime/workspaces/json5-edit.js +0 -170
  169. package/dist/workspace-runtime/workspaces/library-attachments.js +0 -268
  170. package/dist/workspace-runtime/workspaces/optional-platform-skills.js +0 -14
  171. package/dist/workspace-runtime/workspaces/parameters.js +0 -272
  172. package/dist/workspace-runtime/workspaces/prepare.js +0 -278
  173. package/dist/workspace-runtime/workspaces/registry.js +0 -251
  174. package/dist/workspace-runtime/workspaces/remix-references.js +0 -301
  175. package/dist/workspace-runtime/workspaces/revisions.js +0 -31
  176. package/dist/workspace-runtime/workspaces/session-image.js +0 -104
  177. package/dist/workspace-runtime/workspaces/shared-git.js +0 -40
  178. package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +0 -95
  179. package/dist/workspace-runtime/workspaces/skills.js +0 -191
  180. package/dist/workspace-runtime/workspaces/transfer.js +0 -288
  181. package/dist/workspace-runtime/workspaces/turn-changes.js +0 -75
  182. package/dist/workspace-runtime/workspaces/video-attachments.js +0 -49
  183. package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +0 -25
  184. package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +0 -79
  185. package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +0 -29
  186. package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +0 -37
  187. package/project-template-v2/.claude/agents/artist.md +0 -82
  188. package/project-template-v2/.claude/agents/developer.md +0 -153
  189. package/project-template-v2/.claude/agents/game-designer.md +0 -264
  190. package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +0 -533
  191. package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +0 -153
  192. package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +0 -27
  193. package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +0 -208
  194. package/project-template-v2/.claude/agents/refs/atom-skill-library.md +0 -81
  195. package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +0 -432
  196. package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +0 -252
  197. package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +0 -266
  198. package/project-template-v2/.claude/agents/refs/framework-mda.md +0 -147
  199. package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +0 -123
  200. package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +0 -88
  201. package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +0 -67
  202. package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +0 -120
  203. package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +0 -300
  204. package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +0 -495
  205. package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +0 -1141
  206. package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +0 -111
  207. package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +0 -365
  208. package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +0 -412
  209. package/project-template-v2/.claude/agents/reviewer.md +0 -75
  210. package/project-template-v2/.claude/agents/technical-artist.md +0 -86
  211. package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +0 -243
  212. package/project-template-v2/.claude/hooks/user-prompt.mjs +0 -133
  213. package/project-template-v2/.claude/settings.json +0 -33
  214. package/project-template-v2/.claude/skills/brainstorming/SKILL.md +0 -161
  215. package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +0 -270
  216. package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +0 -177
  217. package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +0 -354
  218. package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +0 -148
  219. package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +0 -56
  220. package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +0 -62
  221. package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
  222. package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +0 -309
  223. package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +0 -182
  224. package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +0 -123
  225. package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +0 -205
  226. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +0 -280
  227. package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +0 -184
  228. package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +0 -306
  229. package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +0 -298
  230. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +0 -123
  231. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +0 -254
  232. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +0 -324
  233. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +0 -97
  234. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +0 -118
  235. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +0 -137
  236. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +0 -397
  237. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +0 -296
  238. package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +0 -122
  239. package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +0 -219
  240. package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +0 -373
  241. package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +0 -161
  242. package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +0 -215
  243. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +0 -382
  244. package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +0 -423
  245. package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +0 -158
  246. package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +0 -100
  247. package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +0 -167
  248. package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +0 -270
  249. package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +0 -236
  250. package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +0 -130
  251. package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +0 -390
  252. package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +0 -23
  253. package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +0 -39
  254. package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +0 -87
  255. package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +0 -240
  256. package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +0 -226
  257. package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +0 -308
  258. package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +0 -275
  259. package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +0 -134
  260. package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +0 -142
  261. package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +0 -153
  262. package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +0 -386
  263. package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +0 -182
  264. package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +0 -72
  265. package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +0 -121
  266. package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -102
  267. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +0 -127
  268. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +0 -106
  269. package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +0 -128
  270. package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +0 -67
  271. package/project-template-v2/.claude/snapshot.mjs +0 -263
  272. package/project-template-v2/.playcraft/skills.lock.json +0 -152
  273. package/project-template-v2/CLAUDE.md +0 -146
  274. package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
  275. package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
  276. package/project-template-v2/assets/bundles/.gitkeep +0 -0
  277. package/project-template-v2/assets/images/bg/.gitkeep +0 -0
  278. package/project-template-v2/assets/images/reference/.gitkeep +0 -0
  279. package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
  280. package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
  281. package/project-template-v2/assets/images/ui/.gitkeep +0 -0
  282. package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
  283. package/project-template-v2/assets/models/.gitkeep +0 -0
  284. package/project-template-v2/docs/harness/iteration-1/context-flow.md +0 -254
  285. package/project-template-v2/docs/harness/iteration-1/generate-flow.md +0 -91
  286. package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +0 -214
  287. package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +0 -75
  288. package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +0 -63
  289. package/project-template-v2/docs/harness/iteration-2/context-flow.md +0 -223
  290. package/project-template-v2/docs/harness/iteration-2/generate-flow.md +0 -129
  291. package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +0 -267
  292. package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +0 -164
  293. package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +0 -115
  294. package/project-template-v2/docs/harness/orchestrator-flow.md +0 -364
  295. package/project-template-v2/docs/project-state.json +0 -60
  296. package/project-template-v2/docs/project-state.md +0 -72
  297. package/project-template-v2/docs/standards/README.md +0 -225
  298. package/project-template-v2/docs/standards/agent-behavior-standards.md +0 -174
  299. package/project-template-v2/docs/standards/artifacts/design-brief.md +0 -19
  300. package/project-template-v2/docs/standards/artifacts/design.md +0 -22
  301. package/project-template-v2/docs/standards/artifacts/game-code.md +0 -41
  302. package/project-template-v2/docs/standards/artifacts/todo-list.md +0 -41
  303. package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +0 -343
  304. package/project-template-v2/game/index.ts +0 -18
  305. package/project-template-v2/globals.d.ts +0 -51
  306. package/project-template-v2/index.css +0 -34
  307. package/project-template-v2/index.html +0 -18
  308. package/project-template-v2/main.ts +0 -9
  309. package/project-template-v2/package.json +0 -46
  310. package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +0 -31
  311. package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +0 -86
  312. package/project-template-v2/skills/_shared/scripts/dod-check.mjs +0 -153
  313. package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +0 -70
  314. package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +0 -91
  315. package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +0 -131
  316. package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +0 -80
  317. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +0 -90
  318. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +0 -93
  319. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +0 -23
  320. package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +0 -230
  321. package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +0 -58
  322. package/project-template-v2/skills/_shared/scripts/state-get.mjs +0 -339
  323. package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +0 -39
  324. package/project-template-v2/skills/_shared/scripts/state-set.mjs +0 -94
  325. package/project-template-v2/skills/_shared/scripts/state-store.mjs +0 -783
  326. package/project-template-v2/skills/_shared/scripts/todo-add.mjs +0 -88
  327. package/project-template-v2/skills/_shared/scripts/todo-get.mjs +0 -130
  328. package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +0 -33
  329. package/project-template-v2/skills/_shared/scripts/todo-set.mjs +0 -47
  330. package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +0 -268
  331. package/project-template-v2/skills/_shared/scripts/verify-env.mjs +0 -285
  332. package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +0 -161
  333. package/project-template-v2/skills/playable-autoplay/SKILL.md +0 -176
  334. package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +0 -4
  335. package/project-template-v2/skills/playable-debug/SKILL.md +0 -116
  336. package/project-template-v2/skills/playable-debug/agents/openai.yaml +0 -4
  337. package/project-template-v2/skills/playable-debug/references/debug-config.md +0 -40
  338. package/project-template-v2/skills/playable-record/SKILL.md +0 -140
  339. package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +0 -104
  340. package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +0 -141
  341. package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +0 -425
  342. package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +0 -261
  343. package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +0 -398
  344. package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +0 -322
  345. package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +0 -105
  346. package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +0 -230
  347. package/project-template-v2/skills/playable-validate/SKILL.md +0 -61
  348. package/project-template-v2/skills/playable-validate/validation-rules.md +0 -14
  349. package/project-template-v2/skills/playable-verify-ui/SKILL.md +0 -63
  350. package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +0 -1
  351. package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +0 -218
  352. package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +0 -51
  353. package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +0 -59
  354. package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +0 -106
  355. package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +0 -116
  356. package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +0 -59
  357. package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +0 -64
  358. package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +0 -58
  359. package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +0 -333
  360. package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
  361. package/project-template-v2/tsconfig.json +0 -20
  362. package/project-template-v2/vite.config.ts +0 -27
  363. package/project-templates/batch-locks/phaser.yaml +0 -922
  364. package/project-templates/batch-locks/threejs.yaml +0 -978
  365. package/project-templates/empty/AGENTS.md +0 -41
  366. package/project-templates/empty/gitignore +0 -33
@@ -1,120 +0,0 @@
1
- import { lstat, mkdir, readdir, readFile, realpath, writeFile } from 'node:fs/promises';
2
- import { dirname, join, relative, resolve, sep } from 'node:path';
3
- import { hashWorkspaceFileContent, workspaceFileContentSchema, workspaceFileListSchema, workspaceRelativePathSchema, } from '@playcraft/common/cloud-workspace/runtime';
4
- import { sampleDiskFile } from './disk-snapshot.js';
5
- const SKIP_DIRECTORY_NAMES = new Set(['.git', 'node_modules']);
6
- const LARGE_FILE_STREAM_THRESHOLD = 8 * 1024 * 1024;
7
- export class InvalidWorkspacePathError extends Error {
8
- code = 'invalid_workspace_path';
9
- constructor(path) {
10
- super(path);
11
- this.name = 'InvalidWorkspacePathError';
12
- }
13
- }
14
- export async function ensureWorkspaceRoot(root) {
15
- await mkdir(root, { recursive: true, mode: 0o700 });
16
- return realpath(root);
17
- }
18
- export async function resolveWorkspaceFile(root, relativePath) {
19
- const parsed = workspaceRelativePathSchema.safeParse(relativePath);
20
- if (!parsed.success)
21
- throw new InvalidWorkspacePathError(relativePath);
22
- const rootReal = await ensureWorkspaceRoot(root);
23
- const target = resolve(rootReal, ...parsed.data.split('/'));
24
- assertInsideRoot(rootReal, target);
25
- try {
26
- const existing = await realpath(target);
27
- assertInsideRoot(rootReal, existing);
28
- return existing;
29
- }
30
- catch (error) {
31
- if (error.code !== 'ENOENT')
32
- throw error;
33
- await assertParentInsideRoot(rootReal, dirname(target));
34
- return target;
35
- }
36
- }
37
- export async function writeWorkspaceFile(root, relativePath, content) {
38
- const target = await resolveWorkspaceFile(root, relativePath);
39
- await mkdir(dirname(target), { recursive: true, mode: 0o700 });
40
- const bytes = Buffer.from(content, 'utf8');
41
- await writeFile(target, bytes, { mode: 0o600 });
42
- return { path: relativePath, contentHash: hashWorkspaceFileContent(content), size: bytes.byteLength };
43
- }
44
- export async function readWorkspaceFile(root, relativePath, revisions) {
45
- const target = await resolveWorkspaceFile(root, relativePath);
46
- const content = await readFile(target, 'utf8');
47
- const contentHash = hashWorkspaceFileContent(content);
48
- return workspaceFileContentSchema.parse({
49
- path: relativePath,
50
- revision: revisions[relativePath]?.revision ?? 0,
51
- contentHash,
52
- content,
53
- });
54
- }
55
- export async function listWorkspaceFiles(root, revisions) {
56
- const rootReal = await ensureWorkspaceRoot(root);
57
- const files = [];
58
- for (const relativePath of await walkWorkspaceFiles(rootReal, rootReal)) {
59
- const target = join(rootReal, ...relativePath.split('/'));
60
- if ((await lstat(target)).size > LARGE_FILE_STREAM_THRESHOLD) {
61
- const sampled = await sampleDiskFile(target);
62
- files.push({ path: relativePath, revision: revisions[relativePath]?.revision ?? 0, contentHash: sampled.hash, size: sampled.size });
63
- continue;
64
- }
65
- const content = await readFile(target, 'utf8');
66
- files.push({
67
- path: relativePath,
68
- revision: revisions[relativePath]?.revision ?? 0,
69
- contentHash: hashWorkspaceFileContent(content),
70
- size: Buffer.byteLength(content, 'utf8'),
71
- });
72
- }
73
- files.sort((left, right) => left.path.localeCompare(right.path));
74
- return workspaceFileListSchema.parse({ files });
75
- }
76
- async function walkWorkspaceFiles(rootReal, current) {
77
- const entries = await readdir(current, { withFileTypes: true });
78
- const paths = [];
79
- for (const entry of entries) {
80
- if (entry.isDirectory()) {
81
- if (SKIP_DIRECTORY_NAMES.has(entry.name) || entry.name === '.' || entry.name === '..')
82
- continue;
83
- const child = join(current, entry.name);
84
- const childReal = await realpath(child);
85
- assertInsideRoot(rootReal, childReal);
86
- paths.push(...(await walkWorkspaceFiles(rootReal, childReal)));
87
- continue;
88
- }
89
- if (!entry.isFile())
90
- continue;
91
- const childReal = await realpath(join(current, entry.name));
92
- assertInsideRoot(rootReal, childReal);
93
- paths.push(toPosixRelative(rootReal, childReal));
94
- }
95
- return paths;
96
- }
97
- async function assertParentInsideRoot(rootReal, parent) {
98
- if (parent === rootReal)
99
- return;
100
- try {
101
- const parentReal = await realpath(parent);
102
- assertInsideRoot(rootReal, parentReal);
103
- }
104
- catch (error) {
105
- if (error.code !== 'ENOENT')
106
- throw error;
107
- await mkdir(parent, { recursive: true, mode: 0o700 });
108
- const parentReal = await realpath(parent);
109
- assertInsideRoot(rootReal, parentReal);
110
- }
111
- }
112
- function assertInsideRoot(rootReal, target) {
113
- const rel = relative(rootReal, target);
114
- if (rel === '' || rel.startsWith(`..${sep}`) || rel === '..' || rel.startsWith('..')) {
115
- throw new InvalidWorkspacePathError(target);
116
- }
117
- }
118
- function toPosixRelative(rootReal, target) {
119
- return relative(rootReal, target).split(sep).join('/');
120
- }
@@ -1,312 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { execFile } from 'node:child_process';
3
- import { chmod, chown, lstat, unlink, open, mkdir, mkdtemp, writeFile, readFile, rename, rm } from 'node:fs/promises';
4
- import { constants } from 'node:fs';
5
- import { createServer } from 'node:http';
6
- import { join } from 'node:path';
7
- import { fileURLToPath } from 'node:url';
8
- import { promisify } from 'node:util';
9
- import { isLocalWorkspaceGitAuthMode, isLocalWorkspaceGitEnvironment, matchWorkspaceGitGatewayCloneUrl, workspacePreparationSchema, workspaceDirectoryNameSchema } from '@playcraft/common/cloud-workspace/runtime';
10
- import { WorkspaceGitDirectStore } from './git-direct-store.js';
11
- import { PLATFORM_ROUTES, platformEndpoint } from '@playcraft/common/cloud-workspace/runtime';
12
- const exec = promisify(execFile);
13
- /** Only this control process sees the callback credential. Development processes use the local socket. */
14
- export class WorkspaceGitCredentialBroker {
15
- state;
16
- socketPath;
17
- server;
18
- closed = false;
19
- ready = false;
20
- cache = new Map();
21
- pending = new Map();
22
- direct;
23
- constructor(state) {
24
- this.state = state;
25
- this.direct = new WorkspaceGitDirectStore(state);
26
- const id = createHash('sha256').update(state.config.stateDir).digest('hex').slice(0, 24);
27
- this.socketPath = `/tmp/playcraft-git-${id}.sock`;
28
- }
29
- async start() {
30
- await this.direct.sweep(this.state.journal.destroyed());
31
- const old = await lstat(this.socketPath).catch(error => {
32
- if (error.code !== 'ENOENT')
33
- throw error;
34
- return undefined;
35
- });
36
- if (old) {
37
- if (!old.isSocket() || old.uid !== process.getuid?.())
38
- throw new Error('git_broker_socket_conflict');
39
- await unlink(this.socketPath);
40
- }
41
- const server = this.server = createServer({ maxHeaderSize: 2048, requestTimeout: 10_000 }, async (request, response) => {
42
- try {
43
- if (request.method !== 'POST' || request.url !== '/credential')
44
- throw new Error();
45
- const chunks = [];
46
- let size = 0;
47
- for await (const chunk of request) {
48
- size += chunk.length;
49
- if (size > 16_384)
50
- throw new Error();
51
- chunks.push(Buffer.from(chunk));
52
- }
53
- const result = await this.credential(JSON.parse(Buffer.concat(chunks).toString('utf8')));
54
- response.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });
55
- response.end(JSON.stringify(result));
56
- }
57
- catch {
58
- response.writeHead(403, { 'cache-control': 'no-store', connection: 'close' });
59
- response.end();
60
- }
61
- });
62
- server.maxConnections = 32;
63
- await new Promise((resolve, reject) => { server.once('error', reject); server.listen(this.socketPath, resolve); });
64
- // The workspace group is the image's development group; other system users cannot connect.
65
- const directory = await lstat(this.state.config.workspaceRoot);
66
- await chown(this.socketPath, process.getuid(), directory.gid);
67
- await chmod(this.socketPath, 0o660);
68
- this.ready = true;
69
- }
70
- async close() {
71
- this.closed = true;
72
- this.cache.clear();
73
- if (this.server?.listening) {
74
- this.server.closeAllConnections();
75
- await new Promise(resolve => this.server.close(() => resolve()));
76
- await unlink(this.socketPath).catch(() => undefined);
77
- }
78
- }
79
- async configure(binding, preparation, directoryName = 'basic') {
80
- workspaceDirectoryNameSchema.parse(directoryName);
81
- if (binding.targetKind === 'standalone')
82
- throw new Error('git_access_denied');
83
- if (directoryName !== 'basic')
84
- binding = this.binding(binding.workspaceId, directoryName);
85
- const { directories: _directories, ...stableBinding } = binding;
86
- binding = stableBinding;
87
- const scope = `${binding.workspaceId}:${directoryName}`;
88
- preparation = workspacePreparationSchema.parse(preparation);
89
- if (preparation.mode !== 'clone')
90
- throw new Error('git_access_denied');
91
- const transport = preparation.transport;
92
- const isDirect = isLocalWorkspaceGitAuthMode(preparation.authMode);
93
- if (!isDirect && transport?.kind !== 'git-gateway-v2')
94
- return;
95
- if (isDirect) {
96
- if (!isLocalWorkspaceGitEnvironment(this.state.config.environment) || (preparation.directAccess && preparation.directAccess.repositoryUrl !== binding.repositoryUrl))
97
- throw new Error('git_access_denied');
98
- }
99
- else {
100
- const matched = matchWorkspaceGitGatewayCloneUrl(transport.cloneUrl, this.state.config.gitGatewayOrigins, binding.repositoryUrl);
101
- if (!matched.ok || matched.requestId !== `v2/${binding.workspaceId}`)
102
- throw new Error('workspace_git_gateway_unavailable');
103
- }
104
- // Revocation and switching to gateway invalidate first; direct rotation keeps the old file until rename.
105
- if (!isDirect)
106
- await this.direct.remove(binding.workspaceId, directoryName);
107
- else if (preparation.directAccess === null)
108
- await this.direct.put(binding, null, undefined, directoryName);
109
- this.cache.delete(scope);
110
- const cloneUrl = isDirect ? binding.repositoryUrl : transport.cloneUrl;
111
- const root = join(this.state.config.workspaceRoot, binding.workspaceId, directoryName);
112
- const helper = `!${quote(process.execPath)} ${quote(fileURLToPath(new URL('./git-credential-helper.js', import.meta.url)))} ${quote(this.socketPath)} ${quote(binding.workspaceId)} ${quote(directoryName)}`;
113
- await configureGitFile(root, this.state.config.stateDir, [
114
- ...(!isDirect ? [['--replace-all', `url.${cloneUrl}.insteadOf`, binding.repositoryUrl]] : []),
115
- ['--replace-all', `credential.${cloneUrl}.useHttpPath`, 'true'],
116
- ['--replace-all', `credential.${cloneUrl}.helper`, ''],
117
- ['--add', `credential.${cloneUrl}.helper`, helper],
118
- ['--replace-all', `http.${cloneUrl}.followRedirects`, 'false'],
119
- ['--replace-all', 'playcraft.gitAuthUrl', cloneUrl],
120
- ], binding.repositoryUrl);
121
- if (isDirect)
122
- await this.direct.put(binding, preparation.directAccess, undefined, directoryName);
123
- this.cache.delete(scope);
124
- }
125
- async credential(input) {
126
- if (!this.ready || this.closed || this.state.journal.destroyed() || !input || !['get', 'erase'].includes(input.action)
127
- || typeof input.workspaceId !== 'string' || !/^[a-f0-9-]{36}$/.test(input.workspaceId)
128
- || !['http', 'https'].includes(input.protocol) || typeof input.host !== 'string'
129
- || typeof input.path !== 'string')
130
- throw new Error('git_access_denied');
131
- const directoryName = workspaceDirectoryNameSchema.parse(input.directoryName ?? 'basic');
132
- const binding = this.binding(input.workspaceId, directoryName);
133
- const scope = `${input.workspaceId}:${directoryName}`;
134
- const cloneUrl = `${input.protocol}://${input.host}/${input.path}`;
135
- const direct = await this.direct.get(binding, directoryName);
136
- if (direct) {
137
- if (!direct.access || cloneUrl !== binding.repositoryUrl)
138
- throw new Error('git_access_denied');
139
- if (input.action === 'erase') {
140
- if (input.password === direct.access.password)
141
- await this.direct.put(binding, null, input.password, directoryName);
142
- return {};
143
- }
144
- return { username: direct.access.username, password: direct.access.password };
145
- }
146
- const matched = matchWorkspaceGitGatewayCloneUrl(cloneUrl, this.state.config.gitGatewayOrigins, binding.repositoryUrl);
147
- if (!matched.ok || matched.requestId !== `v2/${binding.workspaceId}`)
148
- throw new Error('git_access_denied');
149
- if (input.action === 'erase') {
150
- if (this.cache.get(scope)?.preparation.gitAccess?.password === input.password)
151
- this.cache.delete(scope);
152
- return {};
153
- }
154
- const key = JSON.stringify([binding, this.state.config.generation, this.state.runtimeEpoch]);
155
- const cached = this.cache.get(scope);
156
- let preparation = cached?.key === key && Date.parse(cached.preparation.gitAccess.expiresAt) > Date.now() + 60_000
157
- ? cached.preparation : undefined;
158
- if (!preparation) {
159
- const pendingKey = `${scope}:${key}`;
160
- let pending = this.pending.get(pendingKey);
161
- if (!pending) {
162
- pending = this.refresh(binding, key, directoryName).finally(() => this.pending.delete(pendingKey));
163
- this.pending.set(pendingKey, pending);
164
- }
165
- preparation = await pending;
166
- }
167
- if (await this.direct.get(binding, directoryName) || preparation.transport?.cloneUrl !== cloneUrl || !preparation.gitAccess)
168
- throw new Error('git_access_denied');
169
- return { username: preparation.gitAccess.username, password: preparation.gitAccess.password };
170
- }
171
- async prepareDirectory(workspaceId, directoryName = 'basic') {
172
- const binding = this.binding(workspaceId, directoryName, true);
173
- return this.refresh(binding, JSON.stringify([binding, this.state.config.generation, this.state.runtimeEpoch]), directoryName);
174
- }
175
- binding(workspaceId, directoryName, recovering = false) {
176
- workspaceDirectoryNameSchema.parse(directoryName);
177
- const binding = this.state.journal.getWorkspace(workspaceId);
178
- if (!binding || binding.targetKind === 'standalone')
179
- throw new Error('git_access_denied');
180
- const { directories: _directories, ...base } = binding;
181
- if (directoryName === 'basic')
182
- return base;
183
- const directory = binding.directories?.find(item => item.directoryName === directoryName && (recovering || item.status !== 'reclaimed'));
184
- if (!directory?.remixId)
185
- throw new Error('git_access_denied');
186
- return { ...base, targetKind: 'remix', remixId: directory.remixId, branchName: directory.branchName };
187
- }
188
- async destroy() {
189
- await this.close();
190
- await this.direct.sweep(true);
191
- }
192
- async refresh(binding, key, directoryName) {
193
- const scope = `${binding.workspaceId}:${directoryName}`;
194
- const endpoint = platformEndpoint(this.state.config.publicBaseUrl, PLATFORM_ROUTES.gitCredentials);
195
- const epoch = this.state.runtimeEpoch;
196
- const response = await fetch(endpoint, {
197
- method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10_000),
198
- headers: { authorization: `Bearer ${this.state.config.controlToken}`, 'content-type': 'application/json', 'playcraft-env': this.state.config.environment },
199
- body: JSON.stringify({ runtimeId: this.state.config.runtimeId, generation: this.state.config.generation,
200
- runtimeEpoch: epoch, workspaceId: binding.workspaceId, directoryName }),
201
- });
202
- if (!response.ok) {
203
- await response.body?.cancel();
204
- throw new Error('git_broker_unavailable');
205
- }
206
- const preparation = workspacePreparationSchema.parse(await response.json());
207
- if (preparation.mode !== 'clone')
208
- throw new Error('git_access_denied');
209
- const matched = preparation.transport && matchWorkspaceGitGatewayCloneUrl(preparation.transport.cloneUrl, this.state.config.gitGatewayOrigins, binding.repositoryUrl);
210
- if (this.closed || this.state.journal.destroyed() || this.state.runtimeEpoch !== epoch
211
- || JSON.stringify(this.binding(binding.workspaceId, directoryName, true)) !== JSON.stringify(binding))
212
- throw new Error('git_access_denied');
213
- if (isLocalWorkspaceGitAuthMode(preparation.authMode)) {
214
- if (!isLocalWorkspaceGitEnvironment(this.state.config.environment) || !preparation.directAccess || preparation.directAccess.repositoryUrl !== binding.repositoryUrl)
215
- throw new Error('git_access_denied');
216
- }
217
- else {
218
- if (preparation.transport?.kind !== 'git-gateway-v2' || !matched?.ok || matched.requestId !== `v2/${binding.workspaceId}`
219
- || !preparation.gitAccess || Date.parse(preparation.gitAccess.issuedAt) > Date.now()
220
- || Date.parse(preparation.gitAccess.expiresAt) <= Date.now() + 10_000)
221
- throw new Error('git_access_denied');
222
- if (this.cache.size >= 128)
223
- this.cache.clear();
224
- this.cache.set(scope, { key, preparation });
225
- }
226
- return preparation;
227
- }
228
- }
229
- function quote(value) { return `'${value.replaceAll("'", "'\\''")}'`; }
230
- /** Pin directories in Linux sandboxes and replace config atomically without following user-owned symlinks. */
231
- async function configureGitFile(root, stateDir, commands, repositoryUrl) {
232
- const directoryFlags = constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW;
233
- const directory = await open(root, directoryFlags);
234
- const pinnedRoot = process.platform === 'linux' ? `/proc/self/fd/${directory.fd}` : root;
235
- let gitDirectory;
236
- let lock;
237
- let temporary;
238
- let lockPath;
239
- try {
240
- gitDirectory = await open(join(pinnedRoot, '.git'), directoryFlags);
241
- const gitPath = process.platform === 'linux' ? `/proc/self/fd/${gitDirectory.fd}` : join(root, '.git');
242
- const configPath = join(gitPath, 'config');
243
- lock = await open(join(gitPath, 'config.lock'), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o640);
244
- lockPath = join(gitPath, 'config.lock');
245
- const source = await open(configPath, constants.O_RDONLY | constants.O_NOFOLLOW);
246
- let content;
247
- try {
248
- const stat = await source.stat();
249
- if (!stat.isFile() || stat.size > 1024 * 1024 || stat.nlink !== 1)
250
- throw new Error('invalid_git_config');
251
- content = await source.readFile('utf8');
252
- await lock.chmod(stat.mode & 0o666);
253
- }
254
- finally {
255
- await source.close();
256
- }
257
- await mkdir(stateDir, { recursive: true, mode: 0o700 });
258
- temporary = await mkdtemp(join(stateDir, 'git-config-'));
259
- const staging = join(temporary, 'config');
260
- await writeFile(staging, content, { mode: 0o600 });
261
- await clearManagedGitConfig(staging, repositoryUrl);
262
- for (const args of commands)
263
- await exec('git', ['config', '--file', staging, '--no-includes', ...args], {
264
- timeout: 5000, maxBuffer: 8192, env: { PATH: process.env.PATH, GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null' },
265
- });
266
- await lock.writeFile(await readFile(staging));
267
- await lock.sync();
268
- await lock.close();
269
- lock = undefined;
270
- await rename(lockPath, configPath);
271
- lockPath = undefined;
272
- }
273
- finally {
274
- await lock?.close();
275
- if (lockPath)
276
- await unlink(lockPath).catch(() => undefined);
277
- if (temporary)
278
- await rm(temporary, { recursive: true, force: true });
279
- await gitDirectory?.close();
280
- await directory.close();
281
- }
282
- }
283
- /** Only remove authentication/routing for this binding and previous PlayCraft endpoints. */
284
- async function clearManagedGitConfig(file, repositoryUrl) {
285
- const run = (...args) => exec('git', ['config', '--file', file, '--no-includes', ...args], {
286
- timeout: 5000, maxBuffer: 1024 * 1024,
287
- env: { PATH: process.env.PATH, GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null' },
288
- });
289
- const entries = (await run('--null', '--list')).stdout.split('\0').filter(Boolean).map(entry => {
290
- const newline = entry.indexOf('\n');
291
- return [entry.slice(0, newline), entry.slice(newline + 1)];
292
- });
293
- const urls = new Set([repositoryUrl]);
294
- const remove = new Set();
295
- for (const [key, value] of entries) {
296
- if (key === 'playcraft.gitauthurl')
297
- urls.add(value);
298
- const rewrite = /^url\.(.+)\.(?:insteadof|pushinsteadof)$/.exec(key);
299
- if (rewrite && value === repositoryUrl) {
300
- urls.add(rewrite[1]);
301
- remove.add(key);
302
- }
303
- }
304
- for (const [key] of entries) {
305
- for (const url of urls) {
306
- if (key.startsWith(`credential.${url}.`) || key.startsWith(`http.${url}.`))
307
- remove.add(key);
308
- }
309
- }
310
- for (const key of remove)
311
- await run('--unset-all', key);
312
- }
@@ -1,68 +0,0 @@
1
- import { request } from 'node:http';
2
- // Git invokes this executable directly. Keep standard output exclusively for the credential protocol.
3
- const [socketPath, workspaceId, third, fourth] = process.argv.slice(2);
4
- const directoryName = fourth === undefined ? 'basic' : third;
5
- const action = fourth ?? third;
6
- try {
7
- if (action === 'get' || action === 'erase') {
8
- const fields = {};
9
- const chunks = [];
10
- let size = 0;
11
- for await (const chunk of process.stdin) {
12
- size += chunk.length;
13
- if (size > 16_384)
14
- throw new Error();
15
- chunks.push(Buffer.from(chunk));
16
- }
17
- for (const line of Buffer.concat(chunks).toString('utf8').split('\n')) {
18
- if (!line)
19
- break;
20
- const separator = line.indexOf('=');
21
- if (separator < 1)
22
- throw new Error();
23
- const key = line.slice(0, separator);
24
- // Git may repeat extension fields such as capability[] and wwwauth[].
25
- if (!['protocol', 'host', 'path', 'password'].includes(key))
26
- continue;
27
- if (key in fields)
28
- throw new Error();
29
- fields[key] = line.slice(separator + 1);
30
- }
31
- const credential = await new Promise((resolve, reject) => {
32
- const client = request({ socketPath, path: '/credential', method: 'POST',
33
- headers: { 'content-type': 'application/json' }, signal: AbortSignal.timeout(12_000) }, async (response) => {
34
- try {
35
- if (response.statusCode !== 200) {
36
- response.resume();
37
- throw new Error();
38
- }
39
- const parts = [];
40
- let length = 0;
41
- for await (const chunk of response) {
42
- length += chunk.length;
43
- if (length > 16_384)
44
- throw new Error();
45
- parts.push(Buffer.from(chunk));
46
- }
47
- resolve(JSON.parse(Buffer.concat(parts).toString('utf8')));
48
- }
49
- catch {
50
- reject(new Error());
51
- }
52
- });
53
- client.once('error', reject);
54
- client.end(JSON.stringify({ workspaceId, directoryName, action, protocol: fields.protocol, host: fields.host,
55
- path: fields.path, ...(action === 'erase' ? { password: fields.password } : {}) }));
56
- });
57
- if (action === 'get') {
58
- if (!credential.username || !credential.password || /[\r\n\0]/.test(credential.username + credential.password))
59
- throw new Error();
60
- process.stdout.write(`username=${credential.username}\npassword=${credential.password}\n\n`);
61
- }
62
- }
63
- }
64
- catch {
65
- if (action === 'get')
66
- process.stdout.write('quit=true\n\n');
67
- process.exitCode = 1;
68
- }
@@ -1,155 +0,0 @@
1
- import { constants } from 'node:fs';
2
- import { lstat, mkdir, open, realpath, readdir, rename, unlink } from 'node:fs/promises';
3
- import { randomUUID } from 'node:crypto';
4
- import { join, resolve, sep } from 'node:path';
5
- import { isLocalWorkspaceGitEnvironment, workspaceGitDirectAccessSchema, workspaceDirectoryNameSchema, sameWorkspaceRegistration } from '@playcraft/common/cloud-workspace/runtime';
6
- /** Control-owned files only. Same-user processes are not an operating-system isolation boundary. */
7
- export class WorkspaceGitDirectStore {
8
- state;
9
- mutation = Promise.resolve();
10
- constructor(state) {
11
- this.state = state;
12
- }
13
- exclusive(action) {
14
- const result = this.mutation.then(action);
15
- this.mutation = result.catch(() => undefined);
16
- return result;
17
- }
18
- identity() {
19
- const c = this.state.config;
20
- return JSON.stringify([c.environment, c.userId, c.runtimeId, c.generation]);
21
- }
22
- name(id, directoryName = 'basic') {
23
- if (!/^[a-f0-9-]{36}$/.test(id))
24
- throw new Error('git_access_denied');
25
- workspaceDirectoryNameSchema.parse(directoryName);
26
- return `${id}${directoryName === 'basic' ? '' : `--${directoryName}`}.json`;
27
- }
28
- async directory(create, action) {
29
- const stateDir = resolve(this.state.config.stateDir);
30
- const directory = join(stateDir, 'direct-git');
31
- if (!create && !(await lstat(directory).catch(error => {
32
- if (error.code === 'ENOENT')
33
- return undefined;
34
- throw error;
35
- })))
36
- return undefined;
37
- const root = await realpath(this.state.config.workspaceRoot);
38
- if (this.state.config.stateDir !== stateDir || await realpath(stateDir) !== stateDir || stateDir === root || stateDir.startsWith(`${root}${sep}`)) {
39
- throw new Error('git_control_directory_rejected');
40
- }
41
- for (const path of [stateDir, directory]) {
42
- if (path === directory && create)
43
- await mkdir(path, { mode: 0o700 }).catch(error => {
44
- if (error.code !== 'EEXIST')
45
- throw error;
46
- });
47
- const stat = await lstat(path);
48
- if (!stat.isDirectory() || stat.isSymbolicLink() || stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0) {
49
- throw new Error('git_control_directory_rejected');
50
- }
51
- }
52
- const handle = await open(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
53
- try {
54
- const pinned = process.platform === 'linux' ? `/proc/self/fd/${handle.fd}` : directory;
55
- return await action(pinned);
56
- }
57
- finally {
58
- await handle.close();
59
- }
60
- }
61
- async read(path) {
62
- const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW).catch(error => {
63
- if (error.code === 'ENOENT')
64
- return undefined;
65
- throw error;
66
- });
67
- if (!handle)
68
- return undefined;
69
- try {
70
- const stat = await handle.stat();
71
- if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 0o777) !== 0o600 || stat.nlink !== 1 || stat.size > 16384) {
72
- throw new Error('git_credential_file_rejected');
73
- }
74
- return JSON.parse(await handle.readFile('utf8'));
75
- }
76
- finally {
77
- await handle.close();
78
- }
79
- }
80
- async get(binding, directoryName = 'basic') {
81
- return this.directory(false, async (path) => {
82
- const stored = await this.read(join(path, this.name(binding.workspaceId, directoryName)));
83
- if (!stored)
84
- return undefined;
85
- if (stored.identity !== this.identity() || !sameWorkspaceRegistration(stored.binding, binding)
86
- || !isLocalWorkspaceGitEnvironment(this.state.config.environment))
87
- throw new Error('git_access_denied');
88
- if (stored.access !== null) {
89
- const access = workspaceGitDirectAccessSchema.parse(stored.access);
90
- if (access.repositoryUrl !== binding.repositoryUrl)
91
- throw new Error('git_access_denied');
92
- }
93
- return stored;
94
- });
95
- }
96
- async put(binding, access, rejectedPassword, directoryName = 'basic') {
97
- if (!isLocalWorkspaceGitEnvironment(this.state.config.environment) || this.state.journal.destroyed())
98
- throw new Error('git_access_denied');
99
- if (access && (workspaceGitDirectAccessSchema.parse(access).repositoryUrl !== binding.repositoryUrl))
100
- throw new Error('git_access_denied');
101
- await this.exclusive(() => this.directory(true, async (path) => {
102
- const target = join(path, this.name(binding.workspaceId, directoryName));
103
- // Refuse symlink/hardlink/permissive old files even though rename would replace them.
104
- const old = await this.read(target);
105
- if (rejectedPassword !== undefined && old?.access?.password !== rejectedPassword)
106
- return;
107
- const temporary = join(path, `${randomUUID()}.tmp`);
108
- const handle = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
109
- try {
110
- await handle.writeFile(JSON.stringify({ identity: this.identity(), binding, access }));
111
- await handle.sync();
112
- await handle.close();
113
- if (this.state.journal.destroyed())
114
- throw new Error('git_access_denied');
115
- await rename(temporary, target);
116
- const dir = await open(path, constants.O_RDONLY | constants.O_DIRECTORY);
117
- try {
118
- await dir.sync();
119
- }
120
- finally {
121
- await dir.close();
122
- }
123
- }
124
- finally {
125
- await handle.close();
126
- await unlink(temporary).catch(error => { if (error.code !== 'ENOENT')
127
- throw error; });
128
- }
129
- }));
130
- }
131
- async remove(workspaceId, directoryName = 'basic') {
132
- await this.exclusive(() => this.directory(false, async (path) => {
133
- const target = join(path, this.name(workspaceId, directoryName));
134
- await this.read(target);
135
- await unlink(target).catch(error => { if (error.code !== 'ENOENT')
136
- throw error; });
137
- }));
138
- }
139
- async sweep(destroy = false) {
140
- await this.exclusive(() => this.directory(false, async (path) => {
141
- for (const name of await readdir(path)) {
142
- if (!/^[a-f0-9-]{36}(?:--remix-[a-z0-9][a-z0-9-]{3,63})?\.(json|tmp)$/.test(name))
143
- throw new Error('git_credential_file_rejected');
144
- const target = join(path, name);
145
- if (name.endsWith('.tmp')) {
146
- await unlink(target);
147
- continue;
148
- }
149
- const stored = await this.read(target);
150
- if (destroy || stored?.identity !== this.identity() || !isLocalWorkspaceGitEnvironment(this.state.config.environment))
151
- await unlink(target);
152
- }
153
- }));
154
- }
155
- }