@playcraft/cli 0.1.54 → 0.1.56-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -15
- package/SKILLS_REGISTRY.md +149 -0
- package/dist/agent/local-backend.js +1 -1
- package/dist/assets/client.js +157 -0
- package/dist/assets/messages.js +48 -0
- package/dist/auth/account-activation.js +157 -0
- package/dist/auth/messages.js +30 -0
- package/dist/cli-root-help.js +4 -0
- package/dist/commands/assets.js +123 -0
- package/dist/commands/build-all.js +5 -19
- package/dist/commands/build-temp.js +26 -0
- package/dist/commands/build.js +53 -28
- package/dist/commands/create.js +26 -33
- package/dist/commands/image-layer-messages.js +37 -0
- package/dist/commands/image-layer-tools.js +185 -0
- package/dist/commands/image.js +2 -73
- package/dist/commands/library-messages.js +46 -0
- package/dist/commands/library.js +130 -0
- package/dist/commands/login.js +84 -42
- package/dist/commands/platform-skills.generated.js +19 -0
- package/dist/commands/playable.js +53 -0
- package/dist/commands/preview-runtime.js +21 -0
- package/dist/commands/skills.js +4 -0
- package/dist/commands/tools-3d.js +11 -0
- package/dist/commands/tools-batch.js +113 -0
- package/dist/commands/tools-generation.js +6 -0
- package/dist/commands/tools.js +2 -0
- package/dist/commands/video.js +122 -0
- package/dist/commands/workspace-runtime.js +54 -0
- package/dist/commands/workspace.js +40 -0
- package/dist/config.js +59 -4
- package/dist/index.js +47 -3
- package/dist/preview-runtime/distribution.js +77 -0
- package/dist/project-skills/builtin-import.js +225 -0
- package/dist/project-skills/commands.js +77 -0
- package/dist/project-skills/lifecycle.js +28 -0
- package/dist/project-skills/local-run.js +112 -0
- package/dist/project-skills/local-store.js +221 -0
- package/dist/project-skills/messages.js +80 -0
- package/dist/project-skills/reconcile.js +62 -0
- package/dist/project-skills/remote-cache.js +51 -0
- package/dist/project-skills/validation.js +19 -0
- package/dist/recording/messages.js +40 -0
- package/dist/recording/project-digest.js +85 -0
- package/dist/recording/recorder.js +293 -0
- package/dist/recording/scenario.js +50 -0
- package/dist/remix/clone.js +2 -0
- package/dist/remix/init-template.js +2 -0
- package/dist/remix/pull.js +2 -0
- package/dist/remix/push.js +21 -1
- package/dist/skill-registry/client.js +345 -0
- package/dist/skill-registry/commands.js +180 -0
- package/dist/skill-registry/evaluation-client.js +156 -0
- package/dist/skill-registry/evaluation-commands.js +174 -0
- package/dist/skill-registry/messages.js +177 -0
- package/dist/skill-registry/package.js +275 -0
- package/dist/skill-registry/platform-package.js +100 -0
- package/dist/utils/agent-api-client.js +245 -30
- package/dist/utils/environment-endpoints.js +17 -0
- package/dist/utils/error-diagnostics.js +20 -0
- package/dist/utils/playcraft-http-client.js +6 -4
- package/dist/utils/tool-operation-journal.js +124 -0
- package/dist/utils/video-evidence-git.js +81 -0
- package/dist/video-replication/client.js +234 -0
- package/dist/video-replication/comparison-evidence.js +149 -0
- package/dist/video-replication/comparison-files.js +57 -0
- package/dist/video-replication/comparison-log.js +72 -0
- package/dist/video-replication/comparison-messages.js +104 -0
- package/dist/video-replication/comparison-output.js +63 -0
- package/dist/video-replication/comparison-preflight.js +66 -0
- package/dist/video-replication/comparison.js +230 -0
- package/dist/video-replication/diagnostics.js +41 -0
- package/dist/video-replication/evidence.js +165 -0
- package/dist/video-replication/media.js +112 -0
- package/dist/video-replication/messages.js +97 -0
- package/dist/workspace/messages.js +67 -0
- package/dist/workspace/transfer-client.js +237 -0
- package/dist/workspace/transfer-errors.js +48 -0
- package/dist/workspace-runtime/batch/content-changes.js +30 -0
- package/dist/workspace-runtime/batch/control.js +86 -0
- package/dist/workspace-runtime/batch/deliver.js +173 -0
- package/dist/workspace-runtime/batch/freeze.js +101 -0
- package/dist/workspace-runtime/batch/generate.js +249 -0
- package/dist/workspace-runtime/batch/git.js +40 -0
- package/dist/workspace-runtime/batch/occupancy.js +8 -0
- package/dist/workspace-runtime/batch/policy.js +70 -0
- package/dist/workspace-runtime/batch/prepare.js +317 -0
- package/dist/workspace-runtime/batch/processes.js +72 -0
- package/dist/workspace-runtime/batch/records.js +113 -0
- package/dist/workspace-runtime/batch/routes.js +61 -0
- package/dist/workspace-runtime/batch/source-candidate.js +86 -0
- package/dist/workspace-runtime/batch/source-capture.js +146 -0
- package/dist/workspace-runtime/batch/source-failure.js +43 -0
- package/dist/workspace-runtime/batch/source-publish.js +130 -0
- package/dist/workspace-runtime/codex/app-server.js +1065 -0
- package/dist/workspace-runtime/codex/compact.js +46 -0
- package/dist/workspace-runtime/codex/config-toml.js +107 -0
- package/dist/workspace-runtime/codex/delivery-tools.js +144 -0
- package/dist/workspace-runtime/codex/jsonrpc-stdio.js +145 -0
- package/dist/workspace-runtime/codex/launcher-client.js +177 -0
- package/dist/workspace-runtime/codex/loopback.js +146 -0
- package/dist/workspace-runtime/codex/mcp-config.js +39 -0
- package/dist/workspace-runtime/codex/model-broker.js +263 -0
- package/dist/workspace-runtime/codex/native-adapter.js +9 -0
- package/dist/workspace-runtime/codex/permissions.js +79 -0
- package/dist/workspace-runtime/codex/thread-image.js +38 -0
- package/dist/workspace-runtime/codex/tool-response.js +10 -0
- package/dist/workspace-runtime/codex/ui-instructions.js +4 -0
- package/dist/workspace-runtime/codex/ui-tools.js +203 -0
- package/dist/workspace-runtime/codex/work-mode.js +15 -0
- package/dist/workspace-runtime/main.js +10 -0
- package/dist/workspace-runtime/persistence/journal.js +365 -0
- package/dist/workspace-runtime/persistence/native-subagents.js +162 -0
- package/dist/workspace-runtime/persistence/sqlite-client.js +77 -0
- package/dist/workspace-runtime/persistence/sqlite-worker.js +1161 -0
- package/dist/workspace-runtime/persistence/stopped-execution.js +77 -0
- package/dist/workspace-runtime/previews/diagnostics.js +44 -0
- package/dist/workspace-runtime/previews/finalize.js +64 -0
- package/dist/workspace-runtime/previews/output.js +36 -0
- package/dist/workspace-runtime/previews/publish.js +306 -0
- package/dist/workspace-runtime/previews/runtime-preview.js +251 -0
- package/dist/workspace-runtime/previews/single-file.js +43 -0
- package/dist/workspace-runtime/processes/managed-writes.js +69 -0
- package/dist/workspace-runtime/serve.js +441 -0
- package/dist/workspace-runtime/server/accept-operation.js +61 -0
- package/dist/workspace-runtime/server/asset-transfers.js +63 -0
- package/dist/workspace-runtime/server/auth.js +21 -0
- package/dist/workspace-runtime/server/connectivity.js +211 -0
- package/dist/workspace-runtime/server/desktop-host.js +429 -0
- package/dist/workspace-runtime/server/desktop-preview-config.js +81 -0
- package/dist/workspace-runtime/server/desktop-preview-service.js +157 -0
- package/dist/workspace-runtime/server/desktop-preview-static.js +53 -0
- package/dist/workspace-runtime/server/desktop-preview.js +189 -0
- package/dist/workspace-runtime/server/dispatch.js +1655 -0
- package/dist/workspace-runtime/server/endpoints.js +21 -0
- package/dist/workspace-runtime/server/execution-group.js +90 -0
- package/dist/workspace-runtime/server/http.js +1363 -0
- package/dist/workspace-runtime/server/lock.js +53 -0
- package/dist/workspace-runtime/server/operation-record.js +33 -0
- package/dist/workspace-runtime/server/rfb-watch-gate.js +168 -0
- package/dist/workspace-runtime/server/session-restore.js +84 -0
- package/dist/workspace-runtime/server/source-uploader.js +138 -0
- package/dist/workspace-runtime/server/submit-operation.js +41 -0
- package/dist/workspace-runtime/server/terminal-host.js +366 -0
- package/dist/workspace-runtime/server/types.js +1 -0
- package/dist/workspace-runtime/server/workspace-transfer.js +168 -0
- package/dist/workspace-runtime/verify-artifact.js +59 -0
- package/dist/workspace-runtime/workspaces/append-only-log.js +62 -0
- package/dist/workspace-runtime/workspaces/archive.js +207 -0
- package/dist/workspace-runtime/workspaces/artifact-events.js +168 -0
- package/dist/workspace-runtime/workspaces/artifact-files.js +201 -0
- package/dist/workspace-runtime/workspaces/artifact-receipts.js +112 -0
- package/dist/workspace-runtime/workspaces/artifacts.js +380 -0
- package/dist/workspace-runtime/workspaces/asset-library-import.js +387 -0
- package/dist/workspace-runtime/workspaces/asset-publication-access.js +29 -0
- package/dist/workspace-runtime/workspaces/asset-transfer-dependencies.js +81 -0
- package/dist/workspace-runtime/workspaces/asset-transfer-publish.js +438 -0
- package/dist/workspace-runtime/workspaces/asset-transfer-storage.js +389 -0
- package/dist/workspace-runtime/workspaces/attachment-content.js +73 -0
- package/dist/workspace-runtime/workspaces/attachments.js +185 -0
- package/dist/workspace-runtime/workspaces/batch-commit.js +142 -0
- package/dist/workspace-runtime/workspaces/binary-content.js +65 -0
- package/dist/workspace-runtime/workspaces/binary-write.js +117 -0
- package/dist/workspace-runtime/workspaces/cli-context.js +23 -0
- package/dist/workspace-runtime/workspaces/cli-credentials.js +115 -0
- package/dist/workspace-runtime/workspaces/composer-context.js +147 -0
- package/dist/workspace-runtime/workspaces/context-error.js +2 -0
- package/dist/workspace-runtime/workspaces/delivery-build.js +421 -0
- package/dist/workspace-runtime/workspaces/delivery-store.js +168 -0
- package/dist/workspace-runtime/workspaces/delivery.js +64 -0
- package/dist/workspace-runtime/workspaces/dependencies.js +279 -0
- package/dist/workspace-runtime/workspaces/developer-command.js +98 -0
- package/dist/workspace-runtime/workspaces/directories.js +135 -0
- package/dist/workspace-runtime/workspaces/directory-access.js +50 -0
- package/dist/workspace-runtime/workspaces/directory-attachments.js +245 -0
- package/dist/workspace-runtime/workspaces/directory-create.js +57 -0
- package/dist/workspace-runtime/workspaces/directory-maintenance.js +111 -0
- package/dist/workspace-runtime/workspaces/directory-mutations.js +238 -0
- package/dist/workspace-runtime/workspaces/directory-revisions.js +56 -0
- package/dist/workspace-runtime/workspaces/directory-stats.js +49 -0
- package/dist/workspace-runtime/workspaces/disk-snapshot.js +31 -0
- package/dist/workspace-runtime/workspaces/empty-template.js +43 -0
- package/dist/workspace-runtime/workspaces/file-content.js +48 -0
- package/dist/workspace-runtime/workspaces/files-search.js +116 -0
- package/dist/workspace-runtime/workspaces/files.js +150 -0
- package/dist/workspace-runtime/workspaces/git-credential-broker.js +312 -0
- package/dist/workspace-runtime/workspaces/git-credential-helper.js +68 -0
- package/dist/workspace-runtime/workspaces/git-direct-store.js +155 -0
- package/dist/workspace-runtime/workspaces/git.js +683 -0
- package/dist/workspace-runtime/workspaces/instructions.js +56 -0
- package/dist/workspace-runtime/workspaces/json5-edit.js +313 -0
- package/dist/workspace-runtime/workspaces/library-attachments.js +268 -0
- package/dist/workspace-runtime/workspaces/operation-log.js +23 -0
- package/dist/workspace-runtime/workspaces/optional-platform-skills.js +14 -0
- package/dist/workspace-runtime/workspaces/parameter-agent-inputs.js +97 -0
- package/dist/workspace-runtime/workspaces/parameter-agent-protocol.js +40 -0
- package/dist/workspace-runtime/workspaces/parameter-agent-store.js +58 -0
- package/dist/workspace-runtime/workspaces/parameter-agent.js +284 -0
- package/dist/workspace-runtime/workspaces/parameter-assets.js +212 -0
- package/dist/workspace-runtime/workspaces/parameters.js +369 -0
- package/dist/workspace-runtime/workspaces/prepare.js +284 -0
- package/dist/workspace-runtime/workspaces/registry.js +251 -0
- package/dist/workspace-runtime/workspaces/remix-references.js +301 -0
- package/dist/workspace-runtime/workspaces/revisions.js +31 -0
- package/dist/workspace-runtime/workspaces/session-image.js +104 -0
- package/dist/workspace-runtime/workspaces/shared-git.js +40 -0
- package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +95 -0
- package/dist/workspace-runtime/workspaces/skills.js +191 -0
- package/dist/workspace-runtime/workspaces/theme-asset-index.js +267 -0
- package/dist/workspace-runtime/workspaces/transfer-quota.js +69 -0
- package/dist/workspace-runtime/workspaces/transfer.js +471 -0
- package/dist/workspace-runtime/workspaces/turn-changes.js +75 -0
- package/dist/workspace-runtime/workspaces/video-attachments.js +49 -0
- package/dist/workspace-runtime/workspaces/worktree-attachments.js +74 -0
- package/dist/workspace-runtime/workspaces/worktree-checkout.js +67 -0
- package/dist/workspace-runtime/workspaces/worktrees.js +73 -0
- package/package.json +16 -5
- package/project-template/.claude/skills/playcraft-audio-generation/SKILL.md +31 -4
- package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +36 -5
- package/project-template/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +1 -1
- package/project-template/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +1 -1
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +1 -1
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +1 -1
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +1 -1
- package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +1 -1
- package/project-template/.claude/skills/playcraft-image-generation/reference/ta-image-generation-detail.md +300 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +1 -1
- package/project-template/.claude/skills/playcraft-image-processing/SKILL.md +50 -25
- package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +25 -0
- package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +79 -0
- package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +29 -0
- package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +37 -0
- package/project-template/.claude/skills/playcraft-workspace-assets/SKILL.md +53 -0
- package/project-template/.claude/skills/playcraft-workspace-assets/references/requests.json +32 -0
- package/project-template-v2/.claude/agents/artist.md +82 -0
- package/project-template-v2/.claude/agents/developer.md +153 -0
- package/project-template-v2/.claude/agents/game-designer.md +264 -0
- package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +533 -0
- package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
- package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +27 -0
- package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
- package/project-template-v2/.claude/agents/refs/atom-skill-library.md +81 -0
- package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +432 -0
- package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +252 -0
- package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +266 -0
- package/project-template-v2/.claude/agents/refs/framework-mda.md +147 -0
- package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
- package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
- package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
- package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
- package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +300 -0
- package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +495 -0
- package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
- package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +111 -0
- package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
- package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +412 -0
- package/project-template-v2/.claude/agents/reviewer.md +75 -0
- package/project-template-v2/.claude/agents/technical-artist.md +86 -0
- package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +243 -0
- package/project-template-v2/.claude/hooks/user-prompt.mjs +133 -0
- package/project-template-v2/.claude/settings.json +33 -0
- package/project-template-v2/.claude/skills/brainstorming/SKILL.md +161 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +177 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +354 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
- package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +309 -0
- package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
- package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
- package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
- package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +307 -0
- package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
- package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +401 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +333 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +95 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +136 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/ta-image-generation-detail.md +300 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
- package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
- package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +244 -0
- package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +373 -0
- package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
- package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +215 -0
- package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +382 -0
- package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
- package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
- package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
- package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
- package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
- package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
- package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
- package/project-template-v2/.claude/skills/playcraft-workspace-assets/SKILL.md +53 -0
- package/project-template-v2/.claude/skills/playcraft-workspace-assets/references/requests.json +32 -0
- package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +390 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +240 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +226 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +275 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +134 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +142 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +153 -0
- package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +386 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +102 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
- package/project-template-v2/.claude/snapshot.mjs +263 -0
- package/project-template-v2/.playcraft/skills.lock.json +152 -0
- package/project-template-v2/CLAUDE.md +146 -0
- package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
- package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
- package/project-template-v2/assets/bundles/.gitkeep +0 -0
- package/project-template-v2/assets/images/bg/.gitkeep +0 -0
- package/project-template-v2/assets/images/reference/.gitkeep +0 -0
- package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
- package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
- package/project-template-v2/assets/images/ui/.gitkeep +0 -0
- package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
- package/project-template-v2/assets/models/.gitkeep +0 -0
- package/project-template-v2/docs/harness/iteration-1/context-flow.md +254 -0
- package/project-template-v2/docs/harness/iteration-1/generate-flow.md +91 -0
- package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +214 -0
- package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +75 -0
- package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +63 -0
- package/project-template-v2/docs/harness/iteration-2/context-flow.md +223 -0
- package/project-template-v2/docs/harness/iteration-2/generate-flow.md +129 -0
- package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +267 -0
- package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +164 -0
- package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +115 -0
- package/project-template-v2/docs/harness/orchestrator-flow.md +364 -0
- package/project-template-v2/docs/project-state.json +60 -0
- package/project-template-v2/docs/project-state.md +72 -0
- package/project-template-v2/docs/standards/README.md +225 -0
- package/project-template-v2/docs/standards/agent-behavior-standards.md +174 -0
- package/project-template-v2/docs/standards/artifacts/design-brief.md +19 -0
- package/project-template-v2/docs/standards/artifacts/design.md +22 -0
- package/project-template-v2/docs/standards/artifacts/game-code.md +41 -0
- package/project-template-v2/docs/standards/artifacts/todo-list.md +41 -0
- package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +343 -0
- package/project-template-v2/game/index.ts +18 -0
- package/project-template-v2/globals.d.ts +51 -0
- package/project-template-v2/index.css +34 -0
- package/project-template-v2/index.html +18 -0
- package/project-template-v2/main.ts +9 -0
- package/project-template-v2/package.json +46 -0
- package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +31 -0
- package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +86 -0
- package/project-template-v2/skills/_shared/scripts/dod-check.mjs +153 -0
- package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +70 -0
- package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +91 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +131 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +80 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +90 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +93 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +23 -0
- package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +230 -0
- package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +58 -0
- package/project-template-v2/skills/_shared/scripts/state-get.mjs +339 -0
- package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +39 -0
- package/project-template-v2/skills/_shared/scripts/state-set.mjs +94 -0
- package/project-template-v2/skills/_shared/scripts/state-store.mjs +783 -0
- package/project-template-v2/skills/_shared/scripts/todo-add.mjs +88 -0
- package/project-template-v2/skills/_shared/scripts/todo-get.mjs +130 -0
- package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +33 -0
- package/project-template-v2/skills/_shared/scripts/todo-set.mjs +47 -0
- package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +268 -0
- package/project-template-v2/skills/_shared/scripts/verify-env.mjs +285 -0
- package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +161 -0
- package/project-template-v2/skills/playable-autoplay/SKILL.md +176 -0
- package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +4 -0
- package/project-template-v2/skills/playable-debug/SKILL.md +116 -0
- package/project-template-v2/skills/playable-debug/agents/openai.yaml +4 -0
- package/project-template-v2/skills/playable-debug/references/debug-config.md +40 -0
- package/project-template-v2/skills/playable-record/SKILL.md +140 -0
- package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
- package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
- package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +425 -0
- package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +261 -0
- package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
- package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +322 -0
- package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +105 -0
- package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +230 -0
- package/project-template-v2/skills/playable-validate/SKILL.md +61 -0
- package/project-template-v2/skills/playable-validate/validation-rules.md +14 -0
- package/project-template-v2/skills/playable-verify-ui/SKILL.md +63 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +106 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
- package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
- package/project-template-v2/tsconfig.json +20 -0
- package/project-template-v2/vite.config.ts +27 -0
- package/project-templates/batch-locks/phaser.yaml +922 -0
- package/project-templates/batch-locks/threejs.yaml +978 -0
- package/project-templates/empty/AGENTS.md +53 -0
- package/project-templates/empty/gitignore +33 -0
- package/project-templates/phaser/.claude/skills/playcraft-audio-generation/SKILL.md +31 -4
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +36 -5
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +1 -1
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +1 -1
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +1 -1
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +1 -1
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +1 -1
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +1 -1
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/ta-image-generation-detail.md +300 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +1 -1
- package/project-templates/phaser/.claude/skills/playcraft-image-processing/SKILL.md +50 -25
- package/project-templates/phaser/.claude/skills/playcraft-workspace-assets/SKILL.md +53 -0
- package/project-templates/phaser/.claude/skills/playcraft-workspace-assets/references/requests.json +32 -0
- package/project-templates/threejs/.claude/skills/playcraft-audio-generation/SKILL.md +31 -4
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +36 -5
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +1 -1
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +1 -1
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +1 -1
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +1 -1
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +1 -1
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +1 -1
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/ta-image-generation-detail.md +300 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +1 -1
- package/project-templates/threejs/.claude/skills/playcraft-image-processing/SKILL.md +50 -25
- package/project-templates/threejs/.claude/skills/playcraft-workspace-assets/SKILL.md +53 -0
- package/project-templates/threejs/.claude/skills/playcraft-workspace-assets/references/requests.json +32 -0
- package/templates/preview-runtime/v1/README.md +79 -0
- package/templates/preview-runtime/v1/build-preview.mjs +1324 -0
- package/templates/preview-runtime/v1/manifest.json +17 -0
- package/templates/preview-runtime/v1/release-guard.mjs +87 -0
- package/templates/preview-runtime/v1/runtime.d.mts +3 -0
- package/templates/preview-runtime/v1/runtime.d.ts +3 -0
- package/templates/preview-runtime/v1/runtime.mjs +1312 -0
- package/templates/preview-runtime/v1/types/adapters.d.ts +28 -0
- package/templates/preview-runtime/v1/types/preview.d.ts +24 -0
- package/templates/preview-runtime/v1/types/protocol.d.ts +131 -0
- package/templates/preview-runtime/v1/types/runtime.d.ts +14 -0
package/dist/commands/create.js
CHANGED
|
@@ -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
|
-
//
|
|
875
|
+
// Platform Skill 住在 playcraft-skills/,与 Atom Skill 的 skills/ 是两个目录。
|
|
876
|
+
// 尝试从 CLI 安装包的位置定位它(monorepo 内或与 CLI 平级安装时可解析)。
|
|
872
877
|
const candidateDirs = [
|
|
873
|
-
path.resolve(cliCommandsDir, '..', '..', '..', '..', 'skills',
|
|
874
|
-
path.resolve(cliCommandsDir, '..', '..', '..', 'skills',
|
|
875
|
-
path.resolve(cliCommandsDir, '..', '..', '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',
|
|
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.
|
|
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.
|
|
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
|
-
// 无法立即解析 →
|
|
908
|
-
|
|
909
|
-
|
|
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,37 @@
|
|
|
1
|
+
const messages = {
|
|
2
|
+
en: {
|
|
3
|
+
decompose: 'Separate an image into a base and editable layers using the cloud workspace pipeline',
|
|
4
|
+
input: 'Input image path or image address', outputDir: 'Output directory',
|
|
5
|
+
engine: 'Layer engine: seedream (workspace) or qwen (legacy)', imageModel: 'Image model reference for workspace layer separation',
|
|
6
|
+
size: 'Workspace layer size: auto, 1K, 1.5K, 2K', prompt: 'Optional layer guidance', team: 'Team used for model configuration',
|
|
7
|
+
numLayers: 'Legacy Qwen layer count, 2–10', safety: 'Disable the legacy Qwen safety checker', format: 'Legacy Qwen output format',
|
|
8
|
+
ninePatch: 'Analyze stretchable nine-patch borders with the cloud workspace vision model',
|
|
9
|
+
output: 'Save the analysis as a file', visionModel: 'Vision model reference',
|
|
10
|
+
invalidFile: 'Input must be a regular image file of at most {maxBytes} bytes', downloadFailed: 'Image download failed ({status})',
|
|
11
|
+
tooLarge: 'Input image exceeds {maxBytes} bytes', unsupportedInput: 'Input must be a single image in a supported format',
|
|
12
|
+
unsupportedOutput: 'Unsupported output image type: {mimeType}', invalidEngine: '--engine must be seedream or qwen',
|
|
13
|
+
legacyOnly: '--num-layers, --no-safety-checker and --output-format require --engine qwen',
|
|
14
|
+
workspaceOnly: '--image-model, --size and --team-id require --engine seedream',
|
|
15
|
+
invalidSize: '--size must be auto, 1K, 1.5K or 2K', invalidCount: '--num-layers must be an integer between 2 and 10',
|
|
16
|
+
saved: 'Saved separated layers and decompose-layers-meta.json to {outputDir}', failed: 'Image command failed: {message}',
|
|
17
|
+
},
|
|
18
|
+
zh: {
|
|
19
|
+
decompose: '使用云工作区同款管线,将图片分解为底图和可编辑图层',
|
|
20
|
+
input: '输入图片路径或图片地址', outputDir: '输出目录',
|
|
21
|
+
engine: '分层引擎:seedream(云工作区)或 qwen(旧版)', imageModel: '云工作区图层分离的图像模型引用',
|
|
22
|
+
size: '云工作区分层尺寸:auto、1K、1.5K、2K', prompt: '可选图层分离提示词', team: '模型配置所属团队',
|
|
23
|
+
numLayers: '旧版 Qwen 图层数量,2–10', safety: '关闭旧版 Qwen 安全检测', format: '旧版 Qwen 输出格式',
|
|
24
|
+
ninePatch: '使用云工作区视觉模型分析点九图可拉伸边界', output: '将分析结果保存到文件', visionModel: '视觉模型引用',
|
|
25
|
+
invalidFile: '输入必须是大小不超过 {maxBytes} 字节的普通图片文件', downloadFailed: '图片下载失败({status})',
|
|
26
|
+
tooLarge: '输入图片超过 {maxBytes} 字节', unsupportedInput: '输入必须是支持格式的单张图片',
|
|
27
|
+
unsupportedOutput: '不支持的输出图片类型:{mimeType}', invalidEngine: '--engine 必须为 seedream 或 qwen',
|
|
28
|
+
legacyOnly: '--num-layers、--no-safety-checker 和 --output-format 需要使用 --engine qwen',
|
|
29
|
+
workspaceOnly: '--image-model、--size 和 --team-id 需要使用 --engine seedream',
|
|
30
|
+
invalidSize: '--size 必须为 auto、1K、1.5K 或 2K', invalidCount: '--num-layers 必须为 2 到 10 之间的整数',
|
|
31
|
+
saved: '已将分离图层与 decompose-layers-meta.json 保存到 {outputDir}', failed: '图片命令执行失败:{message}',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export function imageLayerMessage(key, values = {}) {
|
|
35
|
+
const language = (process.env.PLAYCRAFT_LANGUAGE || process.env.LANG || 'en').startsWith('zh') ? 'zh' : 'en';
|
|
36
|
+
return messages[language][key].replace(/\{(\w+)\}/g, (match, name) => String(values[name] ?? match));
|
|
37
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import sharp from 'sharp';
|
|
4
|
+
import { AgentApiClient } from '../utils/agent-api-client.js';
|
|
5
|
+
import { resolveSandboxPath } from '../utils/sandbox-path.js';
|
|
6
|
+
import { imageLayerMessage as t } from './image-layer-messages.js';
|
|
7
|
+
import { captureGeneratedArtifact } from '../workspace-runtime/workspaces/artifact-receipts.js';
|
|
8
|
+
const MAX_IMAGE_BYTES = 30_000_000;
|
|
9
|
+
const isRemote = (input) => /^https?:\/\//i.test(input);
|
|
10
|
+
function localImageBytes(input, maxBytes) {
|
|
11
|
+
const filename = resolveSandboxPath(input);
|
|
12
|
+
const info = statSync(filename);
|
|
13
|
+
if (!info.isFile() || info.size > maxBytes) {
|
|
14
|
+
throw new Error(t('invalidFile', { maxBytes }));
|
|
15
|
+
}
|
|
16
|
+
return readFileSync(filename);
|
|
17
|
+
}
|
|
18
|
+
async function inputImage(input) {
|
|
19
|
+
let bytes;
|
|
20
|
+
if (isRemote(input)) {
|
|
21
|
+
const response = await fetch(input, { signal: AbortSignal.timeout(60_000) });
|
|
22
|
+
if (!response.ok || !response.body)
|
|
23
|
+
throw new Error(t('downloadFailed', { status: response.status }));
|
|
24
|
+
if (Number(response.headers.get('content-length')) > MAX_IMAGE_BYTES) {
|
|
25
|
+
await response.body.cancel();
|
|
26
|
+
throw new Error(t('tooLarge', { maxBytes: MAX_IMAGE_BYTES }));
|
|
27
|
+
}
|
|
28
|
+
const reader = response.body.getReader();
|
|
29
|
+
const chunks = [];
|
|
30
|
+
let total = 0;
|
|
31
|
+
try {
|
|
32
|
+
while (true) {
|
|
33
|
+
const chunk = await reader.read();
|
|
34
|
+
if (chunk.done)
|
|
35
|
+
break;
|
|
36
|
+
total += chunk.value.byteLength;
|
|
37
|
+
if (total > MAX_IMAGE_BYTES)
|
|
38
|
+
throw new Error(t('tooLarge', { maxBytes: MAX_IMAGE_BYTES }));
|
|
39
|
+
chunks.push(chunk.value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await reader.cancel();
|
|
44
|
+
}
|
|
45
|
+
bytes = Buffer.concat(chunks);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
bytes = localImageBytes(input, MAX_IMAGE_BYTES);
|
|
49
|
+
}
|
|
50
|
+
const metadata = await sharp(bytes).metadata();
|
|
51
|
+
const inputMimeTypes = { png: 'image/png', jpeg: 'image/jpeg', webp: 'image/webp' };
|
|
52
|
+
const mimeType = inputMimeTypes[metadata.format ?? ''];
|
|
53
|
+
if (!mimeType || (metadata.pages ?? 1) > 1)
|
|
54
|
+
throw new Error(t('unsupportedInput'));
|
|
55
|
+
return { imageBase64: bytes.toString('base64'), mimeType };
|
|
56
|
+
}
|
|
57
|
+
function imageExtension(mimeType) {
|
|
58
|
+
const formats = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp' };
|
|
59
|
+
const extension = formats[mimeType];
|
|
60
|
+
if (!extension)
|
|
61
|
+
throw new Error(t('unsupportedOutput', { mimeType }));
|
|
62
|
+
return extension;
|
|
63
|
+
}
|
|
64
|
+
function saveLayer(outputDir, stem, layer) {
|
|
65
|
+
const file = `${stem}.${imageExtension(layer.mimeType)}`;
|
|
66
|
+
writeFileSync(join(outputDir, file), Buffer.from(layer.imageBase64, 'base64'));
|
|
67
|
+
const { imageBase64: _bytes, ...metadata } = layer;
|
|
68
|
+
return { ...metadata, file };
|
|
69
|
+
}
|
|
70
|
+
function fail(error) {
|
|
71
|
+
console.error(t('failed', { message: error instanceof Error ? error.message : String(error) }));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
export function registerImageLayerTools(image) {
|
|
75
|
+
image.command('decompose-layers')
|
|
76
|
+
.description(t('decompose'))
|
|
77
|
+
.requiredOption('--input <path>', t('input'))
|
|
78
|
+
.option('--output-dir <dir>', t('outputDir'), './decompose-layers-output')
|
|
79
|
+
.option('--engine <name>', t('engine'), 'seedream')
|
|
80
|
+
.option('--image-model <ref>', t('imageModel'))
|
|
81
|
+
.option('--size <size>', t('size'))
|
|
82
|
+
.option('--prompt <text>', t('prompt'), '')
|
|
83
|
+
.option('--team-id <id>', t('team'))
|
|
84
|
+
.option('--num-layers <n>', t('numLayers'), (value) => Number(value), 4)
|
|
85
|
+
.option('--no-safety-checker', t('safety'))
|
|
86
|
+
.option('--output-format <format>', t('format'), 'png')
|
|
87
|
+
.action(async (opts, command) => {
|
|
88
|
+
try {
|
|
89
|
+
const legacyFlags = ['numLayers', 'safetyChecker', 'outputFormat'].some(name => command.getOptionValueSource(name) === 'cli');
|
|
90
|
+
const explicitEngine = command.getOptionValueSource('engine') === 'cli';
|
|
91
|
+
const engine = legacyFlags && !explicitEngine ? 'qwen' : opts.engine;
|
|
92
|
+
if (!['seedream', 'qwen'].includes(engine))
|
|
93
|
+
throw new Error(t('invalidEngine'));
|
|
94
|
+
if (engine === 'seedream' && legacyFlags) {
|
|
95
|
+
throw new Error(t('legacyOnly'));
|
|
96
|
+
}
|
|
97
|
+
if (engine === 'qwen' && (opts.imageModel || opts.size || opts.teamId)) {
|
|
98
|
+
throw new Error(t('workspaceOnly'));
|
|
99
|
+
}
|
|
100
|
+
if (opts.size && !['auto', '1K', '1.5K', '2K'].includes(opts.size)) {
|
|
101
|
+
throw new Error(t('invalidSize'));
|
|
102
|
+
}
|
|
103
|
+
if (engine === 'qwen' && (!Number.isInteger(opts.numLayers) || opts.numLayers < 2 || opts.numLayers > 10)) {
|
|
104
|
+
throw new Error(t('invalidCount'));
|
|
105
|
+
}
|
|
106
|
+
const outputDir = resolveSandboxPath(opts.outputDir);
|
|
107
|
+
const input = opts.input;
|
|
108
|
+
const startedAt = Date.now();
|
|
109
|
+
const client = new AgentApiClient();
|
|
110
|
+
let metadata;
|
|
111
|
+
const savedImages = [];
|
|
112
|
+
if (engine === 'seedream') {
|
|
113
|
+
const result = await client.post('/image/layer-decomposition', {
|
|
114
|
+
...await inputImage(input),
|
|
115
|
+
prompt: opts.prompt || undefined,
|
|
116
|
+
size: opts.size || 'auto',
|
|
117
|
+
imageModelRef: opts.imageModel,
|
|
118
|
+
teamId: opts.teamId,
|
|
119
|
+
});
|
|
120
|
+
mkdirSync(outputDir, { recursive: true });
|
|
121
|
+
const base = saveLayer(outputDir, 'base', result.base);
|
|
122
|
+
const layers = result.layers.map((layer, index) => saveLayer(outputDir, `layer_${String(index).padStart(2, '0')}`, layer));
|
|
123
|
+
savedImages.push(...[base, ...layers].map(layer => join(outputDir, layer.file)));
|
|
124
|
+
metadata = { source: input, engine, base, layers, usage: result.usage };
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const maxBytes = Number.parseInt(process.env.PLAYCRAFT_IMAGE_MAX_INPUT_BYTES || '', 10);
|
|
128
|
+
const payload = {
|
|
129
|
+
...(isRemote(input) ? { imageUrl: input } : {
|
|
130
|
+
imageBase64: localImageBytes(input, maxBytes > 0 ? maxBytes : 512 * 1024 * 1024).toString('base64'),
|
|
131
|
+
}),
|
|
132
|
+
numLayers: opts.numLayers,
|
|
133
|
+
prompt: opts.prompt,
|
|
134
|
+
enableSafetyChecker: opts.safetyChecker !== false,
|
|
135
|
+
outputFormat: opts.outputFormat,
|
|
136
|
+
};
|
|
137
|
+
const result = await client.post('/image-layered/decompose', payload);
|
|
138
|
+
mkdirSync(outputDir, { recursive: true });
|
|
139
|
+
const layers = result.layers.map((layer) => {
|
|
140
|
+
const file = `layer_${String(layer.index).padStart(2, '0')}.${imageExtension(layer.mimeType)}`;
|
|
141
|
+
writeFileSync(join(outputDir, file), Buffer.from(layer.imageBase64, 'base64'));
|
|
142
|
+
savedImages.push(join(outputDir, file));
|
|
143
|
+
const { imageBase64: _bytes, ...details } = layer;
|
|
144
|
+
return { ...details, file };
|
|
145
|
+
});
|
|
146
|
+
metadata = { source: input, engine, requestId: result.requestId, numLayers: result.numLayers,
|
|
147
|
+
hasNsfwConcepts: result.hasNsfwConcepts, layers };
|
|
148
|
+
}
|
|
149
|
+
const elapsed = `${((Date.now() - startedAt) / 1000).toFixed(1)}s`;
|
|
150
|
+
writeFileSync(join(outputDir, 'decompose-layers-meta.json'), JSON.stringify({ ...metadata, elapsed }, null, 2));
|
|
151
|
+
for (const imagePath of savedImages)
|
|
152
|
+
await captureGeneratedArtifact(imagePath, 'decompose-layers');
|
|
153
|
+
console.log(t('saved', { outputDir }));
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
fail(error);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
image.command('analyze-nine-patch')
|
|
160
|
+
.description(t('ninePatch'))
|
|
161
|
+
.requiredOption('--input <path>', t('input'))
|
|
162
|
+
.option('--output <path>', t('output'))
|
|
163
|
+
.option('--vision-model <ref>', t('visionModel'))
|
|
164
|
+
.option('--team-id <id>', t('team'))
|
|
165
|
+
.action(async (opts) => {
|
|
166
|
+
try {
|
|
167
|
+
const input = await inputImage(opts.input);
|
|
168
|
+
const result = await new AgentApiClient().post('/image/analyze-nine-patch', {
|
|
169
|
+
imageBase64: input.imageBase64,
|
|
170
|
+
visionModelRef: opts.visionModel,
|
|
171
|
+
teamId: opts.teamId,
|
|
172
|
+
});
|
|
173
|
+
const json = JSON.stringify(result, null, 2);
|
|
174
|
+
if (opts.output) {
|
|
175
|
+
const output = resolveSandboxPath(opts.output);
|
|
176
|
+
mkdirSync(dirname(output), { recursive: true });
|
|
177
|
+
writeFileSync(output, json);
|
|
178
|
+
}
|
|
179
|
+
console.log(json);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
fail(error);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
package/dist/commands/image.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import sharp from 'sharp';
|
|
5
5
|
import yaml from 'js-yaml';
|
|
6
6
|
import { AgentApiClient } from '../utils/agent-api-client.js';
|
|
7
|
+
import { registerImageLayerTools } from './image-layer-tools.js';
|
|
7
8
|
import { resolveSandboxPath } from '../utils/sandbox-path.js';
|
|
8
9
|
/** 单文件输入上限(字节),防止误处理巨型文件导致 OOM */
|
|
9
10
|
const DEFAULT_MAX_INPUT_BYTES = 512 * 1024 * 1024;
|
|
@@ -1853,79 +1854,7 @@ export function registerImageCommands(program) {
|
|
|
1853
1854
|
handleError(e);
|
|
1854
1855
|
}
|
|
1855
1856
|
});
|
|
1856
|
-
|
|
1857
|
-
img
|
|
1858
|
-
.command('decompose-layers')
|
|
1859
|
-
.description([
|
|
1860
|
-
'302 Qwen-Image-Layered 整图语义分层:将合成图拆成 N 个 RGBA 图层',
|
|
1861
|
-
'',
|
|
1862
|
-
' 示例:',
|
|
1863
|
-
' playcraft image decompose-layers --input composite.png --output-dir ./layers',
|
|
1864
|
-
' playcraft image decompose-layers --input https://example.com/a.png --num-layers 4',
|
|
1865
|
-
'',
|
|
1866
|
-
' 需 Admin 配置 provider=302、modelKind=image 的 API Key;耗时约 1–2 分钟。',
|
|
1867
|
-
' 本地文件需 storage 公网 URL,或 --input 使用 HTTPS 直链。',
|
|
1868
|
-
].join('\n'))
|
|
1869
|
-
.requiredOption('--input <path>', '输入图片路径或 HTTPS URL')
|
|
1870
|
-
.option('--output-dir <dir>', '输出目录', './decompose-layers-output')
|
|
1871
|
-
.option('--num-layers <n>', '分层数量 2–10', cliParseInt, 4)
|
|
1872
|
-
.option('--prompt <text>', '可选分层语义引导', '')
|
|
1873
|
-
.option('--no-safety-checker', '关闭安全检测')
|
|
1874
|
-
.option('--output-format <fmt>', '输出格式', 'png')
|
|
1875
|
-
.action(async (opts) => {
|
|
1876
|
-
try {
|
|
1877
|
-
const payload = {
|
|
1878
|
-
numLayers: opts.numLayers,
|
|
1879
|
-
prompt: opts.prompt || '',
|
|
1880
|
-
enableSafetyChecker: opts.safetyChecker !== false,
|
|
1881
|
-
outputFormat: opts.outputFormat,
|
|
1882
|
-
};
|
|
1883
|
-
const input = opts.input;
|
|
1884
|
-
const isUrl = input.startsWith('http://') || input.startsWith('https://');
|
|
1885
|
-
if (isUrl) {
|
|
1886
|
-
payload.imageUrl = input;
|
|
1887
|
-
console.log(`Using image URL: ${input}`);
|
|
1888
|
-
}
|
|
1889
|
-
else {
|
|
1890
|
-
const resolvedDecomposeInput = assertInputWithinLimit(input);
|
|
1891
|
-
payload.imageBase64 = readFileSync(resolvedDecomposeInput).toString('base64');
|
|
1892
|
-
console.log(`Uploading local image: ${input}`);
|
|
1893
|
-
}
|
|
1894
|
-
console.log(`Decomposing into ${payload.numLayers} layer(s) via 302 Qwen-Image-Layered (may take 1–2 min)…`);
|
|
1895
|
-
const t0 = Date.now();
|
|
1896
|
-
const client = new AgentApiClient();
|
|
1897
|
-
const result = await client.post('/image-layered/decompose', payload);
|
|
1898
|
-
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
1899
|
-
const outputDir = opts.outputDir;
|
|
1900
|
-
mkdirSync(outputDir, { recursive: true });
|
|
1901
|
-
for (const layer of result.layers) {
|
|
1902
|
-
const ext = layer.mimeType.includes('png') ? 'png' : 'png';
|
|
1903
|
-
const filename = `layer_${String(layer.index).padStart(2, '0')}.${ext}`;
|
|
1904
|
-
writeFileSync(join(outputDir, filename), Buffer.from(layer.imageBase64, 'base64'));
|
|
1905
|
-
const dim = layer.width && layer.height ? ` ${layer.width}×${layer.height}` : '';
|
|
1906
|
-
console.log(` ${filename}${dim}`);
|
|
1907
|
-
}
|
|
1908
|
-
const meta = {
|
|
1909
|
-
source: input,
|
|
1910
|
-
requestId: result.requestId,
|
|
1911
|
-
numLayers: result.numLayers,
|
|
1912
|
-
elapsed: `${elapsed}s`,
|
|
1913
|
-
hasNsfwConcepts: result.hasNsfwConcepts,
|
|
1914
|
-
layers: result.layers.map((l) => ({
|
|
1915
|
-
index: l.index,
|
|
1916
|
-
url: l.url,
|
|
1917
|
-
width: l.width,
|
|
1918
|
-
height: l.height,
|
|
1919
|
-
seed: l.seed,
|
|
1920
|
-
})),
|
|
1921
|
-
};
|
|
1922
|
-
writeFileSync(join(outputDir, 'decompose-layers-meta.json'), JSON.stringify(meta, null, 2), 'utf-8');
|
|
1923
|
-
console.log(`\nDone in ${elapsed}s — ${result.layers.length} layer(s) → ${outputDir}/`);
|
|
1924
|
-
}
|
|
1925
|
-
catch (e) {
|
|
1926
|
-
handleError(e);
|
|
1927
|
-
}
|
|
1928
|
-
});
|
|
1857
|
+
registerImageLayerTools(img);
|
|
1929
1858
|
// ─── segment ─────────────────────────────────────────────────────────────
|
|
1930
1859
|
img
|
|
1931
1860
|
.command('segment')
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const messages = {
|
|
2
|
+
en: {
|
|
3
|
+
topic: 'Find and download authorized shared asset library content',
|
|
4
|
+
list: 'List library assets with filters and pagination', search: 'Search library names and content metadata',
|
|
5
|
+
get: 'Read asset metadata', files: 'List every file in an asset or bundle', facets: 'Inspect available categories and tags',
|
|
6
|
+
download: 'Download an asset or a complete bundle with relative dependencies',
|
|
7
|
+
query: 'Search text', id: 'Library asset identifier', type: 'Asset type', path: 'Library path prefix',
|
|
8
|
+
category: 'Category', subcategory: 'Subcategory', tags: 'Comma-separated tags',
|
|
9
|
+
visibility: 'public | team', team: 'Authorized team identifier', page: 'Page number, starting at 1',
|
|
10
|
+
limit: 'Results per page (1–100)', sort: 'modifiedAt | createdAt | name | referenceCount | size', order: 'asc | desc',
|
|
11
|
+
expand: 'Return individual files instead of collapsed bundles',
|
|
12
|
+
output: 'New file path, or a new directory for a bundle', outputDir: 'New directory for the downloaded content',
|
|
13
|
+
json: 'Print machine-readable JSON (JavaScript Object Notation, 数据交换格式; always enabled)',
|
|
14
|
+
invalidNumber: 'Expected a positive integer.', invalidId: 'Invalid library asset identifier.',
|
|
15
|
+
invalidManifest: 'The library returned an invalid or unsafe file manifest.',
|
|
16
|
+
outputRequired: 'Specify exactly one of --output or --output-dir.',
|
|
17
|
+
outputExists: 'The output already exists. Choose a new path to preserve existing files.',
|
|
18
|
+
failed: 'Asset library command failed.',
|
|
19
|
+
},
|
|
20
|
+
zh: {
|
|
21
|
+
topic: '检索和下载有权访问的共享素材库内容',
|
|
22
|
+
list: '按条件分页列出素材库内容', search: '检索素材名称和内容元数据',
|
|
23
|
+
get: '读取素材详情', files: '列出素材或素材包内的全部文件', facets: '查看可用分类与标签',
|
|
24
|
+
download: '下载素材或完整素材包,保留依赖文件的相对路径',
|
|
25
|
+
query: '检索文本', id: '素材库中的素材编号', type: '素材类型', path: '素材库路径前缀',
|
|
26
|
+
category: '分类', subcategory: '子分类', tags: '标签,逗号分隔',
|
|
27
|
+
visibility: 'public(公共)或 team(团队)', team: '有权访问的团队编号', page: '页码,从 1 开始',
|
|
28
|
+
limit: '每页数量(1–100)', sort: 'modifiedAt | createdAt | name | referenceCount | size', order: 'asc | desc',
|
|
29
|
+
expand: '返回单独文件,不折叠素材包', output: '新的文件路径;素材包则为新的目录', outputDir: '下载内容的新目录',
|
|
30
|
+
json: '输出机器可读的 JSON (JavaScript Object Notation, 数据交换格式),始终启用',
|
|
31
|
+
invalidNumber: '请输入正整数。', invalidId: '素材库编号无效。',
|
|
32
|
+
invalidManifest: '素材库返回的文件清单无效或包含不安全路径。',
|
|
33
|
+
outputRequired: '请指定 --output 或 --output-dir,且只指定一项。',
|
|
34
|
+
outputExists: '输出路径已存在,请选择新路径以保留已有文件。', failed: '素材库命令执行失败。',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export function libraryMessage(key) {
|
|
38
|
+
return messages[(process.env.PLAYCRAFT_LANGUAGE || process.env.LANG || 'en').startsWith('zh') ? 'zh' : 'en'][key];
|
|
39
|
+
}
|
|
40
|
+
export class LibraryError extends Error {
|
|
41
|
+
code;
|
|
42
|
+
constructor(code) {
|
|
43
|
+
super(libraryMessage(code));
|
|
44
|
+
this.code = code;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { InvalidArgumentError, Option } from 'commander';
|
|
2
|
+
import { link, lstat, mkdir, mkdtemp, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, posix, resolve } from 'node:path';
|
|
4
|
+
import { AgentApiClient } from '../utils/agent-api-client.js';
|
|
5
|
+
import { resolveSandboxPath } from '../utils/sandbox-path.js';
|
|
6
|
+
import { libraryMessage as t, LibraryError } from './library-messages.js';
|
|
7
|
+
function assetId(value) {
|
|
8
|
+
if (!/^(?:\d{1,20}|[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/i.test(value))
|
|
9
|
+
throw new InvalidArgumentError(t('invalidId'));
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function positiveInteger(value) {
|
|
13
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value)))
|
|
14
|
+
throw new InvalidArgumentError(t('invalidNumber'));
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function filters(command) {
|
|
18
|
+
return command.option('--search <text>', t('query')).option('--type <type>', t('type'))
|
|
19
|
+
.option('--path <path>', t('path')).option('--category <category>', t('category'))
|
|
20
|
+
.option('--subcategory <category>', t('subcategory')).option('--tags <tags>', t('tags'))
|
|
21
|
+
.addOption(new Option('--visibility <scope>', t('visibility')).choices(['public', 'team']))
|
|
22
|
+
.option('--team-id <id>', t('team')).option('--page <number>', t('page'), positiveInteger, '1')
|
|
23
|
+
.option('--limit <number>', t('limit'), value => {
|
|
24
|
+
positiveInteger(value);
|
|
25
|
+
if (Number(value) > 100)
|
|
26
|
+
throw new InvalidArgumentError(t('limit'));
|
|
27
|
+
return value;
|
|
28
|
+
}, '30')
|
|
29
|
+
.addOption(new Option('--sort-by <field>', t('sort')).choices(['modifiedAt', 'createdAt', 'name', 'referenceCount', 'size']))
|
|
30
|
+
.addOption(new Option('--sort-order <order>', t('order')).choices(['asc', 'desc']))
|
|
31
|
+
.option('--expand-prefabs', t('expand')).option('--json', t('json'));
|
|
32
|
+
}
|
|
33
|
+
function query(options) {
|
|
34
|
+
return Object.fromEntries(Object.entries(options)
|
|
35
|
+
.filter(([key, value]) => key !== 'json' && value !== undefined && value !== false)
|
|
36
|
+
.map(([key, value]) => [key, String(value)]));
|
|
37
|
+
}
|
|
38
|
+
async function action(run) {
|
|
39
|
+
try {
|
|
40
|
+
console.log(JSON.stringify({ ok: true, result: await run() }, null, 2));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const code = error instanceof LibraryError ? error.code : 'failed';
|
|
44
|
+
console.error(`${t('failed')} ${error instanceof Error ? error.message : String(error)}`);
|
|
45
|
+
console.log(JSON.stringify({ ok: false, error: { code, message: error instanceof Error ? error.message : String(error) } }));
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function validatedFiles(manifest) {
|
|
50
|
+
if (!manifest || !['file', 'bundle'].includes(manifest.kind) || !Array.isArray(manifest.files) ||
|
|
51
|
+
!manifest.files.length || (manifest.kind === 'file' && manifest.files.length !== 1))
|
|
52
|
+
throw new LibraryError('invalidManifest');
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
for (const file of manifest.files) {
|
|
55
|
+
try {
|
|
56
|
+
assetId(file.id);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new LibraryError('invalidManifest');
|
|
60
|
+
}
|
|
61
|
+
const path = file.path;
|
|
62
|
+
if (typeof path !== 'string' || !path || path.startsWith('/') || /[\\:\x00-\x1f]/.test(path) ||
|
|
63
|
+
path.split('/').some(part => !part || part === '.' || part === '..') || posix.normalize(path) !== path)
|
|
64
|
+
throw new LibraryError('invalidManifest');
|
|
65
|
+
const key = path.toLowerCase();
|
|
66
|
+
if (seen.has(key))
|
|
67
|
+
throw new LibraryError('invalidManifest');
|
|
68
|
+
seen.add(key);
|
|
69
|
+
}
|
|
70
|
+
for (const key of seen) {
|
|
71
|
+
for (let parent = posix.dirname(key); parent !== '.'; parent = posix.dirname(parent)) {
|
|
72
|
+
if (seen.has(parent))
|
|
73
|
+
throw new LibraryError('invalidManifest');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return manifest.files;
|
|
77
|
+
}
|
|
78
|
+
export async function downloadLibraryAsset(client, id, options) {
|
|
79
|
+
if (Boolean(options.output) === Boolean(options.outputDir))
|
|
80
|
+
throw new LibraryError('outputRequired');
|
|
81
|
+
assetId(id);
|
|
82
|
+
const manifest = await client.get(`/asset-library/${encodeURIComponent(id)}/files`);
|
|
83
|
+
const files = validatedFiles(manifest);
|
|
84
|
+
const destination = resolve(resolveSandboxPath(options.output ?? options.outputDir));
|
|
85
|
+
const existing = await lstat(destination).catch(error => { if (error.code === 'ENOENT')
|
|
86
|
+
return undefined; throw error; });
|
|
87
|
+
if (existing)
|
|
88
|
+
throw new LibraryError('outputExists');
|
|
89
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
90
|
+
const staging = await mkdtemp(join(dirname(destination), '.playcraft-library-'));
|
|
91
|
+
const directory = manifest.kind === 'bundle' || Boolean(options.outputDir);
|
|
92
|
+
const downloaded = [];
|
|
93
|
+
try {
|
|
94
|
+
for (const file of files) {
|
|
95
|
+
const target = directory ? join(staging, file.path) : join(staging, 'content');
|
|
96
|
+
const { bytes } = await client.download(`/asset-library/${encodeURIComponent(file.id)}/file`, target);
|
|
97
|
+
downloaded.push({ id: file.id, path: directory ? join(destination, file.path) : destination, bytes });
|
|
98
|
+
}
|
|
99
|
+
// Recheck before publishing the complete bundle; failures never expose a partial download.
|
|
100
|
+
if (await lstat(destination).catch(error => { if (error.code === 'ENOENT')
|
|
101
|
+
return undefined; throw error; }))
|
|
102
|
+
throw new LibraryError('outputExists');
|
|
103
|
+
if (directory)
|
|
104
|
+
await rename(staging, destination);
|
|
105
|
+
else
|
|
106
|
+
await link(join(staging, 'content'), destination);
|
|
107
|
+
return { assetId: manifest.assetId, kind: manifest.kind, output: destination, files: downloaded };
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
await rm(staging, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export function registerLibraryCommands(program) {
|
|
114
|
+
const library = program.command('library').description(t('topic'));
|
|
115
|
+
filters(library.command('list').description(t('list')))
|
|
116
|
+
.action((options) => action(() => new AgentApiClient().get('/asset-library', query(options))));
|
|
117
|
+
filters(library.command('search').description(t('search')).argument('<query>', t('query')))
|
|
118
|
+
.action((search, options) => action(() => new AgentApiClient().get('/asset-library', query({ ...options, search }))));
|
|
119
|
+
library.command('facets').description(t('facets')).option('--path <path>', t('path'))
|
|
120
|
+
.addOption(new Option('--visibility <scope>', t('visibility')).choices(['public', 'team']))
|
|
121
|
+
.option('--team-id <id>', t('team')).option('--json', t('json'))
|
|
122
|
+
.action((options) => action(() => new AgentApiClient().get('/asset-library/facets', query(options))));
|
|
123
|
+
for (const command of ['get', 'files']) {
|
|
124
|
+
library.command(command).description(t(command)).argument('<assetId>', t('id'), assetId).option('--json', t('json'))
|
|
125
|
+
.action((id) => action(() => new AgentApiClient().get(`/asset-library/${encodeURIComponent(id)}${command === 'files' ? '/files' : ''}`)));
|
|
126
|
+
}
|
|
127
|
+
library.command('download').description(t('download')).argument('<assetId>', t('id'), assetId)
|
|
128
|
+
.option('--output <path>', t('output')).option('--output-dir <directory>', t('outputDir')).option('--json', t('json'))
|
|
129
|
+
.action((id, options) => action(() => downloadLibraryAsset(new AgentApiClient(), id, options)));
|
|
130
|
+
}
|