@playcraft/cli 0.1.50-beta → 0.1.52
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 +3 -62
- package/dist/agent/local-backend.js +1 -1
- package/dist/cli-root-help.js +0 -2
- package/dist/commands/build-all.js +19 -5
- package/dist/commands/build.js +28 -15
- package/dist/commands/create.js +33 -26
- package/dist/commands/image.js +6 -2
- package/dist/commands/login.js +42 -84
- package/dist/commands/skills.js +0 -4
- package/dist/commands/tools-3d.js +0 -11
- package/dist/commands/tools-generation.js +137 -69
- package/dist/commands/tools-image-context.js +29 -0
- package/dist/commands/tools-utils.js +12 -0
- package/dist/commands/tools.js +2 -2
- package/dist/config.js +4 -59
- package/dist/index.js +3 -40
- package/dist/remix/clone.js +0 -2
- package/dist/remix/init-template.js +0 -2
- package/dist/remix/pull.js +0 -2
- package/dist/remix/push.js +1 -21
- package/dist/utils/agent-api-client.js +30 -210
- package/dist/utils/image-asset-context.js +228 -0
- package/dist/utils/playcraft-http-client.js +4 -6
- package/package.json +5 -15
- package/project-template/.claude/agents/refs/ta-image-generation-detail.md +67 -123
- package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +135 -63
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
- package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
- package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
- package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +59 -59
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +135 -63
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
- package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +59 -59
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +135 -63
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
- package/SKILLS_REGISTRY.md +0 -149
- package/dist/auth/account-activation.js +0 -157
- package/dist/auth/messages.js +0 -30
- package/dist/commands/build-temp.js +0 -26
- package/dist/commands/platform-skills.generated.js +0 -19
- package/dist/commands/playable.js +0 -53
- package/dist/commands/tools-batch.js +0 -113
- package/dist/commands/video.js +0 -122
- package/dist/commands/workspace-runtime.js +0 -54
- package/dist/commands/workspace.js +0 -40
- package/dist/project-skills/builtin-import.js +0 -225
- package/dist/project-skills/commands.js +0 -77
- package/dist/project-skills/lifecycle.js +0 -28
- package/dist/project-skills/local-run.js +0 -112
- package/dist/project-skills/local-store.js +0 -221
- package/dist/project-skills/messages.js +0 -80
- package/dist/project-skills/reconcile.js +0 -62
- package/dist/project-skills/remote-cache.js +0 -51
- package/dist/project-skills/validation.js +0 -19
- package/dist/recording/messages.js +0 -40
- package/dist/recording/project-digest.js +0 -85
- package/dist/recording/recorder.js +0 -293
- package/dist/recording/scenario.js +0 -50
- package/dist/skill-registry/client.js +0 -345
- package/dist/skill-registry/commands.js +0 -180
- package/dist/skill-registry/evaluation-client.js +0 -156
- package/dist/skill-registry/evaluation-commands.js +0 -174
- package/dist/skill-registry/messages.js +0 -177
- package/dist/skill-registry/package.js +0 -275
- package/dist/skill-registry/platform-package.js +0 -100
- package/dist/utils/environment-endpoints.js +0 -17
- package/dist/utils/error-diagnostics.js +0 -20
- package/dist/utils/tool-operation-journal.js +0 -124
- package/dist/utils/video-evidence-git.js +0 -81
- package/dist/video-replication/client.js +0 -234
- package/dist/video-replication/comparison-evidence.js +0 -149
- package/dist/video-replication/comparison-files.js +0 -57
- package/dist/video-replication/comparison-log.js +0 -72
- package/dist/video-replication/comparison-messages.js +0 -104
- package/dist/video-replication/comparison-output.js +0 -63
- package/dist/video-replication/comparison-preflight.js +0 -66
- package/dist/video-replication/comparison.js +0 -230
- package/dist/video-replication/diagnostics.js +0 -41
- package/dist/video-replication/evidence.js +0 -165
- package/dist/video-replication/media.js +0 -112
- package/dist/video-replication/messages.js +0 -97
- package/dist/workspace/messages.js +0 -67
- package/dist/workspace/transfer-client.js +0 -233
- package/dist/workspace/transfer-errors.js +0 -48
- package/dist/workspace-runtime/batch/content-changes.js +0 -30
- package/dist/workspace-runtime/batch/control.js +0 -86
- package/dist/workspace-runtime/batch/deliver.js +0 -173
- package/dist/workspace-runtime/batch/freeze.js +0 -101
- package/dist/workspace-runtime/batch/generate.js +0 -249
- package/dist/workspace-runtime/batch/git.js +0 -40
- package/dist/workspace-runtime/batch/occupancy.js +0 -8
- package/dist/workspace-runtime/batch/policy.js +0 -70
- package/dist/workspace-runtime/batch/prepare.js +0 -317
- package/dist/workspace-runtime/batch/processes.js +0 -72
- package/dist/workspace-runtime/batch/records.js +0 -113
- package/dist/workspace-runtime/batch/routes.js +0 -61
- package/dist/workspace-runtime/batch/source-candidate.js +0 -86
- package/dist/workspace-runtime/batch/source-capture.js +0 -146
- package/dist/workspace-runtime/batch/source-failure.js +0 -43
- package/dist/workspace-runtime/batch/source-publish.js +0 -130
- package/dist/workspace-runtime/codex/app-server.js +0 -1065
- package/dist/workspace-runtime/codex/compact.js +0 -46
- package/dist/workspace-runtime/codex/config-toml.js +0 -107
- package/dist/workspace-runtime/codex/delivery-tools.js +0 -144
- package/dist/workspace-runtime/codex/jsonrpc-stdio.js +0 -145
- package/dist/workspace-runtime/codex/launcher-client.js +0 -177
- package/dist/workspace-runtime/codex/loopback.js +0 -146
- package/dist/workspace-runtime/codex/mcp-config.js +0 -39
- package/dist/workspace-runtime/codex/model-broker.js +0 -263
- package/dist/workspace-runtime/codex/native-adapter.js +0 -9
- package/dist/workspace-runtime/codex/permissions.js +0 -79
- package/dist/workspace-runtime/codex/thread-image.js +0 -38
- package/dist/workspace-runtime/codex/tool-response.js +0 -10
- package/dist/workspace-runtime/codex/ui-instructions.js +0 -4
- package/dist/workspace-runtime/codex/ui-tools.js +0 -203
- package/dist/workspace-runtime/codex/work-mode.js +0 -15
- package/dist/workspace-runtime/main.js +0 -10
- package/dist/workspace-runtime/persistence/journal.js +0 -365
- package/dist/workspace-runtime/persistence/native-subagents.js +0 -162
- package/dist/workspace-runtime/persistence/sqlite-client.js +0 -77
- package/dist/workspace-runtime/persistence/sqlite-worker.js +0 -1161
- package/dist/workspace-runtime/persistence/stopped-execution.js +0 -77
- package/dist/workspace-runtime/previews/diagnostics.js +0 -44
- package/dist/workspace-runtime/previews/output.js +0 -36
- package/dist/workspace-runtime/previews/publish.js +0 -283
- package/dist/workspace-runtime/previews/runtime-preview.js +0 -177
- package/dist/workspace-runtime/previews/single-file.js +0 -43
- package/dist/workspace-runtime/processes/managed-writes.js +0 -69
- package/dist/workspace-runtime/serve.js +0 -441
- package/dist/workspace-runtime/server/accept-operation.js +0 -61
- package/dist/workspace-runtime/server/asset-transfers.js +0 -63
- package/dist/workspace-runtime/server/auth.js +0 -21
- package/dist/workspace-runtime/server/connectivity.js +0 -209
- package/dist/workspace-runtime/server/desktop-host.js +0 -429
- package/dist/workspace-runtime/server/desktop-preview-config.js +0 -81
- package/dist/workspace-runtime/server/desktop-preview-service.js +0 -157
- package/dist/workspace-runtime/server/desktop-preview-static.js +0 -53
- package/dist/workspace-runtime/server/desktop-preview.js +0 -189
- package/dist/workspace-runtime/server/dispatch.js +0 -1632
- package/dist/workspace-runtime/server/endpoints.js +0 -21
- package/dist/workspace-runtime/server/execution-group.js +0 -90
- package/dist/workspace-runtime/server/http.js +0 -1340
- package/dist/workspace-runtime/server/lock.js +0 -53
- package/dist/workspace-runtime/server/operation-record.js +0 -33
- package/dist/workspace-runtime/server/rfb-watch-gate.js +0 -168
- package/dist/workspace-runtime/server/session-restore.js +0 -84
- package/dist/workspace-runtime/server/source-uploader.js +0 -138
- package/dist/workspace-runtime/server/submit-operation.js +0 -41
- package/dist/workspace-runtime/server/terminal-host.js +0 -366
- package/dist/workspace-runtime/server/types.js +0 -1
- package/dist/workspace-runtime/server/workspace-transfer.js +0 -168
- package/dist/workspace-runtime/verify-artifact.js +0 -59
- package/dist/workspace-runtime/workspaces/append-only-log.js +0 -62
- package/dist/workspace-runtime/workspaces/archive.js +0 -207
- package/dist/workspace-runtime/workspaces/artifact-events.js +0 -55
- package/dist/workspace-runtime/workspaces/artifact-files.js +0 -199
- package/dist/workspace-runtime/workspaces/artifact-receipts.js +0 -89
- package/dist/workspace-runtime/workspaces/artifacts.js +0 -282
- package/dist/workspace-runtime/workspaces/asset-library-import.js +0 -366
- package/dist/workspace-runtime/workspaces/asset-transfer-dependencies.js +0 -81
- package/dist/workspace-runtime/workspaces/asset-transfer-publish.js +0 -434
- package/dist/workspace-runtime/workspaces/asset-transfer-storage.js +0 -389
- package/dist/workspace-runtime/workspaces/attachment-content.js +0 -73
- package/dist/workspace-runtime/workspaces/attachments.js +0 -185
- package/dist/workspace-runtime/workspaces/batch-commit.js +0 -142
- package/dist/workspace-runtime/workspaces/binary-content.js +0 -65
- package/dist/workspace-runtime/workspaces/binary-write.js +0 -112
- package/dist/workspace-runtime/workspaces/cli-context.js +0 -23
- package/dist/workspace-runtime/workspaces/cli-credentials.js +0 -115
- package/dist/workspace-runtime/workspaces/composer-context.js +0 -147
- package/dist/workspace-runtime/workspaces/context-error.js +0 -2
- package/dist/workspace-runtime/workspaces/delivery-build.js +0 -331
- package/dist/workspace-runtime/workspaces/delivery-store.js +0 -168
- package/dist/workspace-runtime/workspaces/delivery.js +0 -64
- package/dist/workspace-runtime/workspaces/dependencies.js +0 -279
- package/dist/workspace-runtime/workspaces/developer-command.js +0 -98
- package/dist/workspace-runtime/workspaces/directories.js +0 -132
- package/dist/workspace-runtime/workspaces/directory-access.js +0 -37
- package/dist/workspace-runtime/workspaces/directory-attachments.js +0 -245
- package/dist/workspace-runtime/workspaces/directory-create.js +0 -57
- package/dist/workspace-runtime/workspaces/directory-maintenance.js +0 -111
- package/dist/workspace-runtime/workspaces/directory-mutations.js +0 -238
- package/dist/workspace-runtime/workspaces/directory-revisions.js +0 -56
- package/dist/workspace-runtime/workspaces/directory-stats.js +0 -49
- package/dist/workspace-runtime/workspaces/disk-snapshot.js +0 -31
- package/dist/workspace-runtime/workspaces/empty-template.js +0 -42
- package/dist/workspace-runtime/workspaces/file-content.js +0 -48
- package/dist/workspace-runtime/workspaces/files-search.js +0 -116
- package/dist/workspace-runtime/workspaces/files.js +0 -150
- package/dist/workspace-runtime/workspaces/git-credential-broker.js +0 -312
- package/dist/workspace-runtime/workspaces/git-credential-helper.js +0 -68
- package/dist/workspace-runtime/workspaces/git-direct-store.js +0 -155
- package/dist/workspace-runtime/workspaces/git.js +0 -670
- package/dist/workspace-runtime/workspaces/instructions.js +0 -56
- package/dist/workspace-runtime/workspaces/json5-edit.js +0 -313
- package/dist/workspace-runtime/workspaces/library-attachments.js +0 -268
- package/dist/workspace-runtime/workspaces/operation-log.js +0 -23
- package/dist/workspace-runtime/workspaces/optional-platform-skills.js +0 -14
- package/dist/workspace-runtime/workspaces/parameter-agent-inputs.js +0 -87
- package/dist/workspace-runtime/workspaces/parameter-agent-protocol.js +0 -29
- package/dist/workspace-runtime/workspaces/parameter-agent-store.js +0 -58
- package/dist/workspace-runtime/workspaces/parameter-agent.js +0 -282
- package/dist/workspace-runtime/workspaces/parameters.js +0 -295
- package/dist/workspace-runtime/workspaces/prepare.js +0 -278
- package/dist/workspace-runtime/workspaces/registry.js +0 -251
- package/dist/workspace-runtime/workspaces/remix-references.js +0 -301
- package/dist/workspace-runtime/workspaces/revisions.js +0 -31
- package/dist/workspace-runtime/workspaces/session-image.js +0 -104
- package/dist/workspace-runtime/workspaces/shared-git.js +0 -40
- package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +0 -95
- package/dist/workspace-runtime/workspaces/skills.js +0 -191
- package/dist/workspace-runtime/workspaces/transfer-quota.js +0 -69
- package/dist/workspace-runtime/workspaces/transfer.js +0 -471
- package/dist/workspace-runtime/workspaces/turn-changes.js +0 -75
- package/dist/workspace-runtime/workspaces/video-attachments.js +0 -49
- package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +0 -25
- package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +0 -79
- package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +0 -29
- package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +0 -37
- package/project-template-v2/.claude/agents/artist.md +0 -82
- package/project-template-v2/.claude/agents/developer.md +0 -153
- package/project-template-v2/.claude/agents/game-designer.md +0 -264
- package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +0 -533
- package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +0 -153
- package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +0 -27
- package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +0 -208
- package/project-template-v2/.claude/agents/refs/atom-skill-library.md +0 -81
- package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +0 -432
- package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +0 -252
- package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +0 -266
- package/project-template-v2/.claude/agents/refs/framework-mda.md +0 -147
- package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +0 -123
- package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +0 -88
- package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +0 -67
- package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +0 -120
- package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +0 -300
- package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +0 -495
- package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +0 -1141
- package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +0 -111
- package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +0 -365
- package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +0 -412
- package/project-template-v2/.claude/agents/reviewer.md +0 -75
- package/project-template-v2/.claude/agents/technical-artist.md +0 -86
- package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +0 -243
- package/project-template-v2/.claude/hooks/user-prompt.mjs +0 -133
- package/project-template-v2/.claude/settings.json +0 -33
- package/project-template-v2/.claude/skills/brainstorming/SKILL.md +0 -161
- package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +0 -270
- package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +0 -177
- package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +0 -354
- package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +0 -148
- package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +0 -56
- package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +0 -62
- package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
- package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +0 -309
- package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +0 -182
- package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +0 -123
- package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +0 -205
- package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +0 -280
- package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +0 -184
- package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +0 -306
- package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +0 -298
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +0 -123
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +0 -254
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +0 -324
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +0 -97
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +0 -118
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +0 -137
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +0 -397
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +0 -296
- package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +0 -122
- package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +0 -219
- package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +0 -373
- package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +0 -161
- package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +0 -215
- package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +0 -382
- package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +0 -423
- package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +0 -158
- package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +0 -100
- package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +0 -167
- package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +0 -270
- package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +0 -236
- package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +0 -130
- package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +0 -390
- package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +0 -23
- package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +0 -39
- package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +0 -87
- package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +0 -240
- package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +0 -226
- package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +0 -308
- package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +0 -275
- package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +0 -134
- package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +0 -142
- package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +0 -153
- package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +0 -386
- package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +0 -182
- package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +0 -72
- package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +0 -121
- package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -102
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +0 -127
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +0 -106
- package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +0 -128
- package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +0 -67
- package/project-template-v2/.claude/snapshot.mjs +0 -263
- package/project-template-v2/.playcraft/skills.lock.json +0 -152
- package/project-template-v2/CLAUDE.md +0 -146
- 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 +0 -254
- package/project-template-v2/docs/harness/iteration-1/generate-flow.md +0 -91
- package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +0 -214
- package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +0 -75
- package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +0 -63
- package/project-template-v2/docs/harness/iteration-2/context-flow.md +0 -223
- package/project-template-v2/docs/harness/iteration-2/generate-flow.md +0 -129
- package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +0 -267
- package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +0 -164
- package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +0 -115
- package/project-template-v2/docs/harness/orchestrator-flow.md +0 -364
- package/project-template-v2/docs/project-state.json +0 -60
- package/project-template-v2/docs/project-state.md +0 -72
- package/project-template-v2/docs/standards/README.md +0 -225
- package/project-template-v2/docs/standards/agent-behavior-standards.md +0 -174
- package/project-template-v2/docs/standards/artifacts/design-brief.md +0 -19
- package/project-template-v2/docs/standards/artifacts/design.md +0 -22
- package/project-template-v2/docs/standards/artifacts/game-code.md +0 -41
- package/project-template-v2/docs/standards/artifacts/todo-list.md +0 -41
- package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +0 -343
- package/project-template-v2/game/index.ts +0 -18
- package/project-template-v2/globals.d.ts +0 -51
- package/project-template-v2/index.css +0 -34
- package/project-template-v2/index.html +0 -18
- package/project-template-v2/main.ts +0 -9
- package/project-template-v2/package.json +0 -46
- package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +0 -31
- package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +0 -86
- package/project-template-v2/skills/_shared/scripts/dod-check.mjs +0 -153
- package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +0 -70
- package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +0 -91
- package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +0 -131
- package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +0 -80
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +0 -90
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +0 -93
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +0 -23
- package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +0 -230
- package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +0 -58
- package/project-template-v2/skills/_shared/scripts/state-get.mjs +0 -339
- package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +0 -39
- package/project-template-v2/skills/_shared/scripts/state-set.mjs +0 -94
- package/project-template-v2/skills/_shared/scripts/state-store.mjs +0 -783
- package/project-template-v2/skills/_shared/scripts/todo-add.mjs +0 -88
- package/project-template-v2/skills/_shared/scripts/todo-get.mjs +0 -130
- package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +0 -33
- package/project-template-v2/skills/_shared/scripts/todo-set.mjs +0 -47
- package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +0 -268
- package/project-template-v2/skills/_shared/scripts/verify-env.mjs +0 -285
- package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +0 -161
- package/project-template-v2/skills/playable-autoplay/SKILL.md +0 -176
- package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +0 -4
- package/project-template-v2/skills/playable-debug/SKILL.md +0 -116
- package/project-template-v2/skills/playable-debug/agents/openai.yaml +0 -4
- package/project-template-v2/skills/playable-debug/references/debug-config.md +0 -40
- package/project-template-v2/skills/playable-record/SKILL.md +0 -140
- package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +0 -104
- package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +0 -141
- package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +0 -425
- package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +0 -261
- package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +0 -398
- package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +0 -322
- package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +0 -105
- package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +0 -230
- package/project-template-v2/skills/playable-validate/SKILL.md +0 -61
- package/project-template-v2/skills/playable-validate/validation-rules.md +0 -14
- package/project-template-v2/skills/playable-verify-ui/SKILL.md +0 -63
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +0 -1
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +0 -218
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +0 -51
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +0 -59
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +0 -106
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +0 -116
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +0 -59
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +0 -64
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +0 -58
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +0 -333
- package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
- package/project-template-v2/tsconfig.json +0 -20
- package/project-template-v2/vite.config.ts +0 -27
- package/project-templates/batch-locks/phaser.yaml +0 -922
- package/project-templates/batch-locks/threejs.yaml +0 -978
- package/project-templates/empty/AGENTS.md +0 -41
- package/project-templates/empty/gitignore +0 -33
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { readdir } from 'node:fs/promises';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
|
-
import { canonicalReplicaJson, evaluateReplicaComparison, replicaDefaultPolicy, replicaPolicySchema, replicaValidationResultSchema, validateReplicaComparisonContext, validateReplicaComparisonContent, videoComparisonRequestSchema, videoComparisonRunSchema, videoModelCatalogSchema, } from '@playcraft/common/agent-tools';
|
|
6
|
-
import { ensureVideoEvidenceGitExcluded } from '../utils/video-evidence-git.js';
|
|
7
|
-
import { requireVideoId, resolveVideoWorkspace, uploadVideo } from './client.js';
|
|
8
|
-
import { VideoCliError, videoMessage } from './messages.js';
|
|
9
|
-
import { describeVideoFailure, remoteVideoFailure } from './diagnostics.js';
|
|
10
|
-
import { comparisonMessage, ComparisonCliError } from './comparison-messages.js';
|
|
11
|
-
import { comparisonSha256, readComparisonJson } from './comparison-files.js';
|
|
12
|
-
import { preflightComparison } from './comparison-preflight.js';
|
|
13
|
-
import { comparisonOutputPath, comparisonOutputRoot, writeComparisonJson } from './comparison-output.js';
|
|
14
|
-
async function prepareComparisonOutput(out, runId, resume) {
|
|
15
|
-
if (!(await ensureVideoEvidenceGitExcluded(process.cwd())))
|
|
16
|
-
console.error(videoMessage('gitExcludeWarning'));
|
|
17
|
-
await comparisonOutputRoot(out, true);
|
|
18
|
-
await comparisonOutputPath(out, 'request.json');
|
|
19
|
-
const names = await readdir(out);
|
|
20
|
-
if (!names.length && !resume)
|
|
21
|
-
return;
|
|
22
|
-
try {
|
|
23
|
-
const saved = await readComparisonJson(join(out, 'request.json'));
|
|
24
|
-
if (resume && saved.schemaVersion === 'playcraft.video-comparison-local-request.v1' && saved.runId === runId && saved.out === out)
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
catch { /* Preserve unrelated output directories. */ }
|
|
28
|
-
throw new VideoCliError('outputConflict', 64);
|
|
29
|
-
}
|
|
30
|
-
export async function readComparisonRun(client, runId, workspaceId, signal, cancel = false) {
|
|
31
|
-
const value = cancel ? await client.post(`/video-comparison/${runId}/cancel`, { workspaceId, runId }, { signal })
|
|
32
|
-
: await client.get(`/video-comparison/${runId}`, { workspaceId }, { signal });
|
|
33
|
-
let run;
|
|
34
|
-
try {
|
|
35
|
-
run = videoComparisonRunSchema.parse(value);
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
throw new ComparisonCliError('comparisonResult');
|
|
39
|
-
}
|
|
40
|
-
if (run.runId !== runId || run.workspaceId !== workspaceId || run.comparison && run.comparison.runId !== runId)
|
|
41
|
-
throw new ComparisonCliError('comparisonResult');
|
|
42
|
-
return run;
|
|
43
|
-
}
|
|
44
|
-
export function comparisonRunResult(command, run) {
|
|
45
|
-
const status = run.comparison?.status;
|
|
46
|
-
const exitCode = run.state === 'uncertain' ? 75 : ['failed', 'cancelled'].includes(run.state) ? 4
|
|
47
|
-
: status === 'fail' ? 2 : status === 'inconclusive' ? 3 : 0;
|
|
48
|
-
const error = remoteVideoFailure(run.state, run.error);
|
|
49
|
-
return { exitCode, envelope: { schemaVersion: 'playcraft.video-cli.v1', command, runId: run.runId, state: run.state,
|
|
50
|
-
ok: run.state === 'completed' && status === 'pass', result: run,
|
|
51
|
-
...(error && { error }) } };
|
|
52
|
-
}
|
|
53
|
-
export function validateComparisonResult(value, request, local) {
|
|
54
|
-
try {
|
|
55
|
-
const result = replicaValidationResultSchema.parse(value);
|
|
56
|
-
if (result.runId !== request.runId || result.model.configId !== request.modelConfigId || result.model.processing !== request.processing
|
|
57
|
-
|| result.recordingHash !== comparisonSha256(canonicalReplicaJson(request.recording))
|
|
58
|
-
|| result.policySha256 !== comparisonSha256(canonicalReplicaJson(request.policy))
|
|
59
|
-
|| canonicalReplicaJson(result.policy) !== canonicalReplicaJson(request.policy))
|
|
60
|
-
throw new Error('identity');
|
|
61
|
-
const expectedInputs = local.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source }));
|
|
62
|
-
if (canonicalReplicaJson([...result.inputs].sort((a, b) => a.id.localeCompare(b.id))) !== canonicalReplicaJson([...expectedInputs].sort((a, b) => a.id.localeCompare(b.id))))
|
|
63
|
-
throw new Error('sources');
|
|
64
|
-
const context = { inputs: result.inputs, analysis: request.analysis, recording: request.recording, policy: request.policy, processing: request.processing, fps: request.fps };
|
|
65
|
-
const content = validateReplicaComparisonContent({ summary: result.summary, evidence: result.evidence, alignments: result.alignments,
|
|
66
|
-
dimensions: result.dimensions, findings: result.findings, controlChecks: result.controlChecks,
|
|
67
|
-
timings: result.timings, missingEvidence: result.missingEvidence }, context);
|
|
68
|
-
// Recompute acceptance, including all missing evidence reported by the service.
|
|
69
|
-
const computed = evaluateReplicaComparison(content, context);
|
|
70
|
-
if (result.status !== computed.status || result.ok !== computed.ok || result.scope !== request.policy.scope
|
|
71
|
-
|| canonicalReplicaJson(result.coverage) !== canonicalReplicaJson(computed.coverage)
|
|
72
|
-
|| canonicalReplicaJson(result.timingChecks) !== canonicalReplicaJson(computed.timingChecks))
|
|
73
|
-
throw new Error('decision');
|
|
74
|
-
return result;
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
throw new ComparisonCliError('comparisonResult');
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
async function recheckComparisonInputs(saved, deps, signal) {
|
|
81
|
-
const actual = await preflightComparison({ reference: saved.referencePath, analysis: saved.analysisPath, candidate: saved.candidatePath,
|
|
82
|
-
recording: saved.recordingPath, out: saved.out, signal, probe: deps.probe });
|
|
83
|
-
if (canonicalReplicaJson(actual.recording) !== canonicalReplicaJson(saved.local.recording)
|
|
84
|
-
|| canonicalReplicaJson(actual.analysis) !== canonicalReplicaJson(saved.local.analysis)
|
|
85
|
-
|| canonicalReplicaJson(actual.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source })))
|
|
86
|
-
!== canonicalReplicaJson(saved.local.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source }))))
|
|
87
|
-
throw new ComparisonCliError('invalidRecording', 64);
|
|
88
|
-
return actual;
|
|
89
|
-
}
|
|
90
|
-
export async function executeVideoComparison(options, signal, deps) {
|
|
91
|
-
let saved;
|
|
92
|
-
let runId;
|
|
93
|
-
let out;
|
|
94
|
-
let submitted = false;
|
|
95
|
-
let stage = 'prepare';
|
|
96
|
-
try {
|
|
97
|
-
const workspaceId = await resolveVideoWorkspace(options.workspace);
|
|
98
|
-
if (options.resume && [options.reference, options.analysis, options.candidate, options.recording, options.policy, options.processing, options.fps, options.modelConfig].some(value => value !== undefined))
|
|
99
|
-
throw new ComparisonCliError('invalidComparison', 64);
|
|
100
|
-
if (!options.resume && (!options.reference || !options.analysis || !options.candidate || !options.recording))
|
|
101
|
-
throw new ComparisonCliError('invalidComparison', 64);
|
|
102
|
-
const processing = options.processing ?? 'static';
|
|
103
|
-
const fps = options.fps === undefined ? processing === 'static' ? 4 : undefined : Number(options.fps);
|
|
104
|
-
if (!['static', 'agentic'].includes(processing) || fps !== undefined && ![1, 2, 4, 8].includes(fps) || processing === 'agentic' && fps !== undefined)
|
|
105
|
-
throw new ComparisonCliError('invalidComparison', 64);
|
|
106
|
-
runId = options.resume ? requireVideoId(options.resume) : randomUUID();
|
|
107
|
-
out = resolve(options.out ?? join('.playcraft/video-replication', runId));
|
|
108
|
-
await prepareComparisonOutput(out, runId, !!options.resume);
|
|
109
|
-
let run;
|
|
110
|
-
if (options.resume) {
|
|
111
|
-
try {
|
|
112
|
-
saved = await readComparisonJson(join(out, 'request.json'), 8 * 1024 * 1024);
|
|
113
|
-
if (saved.schemaVersion !== 'playcraft.video-comparison-local-request.v1' || saved.runId !== runId || saved.workspaceId !== workspaceId || saved.out !== out || !saved.request)
|
|
114
|
-
throw new Error('identity');
|
|
115
|
-
saved.request = videoComparisonRequestSchema.parse(saved.request);
|
|
116
|
-
if (saved.request.runId !== runId || saved.request.workspaceId !== workspaceId)
|
|
117
|
-
throw new Error('request');
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
throw new ComparisonCliError('comparisonResume', 64);
|
|
121
|
-
}
|
|
122
|
-
submitted = true;
|
|
123
|
-
stage = 'probe';
|
|
124
|
-
saved.local = await preflightComparison({ reference: saved.referencePath, analysis: saved.analysisPath, candidate: saved.candidatePath,
|
|
125
|
-
recording: saved.recordingPath, out, signal, probe: deps.probe });
|
|
126
|
-
if (canonicalReplicaJson(saved.local.recording) !== canonicalReplicaJson(saved.request.recording)
|
|
127
|
-
|| canonicalReplicaJson(saved.local.analysis) !== canonicalReplicaJson(saved.request.analysis))
|
|
128
|
-
throw new ComparisonCliError('comparisonResume', 64);
|
|
129
|
-
// Recovery only reads the existing task. It cannot upload or submit another inference.
|
|
130
|
-
stage = 'poll';
|
|
131
|
-
run = await readComparisonRun(deps.client, runId, workspaceId, signal);
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
let policy;
|
|
135
|
-
try {
|
|
136
|
-
policy = options.policy ? replicaPolicySchema.parse(await readComparisonJson(resolve(options.policy))) : replicaDefaultPolicy;
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
throw new ComparisonCliError('invalidComparison', 64);
|
|
140
|
-
}
|
|
141
|
-
deps.progress('probing');
|
|
142
|
-
stage = 'probe';
|
|
143
|
-
const local = await preflightComparison({ reference: options.reference, analysis: options.analysis, candidate: options.candidate,
|
|
144
|
-
recording: options.recording, out, signal, probe: deps.probe });
|
|
145
|
-
try {
|
|
146
|
-
validateReplicaComparisonContext({ inputs: local.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source })),
|
|
147
|
-
analysis: local.analysis, recording: local.recording, policy, processing: processing, fps });
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
throw new ComparisonCliError('invalidComparison', 64);
|
|
151
|
-
}
|
|
152
|
-
stage = 'models';
|
|
153
|
-
const catalog = videoModelCatalogSchema.parse(await deps.client.get('/video-models', { workspaceId }, { signal }));
|
|
154
|
-
const configId = options.modelConfig ? requireVideoId(options.modelConfig) : catalog.defaultConfigId;
|
|
155
|
-
const model = catalog.models.find(item => item.configId === configId);
|
|
156
|
-
if (!model || !model.processing.includes(processing) || model.maxVideos < local.inputs.length)
|
|
157
|
-
throw new ComparisonCliError('comparisonUnavailable', 64);
|
|
158
|
-
saved = { schemaVersion: 'playcraft.video-comparison-local-request.v1', runId, workspaceId, out,
|
|
159
|
-
referencePath: resolve(options.reference), analysisPath: resolve(options.analysis), candidatePath: resolve(options.candidate), recordingPath: resolve(options.recording),
|
|
160
|
-
local, state: 'uploading', createdAt: new Date().toISOString() };
|
|
161
|
-
await writeComparisonJson(out, 'request.json', saved);
|
|
162
|
-
const inputs = [];
|
|
163
|
-
for (const input of local.inputs) {
|
|
164
|
-
deps.progress('uploading');
|
|
165
|
-
const mediaId = await uploadVideo(input.video, workspaceId, deps, signal, value => { stage = value; });
|
|
166
|
-
inputs.push({ id: input.id, role: input.role, mediaId });
|
|
167
|
-
}
|
|
168
|
-
stage = 'submit';
|
|
169
|
-
const request = videoComparisonRequestSchema.parse({ schemaVersion: 'playcraft.video-comparison-request.v1', workspaceId, runId,
|
|
170
|
-
inputs, analysis: local.analysis, recording: local.recording, policy, processing, ...(fps !== undefined && { fps }), modelConfigId: configId });
|
|
171
|
-
saved = { ...saved, request, state: 'submitting' };
|
|
172
|
-
await writeComparisonJson(out, 'request.json', saved);
|
|
173
|
-
await recheckComparisonInputs(saved, deps, signal);
|
|
174
|
-
deps.progress('submitting');
|
|
175
|
-
submitted = true;
|
|
176
|
-
const response = await deps.client.post('/video-comparison', request, { signal, onPrepared: async (operation) => {
|
|
177
|
-
saved = { ...saved, requestId: operation.requestId };
|
|
178
|
-
await writeComparisonJson(out, 'request.json', saved);
|
|
179
|
-
} });
|
|
180
|
-
try {
|
|
181
|
-
run = videoComparisonRunSchema.parse(response);
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
throw new ComparisonCliError('comparisonResult');
|
|
185
|
-
}
|
|
186
|
-
if (run.runId !== runId || run.workspaceId !== workspaceId)
|
|
187
|
-
throw new ComparisonCliError('comparisonResult');
|
|
188
|
-
}
|
|
189
|
-
stage = 'poll';
|
|
190
|
-
while (['pending', 'uploading', 'submitting', 'running'].includes(run.state)) {
|
|
191
|
-
deps.progress('waiting');
|
|
192
|
-
await delay(Math.max(250, Math.min(run.pollAfterMs, 10_000)), undefined, { signal });
|
|
193
|
-
run = await readComparisonRun(deps.client, runId, workspaceId, signal);
|
|
194
|
-
}
|
|
195
|
-
await writeComparisonJson(out, 'task.json', run);
|
|
196
|
-
if (run.state === 'uncertain' && saved)
|
|
197
|
-
await writeComparisonJson(out, 'request.json', { ...saved, state: 'uncertain' }).catch(() => { });
|
|
198
|
-
if (run.state !== 'completed') {
|
|
199
|
-
const result = comparisonRunResult('video compare', run);
|
|
200
|
-
result.envelope.artifacts = { request: join(out, 'request.json') };
|
|
201
|
-
return result;
|
|
202
|
-
}
|
|
203
|
-
if (!saved.request || !run.comparison)
|
|
204
|
-
throw new ComparisonCliError('comparisonResult');
|
|
205
|
-
await recheckComparisonInputs(saved, deps, signal);
|
|
206
|
-
const comparison = validateComparisonResult(run.comparison, saved.request, saved.local);
|
|
207
|
-
deps.progress('extracting');
|
|
208
|
-
stage = 'extract';
|
|
209
|
-
const artifacts = await deps.materialize({ comparison, request: saved.request, inputs: saved.local.inputs, out, signal });
|
|
210
|
-
await writeComparisonJson(out, 'request.json', { ...saved, state: 'completed' });
|
|
211
|
-
const result = comparisonRunResult('video compare', { ...run, comparison });
|
|
212
|
-
result.envelope.artifacts = artifacts;
|
|
213
|
-
result.envelope.result = { status: comparison.status, scope: comparison.scope, coverage: comparison.coverage };
|
|
214
|
-
return result;
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
const timeout = signal.aborted && signal.reason?.name === 'TimeoutError';
|
|
218
|
-
const interrupted = signal.aborted && !timeout;
|
|
219
|
-
const known = error instanceof VideoCliError || error instanceof ComparisonCliError;
|
|
220
|
-
const code = timeout ? 'timeoutReached' : interrupted ? 'interrupted' : known ? error.code : submitted ? 'uncertain' : 'comparisonFailed';
|
|
221
|
-
const exitCode = timeout ? 75 : interrupted ? 130 : known ? error.exitCode : submitted ? 75 : 4;
|
|
222
|
-
if (saved && out && submitted && (exitCode === 75 || exitCode === 130))
|
|
223
|
-
await writeComparisonJson(out, 'request.json', { ...saved, state: 'uncertain' }).catch(() => { });
|
|
224
|
-
const message = error instanceof ComparisonCliError ? comparisonMessage(error.code) : error instanceof VideoCliError ? videoMessage(error.code)
|
|
225
|
-
: timeout ? videoMessage('timeoutReached') : interrupted ? videoMessage('interrupted') : submitted ? videoMessage('uncertain') : comparisonMessage('comparisonFailed');
|
|
226
|
-
return { exitCode, envelope: { schemaVersion: 'playcraft.video-cli.v1', command: 'video compare', runId,
|
|
227
|
-
state: exitCode === 75 || exitCode === 130 ? 'uncertain' : 'failed', ok: false,
|
|
228
|
-
...(saved && out && { artifacts: { request: join(out, 'request.json') } }), error: { code, ...describeVideoFailure(message, error, stage), ...(interrupted && { remoteCancellationConfirmed: false }) } } };
|
|
229
|
-
}
|
|
230
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { videoMessage } from './messages.js';
|
|
2
|
-
import { redactDiagnosticText } from '../utils/error-diagnostics.js';
|
|
3
|
-
export { redactDiagnosticText as redactVideoDiagnostic } from '../utils/error-diagnostics.js';
|
|
4
|
-
const stageMessages = {
|
|
5
|
-
prepare: 'preparing', probe: 'probing', models: 'loadingModels',
|
|
6
|
-
'upload-init': 'initializingUpload', 'upload-content': 'uploading', 'upload-finalize': 'finalizingUpload',
|
|
7
|
-
submit: 'submitting', poll: 'waiting', extract: 'extracting', cancel: 'cancel',
|
|
8
|
-
};
|
|
9
|
-
/** Read messages and a bounded cause chain, never arbitrary error/response objects. */
|
|
10
|
-
export function videoFailureDetails(error) {
|
|
11
|
-
const messages = [], seen = new Set();
|
|
12
|
-
let current = error;
|
|
13
|
-
for (let depth = 0; depth < 5 && current != null && !seen.has(current); depth++) {
|
|
14
|
-
seen.add(current);
|
|
15
|
-
const record = typeof current === 'object' ? current : undefined;
|
|
16
|
-
const raw = typeof current === 'string' ? current : typeof record?.message === 'string' ? record.message : '';
|
|
17
|
-
const code = typeof record?.code === 'string' && /^[A-Z][A-Z0-9_]+$/.test(record.code) ? record.code : '';
|
|
18
|
-
const text = redactDiagnosticText(code && !raw.includes(code) ? `${code}: ${raw}` : raw).slice(0, 1600);
|
|
19
|
-
if (text && !messages.some(previous => previous.includes(text)))
|
|
20
|
-
messages.push(text);
|
|
21
|
-
current = record?.cause;
|
|
22
|
-
}
|
|
23
|
-
return messages.join(' <- ').slice(0, 4000) || undefined;
|
|
24
|
-
}
|
|
25
|
-
export function describeVideoFailure(summary, error, stage) {
|
|
26
|
-
const details = videoFailureDetails(error);
|
|
27
|
-
const reason = details?.startsWith(`${summary} <- `) ? details.slice(summary.length + 4)
|
|
28
|
-
: details && details !== summary ? details : undefined;
|
|
29
|
-
return {
|
|
30
|
-
message: [summary, stage && `${videoMessage('failureStage')}: ${videoMessage(stageMessages[stage])}.`,
|
|
31
|
-
reason && `${videoMessage('failureReason')}: ${reason}`].filter(Boolean).join(' '),
|
|
32
|
-
...(reason && { details: reason }), ...(stage && { stage }),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
export function remoteVideoFailure(state, error) {
|
|
36
|
-
if (!['failed', 'cancelled', 'uncertain'].includes(state))
|
|
37
|
-
return undefined;
|
|
38
|
-
const key = state === 'uncertain' ? 'uncertain' : 'remoteFailed';
|
|
39
|
-
return { code: state === 'uncertain' ? key : error?.code ?? key,
|
|
40
|
-
...describeVideoFailure(videoMessage(key), error?.code, 'poll') };
|
|
41
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readFile, readdir, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
|
-
import sharp from 'sharp';
|
|
5
|
-
import { videoUnderstandingAnalysisSchema, validateVideoUnderstandingContent, } from '@playcraft/common/agent-tools';
|
|
6
|
-
import { openVideo, runMediaTool, verifyVideo } from './media.js';
|
|
7
|
-
import { VideoCliError, videoMessage } from './messages.js';
|
|
8
|
-
import { ensureVideoEvidenceGitExcluded } from '../utils/video-evidence-git.js';
|
|
9
|
-
import { comparisonOutputPath, comparisonOutputRoot, writeComparisonArtifact, writeComparisonJson } from './comparison-output.js';
|
|
10
|
-
import { ComparisonCliError } from './comparison-messages.js';
|
|
11
|
-
async function videoOutput(action) {
|
|
12
|
-
try {
|
|
13
|
-
return await action();
|
|
14
|
-
}
|
|
15
|
-
catch (error) {
|
|
16
|
-
if (error instanceof ComparisonCliError && error.code === 'unsafeOutput')
|
|
17
|
-
throw new VideoCliError('unsafeOutput', 64);
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
export async function writeJsonAtomic(path, value) {
|
|
22
|
-
await videoOutput(async () => {
|
|
23
|
-
await comparisonOutputRoot(dirname(path), true);
|
|
24
|
-
await writeComparisonJson(dirname(path), basename(path), value);
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
export async function prepareArtifactDirectory(out, runId, resume) {
|
|
28
|
-
if (!(await ensureVideoEvidenceGitExcluded(process.cwd())))
|
|
29
|
-
console.error(videoMessage('gitExcludeWarning'));
|
|
30
|
-
await videoOutput(async () => {
|
|
31
|
-
await comparisonOutputRoot(out, true);
|
|
32
|
-
await comparisonOutputPath(out, 'request.json');
|
|
33
|
-
});
|
|
34
|
-
const names = await readdir(out);
|
|
35
|
-
if (!names.length && !resume)
|
|
36
|
-
return;
|
|
37
|
-
try {
|
|
38
|
-
const saved = JSON.parse(await readFile(join(out, 'request.json'), 'utf8'));
|
|
39
|
-
if (resume && saved.schemaVersion === 'playcraft.video-request.v1' && saved.runId === runId && saved.out === resolve(out))
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
catch { /* An unrelated or incomplete output directory must not be overwritten. */ }
|
|
43
|
-
throw new VideoCliError('outputConflict', 64);
|
|
44
|
-
}
|
|
45
|
-
export async function loadSavedRequest(out, runId, workspaceId) {
|
|
46
|
-
try {
|
|
47
|
-
await videoOutput(() => comparisonOutputPath(out, 'request.json'));
|
|
48
|
-
const value = JSON.parse(await readFile(join(out, 'request.json'), 'utf8'));
|
|
49
|
-
if (value.schemaVersion !== 'playcraft.video-request.v1' || value.runId !== runId || value.workspaceId !== workspaceId || value.out !== resolve(out) || typeof value.videoPath !== 'string')
|
|
50
|
-
throw new Error('invalid');
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
throw new VideoCliError('resumeMissing', 64);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
const escapeXml = (text) => text.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
|
58
|
-
const escapeMarkdown = (text) => text.replace(/([\\`*_[\]<>])/g, '\\$1');
|
|
59
|
-
export function renderVideoAnalysis(analysis, frames) {
|
|
60
|
-
const lines = [`# ${videoMessage('summary')}`, '', escapeMarkdown(analysis.summary), '', videoMessage('transcriptNotice'), ''];
|
|
61
|
-
for (const [key, items] of [
|
|
62
|
-
['observations', analysis.observations], ['inferences', analysis.inferences], ['unknowns', analysis.unknowns],
|
|
63
|
-
]) {
|
|
64
|
-
lines.push(`## ${videoMessage(key)}`, '');
|
|
65
|
-
for (const item of items) {
|
|
66
|
-
const confidence = 'confidence' in item ? ` (${item.confidence})` : '';
|
|
67
|
-
lines.push(`- **${escapeMarkdown(item.id)}**${confidence}: ${escapeMarkdown(item.text)}`);
|
|
68
|
-
}
|
|
69
|
-
lines.push('');
|
|
70
|
-
}
|
|
71
|
-
lines.push(`## ${videoMessage('events')}`, '');
|
|
72
|
-
for (const event of analysis.events)
|
|
73
|
-
lines.push(`- ${escapeMarkdown(event.id)} [${event.startMs}–${event.endMs} ms]: ${event.kind}`);
|
|
74
|
-
lines.push('', `## ${videoMessage('hints')}`, '');
|
|
75
|
-
for (const [key, items] of Object.entries(analysis.reconstructionHints)) {
|
|
76
|
-
lines.push(`### ${videoMessage(key === 'candidateRules' ? 'rules' : key)}`, '');
|
|
77
|
-
for (const item of items)
|
|
78
|
-
lines.push(`- ${escapeMarkdown(item)}`);
|
|
79
|
-
lines.push('');
|
|
80
|
-
}
|
|
81
|
-
lines.push(`## ${videoMessage('evidence')}`, '');
|
|
82
|
-
for (const evidence of analysis.evidence)
|
|
83
|
-
lines.push(`- ${escapeMarkdown(evidence.id)} [${evidence.startMs}–${evidence.endMs} ms]: [frame](${frames.get(evidence.id)})`);
|
|
84
|
-
return `${lines.join('\n')}\n`;
|
|
85
|
-
}
|
|
86
|
-
/** Materialize only validated original-video timestamps; the model never supplies file paths. */
|
|
87
|
-
export async function materializeVideoEvidence(input) {
|
|
88
|
-
let analysis;
|
|
89
|
-
try {
|
|
90
|
-
analysis = videoUnderstandingAnalysisSchema.parse(input.analysis);
|
|
91
|
-
validateVideoUnderstandingContent({
|
|
92
|
-
summary: analysis.summary, observations: analysis.observations, events: analysis.events,
|
|
93
|
-
evidence: analysis.evidence, inferences: analysis.inferences, unknowns: analysis.unknowns,
|
|
94
|
-
reconstructionHints: analysis.reconstructionHints,
|
|
95
|
-
}, analysis.source, input.request?.clip);
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
throw new VideoCliError('invalidResult');
|
|
99
|
-
}
|
|
100
|
-
// Refuse any redirected destination before replacing evidence from a previous attempt.
|
|
101
|
-
await videoOutput(async () => {
|
|
102
|
-
await comparisonOutputRoot(input.out, true);
|
|
103
|
-
for (const path of [...analysis.evidence.map(item => `keyframes/${item.id}.png`), 'analysis.json', 'analysis.md', 'manifest.json', 'contact-sheet.jpg'])
|
|
104
|
-
await comparisonOutputPath(input.out, path);
|
|
105
|
-
});
|
|
106
|
-
const file = await openVideo(input.sourcePath);
|
|
107
|
-
const frames = new Map();
|
|
108
|
-
const manifestFrames = [];
|
|
109
|
-
const tiles = [];
|
|
110
|
-
const width = 240;
|
|
111
|
-
const height = 200;
|
|
112
|
-
const columns = Math.min(4, analysis.evidence.length);
|
|
113
|
-
try {
|
|
114
|
-
await verifyVideo(file, analysis.source);
|
|
115
|
-
const frameOutput = await runMediaTool('ffprobe', [
|
|
116
|
-
'-v', 'error', '-protocol_whitelist', 'file,pipe', '-format_whitelist', 'mov,matroska,webm',
|
|
117
|
-
'-select_streams', 'v:0', '-show_entries', 'frame=best_effort_timestamp_time', '-of', 'csv=p=0', '/dev/fd/3',
|
|
118
|
-
], file.fd, input.signal);
|
|
119
|
-
const rawFrameTimes = frameOutput.split('\n').map(line => Number.parseFloat(line) * 1000).filter(Number.isFinite);
|
|
120
|
-
const firstFrameTime = rawFrameTimes[0] ?? 0;
|
|
121
|
-
const frameTimes = rawFrameTimes.map(time => time - firstFrameTime).filter(time => time >= 0 && time < analysis.source.durationMs);
|
|
122
|
-
if (!frameTimes.length)
|
|
123
|
-
throw new VideoCliError('invalidResult');
|
|
124
|
-
for (const [index, evidence] of analysis.evidence.entries()) {
|
|
125
|
-
input.signal?.throwIfAborted();
|
|
126
|
-
const path = `keyframes/${evidence.id}.png`;
|
|
127
|
-
// Quantize the model's suggested midpoint to a real decoded frame, including the final frame.
|
|
128
|
-
const requestedTimeMs = (evidence.startMs + evidence.endMs) / 2;
|
|
129
|
-
const timeMs = frameTimes.reduce((best, candidate) => Math.abs(candidate - requestedTimeMs) < Math.abs(best - requestedTimeMs) ? candidate : best);
|
|
130
|
-
await videoOutput(() => writeComparisonArtifact(input.out, path, temporary => runMediaTool('ffmpeg', [
|
|
131
|
-
'-v', 'error', '-nostdin', '-y', '-protocol_whitelist', 'file,pipe', '-format_whitelist', 'mov,matroska,webm',
|
|
132
|
-
'-ss', (Math.max(0, timeMs - 0.01) / 1000).toFixed(6), '-i', '/dev/fd/3', '-map', '0:v:0',
|
|
133
|
-
'-frames:v', '1', '-vf', 'scale=min(1280\\,iw):-2', '-threads', '1', temporary,
|
|
134
|
-
], file.fd, input.signal)));
|
|
135
|
-
const bytes = await readFile(join(input.out, path));
|
|
136
|
-
const metadata = await sharp(bytes).metadata();
|
|
137
|
-
if (!metadata.width || !metadata.height)
|
|
138
|
-
throw new VideoCliError('invalidResult');
|
|
139
|
-
frames.set(evidence.id, path);
|
|
140
|
-
manifestFrames.push({ evidenceId: evidence.id, timeMs, requestedTimeMs, path, sha256: createHash('sha256').update(bytes).digest('hex') });
|
|
141
|
-
const thumbnail = await sharp(bytes).resize(width, height - 30, { fit: 'contain', background: '#f4f4f4' }).png().toBuffer();
|
|
142
|
-
const label = Buffer.from(`<svg width="${width}" height="30"><rect width="100%" height="100%" fill="white"/><text x="8" y="20" font-size="12">${escapeXml(evidence.id)} ${(timeMs / 1000).toFixed(2)}s</text></svg>`);
|
|
143
|
-
const tile = await sharp({ create: { width, height, channels: 3, background: '#ffffff' } }).composite([{ input: thumbnail, top: 0, left: 0 }, { input: label, top: height - 30, left: 0 }]).png().toBuffer();
|
|
144
|
-
tiles.push({ input: tile, left: (index % columns) * width, top: Math.floor(index / columns) * height });
|
|
145
|
-
}
|
|
146
|
-
await verifyVideo(file, analysis.source);
|
|
147
|
-
}
|
|
148
|
-
finally {
|
|
149
|
-
await file.close();
|
|
150
|
-
}
|
|
151
|
-
const contactSheet = join(input.out, 'contact-sheet.jpg');
|
|
152
|
-
await videoOutput(() => writeComparisonArtifact(input.out, 'contact-sheet.jpg', temporary => sharp({ create: { width: columns * width, height: Math.ceil(tiles.length / columns) * height, channels: 3, background: '#dddddd' } }).composite(tiles).jpeg({ quality: 85 }).toFile(temporary)));
|
|
153
|
-
// Keep analysis.json compatible with the shared schema; local frame references live in the manifest.
|
|
154
|
-
const analysisPath = join(input.out, 'analysis.json');
|
|
155
|
-
const markdown = join(input.out, 'analysis.md');
|
|
156
|
-
await writeJsonAtomic(analysisPath, analysis);
|
|
157
|
-
await videoOutput(() => writeComparisonArtifact(input.out, 'analysis.md', temporary => writeFile(temporary, renderVideoAnalysis(analysis, frames), { mode: 0o600, flag: 'wx' })));
|
|
158
|
-
const manifest = join(input.out, 'manifest.json');
|
|
159
|
-
await writeJsonAtomic(manifest, {
|
|
160
|
-
schemaVersion: 'playcraft.video-evidence.v1', runId: analysis.runId, state: 'completed', source: analysis.source, model: analysis.model,
|
|
161
|
-
analysis: 'analysis.json', markdown: 'analysis.md', contactSheet: 'contact-sheet.jpg',
|
|
162
|
-
frames: manifestFrames, frameTimeSemantics: 'nearest-decoded-original-video-frame',
|
|
163
|
-
});
|
|
164
|
-
return { manifest, analysis: analysisPath, markdown, contactSheet, keyframes: manifestFrames.map(frame => join(input.out, frame.path)) };
|
|
165
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { open, realpath } from 'node:fs/promises';
|
|
4
|
-
import { isAbsolute } from 'node:path';
|
|
5
|
-
import { VIDEO_MAX_BYTES, videoSourceSchema, } from '@playcraft/common/agent-tools';
|
|
6
|
-
import { VideoCliError, videoMessage } from './messages.js';
|
|
7
|
-
/** No shell, external protocols, or unbounded child-process output. */
|
|
8
|
-
export function runMediaTool(tool, args, inputFd, signal) {
|
|
9
|
-
return new Promise((resolve, reject) => {
|
|
10
|
-
const override = process.env[tool === 'ffmpeg' ? 'FFMPEG_PATH' : 'FFPROBE_PATH'];
|
|
11
|
-
if (override && !isAbsolute(override)) {
|
|
12
|
-
reject(new VideoCliError('mediaTool'));
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
const child = spawn(override || tool, args, { stdio: ['ignore', 'pipe', 'pipe', inputFd], signal });
|
|
16
|
-
let output = '';
|
|
17
|
-
let diagnostic = '';
|
|
18
|
-
let settled = false;
|
|
19
|
-
const finish = (error) => {
|
|
20
|
-
if (settled)
|
|
21
|
-
return;
|
|
22
|
-
settled = true;
|
|
23
|
-
clearTimeout(timer);
|
|
24
|
-
if (error)
|
|
25
|
-
reject(error);
|
|
26
|
-
else
|
|
27
|
-
resolve(output);
|
|
28
|
-
};
|
|
29
|
-
const timer = setTimeout(() => {
|
|
30
|
-
child.kill('SIGKILL');
|
|
31
|
-
finish(new VideoCliError('mediaTool', 4, { cause: new Error(`${tool}: ${videoMessage('mediaTimedOut')}`) }));
|
|
32
|
-
}, 30_000);
|
|
33
|
-
child.stdout.on('data', chunk => {
|
|
34
|
-
output += chunk.toString();
|
|
35
|
-
if (output.length > 1024 * 1024) {
|
|
36
|
-
child.kill('SIGKILL');
|
|
37
|
-
finish(new VideoCliError('videoFormat', 4, { cause: new Error(`${tool}: ${videoMessage('mediaOutputLimit')}`) }));
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
child.stderr.on('data', chunk => { diagnostic = (diagnostic + chunk.toString()).slice(0, 4096); });
|
|
41
|
-
child.once('error', error => finish(error.name === 'AbortError' ? error : new VideoCliError('mediaTool', 4, { cause: error })));
|
|
42
|
-
child.once('close', code => finish(code === 0 ? undefined : new VideoCliError('videoFormat', 4, { cause: new Error(`${tool}: ${videoMessage('mediaExit')} ${code}. ${diagnostic}`) })));
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
export async function openVideo(path) {
|
|
46
|
-
try {
|
|
47
|
-
const file = await open(path, 'r');
|
|
48
|
-
const stat = await file.stat();
|
|
49
|
-
if (!stat.isFile()) {
|
|
50
|
-
await file.close();
|
|
51
|
-
throw new VideoCliError('videoMissing', 64);
|
|
52
|
-
}
|
|
53
|
-
if (stat.size <= 0 || stat.size > VIDEO_MAX_BYTES) {
|
|
54
|
-
await file.close();
|
|
55
|
-
throw new VideoCliError('videoQuota', 64);
|
|
56
|
-
}
|
|
57
|
-
return file;
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
if (error instanceof VideoCliError)
|
|
61
|
-
throw error;
|
|
62
|
-
throw new VideoCliError('videoMissing', 64, { cause: error });
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
async function digests(file) {
|
|
66
|
-
const sha = createHash('sha256');
|
|
67
|
-
const md5 = createHash('md5');
|
|
68
|
-
for await (const chunk of file.createReadStream({ autoClose: false, start: 0 })) {
|
|
69
|
-
sha.update(chunk);
|
|
70
|
-
md5.update(chunk);
|
|
71
|
-
}
|
|
72
|
-
return { sha256: sha.digest('hex'), contentMd5: md5.digest('base64') };
|
|
73
|
-
}
|
|
74
|
-
export async function probeVideo(path, signal) {
|
|
75
|
-
const absolute = await realpath(path).catch(error => { throw new VideoCliError('videoMissing', 64, { cause: error }); });
|
|
76
|
-
const file = await openVideo(absolute);
|
|
77
|
-
try {
|
|
78
|
-
const stat = await file.stat();
|
|
79
|
-
const magic = Buffer.alloc(16);
|
|
80
|
-
await file.read(magic, 0, magic.length, 0);
|
|
81
|
-
const isIso = magic.subarray(4, 8).toString('ascii') === 'ftyp';
|
|
82
|
-
const isWebm = magic.readUInt32BE(0) === 0x1a45dfa3;
|
|
83
|
-
if (!isIso && !isWebm)
|
|
84
|
-
throw new VideoCliError('videoFormat', 64);
|
|
85
|
-
const output = await runMediaTool('ffprobe', [
|
|
86
|
-
'-v', 'error', '-protocol_whitelist', 'file,pipe', '-format_whitelist', 'mov,matroska,webm',
|
|
87
|
-
'-show_entries', 'format=duration,format_name:stream=codec_type,width,height,duration',
|
|
88
|
-
'-of', 'json', '/dev/fd/3',
|
|
89
|
-
], file.fd, signal);
|
|
90
|
-
const result = JSON.parse(output);
|
|
91
|
-
const video = result.streams?.find(stream => stream.codec_type === 'video');
|
|
92
|
-
if (!video)
|
|
93
|
-
throw new VideoCliError('videoFormat', 64);
|
|
94
|
-
const durationMs = Number(result.format?.duration ?? video.duration) * 1000;
|
|
95
|
-
const hashes = await digests(file);
|
|
96
|
-
const parsed = videoSourceSchema.safeParse({
|
|
97
|
-
sha256: hashes.sha256, sizeBytes: stat.size, durationMs, width: video.width, height: video.height,
|
|
98
|
-
mimeType: isWebm ? 'video/webm' : magic.subarray(8, 12).toString('ascii') === 'qt ' ? 'video/quicktime' : 'video/mp4',
|
|
99
|
-
});
|
|
100
|
-
if (!parsed.success)
|
|
101
|
-
throw new VideoCliError('videoQuota', 64);
|
|
102
|
-
return { path: absolute, source: parsed.data, contentMd5: hashes.contentMd5 };
|
|
103
|
-
}
|
|
104
|
-
finally {
|
|
105
|
-
await file.close();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
export async function verifyVideo(file, expected) {
|
|
109
|
-
const stat = await file.stat();
|
|
110
|
-
if (stat.size !== expected.sizeBytes || (await digests(file)).sha256 !== expected.sha256)
|
|
111
|
-
throw new VideoCliError('sourceChanged', 64);
|
|
112
|
-
}
|