gflow-cli 0.13.0__tar.gz → 0.14.0__tar.gz
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.
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/CHANGELOG.md +34 -1
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/PKG-INFO +1 -1
- gflow_cli-0.14.0/PR162_MOVIE_CHARACTER_REVIEW.md +137 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/INDEX.md +1 -0
- gflow_cli-0.14.0/docs/MOVIE.md +141 -0
- gflow_cli-0.14.0/docs/schemas/movie-handoff.schema.json +59 -0
- gflow_cli-0.14.0/docs/superpowers/plans/2026-06-06-movie-p0-asyncio-hotfix.md +223 -0
- gflow_cli-0.14.0/docs/superpowers/plans/2026-06-06-movie-p1-composition-manifest.md +1208 -0
- gflow_cli-0.14.0/docs/superpowers/plans/2026-06-06-movie-p2-native-identity-voice.md +612 -0
- gflow_cli-0.14.0/docs/superpowers/specs/2026-06-06-movie-consistency-design.md +310 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/pyproject.toml +2 -1
- gflow_cli-0.14.0/scripts/dev/make_project.py +41 -0
- gflow_cli-0.14.0/scripts/dev/patch_character.py +58 -0
- gflow_cli-0.14.0/scripts/dev/spike_movie_attach_payload.py +250 -0
- gflow_cli-0.14.0/scripts/dev/spike_movie_entity_recon.py +190 -0
- gflow_cli-0.14.0/scripts/dev/spike_movie_gen_capture.py +225 -0
- gflow_cli-0.14.0/scripts/dev/spike_movie_picker_select.py +327 -0
- gflow_cli-0.14.0/scripts/dev/spike_movie_voice_list.py +153 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/__init__.py +1 -1
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/client.py +4 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/routes.py +9 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/base.py +1 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/ui_automation.py +95 -50
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/ui_automation_video.py +221 -12
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/video.py +16 -4
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/cli.py +2 -0
- gflow_cli-0.14.0/src/gflow_cli/cli_movie.py +747 -0
- gflow_cli-0.14.0/src/gflow_cli/composition.py +312 -0
- gflow_cli-0.14.0/src/gflow_cli/movie_manifest.py +480 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tasks/lessons.md +24 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_client.py +1 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_video.py +1 -1
- gflow_cli-0.14.0/tests/api/test_video_request.py +48 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_ui_automation_video.py +117 -0
- gflow_cli-0.14.0/tests/cli/test_cli_movie.py +906 -0
- gflow_cli-0.14.0/tests/cli/test_movie_manifest.py +483 -0
- gflow_cli-0.14.0/tests/composition/test_character.py +43 -0
- gflow_cli-0.14.0/tests/composition/test_compose_prompt.py +93 -0
- gflow_cli-0.14.0/tests/composition/test_handoff.py +134 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/uv.lock +183 -1
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/README.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/active.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/branch-review.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/changelog.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/check.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/doc-review.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/known-issues.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/next.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/plan.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/pr-council-review.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/predict.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/release.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/scenario.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.claude/commands/gflow/status.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.continue-here.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.env.template +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.gitattributes +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.github/CODEOWNERS +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.github/copilot-instructions.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.github/dependabot.yml +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.github/workflows/ci.yml +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.github/workflows/external-pr-triage.yml +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.github/workflows/governance-advisory.yml +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.github/workflows/governance-benchmark.yml +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.github/workflows/release.yml +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.gitignore +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.gitleaks.toml +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.pre-commit-config.yaml +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/.secrets.baseline +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/AGENTS.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/CLAUDE.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/CONFIGURATION.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/CONTRIBUTING.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/DISCLAIMER.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/GEMINI.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/KNOWN_ISSUES.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/LICENSE +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/PLAN.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/README.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/RELEASE.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/ROADMAP.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/conftest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docker-compose.yml +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/AGENT_GUIDE.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/ARCHITECTURE.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/AUTHENTICATION.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/CHARACTER.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/CHARACTER_RECON.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/CONFIGURATION.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/DATA_LAYER.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/DEBUGGING.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/DEMOS.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/DEVELOPMENT.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/E2E_TESTING.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/EXTERNAL_STORAGE.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/GITHUB.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/GOVERNANCE_BENCHMARK.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_data_layer.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_image_batch.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_v0.10.0.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_v0.11.0.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_v0.12.0.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_v0.13.0.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_v0.7.0.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_v0.8.1.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_v0.9.0.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_v0.9.1.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/LIVE_VERIFICATION_video_download.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/PROJECT_STATUS.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/SECURITY.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/USAGE.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/USER_GUIDE.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/assets/demo-split-pf.gif +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/assets/example-run.gif +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/superpowers/2026-05-17-issue-15-handover.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/superpowers/character-scenario.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/superpowers/plans/2026-06-01-governance-enforcement-advisory/PLAN.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/superpowers/plans/2026-06-04-character-create-recording.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/docs/superpowers/verifications/2026-05-11-phase-4-stage-g.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/examples/README.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/examples/batch_from_config.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/examples/multi_prompt_t2i.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/examples/sample_config.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/examples/sample_prompts.txt +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/examples/single_image_t2i.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/examples/workflow_chain.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/llms.txt +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/README.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/01_upload_image.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/04_archive_workflow.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/05_createProject.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/06_batchGenerateImages.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/08_batchAsyncGenerateVideoStartAndEndImage.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/09_batchAsyncGenerateVideoReferenceImages.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/10_batchCheckAsyncVideoGenerationStatus_successful.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/11_batchCheckAsyncVideoGenerationStatus_failed.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/12_create_scene.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/13_sceneWorkflows_update.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/14_get_scene_workflows.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/samples/captured/15_commit_flowWorkflow.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/ci/check_doc_links.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/ci/check_materiality.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/ci/check_repo_hygiene.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/debug_editor.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/debug_gen_settings.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/debug_settings.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/_recording_client.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/_spike_common.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/active_plan.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/capture_i2v_frame_slots_dom.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/capture_i2v_intercept_submit.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/capture_i2v_model_select_repro.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/capture_i2v_post_bind_state.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/capture_image_add_media_dom.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/capture_locale_invariants.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/cdp_drive_and_probe.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/character_create_spike.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/character_create_spike_v2.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/dump_character_selectors.js +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/materiality_backtest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/monitor_pr_38.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/record_flow_capture.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/skillopt/README.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/skillopt/harness.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/skillopt/tasks.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/spike_char_editor_dom.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/spike_char_gen_capture.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/dev/spike_patch_entity.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/diag_capture_flow_traffic.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/diag_recaptcha_mint.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/record_demo.ps1 +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/smoke_image.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/smoke_real_chrome_image.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/smoke_video_editor.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/smoke_worker_style.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/scripts/verify_chrome_auth_viability.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/skills/README.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/skills/gflow-cli/SKILL.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/skills/plan/SKILL.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/skills/pr-council-review/SKILL.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/skills/predict/SKILL.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/skills/scenario/SKILL.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/skills/status/SKILL.md +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/sonar-project.properties +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/__main__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/_cli_helpers.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/_retry.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/_sapisidhash.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/character.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/dto.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/image.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/recaptcha.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/scene.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/_common.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/_fingerprint.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/experimental/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/experimental/bearer.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/experimental/evaluate_fetch.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/api/transports/experimental/sapisidhash.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/auth/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/auth/base.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/auth/factory.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/auth/internal_chromium.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/auth/real_chrome.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/auth/strategies.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/auth/verification.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/browser_manager.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/chain.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/chain_manifest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/cli_character.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/cli_data.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/cli_image.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/cli_models.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/cli_run.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/cli_scene.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/cli_video.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/config.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/chain_repo.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/migrations/0001_initial.sql +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/migrations/0002_add_cloud_storage.sql +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/migrations/0003_add_scene_tables.sql +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/migrations/0004_add_scene_output_path.sql +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/migrations/0005_add_chain_links.sql +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/migrations/0006_add_operations_metadata.sql +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/migrations/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/models.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/queries.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/recorder.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/redaction.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/repository.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/data/store.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/errors.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/exceptions.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/image_batch.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/json_output.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/manifest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/media.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/observability.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/paths.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/profile_store.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/services/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/services/character_create.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/src/gflow_cli/storage.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/test_assets/sample_batch.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/test_assets/sample_batch.tsv +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/test_assets/sample_batch_invalid.tsv +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/fixtures/character_gen_response.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/fixtures/patch_entity_response.json +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_aisandbox_auth_error.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_aisandbox_auth_headers.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_bearer_redaction.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_character.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_client_character.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_client_delete_characters.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_client_generate_character.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_client_image.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_client_launch_kwargs.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_client_patch_entity.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_client_scene.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_concurrency.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_dto.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_image.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_image_dto.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_post_json_aisandbox_auth.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_recaptcha.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_retry.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_routes.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_routes_character.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_routes_scene.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_sapisidhash_helper.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/test_scene_models.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_base.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_bearer.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_common.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_evaluate_fetch.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_factory.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_fingerprint.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_sapisidhash.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_transport_timeout.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_ui_automation.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_ui_automation_batch.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_ui_automation_image_mode.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/api/transports/test_ui_character_editor.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/auth/strategies/test_factory.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/auth/strategies/test_strategies.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/auth/test_verification.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_auth_list.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_character.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_character_create.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_data.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_image.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_image_seed_removed.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_models.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_run.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_scene.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_video.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_cli_video_chain.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_error_handling.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_helpers.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/cli/test_t2i_multi_prompt.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/conftest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_chain_repo.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_find_incomplete_character.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_models.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_packaging.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_recorder.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_recorder_character.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_redaction.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_repository.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_scene_persistence.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_settings_and_errors.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/data/test_store_migrations.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/dev/test_record_flow_capture.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/dev/test_recording_client.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/conftest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_aisandbox_auth_live.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_auth_verification_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_chain_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_character_create_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_data_layer_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_i2v_flags_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_image_batch_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_image_i2i_ref_cap_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_json_output_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_locale_selectors_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_scene_compose_live.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_transports_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_video_r2v_ref_cap_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/e2e/test_video_t2v_e2e.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/auth.feature +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/auth_login.feature +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/character_create.feature +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/character_read.feature +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/conftest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/image.feature +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/test_auth_login_steps.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/test_auth_steps.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/test_character_create_steps.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/test_character_read_steps.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/test_image_steps.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/test_step_collision_guard.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/test_video_chain_steps.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/features/video_chain.feature +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/fixtures/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/fixtures/seeded_catalog.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/image_batch/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/image_batch/test_image_manifest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/image_batch/test_observability_events.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/integration/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/integration/conftest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/integration/constants.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/integration/test_storage_gcs.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/integration/test_storage_s3.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/scripts/test_capture_locale_invariants.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/scripts/test_check_materiality.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/scripts/test_check_repo_hygiene.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/scripts/test_materiality_backtest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/services/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/services/test_character_create_redaction.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/services/test_character_create_saga.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/services/test_character_gen_no_direct_post.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/smoke/__init__.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/smoke/test_profile_account_smoke.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/smoke/test_real_flow.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_auth.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_browser_manager.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_chain.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_chain_manifest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_cli_data.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_config.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_conftest_isolation.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_data_queries.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_documentation_gate.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_errors.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_errors_403.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_json_output.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_manifest.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_marker_registry.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_media.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_observability.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_paths.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_profile_store.py +0 -0
- {gflow_cli-0.13.0 → gflow_cli-0.14.0}/tests/test_smoke.py +0 -0
|
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.14.0] — 2026-06-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`gflow movie` — multi-scene, character-consistent video generation.** A TOML
|
|
15
|
+
manifest (`gflow movie template` / `gflow movie run`) drives a sequence of clips
|
|
16
|
+
that reuse a single Flow CHARACTER entity (reference-to-video) so the same face
|
|
17
|
+
and voice carry across every scene. Generate-only by default; `--stitch`
|
|
18
|
+
produces an ffmpeg preview concat; runs are crash-resumable via the sibling
|
|
19
|
+
`<manifest>-state.json`; a versioned handoff manifest is written for downstream
|
|
20
|
+
composition (e.g. Remotion). Deterministic prompt assembly (`composition.py`),
|
|
21
|
+
scene = clip.
|
|
22
|
+
- **`docs/MOVIE.md`** — manifest format, the run lifecycle (the headed browser is
|
|
23
|
+
required through generate → poll → download), the character-entity attach
|
|
24
|
+
mechanism, and the best-effort consistency model.
|
|
25
|
+
- Dev utilities: `scripts/dev/make_project.py` (create a Flow project) and
|
|
26
|
+
`scripts/dev/patch_character.py` (rename / set voice + personality on an entity).
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **R2V character reuse now actually rides the wire.** The entity is attached via
|
|
31
|
+
the resource picker's **Personagens tab → right-click → "Incluir no comando"**
|
|
32
|
+
(which stages `referenceEntities`; a left-click on the Tudo tile only stages the
|
|
33
|
+
thumbnail as a `referenceImage`). The submit backstop now reads the response's
|
|
34
|
+
real `media[].mediaMetadata.requestData.videoGenerationRequestData.videoGenerationEntityInputs`
|
|
35
|
+
path instead of the request-shape `requests[].referenceEntities` — which had
|
|
36
|
+
false-rejected every successful entity generation. `omni-flash` R2V verified to
|
|
37
|
+
carry the entity.
|
|
38
|
+
- Cleared pre-existing type/test debt: `pyright src` is clean again (the missing
|
|
39
|
+
`project_id` parameter was added to the `VideoCapableTransport` protocol and the
|
|
40
|
+
`_enter_editor` type stub); regenerated `uv.lock` (jsonschema dev dependency).
|
|
41
|
+
|
|
10
42
|
## [0.13.0] — 2026-06-04
|
|
11
43
|
|
|
12
44
|
### Added
|
|
@@ -1402,7 +1434,8 @@ shell-script template that branches on these codes.
|
|
|
1402
1434
|
|
|
1403
1435
|
First skeleton. Not functional end-to-end yet.
|
|
1404
1436
|
|
|
1405
|
-
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.
|
|
1437
|
+
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.14.0...HEAD
|
|
1438
|
+
[0.14.0]: https://github.com/ffroliva/gflow-cli/compare/v0.13.0...v0.14.0
|
|
1406
1439
|
[0.13.0]: https://github.com/ffroliva/gflow-cli/compare/v0.12.0...v0.13.0
|
|
1407
1440
|
[0.12.0]: https://github.com/ffroliva/gflow-cli/compare/v0.11.0...v0.12.0
|
|
1408
1441
|
[0.11.0]: https://github.com/ffroliva/gflow-cli/compare/v0.10.0...v0.11.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gflow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: Unofficial CLI for Google Flow — drive Veo image-to-video generations from the terminal.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ffroliva/gflow-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/ffroliva/gflow-cli/issues
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# PR #162 Review — Movie Orchestration Character Consistency
|
|
2
|
+
|
|
3
|
+
**Branch:** `pr162` · **Scope:** `gflow movie` orchestrator (`cli_movie.py`, `movie_manifest.py`) + video transport
|
|
4
|
+
**Verdict:** Sound foundation, correct project consolidation, but the headline goal (consistent character) is **not achievable with the current r2v-by-uploaded-image approach** — and there is a **release-blocking `NameError` that aborts every multi-scene run after scene 1.**
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## TL;DR
|
|
9
|
+
|
|
10
|
+
| # | Objective | Finding |
|
|
11
|
+
|---|-----------|---------|
|
|
12
|
+
| 0 | *(not in brief)* | 🔴 **CRITICAL** — `asyncio.sleep(5)` at `cli_movie.py:344` with **no `import asyncio`** → `NameError` on the 2nd scene of every movie, outside the try/except → whole run aborts after 1 scene. |
|
|
13
|
+
| 1 | Native character integration | ✅ **Feasible & protocol-ready.** Flow's wire has a native field `referenceEntities:[{entityId}]` on `video:batchAsyncGenerateVideoReferenceImages` (live-verified, `docs/CHARACTER.md §6.6/§8`). The entity_id is *already saved* in state but never used. Needs a new transport path (composer resource-picker → "Personagens") + DTO field. |
|
|
14
|
+
| 2 | r2v vs i2v / chain | ✅ The chaining engine **already exists** (`chain.py`, `media.extract_last_frame`, `gflow video chain`). It's just not wired into `movie.toml`. r2v+entity = identity; i2v-chain = motion continuity; **mutually exclusive per generation** (DTO enforces frames XOR references). |
|
|
15
|
+
| 3 | Project consolidation | ✅ **Correct and it's the enabler.** `manifest.project` is threaded to character-create *and* every scene; `_enter_editor` deep-links to that project. This is exactly the precondition that makes native `referenceEntities` selection possible. |
|
|
16
|
+
| 4 | Error handling (`generate_resp`/403) | ✅ **Already fixed.** `generate_resp={}` bound before `try` (`:1421`); 403→`WafRejectionError`, silent→`TimeoutError`; listeners freed in `finally`. No UnboundLocal remains. |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 0. 🔴 CRITICAL — `asyncio` is not imported (multi-scene runs crash)
|
|
21
|
+
|
|
22
|
+
`cli_movie.py:342-345`:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
# reCAPTCHA cooldown
|
|
26
|
+
if completed_scene_ids:
|
|
27
|
+
await asyncio.sleep(5) # NameError: name 'asyncio' is not defined
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- `asyncio` appears **exactly once** in the file (the usage); it is **not** in the import block (`:17-44`).
|
|
31
|
+
- `completed_scene_ids` is non-empty from the 2nd scene onward (appended at `:373`, and on resume at `:332`).
|
|
32
|
+
- The call sits **outside** the per-scene `try/except` (`try` starts at `:346`), so the `NameError` propagates out of `_run_movie` → caught by `run_with_handlers` → **the entire movie aborts after exactly one scene**, regardless of `--continue-on-error`.
|
|
33
|
+
- On *resume* it's worse: the first already-completed scene appends to `completed_scene_ids`, so the very first new scene hits the cooldown and crashes → **resume can make zero forward progress.**
|
|
34
|
+
|
|
35
|
+
**Why it escaped CI:** the only async-orchestrator test (`tests/cli/test_cli_movie.py:368 test_happy_path_no_characters`) uses a **single** scene. No test drives `_run_movie` with ≥2 scenes, so the cooldown branch is never executed. (The `out/stickman-v3/` artifacts predate this regression — file mtime is later than the renders.)
|
|
36
|
+
|
|
37
|
+
**Fix (one line):** add `import asyncio` to the import block. **Then add a 2-scene orchestrator test** so this can't regress (see §6).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 1. Native character integration — the real fix for divergence
|
|
42
|
+
|
|
43
|
+
### Current behaviour (confirms the brief)
|
|
44
|
+
- `character_create` downloads face/body to **local disk**; `CharacterState` stores both `entity_id` *and* `image_paths` (`cli_movie.py:454-457`).
|
|
45
|
+
- `_collect_refs` (`cli_movie.py:470-483`) returns **only `image_paths`** for r2v. `entity_id` is captured and then ignored.
|
|
46
|
+
- `_generate_scene` passes those local paths as `reference_images`; the transport's `_attach_references` (`ui_automation_video.py:1092`) **re-uploads each file** via the "Add Media" dialog → fresh media IDs every scene → Flow sees unrelated assets → the Stickman drifts. **Exactly the diagnosed flaw.**
|
|
47
|
+
|
|
48
|
+
### The native path exists and is verified
|
|
49
|
+
From `docs/CHARACTER.md §6.6` (live-verified 2026-06-02, `labs.google23.har`):
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
"video:batchAsyncGenerateVideoReferenceImages" → {
|
|
53
|
+
"referenceImages": [ { "mediaId", "imageUsageType":"IMAGE_USAGE_TYPE_ASSET" } ],
|
|
54
|
+
"referenceEntities": [ { "entityId" } ] // ← native character identity
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `referenceEntities` is a **list** → multi-character per scene (`VIDEO_MODEL_CAPABILITY_MULTI_REFERENCE`).
|
|
59
|
+
- UI flow: composer → resource picker (*"Pesquisar recursos"*) → **Personagens** tab → select → **"Incluir no comando"** → injects `referenceEntities`.
|
|
60
|
+
- CLI `gflow video … --character <id>` is documented **backlog Phase 3 — unimplemented** (`docs/CHARACTER.md §8`). So this is *new automation*, not a refactor of existing code.
|
|
61
|
+
|
|
62
|
+
### Proposed refactor
|
|
63
|
+
|
|
64
|
+
**(a) DTO** — `api/video.py`, `GenerateVideoRequest`:
|
|
65
|
+
```python
|
|
66
|
+
reference_entities: tuple[str, ...] = () # R2V — Flow CHARACTER entity ids
|
|
67
|
+
```
|
|
68
|
+
⚠️ Relax `__post_init__`: today R2V **requires** `reference_images` (`video.py:249-252`). Change to *require `reference_images` **or** `reference_entities`* (either anchors the clip). Keep frames XOR references.
|
|
69
|
+
|
|
70
|
+
**(b) Transport** — new `_attach_character_entities(page, names, out_dir)` that, in `references` sub-mode, opens the resource picker, switches to the Characters/"Personagens" tab, selects each character **by display name**, clicks "Include in prompt". In `_generate_video_locked` R2V branch (`:1406`): if `request.reference_entities` → use entity attach; else fall back to `_attach_references`. (Both *may* coexist per the wire.)
|
|
71
|
+
- **Defense-in-depth (mirror the #125 backstop):** after submit, assert the captured generate payload's `referenceEntities` contains the expected `entityId`; if it's missing, raise rather than report a false success — otherwise an entity that silently failed to attach degrades to a plain text-only clip with no warning.
|
|
72
|
+
|
|
73
|
+
**(c) Orchestrator** — `_collect_refs` → return entity refs when `CharacterState.entity_id` is present, image paths only as fallback. Pass `reference_entities=(...entity_ids...)` into `GenerateVideoRequest`. Selection is **by name** (what the picker shows); `entity_id` is the verification key. No `movie.toml` change needed — `characters = ["Stickman"]` already names them; the orchestrator maps name→entity_id from state.
|
|
74
|
+
|
|
75
|
+
**(d) Pre-flight guard** — before the scene loop, verify each named entity actually exists in the project (`client.get_character(entity_id=...)`). Currently `_collect_refs` silently **drops** a missing character (`log.warning` + `continue`, `:478`) → the scene generates with **no refs at all** → guaranteed divergence with no hard failure. Fail loud instead.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 2. r2v vs i2v for journey continuity — the chain engine already exists
|
|
80
|
+
|
|
81
|
+
**Key constraint:** R2V "references" and I2V "frames" are **separate, mutually-exclusive composer sub-modes** (`_switch_video_sub_mode`, `:903`) and the DTO enforces it (`video.py:245-252`). You **cannot** have character-entity identity *and* a seeded start-frame in the *same* generation.
|
|
82
|
+
|
|
83
|
+
What each buys you:
|
|
84
|
+
- **r2v + `referenceEntities`** → strong **identity** consistency per clip; hard cuts between scenes.
|
|
85
|
+
- **i2v chain** (scene N+1 start = scene N's last frame) → **motion/visual continuity**; but link 0 has no identity anchor and identity drifts frame-to-frame.
|
|
86
|
+
|
|
87
|
+
**You already have the chain machinery** — it's just not exposed in `movie.toml`:
|
|
88
|
+
- `media.extract_last_frame(src, dst, *, offset_ms=0)` (`media.py:31`)
|
|
89
|
+
- `chain.run_chain(...)` + `ChainLinkSpec`/`ChainLinkResult`/`FrameExtractor` protocols (`chain.py:74-185`) — record-before-extract, crash-resumable.
|
|
90
|
+
- `chain_repo.ChainLinkRecorder` + migration `0005_add_chain_links.sql`.
|
|
91
|
+
- `gflow video chain` (`cli_video.py:324 _run_chain`): "link 0 as T2V, every later link as I2V seeded by the previous clip's last frame."
|
|
92
|
+
|
|
93
|
+
**Recommendation:** add an opt-in chain mode to `movie.toml` that routes through the existing `chain.run_chain()` rather than reimplementing it (e.g. top-level `mode = "chain"`, or scene-level `chain_from = "<previous scene title>"`). Then give honest guidance in docs:
|
|
94
|
+
|
|
95
|
+
> For a **journey where the character must stay the same**, prefer **r2v + native character** on *every* scene (each clip independently anchored to the entity → best identity, accepts cuts). Use **chain** when smooth motion handoff matters more than identity, accepting drift. They can be mixed per-scene but never combined within one clip.
|
|
96
|
+
|
|
97
|
+
The strongest practical journey today = r2v+entity on each scene; chaining is complementary, not a substitute, for the *consistency* goal specifically.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 3. Project consolidation — verified, and it's load-bearing
|
|
102
|
+
|
|
103
|
+
`manifest.project` (single value) is passed to `_create_character(project_id=…)` (`:309`) **and** `_generate_scene(project_id=…)` (`:356`); `_enter_editor` deep-links via `routes.project_editor_url(locale, project_id)` → `page.goto` (`ui_automation.py:815-818`); characters are created at the project's character-editor URL (`:2275`). So every character and every scene live in **one** project. ✅
|
|
104
|
+
|
|
105
|
+
This is precisely why the native-character refactor (§1) works: the entity must be in the **active** project to appear in the composer's "Personagens" picker. The consolidation you added is the enabler — keep it, and add the §1(d) guard so a missing entity fails loudly instead of silently degrading.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 4. Error handling (`generate_resp` / `responses` / 403) — already correct
|
|
110
|
+
|
|
111
|
+
- `generate_resp: dict[str, Any] = {}` is bound **before** the `try` (`ui_automation_video.py:1421`) → no access-before-assignment on any failure path.
|
|
112
|
+
- `_await_generate_response` raises a clear `TimeoutError` when reCAPTCHA fails *silently* (no response captured, `:1172-1179`).
|
|
113
|
+
- `_parse_generate_response` maps **401→`AuthExpiredError`**, **403→`WafRejectionError`**, other non-200→`WireFormatError` (`:1245-1262`).
|
|
114
|
+
- Both response listeners are removed in `finally` (`:1484-1488`); `generate_handler`/`status_handler` are bound before the `try`.
|
|
115
|
+
- `responses` only appears in comments — no live variable by that name. **No remaining UnboundLocal/TypeError on the 403 path.** Nothing to fix here.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 5. Other observations (non-blocking)
|
|
120
|
+
|
|
121
|
+
- **Dry-run vs reality mismatch:** the plan prints `refs=[<names>]` (`:260`) but r2v actually consumes image *paths*; after §1 this becomes accurate again (names = entities). Minor.
|
|
122
|
+
- `CharacterState`/`SceneState` are non-frozen dataclasses — intentional (mutated in place); fine.
|
|
123
|
+
- Summary stitch hint uses `flow_operation_id or media_id` (`:371`) — good.
|
|
124
|
+
|
|
125
|
+
## 6. Testing gap (ties to the DoD: "full e2e covering all scenarios")
|
|
126
|
+
|
|
127
|
+
- No test drives `_run_movie` with **≥2 scenes** → the cooldown/`asyncio` path and the resume-append path are unexercised. Add: a 2-scene happy path (mock `_generate_scene`) that would have caught §0, and a resume test (one scene pre-completed in state).
|
|
128
|
+
- Add a unit test asserting `_collect_refs` (post-refactor) returns entity ids when present, and a transport test asserting `referenceEntities` lands in the captured payload.
|
|
129
|
+
- Per project DoD memory, a movie feature isn't done until a live e2e covers happy + all error/exit paths on a real project.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Suggested sequencing
|
|
134
|
+
|
|
135
|
+
1. **Hotfix now:** `import asyncio` + 2-scene orchestrator test. (Unblocks the feature as-is, even with uploaded-image refs.)
|
|
136
|
+
2. **Identity fix:** DTO `reference_entities` + validation relax + transport entity-attach + payload backstop + orchestrator wiring + missing-entity guard.
|
|
137
|
+
3. **Journey mode (opt-in):** wire `movie.toml` → existing `chain.run_chain()`; document the identity-vs-continuity tradeoff.
|
|
@@ -33,6 +33,7 @@ Welcome to the `gflow-cli` documentation. This index is the routing layer: it te
|
|
|
33
33
|
| **[docs/SECURITY.md](SECURITY.md)** | What secrets are stored where, threat model, hardening | Audit, code review, multi-user machines |
|
|
34
34
|
| **[docs/DATA_LAYER.md](DATA_LAYER.md)** | Local SQLite catalog: goals, schema, recording flow, redaction, `gflow data` CLI, migrations, extension guide | Anything touching `gflow_cli.data`, debugging missing rows, building I2V/repair tooling, auditing what is stored |
|
|
35
35
|
| **[docs/CHARACTER.md](CHARACTER.md)** | Characters feature spec & system design: domain model, endpoint/cost matrix, sequence diagrams, JSON payloads (I/O), CLI surface, reuse via `referenceEntities` (#145) | Working on `gflow character`, reusing a character in generations, or understanding Flow's character wire protocol |
|
|
36
|
+
| **[docs/MOVIE.md](MOVIE.md)** | `gflow movie` — multi-scene character-consistent films: manifest format, run lifecycle (browser stays open through generate→poll→download), entity-attach mechanism (Personagens right-click), resume/handoff, credits | Working on `gflow movie`, debugging scene generation, or understanding the character-entity attach + consistency model |
|
|
36
37
|
| **[tasks/lessons.md](../tasks/lessons.md)** | Running notebook of patterns + reviewer findings, dated and traced to commits | Starting a new phase; debugging "why did the council flag this?" |
|
|
37
38
|
| **[skills/README.md](../skills/README.md)** | Installable agent skill docs (gflow-cli, predict, pr-council-review, scenario) — cross-tool portable Markdown consumed by Claude Code, Cursor, Codex, Gemini CLI, Aider, etc. | Any agent wanting to use gflow-cli correctly |
|
|
38
39
|
| **[scripts/dev/skillopt/README.md](../scripts/dev/skillopt/README.md)** | SkillOpt mock harness — rollout→score loop for measuring and improving skill doc accuracy across multiple LLM providers | Measuring a skill edit's impact; comparing Claude vs GPT-4o vs Gemini on gflow tasks |
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Movie — multi-scene, character-consistent films
|
|
2
|
+
|
|
3
|
+
`gflow movie` turns a single TOML manifest into a sequence of generated video
|
|
4
|
+
clips that share the **same characters** (face + voice) across every scene. It
|
|
5
|
+
is the orchestration layer on top of `gflow character` (entity creation) and
|
|
6
|
+
`gflow video` (R2V generation).
|
|
7
|
+
|
|
8
|
+
> Status: under active development on the movie feature branch. The wire
|
|
9
|
+
> protocol and CLI surface below are live-verified but may still change before
|
|
10
|
+
> release.
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 1. Scaffold a manifest
|
|
16
|
+
gflow movie template movie.toml
|
|
17
|
+
|
|
18
|
+
# 2. Edit movie.toml (characters + scenes), then run
|
|
19
|
+
gflow movie run movie.toml --profile <chrome-profile>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`movie run` is **generate-only** by default: each scene becomes its own clip in
|
|
23
|
+
the output directory. Pass `--stitch` for an optional ffmpeg hard-concat preview
|
|
24
|
+
(no transitions — not a deliverable).
|
|
25
|
+
|
|
26
|
+
## Manifest (`movie.toml`)
|
|
27
|
+
|
|
28
|
+
```toml
|
|
29
|
+
schema_version = 1
|
|
30
|
+
title = "E2E Stickman"
|
|
31
|
+
project = "6ba50219-…" # reusable Flow project id
|
|
32
|
+
output_dir = "./out"
|
|
33
|
+
|
|
34
|
+
[[characters]]
|
|
35
|
+
name = "Stickman"
|
|
36
|
+
identity = "entity" # reuse a Flow CHARACTER entity across scenes
|
|
37
|
+
face_prompt = "Simple round stickman, black ink lines, smiley face"
|
|
38
|
+
voice = "alnilam" # voice baked into the entity at creation
|
|
39
|
+
|
|
40
|
+
[[scenes]]
|
|
41
|
+
id = "summit" # stable key — used for resume
|
|
42
|
+
action = "stands on a clifftop at sunset, waves at the camera"
|
|
43
|
+
framing = "wide"
|
|
44
|
+
characters = ["Stickman"] # which characters appear (drives R2V reuse)
|
|
45
|
+
speaker = "Stickman"
|
|
46
|
+
line = "We finally made it to the top!"
|
|
47
|
+
aspect = "9:16"
|
|
48
|
+
model = "veo-lite"
|
|
49
|
+
duration = 8
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
On first run, characters with `identity = "entity"` are created once (image
|
|
53
|
+
generation — **free**, no credits) and cached. Each scene then generates a clip
|
|
54
|
+
that **reuses the same Flow CHARACTER entity** so the character drives every
|
|
55
|
+
scene from one identity.
|
|
56
|
+
|
|
57
|
+
> **Consistency is best-effort, not pixel-exact.** gflow guarantees the *right
|
|
58
|
+
> entity rides the wire* (`consistency_method = entity`); the final on-screen
|
|
59
|
+
> fidelity is Flow's Veo R2V model, which may reinterpret minimalist or
|
|
60
|
+
> hand-drawn references (e.g. a single round body can render as stacked
|
|
61
|
+
> circles). For tighter results, use clearer/richer reference images and a
|
|
62
|
+
> higher-tier model (`veo-quality`/`veo-fast` rather than `veo-lite`) — the same
|
|
63
|
+
> trade-offs you would hit driving Flow's UI by hand.
|
|
64
|
+
|
|
65
|
+
## Run lifecycle — keep the browser open
|
|
66
|
+
|
|
67
|
+
`gflow movie run` drives the **real Flow web UI** in a headed Chrome window
|
|
68
|
+
(your `--browser chrome` profile). For **each scene** the same browser window
|
|
69
|
+
is used end-to-end:
|
|
70
|
+
|
|
71
|
+
1. **Attach** the character entity to the generation (see below).
|
|
72
|
+
2. **Submit** the prompt — this passes reCAPTCHA and **spends 1 video credit**.
|
|
73
|
+
3. **Poll** Flow for completion (the clip renders server-side, ~30–90s).
|
|
74
|
+
4. **Download** the finished mp4 into `output_dir`.
|
|
75
|
+
|
|
76
|
+
> **Do not close the browser window while a run is in progress.** Steps 3 and 4
|
|
77
|
+
> still use the browser page (polling even brings it to the foreground). If the
|
|
78
|
+
> window is closed before a scene finishes downloading, the in-flight scene
|
|
79
|
+
> **aborts** — you will see a "Target/page/context closed" or `scene_failed`
|
|
80
|
+
> error. **This is expected, not a bug.**
|
|
81
|
+
|
|
82
|
+
If a run is interrupted (closed window, crash, network drop), it is **safe to
|
|
83
|
+
re-run**: the sibling `<manifest>-state.json` records completed scenes (keyed on
|
|
84
|
+
`scene.id`) and they are skipped, so the command resumes where it left off. A
|
|
85
|
+
versioned handoff manifest (`<manifest>-handoff.json`) is always written at the
|
|
86
|
+
end.
|
|
87
|
+
|
|
88
|
+
> **Future — fire-and-forget.** Flow's status-poll and download endpoints are
|
|
89
|
+
> non-generative and *could* be driven browser-free over Bearer REST (no
|
|
90
|
+
> reCAPTCHA, no extra credits), letting the browser close right after submit
|
|
91
|
+
> while gflow finishes over the API. That mode is **not implemented yet**; today
|
|
92
|
+
> the browser is required for the full generate → poll → download cycle.
|
|
93
|
+
|
|
94
|
+
## Character consistency (how the entity rides)
|
|
95
|
+
|
|
96
|
+
A scene listing `characters = ["Stickman"]` is generated as **R2V**
|
|
97
|
+
(reference-to-video) so the named Flow CHARACTER entity is reused. The entity is
|
|
98
|
+
attached by driving Flow's resource picker:
|
|
99
|
+
|
|
100
|
+
1. Click **Add Media** in the composer (references sub-mode).
|
|
101
|
+
2. Switch to the **Personagens** (Characters) tab.
|
|
102
|
+
3. **Right-click** the entity tile — addressed by entity id as
|
|
103
|
+
`data-tile-id="fe_id_<entityId>"` — and choose **"Incluir no comando"** from
|
|
104
|
+
the context menu. *(A plain left-click navigates into the character editor;
|
|
105
|
+
the inline "Incluir" button on the **Tudo** tab attaches the character's
|
|
106
|
+
thumbnail as a plain image, not the entity.)*
|
|
107
|
+
|
|
108
|
+
This puts `referenceEntities:[{entityId}]` on the
|
|
109
|
+
`video:batchAsyncGenerateVideoReferenceImages` **request**. Flow's **response**
|
|
110
|
+
echoes the accepted entity at:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
media[].mediaMetadata.requestData.videoGenerationRequestData
|
|
114
|
+
.videoGenerationEntityInputs[].entityId
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
gflow asserts this on every entity scene (`_assert_entities_attached`) and
|
|
118
|
+
**refuses to report success** if the entity did not ride — a text/image-only
|
|
119
|
+
clip is never silently passed off as character-consistent. The recorded
|
|
120
|
+
`consistency_method` in the run state is `entity` when the entity rode (vs
|
|
121
|
+
`text`).
|
|
122
|
+
|
|
123
|
+
See [CHARACTER.md](CHARACTER.md) for the underlying entity model and
|
|
124
|
+
[CHARACTER_RECON.md](CHARACTER_RECON.md) for the reverse-engineered wire
|
|
125
|
+
protocol.
|
|
126
|
+
|
|
127
|
+
## `movie run` options
|
|
128
|
+
|
|
129
|
+
| Flag | Default | Effect |
|
|
130
|
+
|------|---------|--------|
|
|
131
|
+
| `--profile <name>` | default profile | Chrome profile to drive (must be a `chrome`-strategy profile). |
|
|
132
|
+
| `--out-dir <dir>` | manifest `output_dir` | Override the output directory. |
|
|
133
|
+
| `--dry-run` | off | Print the plan + credit estimate; make **no** API calls. |
|
|
134
|
+
| `--fail-fast` / `--continue-on-error` | continue | Stop on the first scene failure, or attempt the rest (default). |
|
|
135
|
+
| `--stitch` | off | After generating, hard-concat all clips into one preview mp4 (ffmpeg, no transitions). |
|
|
136
|
+
|
|
137
|
+
## Credits
|
|
138
|
+
|
|
139
|
+
- Character creation (image generation) is **free** — no credits, no reCAPTCHA.
|
|
140
|
+
- Each **scene** is one video generation = **1 credit**, spent at submit (step 2
|
|
141
|
+
above). A `--dry-run` shows the estimate without spending anything.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "gflow movie handoff manifest",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["schema_version", "generator", "movie", "clips"],
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"properties": {
|
|
8
|
+
"schema_version": { "const": 1 },
|
|
9
|
+
"generator": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"required": ["name", "version"],
|
|
12
|
+
"properties": { "name": { "type": "string" }, "version": { "type": "string" } }
|
|
13
|
+
},
|
|
14
|
+
"movie": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"required": ["title"],
|
|
17
|
+
"properties": {
|
|
18
|
+
"title": { "type": "string" },
|
|
19
|
+
"output_dir": { "type": "string" },
|
|
20
|
+
"total_duration_seconds": { "type": "number" }
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"style": { "type": "object" },
|
|
24
|
+
"characters": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"required": ["name", "identity"],
|
|
29
|
+
"properties": {
|
|
30
|
+
"name": { "type": "string" },
|
|
31
|
+
"identity": { "enum": ["text", "entity"] },
|
|
32
|
+
"voice": { "type": ["string", "null"] },
|
|
33
|
+
"x_gflow": { "type": "object" }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"clips": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"required": ["id", "index", "file", "status"],
|
|
42
|
+
"properties": {
|
|
43
|
+
"id": { "type": "string" },
|
|
44
|
+
"index": { "type": "integer", "minimum": 0 },
|
|
45
|
+
"file": { "type": ["string", "null"], "pattern": "^[^\\\\]*$" },
|
|
46
|
+
"duration_seconds": { "type": ["number", "null"] },
|
|
47
|
+
"framing": { "type": ["string", "null"] },
|
|
48
|
+
"characters": { "type": "array", "items": { "type": "string" } },
|
|
49
|
+
"consistency_method": { "enum": ["text", "entity", "degraded"] },
|
|
50
|
+
"dialogue": { "type": "array" },
|
|
51
|
+
"prompt": { "type": ["string", "null"] },
|
|
52
|
+
"status": { "enum": ["completed", "failed"] },
|
|
53
|
+
"x_gflow": { "type": "object" }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"stitch": { "type": "object" }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Movie P0 — `asyncio` Multi-Scene Hotfix Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Fix the release-blocking `NameError` that aborts every multi-scene `gflow movie run` after the first scene, and add the regression tests that should have caught it.
|
|
6
|
+
|
|
7
|
+
**Architecture:** `gflow_cli/cli_movie.py` calls `await asyncio.sleep(5)` as a reCAPTCHA cooldown between scenes, but never imports `asyncio`. The call sits *outside* the per-scene `try/except`, so on the 2nd scene it raises `NameError` and aborts the whole run (and on resume, the first already-completed scene makes the first *new* scene hit it → zero forward progress). Fix = add the import and relocate the cooldown inside the `try` so any failure there is handled per-scene. The existing async-orchestrator tests only ever use single-scene manifests, so the bug was invisible — we add a 2-scene run test and a resume test.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Python 3.13, pytest (`pytest-asyncio`), `unittest.mock`. Run tests with the worktree venv: `.venv/Scripts/python.exe -m pytest` (do **not** use `uv run pytest` on Windows — it's broken here).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: Add the failing 2-scene regression test, then fix the import
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Test: `tests/cli/test_cli_movie.py` (add a method to `class TestRunMovieOrchestrator`, near line 400)
|
|
17
|
+
- Modify: `src/gflow_cli/cli_movie.py` (imports ~line 19; cooldown block ~lines 341-346)
|
|
18
|
+
|
|
19
|
+
- [ ] **Step 1: Write the failing test**
|
|
20
|
+
|
|
21
|
+
Add this method inside `class TestRunMovieOrchestrator` in `tests/cli/test_cli_movie.py` (it reuses the file's existing helpers `_mock_client_cm`, `_make_video_result` and imports `MovieManifest`, `SceneDef`, `MovieState`, `AsyncMock`, `MagicMock`, `patch`, already imported at the top of that test module):
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
async def test_two_scene_run_does_not_crash_on_cooldown(self, tmp_path: Path) -> None:
|
|
25
|
+
"""Regression: the reCAPTCHA cooldown on scene 2+ must not NameError.
|
|
26
|
+
|
|
27
|
+
Before the fix, `cli_movie` calls `asyncio.sleep` without importing
|
|
28
|
+
asyncio, so the 2nd scene aborts the whole run. `patch("asyncio.sleep")`
|
|
29
|
+
makes the (post-fix) cooldown instant; pre-fix it raises NameError.
|
|
30
|
+
"""
|
|
31
|
+
from gflow_cli.cli_movie import _run_movie
|
|
32
|
+
|
|
33
|
+
manifest = MovieManifest(
|
|
34
|
+
title="T",
|
|
35
|
+
project="p",
|
|
36
|
+
characters=(),
|
|
37
|
+
scenes=(
|
|
38
|
+
SceneDef(title="S1", type="t2v", prompt="x"),
|
|
39
|
+
SceneDef(title="S2", type="t2v", prompt="y"),
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
state = MovieState(title="T", project="p")
|
|
43
|
+
state_path = tmp_path / "state.json"
|
|
44
|
+
|
|
45
|
+
with (
|
|
46
|
+
patch("gflow_cli.cli_movie.get_settings"),
|
|
47
|
+
patch("gflow_cli.cli_movie.OperationRecorder") as mock_recorder_cls,
|
|
48
|
+
patch("gflow_cli.cli_movie.FlowApiClient", return_value=_mock_client_cm()),
|
|
49
|
+
patch(
|
|
50
|
+
"gflow_cli.cli_movie._generate_scene",
|
|
51
|
+
new=AsyncMock(return_value=_make_video_result()),
|
|
52
|
+
),
|
|
53
|
+
patch("asyncio.sleep", new=AsyncMock()),
|
|
54
|
+
):
|
|
55
|
+
mock_recorder_cls.open.return_value = MagicMock()
|
|
56
|
+
await _run_movie(
|
|
57
|
+
manifest=manifest,
|
|
58
|
+
state=state,
|
|
59
|
+
state_path=state_path,
|
|
60
|
+
profile_name="default",
|
|
61
|
+
profile_dir=tmp_path / "profile",
|
|
62
|
+
out_dir=tmp_path / "out",
|
|
63
|
+
continue_on_error=True,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
assert state.scenes["S1"].status == "completed"
|
|
67
|
+
assert state.scenes["S2"].status == "completed"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- [ ] **Step 2: Run the test to verify it fails**
|
|
71
|
+
|
|
72
|
+
Run: `.venv/Scripts/python.exe -m pytest tests/cli/test_cli_movie.py::TestRunMovieOrchestrator::test_two_scene_run_does_not_crash_on_cooldown -v`
|
|
73
|
+
Expected: **FAIL** — `NameError: name 'asyncio' is not defined` (raised from the cooldown in `_run_movie`, so scene `S2` never completes).
|
|
74
|
+
|
|
75
|
+
- [ ] **Step 3: Add the `import asyncio` and relocate the cooldown into the try**
|
|
76
|
+
|
|
77
|
+
In `src/gflow_cli/cli_movie.py`, add the import (alphabetical, before `import sys`). Change:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from __future__ import annotations
|
|
81
|
+
|
|
82
|
+
import sys
|
|
83
|
+
from pathlib import Path
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
to:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from __future__ import annotations
|
|
90
|
+
|
|
91
|
+
import asyncio
|
|
92
|
+
import sys
|
|
93
|
+
from pathlib import Path
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then relocate the cooldown so a failure there is handled per-scene rather than aborting the run. Change this block (currently ~lines 340-346):
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
refs = _collect_refs(scene_def, state)
|
|
100
|
+
|
|
101
|
+
# reCAPTCHA cooldown
|
|
102
|
+
if completed_scene_ids:
|
|
103
|
+
await asyncio.sleep(5)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
|
|
107
|
+
video_result = await _generate_scene(
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
to:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
refs = _collect_refs(scene_def, state)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
# reCAPTCHA cooldown between scenes — inside the try so any
|
|
117
|
+
# failure here is handled per-scene and never aborts the run.
|
|
118
|
+
if completed_scene_ids:
|
|
119
|
+
await asyncio.sleep(5)
|
|
120
|
+
|
|
121
|
+
video_result = await _generate_scene(
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
(Leave the rest of the `try` body and the `except`/`state.save` unchanged.)
|
|
125
|
+
|
|
126
|
+
- [ ] **Step 4: Run the test to verify it passes**
|
|
127
|
+
|
|
128
|
+
Run: `.venv/Scripts/python.exe -m pytest tests/cli/test_cli_movie.py::TestRunMovieOrchestrator::test_two_scene_run_does_not_crash_on_cooldown -v`
|
|
129
|
+
Expected: **PASS** (both `S1` and `S2` complete; `asyncio.sleep` is patched so no real delay).
|
|
130
|
+
|
|
131
|
+
- [ ] **Step 5: Commit**
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git add src/gflow_cli/cli_movie.py tests/cli/test_cli_movie.py
|
|
135
|
+
git commit -m "fix(movie): import asyncio so multi-scene runs don't crash after scene 1"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### Task 2: Add the resume regression test
|
|
141
|
+
|
|
142
|
+
**Files:**
|
|
143
|
+
- Test: `tests/cli/test_cli_movie.py` (add a method to `class TestRunMovieOrchestrator`)
|
|
144
|
+
|
|
145
|
+
This proves the *resume* path makes forward progress: with scene 1 already completed in state, scene 2 must still generate (pre-fix, the completed-scene append made the first new scene hit the un-imported cooldown and crash → zero progress).
|
|
146
|
+
|
|
147
|
+
- [ ] **Step 1: Write the test**
|
|
148
|
+
|
|
149
|
+
Add inside `class TestRunMovieOrchestrator`:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
async def test_resume_generates_first_new_scene(self, tmp_path: Path) -> None:
|
|
153
|
+
"""Regression: on resume, the first NEW scene must generate (not crash
|
|
154
|
+
on the cooldown triggered by the resumed completed scene)."""
|
|
155
|
+
from gflow_cli.cli_movie import _run_movie
|
|
156
|
+
|
|
157
|
+
manifest = MovieManifest(
|
|
158
|
+
title="T",
|
|
159
|
+
project="p",
|
|
160
|
+
characters=(),
|
|
161
|
+
scenes=(
|
|
162
|
+
SceneDef(title="S1", type="t2v", prompt="x"),
|
|
163
|
+
SceneDef(title="S2", type="t2v", prompt="y"),
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
state = MovieState(title="T", project="p")
|
|
167
|
+
state.scenes["S1"] = SceneState(
|
|
168
|
+
media_id="m",
|
|
169
|
+
flow_operation_id="op-old",
|
|
170
|
+
local_path="/out/v.mp4",
|
|
171
|
+
status="completed",
|
|
172
|
+
)
|
|
173
|
+
state_path = tmp_path / "state.json"
|
|
174
|
+
gen = AsyncMock(return_value=_make_video_result())
|
|
175
|
+
|
|
176
|
+
with (
|
|
177
|
+
patch("gflow_cli.cli_movie.get_settings"),
|
|
178
|
+
patch("gflow_cli.cli_movie.OperationRecorder") as mock_recorder_cls,
|
|
179
|
+
patch("gflow_cli.cli_movie.FlowApiClient", return_value=_mock_client_cm()),
|
|
180
|
+
patch("gflow_cli.cli_movie._generate_scene", new=gen),
|
|
181
|
+
patch("asyncio.sleep", new=AsyncMock()),
|
|
182
|
+
):
|
|
183
|
+
mock_recorder_cls.open.return_value = MagicMock()
|
|
184
|
+
await _run_movie(
|
|
185
|
+
manifest=manifest,
|
|
186
|
+
state=state,
|
|
187
|
+
state_path=state_path,
|
|
188
|
+
profile_name="default",
|
|
189
|
+
profile_dir=tmp_path / "profile",
|
|
190
|
+
out_dir=tmp_path / "out",
|
|
191
|
+
continue_on_error=True,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
gen.assert_awaited_once() # S1 skipped, S2 generated
|
|
195
|
+
assert state.scenes["S2"].status == "completed"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- [ ] **Step 2: Run the test to verify it passes**
|
|
199
|
+
|
|
200
|
+
Run: `.venv/Scripts/python.exe -m pytest tests/cli/test_cli_movie.py::TestRunMovieOrchestrator::test_resume_generates_first_new_scene -v`
|
|
201
|
+
Expected: **PASS** (with Task 1's fix in place). `_generate_scene` is awaited exactly once (for `S2`).
|
|
202
|
+
|
|
203
|
+
- [ ] **Step 3: Run the full movie test module to confirm no regressions**
|
|
204
|
+
|
|
205
|
+
Run: `.venv/Scripts/python.exe -m pytest tests/cli/test_cli_movie.py -q`
|
|
206
|
+
Expected: all tests **PASS**.
|
|
207
|
+
|
|
208
|
+
- [ ] **Step 4: Commit**
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
git add tests/cli/test_cli_movie.py
|
|
212
|
+
git commit -m "test(movie): cover multi-scene run + resume (guards asyncio cooldown)"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Self-Review
|
|
218
|
+
|
|
219
|
+
**Spec coverage:** Implements spec §11 P0 (`import asyncio`, relocate cooldown) and §14 (multi-scene run test that "catches P0" + resume test). No other spec section is in P0 scope.
|
|
220
|
+
|
|
221
|
+
**Placeholder scan:** None — every step has exact paths, full test code, exact commands, and expected output.
|
|
222
|
+
|
|
223
|
+
**Type consistency:** Reuses the test module's existing helpers verbatim (`_mock_client_cm`, `_make_video_result`) and `_run_movie`'s real keyword signature (`manifest`, `state`, `state_path`, `profile_name`, `profile_dir`, `out_dir`, `continue_on_error`), matching the surrounding tests (e.g. `test_happy_path_no_characters`). `SceneState` fields (`media_id`, `flow_operation_id`, `local_path`, `status`) match `movie_manifest.py`.
|