gflow-cli 0.25.0__tar.gz → 0.26.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.25.0 → gflow_cli-0.26.0}/CHANGELOG.md +20 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/PKG-INFO +1 -1
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/INDEX.md +1 -1
- gflow_cli-0.26.0/docs/LIVE_VERIFICATION_v0.26.0.md +69 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/MCP.md +1 -1
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/pyproject.toml +1 -1
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/__init__.py +1 -1
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/dto.py +35 -2
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/image.py +11 -2
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/ui_automation.py +10 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/ui_automation_video.py +115 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/recorder.py +6 -1
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/mcp/tools.py +53 -11
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/worker/daemon.py +13 -1
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_image_dto.py +30 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/mcp/test_tools_helpers.py +36 -5
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/mcp/test_tools_wired.py +1 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/worker/test_daemon.py +1 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/uv.lock +1 -1
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/README.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/active.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/branch-review.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/changelog.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/check.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/doc-review.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/issue-assessment.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/issue-resolve.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/known-issues.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/next.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/plan.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/pr-council-review.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/predict.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/release.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/scenario.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/sonar.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.claude/commands/gflow/status.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.env.template +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.gitattributes +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/CODEOWNERS +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/copilot-instructions.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/dependabot.yml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/workflows/ci.yml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/workflows/external-pr-triage.yml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/workflows/governance-advisory.yml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/workflows/governance-benchmark.yml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/workflows/main-base-guard.yml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.github/workflows/release.yml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.gitignore +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.gitleaks.toml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.pre-commit-config.yaml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/.secrets.baseline +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/AGENTS.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/CLAUDE.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/CONFIGURATION.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/CONTRIBUTING.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/DISCLAIMER.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/GEMINI.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/KNOWN_ISSUES.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/LICENSE +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/PLAN.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/README.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/RELEASE.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/ROADMAP.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/conftest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docker-compose.yml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/AGENT_GUIDE.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/AGENT_UI_E2E.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/AGENT_UI_RECON.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/ARCHITECTURE.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/AUTHENTICATION.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/CHARACTER.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/CHARACTER_RECON.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/CONFIGURATION.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/DATA_LAYER.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/DEBUGGING.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/DEMOS.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/DEVELOPMENT.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/E2E_TESTING.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/EXTERNAL_STORAGE.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/GITHUB.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/GOVERNANCE_BENCHMARK.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/IMAGE_UPSCALE_RECON.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_data_layer.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_image_batch.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.10.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.11.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.12.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.13.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.16.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.17.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.18.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.19.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.20.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.20.1.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.21.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.22.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.23.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.24.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.25.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.7.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.8.1.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.9.0.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_v0.9.1.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/LIVE_VERIFICATION_video_download.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/MOVIE.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/PROJECT_STATUS.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/PROMPT_EXPANSION.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/SECURITY.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/TOOLS.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/USAGE.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/USER_GUIDE.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/assets/demo-split-pf.gif +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/assets/example-run.gif +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/assets/examples.webp +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/medium_tutorial.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/schemas/movie-handoff.schema.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/sonar-cleanup-tracker.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-12-issue-174-library-ui-attach/PLAN.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-22-mcp-server/PLAN.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-22-mcp-server/PREDICT.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-22-mcp-server/SCENARIO.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-22-prompt-expansion/PLAN.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-22-prompt-expansion/PREDICT.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-22-prompt-expansion/SCENARIO.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-24-gflow-studio-scaffold/PLAN.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-24-rest-api-layer/PLAN.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/plans/2026-06-26-mcp-e2e-test/PLAN.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/research/2026-06-27-tool-abstraction-evaluation.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/docs/superpowers/specs/2026-06-29-issue-assessment-workflow-design.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/examples/README.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/examples/batch_from_config.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/examples/multi_prompt_t2i.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/examples/sample_config.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/examples/sample_prompts.txt +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/examples/single_image_t2i.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/examples/workflow_chain.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/llms.txt +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/README.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/01_upload_image.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/04_archive_workflow.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/05_createProject.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/06_batchGenerateImages.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/08_batchAsyncGenerateVideoStartAndEndImage.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/09_batchAsyncGenerateVideoReferenceImages.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/10_batchCheckAsyncVideoGenerationStatus_successful.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/11_batchCheckAsyncVideoGenerationStatus_failed.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/12_create_scene.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/13_sceneWorkflows_update.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/14_get_scene_workflows.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/samples/captured/15_commit_flowWorkflow.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/ci/check_doc_links.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/ci/check_materiality.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/ci/check_repo_hygiene.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/debug_editor.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/debug_gen_settings.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/debug_settings.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/_recording_client.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/_spike_common.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/active_plan.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/analyze_agent_ui_capture.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/capture_i2v_frame_slots_dom.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/capture_i2v_intercept_submit.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/capture_i2v_model_select_repro.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/capture_i2v_post_bind_state.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/capture_image_add_media_dom.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/capture_locale_invariants.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/cdp_drive_and_probe.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/character_create_spike.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/character_create_spike_v2.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/dump_character_selectors.js +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/make_project.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/materiality_backtest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/monitor_pr_38.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/patch_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/skillopt/README.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/skillopt/harness.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/skillopt/tasks.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_char_editor_dom.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_char_gen_capture.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_image_upscale_capture.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_image_upscale_drive.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_image_upscale_recaptcha_action.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_image_upscale_rest_probe.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_issue170_picker_locale_recon.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_issue174_library_ui_recon.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_movie_attach_payload.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_movie_entity_recon.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_movie_gen_capture.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_movie_picker_select.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_movie_voice_list.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_patch_entity.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/dev/spike_patchright.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/diag/README.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/diag/capture_flow_traffic.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/diag/memory_profile.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/diag/recaptcha_mint.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/e2e/agentic_image_e2e.ps1 +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/e2e/capture_agent_toggle.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/record_demo.ps1 +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/smoke_image.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/smoke_real_chrome_image.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/smoke_video_editor.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/smoke_worker_style.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/scripts/verify_chrome_auth_viability.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/skills/README.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/skills/gflow-cli/SKILL.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/skills/issue-assessment/SKILL.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/skills/issue-resolve/SKILL.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/skills/plan/SKILL.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/skills/pr-council-review/SKILL.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/skills/predict/SKILL.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/skills/scenario/SKILL.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/skills/status/SKILL.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/sonar-project.properties +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/__main__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/_cli_helpers.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/_engine.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/_retry.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/_sapisidhash.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/client.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/image_upscale.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/recaptcha.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/routes.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/scene.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/_common.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/_fingerprint.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/base.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/drivers/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/drivers/agentic.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/drivers/base.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/drivers/classic.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/drivers/factory.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/experimental/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/experimental/bearer.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/experimental/evaluate_fetch.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/transports/experimental/sapisidhash.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/api/video.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/auth/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/auth/base.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/auth/cookies.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/auth/factory.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/auth/internal_chromium.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/auth/real_chrome.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/auth/strategies.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/auth/verification.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/browser_manager.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/chain.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/chain_manifest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli_data.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli_image.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli_models.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli_movie.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli_run.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli_scene.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli_tools.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/cli_video.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/composition.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/config.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/chain_repo.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/migrations/0001_initial.sql +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/migrations/0002_add_cloud_storage.sql +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/migrations/0003_add_scene_tables.sql +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/migrations/0004_add_scene_output_path.sql +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/migrations/0005_add_chain_links.sql +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/migrations/0006_add_operations_metadata.sql +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/migrations/0007_queue.sql +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/migrations/0008_add_operation_expanded_prompt.sql +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/migrations/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/models.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/queries.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/redaction.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/repository.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/data/store.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/errors.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/exceptions.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/image_batch.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/json_output.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/manifest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/mcp/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/mcp/prompts.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/mcp/resources.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/mcp/server.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/media.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/movie_manifest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/observability.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/paths.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/profile_store.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/services/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/services/character_create.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/storage.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/banned.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/builtin/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/builtin/creative-director.toml +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/expander.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/invocation.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/loader.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/registry.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/runtime.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/tools/spec.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/ui/app.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/ui/server.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/src/gflow_cli/worker/queue.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tasks/lessons.md +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/test_assets/sample_batch.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/test_assets/sample_batch.tsv +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/test_assets/sample_batch_invalid.tsv +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/fixtures/character_gen_response.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/fixtures/patch_entity_response.json +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_aisandbox_auth_error.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_aisandbox_auth_headers.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_bearer_redaction.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_client.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_client_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_client_delete_characters.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_client_generate_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_client_image.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_client_launch_kwargs.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_client_patch_entity.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_client_scene.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_client_upscale.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_concurrency.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_dto.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_engine.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_image.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_image_upscale.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_post_json_aisandbox_auth.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_recaptcha.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_retry.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_routes.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_routes_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_routes_scene.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_sapisidhash_helper.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_scene_models.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_video.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/test_video_request.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/conftest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/drivers/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/drivers/test_agentic.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/drivers/test_factory.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_base.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_bearer.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_common.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_evaluate_fetch.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_factory.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_fingerprint.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_sapisidhash.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_transport_timeout.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_ui_automation.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_ui_automation_batch.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_ui_automation_image_mode.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_ui_automation_video.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/api/transports/test_ui_character_editor.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/auth/strategies/test_factory.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/auth/strategies/test_strategies.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/auth/test_cookies.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/auth/test_verification.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_auth_list.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_character_create.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_data.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_image.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_image_seed_removed.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_image_selector_drift.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_image_upscale.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_models.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_movie.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_run.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_scene.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_tools.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_video.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_cli_video_chain.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_error_handling.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_helpers.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_movie_manifest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_settings_validation.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/cli/test_t2i_multi_prompt.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/composition/test_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/composition/test_compose_prompt.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/composition/test_handoff.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/conftest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_chain_repo.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_find_incomplete_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_models.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_packaging.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_recorder.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_recorder_character.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_redaction.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_repository.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_scene_persistence.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_settings_and_errors.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/data/test_store_migrations.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/dev/test_recording_client.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/conftest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_aisandbox_auth_live.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_auth_verification_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_chain_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_character_create_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_daemon_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_data_layer_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_i2v_flags_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_image_batch_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_image_i2i_ref_cap_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_json_output_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_locale_selectors_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_scene_compose_live.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_tools_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_transports_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_video_r2v_ref_cap_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/e2e/test_video_t2v_e2e.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/auth.feature +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/auth_login.feature +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/character_create.feature +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/character_read.feature +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/conftest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/image.feature +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/image_upscale.feature +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/locale_picker_include.feature +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_auth_login_steps.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_auth_steps.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_character_create_steps.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_character_read_steps.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_image_steps.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_image_upscale_steps.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_locale_picker_include_steps.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_step_collision_guard.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_video_agent_ui_steps.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/test_video_chain_steps.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/video_agent_ui.feature +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/features/video_chain.feature +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/fixtures/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/fixtures/seeded_catalog.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/image_batch/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/image_batch/test_image_manifest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/image_batch/test_observability_events.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/integration/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/integration/conftest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/integration/constants.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/integration/test_storage_gcs.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/integration/test_storage_s3.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/mcp/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/mcp/conftest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/mcp/test_server.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/mcp/test_stdio_transport.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/scripts/test_analyze_agent_ui_capture.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/scripts/test_capture_locale_invariants.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/scripts/test_check_materiality.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/scripts/test_check_repo_hygiene.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/scripts/test_materiality_backtest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/services/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/services/test_character_create_redaction.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/services/test_character_create_saga.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/services/test_character_gen_no_direct_post.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/smoke/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/smoke/test_profile_account_smoke.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/smoke/test_real_flow.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_auth.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_browser_manager.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_chain.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_chain_manifest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_cli_data.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_config.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_conftest_isolation.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_data_queries.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_documentation_gate.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_errors.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_errors_403.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_json_output.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_manifest.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_marker_registry.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_media.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_observability.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_paths.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_profile_store.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/test_smoke.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/tools/__init__.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/tools/test_banned.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/tools/test_expander.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/tools/test_invocation.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/tools/test_loader.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/tools/test_registry.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/tools/test_runtime.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/tools/test_spec.py +0 -0
- {gflow_cli-0.25.0 → gflow_cli-0.26.0}/tests/ui/test_app.py +0 -0
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.26.0] — 2026-07-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Reference a generated image in `image i2i` by its Flow UUID (`gflow_generate_image`,
|
|
15
|
+
`reference_images`)**: pass a generated image's media UUID as a reference and gflow
|
|
16
|
+
attaches it by **selecting the already-existing asset in Flow's reference picker** —
|
|
17
|
+
it does **not** upload a duplicate copy (avoiding duplication is the preferred path).
|
|
18
|
+
The asset's tile is located by its media id in the thumbnail URL (robust to
|
|
19
|
+
display-name collisions), surfaced by a display-name search when it isn't already
|
|
20
|
+
visible, and attached in place. When the asset can't be located (e.g. it's in a
|
|
21
|
+
different project's picker), gflow falls back to uploading its on-disk local file. A
|
|
22
|
+
UUID that isn't in your catalog still attaches by media id (no error). Live-verified:
|
|
23
|
+
a generated image's UUID → i2i output that references it, with no duplicate upload.
|
|
24
|
+
- **Generated images now record their Flow display name**: the display name is extracted
|
|
25
|
+
from the `batchGenerateImages` response's `workflows[]` array (previously ignored) and
|
|
26
|
+
persisted in the asset catalog — the searchable label the media picker shows, and what
|
|
27
|
+
the UUID-reference path searches by. (Original find and approach by **@C1ph3r404**,
|
|
28
|
+
#253/#255.)
|
|
29
|
+
|
|
10
30
|
## [0.25.0] — 2026-07-06
|
|
11
31
|
|
|
12
32
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gflow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.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
|
|
@@ -117,7 +117,7 @@ Slash commands for Claude Code, stored in `.claude/commands/gflow/`. All prefixe
|
|
|
117
117
|
**"A gflow command hangs / fails — where do I start?"** → [DEBUGGING § Quick reference](DEBUGGING.md#quick-reference)
|
|
118
118
|
**"Flow's UI broke a selector — how do I diagnose it?"** → [DEBUGGING § Inspecting Flow's live UI](DEBUGGING.md#inspecting-flows-live-ui)
|
|
119
119
|
**"What does each `ui_automation.*` log event mean?"** → [DEBUGGING § Listener & HTTP-layer debugging](DEBUGGING.md#listener--http-layer-debugging)
|
|
120
|
-
**"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.25.0](LIVE_VERIFICATION_v0.25.0.md) (**#237 remote-UUID i2v proven live e2e GREEN** — a real 8s 720×1280 interpolation from a generated image's UUID, after the picker-search attach was reworked to local upload; #240 home-`.env` matrix live; plus two silent-failure guards root-caused live: video-as-image download rejection and rejected-upload fail-loud). Prior: [v0.24.0](LIVE_VERIFICATION_v0.24.0.md) (--project parity across CLI + MCP — automated coverage; live gen auth-gated). Prior: [v0.23.0](LIVE_VERIFICATION_v0.23.0.md) (MCP generation wiring proven live end-to-end — tool→worker→real Flow REST→structured-error translation; final image-write blocked by an expired session, environmental. #222 macOS fix reporter-verified e2e on Apple Silicon). Prior: [v0.22.0](LIVE_VERIFICATION_v0.22.0.md) (Tools framework — automated/CI coverage complete; credit/key-gated live feature run is a pending owner gate, see the doc) · [v0.21.0](LIVE_VERIFICATION_v0.21.0.md) (MCP server over stdio + HTTP/SSE — credit-free protocol handshake; 4 tools / 2 prompts / 3 resources confirmed on stdout) · [v0.20.1](LIVE_VERIFICATION_v0.20.1.md) · [v0.20.0](LIVE_VERIFICATION_v0.20.0.md) · [v0.19.0](LIVE_VERIFICATION_v0.19.0.md) · [v0.18.0](LIVE_VERIFICATION_v0.18.0.md) · [v0.17.0](LIVE_VERIFICATION_v0.17.0.md) · [v0.16.0](LIVE_VERIFICATION_v0.16.0.md) · [v0.13.0](LIVE_VERIFICATION_v0.13.0.md) · [v0.12.0](LIVE_VERIFICATION_v0.12.0.md) · [v0.11.0](LIVE_VERIFICATION_v0.11.0.md) · [v0.10.0](LIVE_VERIFICATION_v0.10.0.md) · [v0.9.1](LIVE_VERIFICATION_v0.9.1.md) · [v0.9.0](LIVE_VERIFICATION_v0.9.0.md) · [v0.8.1](LIVE_VERIFICATION_v0.8.1.md) · [v0.7.0](LIVE_VERIFICATION_v0.7.0.md)
|
|
120
|
+
**"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.26.0](LIVE_VERIFICATION_v0.26.0.md) (**image i2i by generated-image UUID proven live e2e GREEN** — the existing asset is *selected in place* by its media UUID, no duplicate upload, producing an i2i output that references it; plus generated-image `display_name` capture, credited @C1ph3r404). Prior: [v0.25.0](LIVE_VERIFICATION_v0.25.0.md) (**#237 remote-UUID i2v proven live e2e GREEN** — a real 8s 720×1280 interpolation from a generated image's UUID, after the picker-search attach was reworked to local upload; #240 home-`.env` matrix live; plus two silent-failure guards root-caused live: video-as-image download rejection and rejected-upload fail-loud). Prior: [v0.24.0](LIVE_VERIFICATION_v0.24.0.md) (--project parity across CLI + MCP — automated coverage; live gen auth-gated). Prior: [v0.23.0](LIVE_VERIFICATION_v0.23.0.md) (MCP generation wiring proven live end-to-end — tool→worker→real Flow REST→structured-error translation; final image-write blocked by an expired session, environmental. #222 macOS fix reporter-verified e2e on Apple Silicon). Prior: [v0.22.0](LIVE_VERIFICATION_v0.22.0.md) (Tools framework — automated/CI coverage complete; credit/key-gated live feature run is a pending owner gate, see the doc) · [v0.21.0](LIVE_VERIFICATION_v0.21.0.md) (MCP server over stdio + HTTP/SSE — credit-free protocol handshake; 4 tools / 2 prompts / 3 resources confirmed on stdout) · [v0.20.1](LIVE_VERIFICATION_v0.20.1.md) · [v0.20.0](LIVE_VERIFICATION_v0.20.0.md) · [v0.19.0](LIVE_VERIFICATION_v0.19.0.md) · [v0.18.0](LIVE_VERIFICATION_v0.18.0.md) · [v0.17.0](LIVE_VERIFICATION_v0.17.0.md) · [v0.16.0](LIVE_VERIFICATION_v0.16.0.md) · [v0.13.0](LIVE_VERIFICATION_v0.13.0.md) · [v0.12.0](LIVE_VERIFICATION_v0.12.0.md) · [v0.11.0](LIVE_VERIFICATION_v0.11.0.md) · [v0.10.0](LIVE_VERIFICATION_v0.10.0.md) · [v0.9.1](LIVE_VERIFICATION_v0.9.1.md) · [v0.9.0](LIVE_VERIFICATION_v0.9.0.md) · [v0.8.1](LIVE_VERIFICATION_v0.8.1.md) · [v0.7.0](LIVE_VERIFICATION_v0.7.0.md)
|
|
121
121
|
**"Where is the reverse-engineered wire protocol for a feature?"** → the `*_RECON.md` design docs: [CHARACTER_RECON](CHARACTER_RECON.md) (Flow character entity protocol), [IMAGE_UPSCALE_RECON](IMAGE_UPSCALE_RECON.md) (`/v1/flow/upsampleImage` wire). Naming convention: one `<FEATURE>_RECON.md` per reverse-engineered surface, kept as the durable spec after the feature ships.
|
|
122
122
|
**"What was live-verified for the data layer (PR #58)?"** → [LIVE_VERIFICATION_data_layer](LIVE_VERIFICATION_data_layer.md) — 1 Imagen + 1 Veo credit on denon82, 6-layer ledger (file + magic + Pillow + DB rows + CLI round-trip + structlog)
|
|
123
123
|
**"What was live-verified for the video-download feature (#29)?"** → [LIVE_VERIFICATION_video_download](LIVE_VERIFICATION_video_download.md)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Live Verification — v0.26.0
|
|
2
|
+
|
|
3
|
+
Release date: 2026-07-06. Headline: **reference a generated image in `image i2i`
|
|
4
|
+
by its Flow UUID**, attached by **selecting the already-existing asset** in Flow's
|
|
5
|
+
reference picker rather than uploading a duplicate copy (the founder principle:
|
|
6
|
+
avoid duplication). Plus generated images now record their Flow **display name**
|
|
7
|
+
(the searchable picker label), extracted from the response's `workflows[]` array.
|
|
8
|
+
|
|
9
|
+
Live run: 2026-07-06, headed Playwright, profile `ffroliva`, real Flow.
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
| Change | Surface | Verdict |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Image i2i UUID ref → **select existing asset** (no duplicate) | `mcp/tools.py`, `worker/daemon.py`, `api/image.py`, `api/transports/ui_automation*.py` | ✅ **Live e2e GREEN** |
|
|
16
|
+
| Generated-image `display_name` from `workflows[]` | `api/dto.py`, `data/recorder.py` | ✅ Unit-tested (real captured sample); credited @C1ph3r404 |
|
|
17
|
+
|
|
18
|
+
## Mechanism
|
|
19
|
+
|
|
20
|
+
An `image i2i` `reference_images` entry that is a Flow media UUID is resolved at
|
|
21
|
+
enqueue time (best-effort, never errors — an uncatalogued UUID still attaches by
|
|
22
|
+
media id, PR #245) to the asset's `display_name` + on-disk `local_path`. The
|
|
23
|
+
transport then **prefers selecting the existing asset in place**:
|
|
24
|
+
|
|
25
|
+
1. Locate the tile by the **media UUID in its thumbnail URL** (`img[src*=<uuid>]`)
|
|
26
|
+
— robust to display-name collisions, needs no search term.
|
|
27
|
+
2. If not already visible, **search the display name** to surface it, then re-locate
|
|
28
|
+
by UUID.
|
|
29
|
+
3. Attach in place. (The image picker attaches on tile-click and auto-closes; the
|
|
30
|
+
code also handles the video-style explicit "Add to Prompt" include.)
|
|
31
|
+
4. **Fallback — local upload** only when the asset can't be located (e.g. it lives
|
|
32
|
+
in a different project's picker) and a local file exists.
|
|
33
|
+
|
|
34
|
+
## Live end-to-end result — GREEN
|
|
35
|
+
|
|
36
|
+
`gflow_generate_image(prompt="the same rustic workshop wall, wider shot, more
|
|
37
|
+
tools", reference_images=["60dcb880-…" (a generated image's UUID)],
|
|
38
|
+
project="0f4e7eaa-…" (the asset's project), profile="ffroliva")` via the real
|
|
39
|
+
stdio MCP server. 5-layer ledger:
|
|
40
|
+
|
|
41
|
+
1. **Attach path**: structlog `ui_automation_video.image_ref_selected_existing`
|
|
42
|
+
fired — and **no** `image_ref_upload_fallback` / `image_uploaded` event. The
|
|
43
|
+
existing asset was selected; **no duplicate was uploaded**.
|
|
44
|
+
2. **Wire**: the generation ran the `batchGenerateImages` route carrying the
|
|
45
|
+
reference `imageInput` — the ref reached the generation, not a plain t2i.
|
|
46
|
+
3. **File**: exactly one output written, `bee1e8a9-…_1.jpg`, 893 KB.
|
|
47
|
+
4. **Magic bytes**: `ff d8 ff e0` (valid JPEG); task `status: completed`.
|
|
48
|
+
5. **User-confirmable artifact**: the output shows the **same** rustic workshop
|
|
49
|
+
wall and the "v0.25.0" sign as the referenced image, as a wider shot with more
|
|
50
|
+
tools — the i2i reference visibly took.
|
|
51
|
+
|
|
52
|
+
### Pre-fix note
|
|
53
|
+
|
|
54
|
+
The first live attempt selected the asset correctly but errored looking for a
|
|
55
|
+
separate "Add to Prompt" button — the image picker attaches on tile-click and
|
|
56
|
+
auto-closes (one step), unlike the video r2v picker. Fixed to treat an
|
|
57
|
+
auto-closed dialog as success; the re-run above is GREEN. (A hung Chrome holding
|
|
58
|
+
the profile's `SingletonLock` also caused two transient `launch_persistent_context`
|
|
59
|
+
failures between runs — cleared by killing the process + removing the lock.)
|
|
60
|
+
|
|
61
|
+
## Automated coverage
|
|
62
|
+
|
|
63
|
+
- `_enrich_image_refs` unit tests (`tests/mcp/test_tools_helpers.py`): enriches a
|
|
64
|
+
catalogued ref with display_name + local_path; partial meta when no on-disk file;
|
|
65
|
+
uncatalogued UUID passes through with no error and no `ref_meta`.
|
|
66
|
+
- `display_name` extraction tests (`tests/api/test_image_dto.py`) against the real
|
|
67
|
+
captured `06_batchGenerateImages.json` sample.
|
|
68
|
+
- Full suite green (1994 passed; the one deselected `test_packaging` case is a
|
|
69
|
+
network-dependent sdist build, unrelated). `pyright src` 0 errors, ruff clean.
|
|
@@ -45,7 +45,7 @@ Both Click CLI (`src/gflow_cli/cli.py`) and MCP Server (`src/gflow_cli/mcp/`) ar
|
|
|
45
45
|
The server registers three protocol surfaces:
|
|
46
46
|
|
|
47
47
|
### Tools (Executable actions)
|
|
48
|
-
* `gflow_generate_image(prompt, model, aspect, count, seed, reference_images, tools, profile, project)`: Triggers text-to-image / image-to-image (Imagen / Nano Banana). `reference_images` switches to i2i; `project` generates into an existing Flow project id (mirrors CLI `--project`).
|
|
48
|
+
* `gflow_generate_image(prompt, model, aspect, count, seed, reference_images, tools, profile, project)`: Triggers text-to-image / image-to-image (Imagen / Nano Banana). `reference_images` switches to i2i and accepts **either a local file path or a generated image's Flow media UUID**. A UUID reference is attached by **selecting the already-existing asset in Flow's reference picker — no duplicate copy is uploaded** (locating the tile by the media id in its thumbnail URL, and searching the recorded display name to surface it when needed); gflow falls back to uploading the asset's on-disk local file only when it can't be located in place (e.g. it lives in a different project's picker). `project` generates into an existing Flow project id (mirrors CLI `--project`) — pass the reference's project to keep it selectable in place.
|
|
49
49
|
* `gflow_generate_video(prompt, mode, aspect, initial_frame, end_frame, reference_images, tools, profile, project)`: Triggers vertical or landscape video generation (Veo). `mode` is `t2v`/`i2v`/`r2v`; `i2v` requires `initial_frame`, `r2v` requires `reference_images`; `project` generates into an existing Flow project id (mirrors CLI `--project`). `initial_frame`, `end_frame`, and `reference_images` each accept **either a local file path or the Flow image UUID of a generated asset** — pass a generated image's id straight in to chain image→video, and gflow attaches it for you (the UUID is resolved to the asset's already-on-disk local file and uploaded as the frame; no manual download/re-upload step). A UUID that isn't in your local asset catalog is rejected up front with a clear "Reference Not Found" error; a catalogued asset whose local file is no longer on disk gives a "Reference Not On Disk" error (re-generate it or pass a local path).
|
|
50
50
|
* `gflow_list_projects(profile, limit)`: Queries SQLite catalog for recent generation folders.
|
|
51
51
|
* `gflow_list_characters(profile)`: Lists Flow Character entities (requires an active browser session).
|
|
@@ -8,7 +8,7 @@ KeyErrors leak.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
from dataclasses import dataclass
|
|
11
|
+
from dataclasses import dataclass, replace
|
|
12
12
|
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
@@ -128,6 +128,10 @@ class GeneratedImage:
|
|
|
128
128
|
fife_url: str # CDN URL — usually expires after ~6 hours
|
|
129
129
|
dimensions: tuple[int, int] # (width, height)
|
|
130
130
|
media_generation_id: str | None = None
|
|
131
|
+
# Flow-assigned display name (from the response's `workflows[]` array). This
|
|
132
|
+
# is the searchable label the media picker shows — recorded so a generated
|
|
133
|
+
# image can be referenced by name later. Original find by @C1ph3r404 (#253).
|
|
134
|
+
display_name: str | None = None
|
|
131
135
|
|
|
132
136
|
@property
|
|
133
137
|
def is_signed_url(self) -> bool:
|
|
@@ -172,8 +176,37 @@ class GeneratedImage:
|
|
|
172
176
|
if not isinstance(media, list):
|
|
173
177
|
msg = "unexpected batchGenerateImages response shape: media is not a list"
|
|
174
178
|
raise ValueError(msg)
|
|
179
|
+
# Flow returns display names in a sibling `workflows[]` array keyed by
|
|
180
|
+
# workflow id (mirrors AssetInfo.from_upload_response). Build the lookup
|
|
181
|
+
# once and inject the name onto each parsed image. (Find by @C1ph3r404.)
|
|
182
|
+
workflow_names = cls._workflow_display_names(data.get("workflows"))
|
|
175
183
|
items = cast("list[dict[str, Any]]", media)
|
|
176
|
-
|
|
184
|
+
results: list[GeneratedImage] = []
|
|
185
|
+
for item in items:
|
|
186
|
+
img = cls.from_response_item(item)
|
|
187
|
+
name = workflow_names.get(img.workflow_id)
|
|
188
|
+
if name:
|
|
189
|
+
img = replace(img, display_name=name)
|
|
190
|
+
results.append(img)
|
|
191
|
+
return results
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _workflow_display_names(workflows: object) -> dict[str, str]:
|
|
195
|
+
"""Map workflow id → displayName from the response's ``workflows[]``."""
|
|
196
|
+
names: dict[str, str] = {}
|
|
197
|
+
if not isinstance(workflows, list):
|
|
198
|
+
return names
|
|
199
|
+
for w in cast("list[Any]", workflows):
|
|
200
|
+
if not isinstance(w, dict):
|
|
201
|
+
continue
|
|
202
|
+
entry = cast("dict[str, Any]", w)
|
|
203
|
+
w_id = entry.get("name")
|
|
204
|
+
metadata = entry.get("metadata")
|
|
205
|
+
if isinstance(w_id, str) and isinstance(metadata, dict):
|
|
206
|
+
display_name = cast("dict[str, Any]", metadata).get("displayName")
|
|
207
|
+
if isinstance(display_name, str) and display_name:
|
|
208
|
+
names[w_id] = display_name
|
|
209
|
+
return names
|
|
177
210
|
|
|
178
211
|
|
|
179
212
|
@dataclass(frozen=True)
|
|
@@ -185,11 +185,20 @@ class ImageRef:
|
|
|
185
185
|
"""A reference image for I2I, identified by the upload's media UUID.
|
|
186
186
|
|
|
187
187
|
The wire shape is the dict returned by :meth:`to_wire`. The UUID comes
|
|
188
|
-
from a prior ``/v1/flow/uploadImage`` call
|
|
189
|
-
``samples/captured/01_upload_image.json``).
|
|
188
|
+
from a prior ``/v1/flow/uploadImage`` call OR a generated image's media id
|
|
189
|
+
(see ``samples/captured/01_upload_image.json``).
|
|
190
|
+
|
|
191
|
+
``display_name`` and ``local_path`` are UI-automation-only hints (ignored by
|
|
192
|
+
:meth:`to_wire`): they let the transport attach the ref by **selecting the
|
|
193
|
+
already-existing Flow asset in the picker** — located by ``name`` (the media
|
|
194
|
+
UUID) in the tile's image URL, surfaced by ``display_name`` when a search is
|
|
195
|
+
needed — and preferred over re-uploading a duplicate. ``local_path`` is the
|
|
196
|
+
fallback file to upload only when the asset can't be located in place.
|
|
190
197
|
"""
|
|
191
198
|
|
|
192
199
|
name: str
|
|
200
|
+
display_name: str = ""
|
|
201
|
+
local_path: str = ""
|
|
193
202
|
|
|
194
203
|
def __post_init__(self) -> None:
|
|
195
204
|
# Reject empty, whitespace-only, AND whitespace-padded UUIDs.
|
|
@@ -2110,6 +2110,16 @@ class UiAutomationTransport(VideoGenerationMixin):
|
|
|
2110
2110
|
if request.ref_paths:
|
|
2111
2111
|
await self._attach_references(page, list(request.ref_paths), out_dir=out_dir)
|
|
2112
2112
|
|
|
2113
|
+
# Pre-generated image UUID refs: attach by selecting the EXISTING Flow
|
|
2114
|
+
# asset in the reference picker (no duplicate upload — founder principle),
|
|
2115
|
+
# falling back to uploading the local file only when it can't be located.
|
|
2116
|
+
if request.refs:
|
|
2117
|
+
await self._attach_image_uuid_refs(
|
|
2118
|
+
page,
|
|
2119
|
+
[(r.name, r.display_name, r.local_path) for r in request.refs],
|
|
2120
|
+
out_dir=out_dir,
|
|
2121
|
+
)
|
|
2122
|
+
|
|
2113
2123
|
# Entity references: attach locked CHARACTER entities via the Personagens
|
|
2114
2124
|
# picker (inherited from the video transport). The entity must live in the
|
|
2115
2125
|
# project we generate in (pass --project / project_id). Flow's JS then
|
|
@@ -1479,6 +1479,121 @@ class VideoGenerationMixin:
|
|
|
1479
1479
|
)
|
|
1480
1480
|
log.info("ui_automation_video.remote_reference_attached", display_name=name)
|
|
1481
1481
|
|
|
1482
|
+
@staticmethod
|
|
1483
|
+
def _existing_asset_tile(page: Page, media_id: str) -> Locator:
|
|
1484
|
+
"""Locate a picker tile for an already-existing asset by its media UUID.
|
|
1485
|
+
|
|
1486
|
+
The tile's thumbnail URL is ``media.getMediaUrlRedirect?name=<uuid>``, so
|
|
1487
|
+
matching ``img[src*=<uuid>]`` selects the exact asset with no dependence
|
|
1488
|
+
on a display name (robust to name collisions) and no search term.
|
|
1489
|
+
"""
|
|
1490
|
+
return page.locator(f"[role='option']:has(img[src*='{media_id}'])").first
|
|
1491
|
+
|
|
1492
|
+
@staticmethod
|
|
1493
|
+
async def _select_existing_asset(
|
|
1494
|
+
page: Page,
|
|
1495
|
+
media_id: str,
|
|
1496
|
+
display_name: str,
|
|
1497
|
+
*,
|
|
1498
|
+
out_dir: Path | None,
|
|
1499
|
+
dialog_timeout_s: float = REMOTE_PICKER_CLOSE_TIMEOUT_S,
|
|
1500
|
+
) -> bool:
|
|
1501
|
+
"""In the OPEN reference picker, select the already-existing Flow asset
|
|
1502
|
+
identified by ``media_id`` (preferred over uploading a duplicate).
|
|
1503
|
+
|
|
1504
|
+
Locates the tile by the media UUID in its thumbnail URL; if it isn't
|
|
1505
|
+
already visible, searches ``display_name`` to surface it, then re-locates
|
|
1506
|
+
by UUID. Returns ``True`` once attached, ``False`` when the asset can't be
|
|
1507
|
+
located (the caller then falls back to a local upload).
|
|
1508
|
+
"""
|
|
1509
|
+
tile = VideoGenerationMixin._existing_asset_tile(page, media_id)
|
|
1510
|
+
try:
|
|
1511
|
+
await tile.wait_for(state="visible", timeout=4000)
|
|
1512
|
+
except Exception: # noqa: BLE001 - not-visible is an expected branch
|
|
1513
|
+
if not display_name:
|
|
1514
|
+
return False
|
|
1515
|
+
search = page.locator(PICKER_SEARCH_INPUT)
|
|
1516
|
+
# Human-like typing jitter to dodge WAF heuristics — not security.
|
|
1517
|
+
await search.press_sequentially(display_name, delay=random.randint(10, 50)) # NOSONAR
|
|
1518
|
+
await page.wait_for_timeout(800)
|
|
1519
|
+
try:
|
|
1520
|
+
await tile.wait_for(state="visible", timeout=6000)
|
|
1521
|
+
except Exception: # noqa: BLE001 - still not found -> caller uploads
|
|
1522
|
+
return False
|
|
1523
|
+
|
|
1524
|
+
await tile.click()
|
|
1525
|
+
await page.wait_for_timeout(400)
|
|
1526
|
+
|
|
1527
|
+
# The image reference picker attaches on tile-click and auto-closes the
|
|
1528
|
+
# dialog (one step). The video r2v picker instead needs an explicit
|
|
1529
|
+
# "Add to Prompt" include after selecting. Handle both: if the dialog
|
|
1530
|
+
# already closed, the attach registered on click; otherwise resolve and
|
|
1531
|
+
# click the locale-safe include button.
|
|
1532
|
+
dialog = page.locator(DIALOG_ANY).last
|
|
1533
|
+
try:
|
|
1534
|
+
await dialog.wait_for(state="hidden", timeout=2500)
|
|
1535
|
+
return True
|
|
1536
|
+
except Exception: # noqa: BLE001 - still open -> needs explicit include
|
|
1537
|
+
pass
|
|
1538
|
+
|
|
1539
|
+
include = await VideoGenerationMixin._resolve_include_action(
|
|
1540
|
+
page,
|
|
1541
|
+
PICKER_INCLUDE_BUTTON,
|
|
1542
|
+
_INCLUDE_BUTTON_TIER_NAMES,
|
|
1543
|
+
surface="image_ref_include",
|
|
1544
|
+
detail=f"image ref {media_id}",
|
|
1545
|
+
out_dir=out_dir,
|
|
1546
|
+
screenshot_name="image_ref_include_missing.png",
|
|
1547
|
+
)
|
|
1548
|
+
await include.click(timeout=3000)
|
|
1549
|
+
await page.wait_for_timeout(600)
|
|
1550
|
+
try:
|
|
1551
|
+
await dialog.wait_for(state="hidden", timeout=dialog_timeout_s * 1000)
|
|
1552
|
+
except Exception as e:
|
|
1553
|
+
raise TransportTimeoutError(
|
|
1554
|
+
f"image ref {media_id} picker dialog did not close after "
|
|
1555
|
+
f"{dialog_timeout_s}s (the include action may not have registered)",
|
|
1556
|
+
) from e
|
|
1557
|
+
return True
|
|
1558
|
+
|
|
1559
|
+
@staticmethod
|
|
1560
|
+
async def _attach_image_uuid_refs(
|
|
1561
|
+
page: Page,
|
|
1562
|
+
refs: list[tuple[str, str, str]],
|
|
1563
|
+
*,
|
|
1564
|
+
out_dir: Path | None,
|
|
1565
|
+
) -> None:
|
|
1566
|
+
"""Attach pre-generated image UUID references for I2I.
|
|
1567
|
+
|
|
1568
|
+
Each ref is ``(media_id, display_name, local_path)``. **Prefers selecting
|
|
1569
|
+
the already-existing Flow asset** in the picker (no duplicate upload —
|
|
1570
|
+
the founder principle); uploads ``local_path`` only as a fallback when the
|
|
1571
|
+
asset can't be located in place. Raises when neither is possible.
|
|
1572
|
+
"""
|
|
1573
|
+
for media_id, display_name, local_path in refs:
|
|
1574
|
+
add = page.locator(ADD_MEDIA_BUTTON).first
|
|
1575
|
+
await add.wait_for(state="visible", timeout=8000)
|
|
1576
|
+
await add.click()
|
|
1577
|
+
await page.wait_for_timeout(800)
|
|
1578
|
+
|
|
1579
|
+
if await VideoGenerationMixin._select_existing_asset(
|
|
1580
|
+
page, media_id, display_name, out_dir=out_dir
|
|
1581
|
+
):
|
|
1582
|
+
log.info("ui_automation_video.image_ref_selected_existing", media_id=media_id)
|
|
1583
|
+
continue
|
|
1584
|
+
|
|
1585
|
+
if local_path:
|
|
1586
|
+
log.info("ui_automation_video.image_ref_upload_fallback", media_id=media_id)
|
|
1587
|
+
await VideoGenerationMixin._upload_via_open_dialog(
|
|
1588
|
+
page, Path(local_path), log_label="image_ref", out_dir=out_dir
|
|
1589
|
+
)
|
|
1590
|
+
continue
|
|
1591
|
+
|
|
1592
|
+
raise TransportTimeoutError(
|
|
1593
|
+
f"image ref {media_id!r} could not be selected in the picker and "
|
|
1594
|
+
"has no local file to upload — re-generate it or pass a local path.",
|
|
1595
|
+
)
|
|
1596
|
+
|
|
1482
1597
|
@staticmethod
|
|
1483
1598
|
async def _scroll_picker_grid(page: Page, delta_px: int = PICKER_GRID_SCROLL_DELTA_PX) -> None:
|
|
1484
1599
|
"""Wheel-scroll the open resource picker down one step. The Tudo grid is
|
|
@@ -245,6 +245,11 @@ class OperationRecorder:
|
|
|
245
245
|
media_type = mimetypes.guess_type(saved_path.name)[0]
|
|
246
246
|
asset_id = _new_id()
|
|
247
247
|
width, height = image.dimensions
|
|
248
|
+
# Persist the Flow-assigned display name (when present) so a generated
|
|
249
|
+
# image can later be referenced by name — the picker's searchable label.
|
|
250
|
+
metadata: dict[str, str] = {"fife_url": image.fife_url}
|
|
251
|
+
if image.display_name:
|
|
252
|
+
metadata["display_name"] = image.display_name
|
|
248
253
|
repo.upsert_asset(
|
|
249
254
|
AssetRecord(
|
|
250
255
|
id=asset_id,
|
|
@@ -261,7 +266,7 @@ class OperationRecorder:
|
|
|
261
266
|
height=height,
|
|
262
267
|
duration_seconds=None,
|
|
263
268
|
seed=image.seed,
|
|
264
|
-
metadata_json=redact_metadata(
|
|
269
|
+
metadata_json=redact_metadata(metadata),
|
|
265
270
|
),
|
|
266
271
|
)
|
|
267
272
|
repo.link_operation_asset(op_id, asset_id, OperationAssetRole.OUTPUT, i)
|
|
@@ -386,28 +386,70 @@ def _resolve_ref_local_path(
|
|
|
386
386
|
|
|
387
387
|
|
|
388
388
|
# Video task types whose media-id refs are resolved to local file paths and
|
|
389
|
-
# attached via the local-upload path (#237 fix — see _resolve_ref_local_path)
|
|
390
|
-
#
|
|
391
|
-
#
|
|
389
|
+
# attached via the local-upload path (#237 fix — see _resolve_ref_local_path):
|
|
390
|
+
# the video FRAME picker does not surface generated media, so there is no
|
|
391
|
+
# existing tile to select. Image task types DO surface generated media in the
|
|
392
|
+
# reference picker, so their refs are enriched (not rewritten) and attached by
|
|
393
|
+
# selecting the existing asset — see _enrich_image_refs.
|
|
392
394
|
_VIDEO_TASK_TYPES = frozenset({"t2v", "i2v", "r2v"})
|
|
393
395
|
|
|
394
396
|
|
|
397
|
+
def _enrich_image_refs(
|
|
398
|
+
data_repo: DataRepository,
|
|
399
|
+
profile: str,
|
|
400
|
+
payload: dict[str, Any],
|
|
401
|
+
) -> None:
|
|
402
|
+
"""Annotate each media-id ref in an image ``payload`` with its Flow
|
|
403
|
+
``display_name`` and on-disk ``local_path`` (best-effort), so the transport
|
|
404
|
+
can attach it by **selecting the already-existing Flow asset** in the
|
|
405
|
+
reference picker — the preferred path (no duplicate upload) — with the local
|
|
406
|
+
file as an upload fallback. Never errors: an uncatalogued UUID is still a
|
|
407
|
+
valid media id to attach in place (PR #245 — image refs pass through).
|
|
408
|
+
"""
|
|
409
|
+
refs = payload.get("refs")
|
|
410
|
+
if not refs:
|
|
411
|
+
return
|
|
412
|
+
meta: dict[str, dict[str, str]] = {}
|
|
413
|
+
for ref in refs:
|
|
414
|
+
asset = data_repo.get_asset_by_any_id(profile, ref)
|
|
415
|
+
if asset is None:
|
|
416
|
+
continue
|
|
417
|
+
entry: dict[str, str] = {}
|
|
418
|
+
name = asset.metadata_json.get("display_name")
|
|
419
|
+
if isinstance(name, str) and name:
|
|
420
|
+
entry["display_name"] = name
|
|
421
|
+
for local_file in asset.local_files:
|
|
422
|
+
if (
|
|
423
|
+
local_file.storage_provider is None
|
|
424
|
+
and local_file.path is not None
|
|
425
|
+
and local_file.path.is_file()
|
|
426
|
+
):
|
|
427
|
+
entry["local_path"] = str(local_file.path)
|
|
428
|
+
break
|
|
429
|
+
if entry:
|
|
430
|
+
meta[ref] = entry
|
|
431
|
+
if meta:
|
|
432
|
+
payload["ref_meta"] = meta
|
|
433
|
+
|
|
434
|
+
|
|
395
435
|
def _resolve_payload_refs(
|
|
396
436
|
data_repo: DataRepository,
|
|
397
437
|
profile: str,
|
|
398
438
|
payload: dict[str, Any],
|
|
399
439
|
task_type: str,
|
|
400
440
|
) -> dict[str, Any] | None:
|
|
401
|
-
"""
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
envelope on the first
|
|
441
|
+
"""Resolve media-id refs in ``payload`` in place, by task type.
|
|
442
|
+
|
|
443
|
+
Video task types: a start/end frame ref becomes the local
|
|
444
|
+
``start_image``/``end_image`` path and r2v ``refs`` are merged into
|
|
445
|
+
``reference_images`` (local-upload attach — the video frame picker doesn't
|
|
446
|
+
surface generated media). Image task types: refs are *enriched* with
|
|
447
|
+
display_name/local_path so the transport selects the existing asset in the
|
|
448
|
+
picker (see _enrich_image_refs). Returns an error envelope on the first
|
|
449
|
+
unresolvable VIDEO UUID, else ``None`` (image enrichment never errors).
|
|
409
450
|
"""
|
|
410
451
|
if task_type not in _VIDEO_TASK_TYPES:
|
|
452
|
+
_enrich_image_refs(data_repo, profile, payload)
|
|
411
453
|
return None
|
|
412
454
|
if "refs" in payload:
|
|
413
455
|
resolved_paths: list[str] = []
|
|
@@ -291,7 +291,19 @@ class FlowWorker:
|
|
|
291
291
|
model_val = payload.get("model")
|
|
292
292
|
model = ImageModel.from_cli(model_val) if model_val else ImageModel.NARWHAL
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
# ref_meta (set by the MCP layer's _enrich_image_refs) carries the
|
|
295
|
+
# display_name + on-disk local_path per media-id ref, so the transport
|
|
296
|
+
# can select the EXISTING asset in the picker (preferred, no duplicate)
|
|
297
|
+
# and fall back to uploading local_path only if it can't be located.
|
|
298
|
+
ref_meta: dict[str, dict[str, str]] = payload.get("ref_meta", {})
|
|
299
|
+
refs = tuple(
|
|
300
|
+
ImageRef(
|
|
301
|
+
r,
|
|
302
|
+
display_name=ref_meta.get(r, {}).get("display_name", ""),
|
|
303
|
+
local_path=ref_meta.get(r, {}).get("local_path", ""),
|
|
304
|
+
)
|
|
305
|
+
for r in payload.get("refs", [])
|
|
306
|
+
)
|
|
295
307
|
ref_paths = tuple(Path(p) for p in payload.get("ref_paths", []))
|
|
296
308
|
# NOTE: the payload may carry "ref_names" (the MCP layer resolves them
|
|
297
309
|
# for the video request); the image transport attaches remote refs by
|
|
@@ -68,6 +68,36 @@ class TestGeneratedImage:
|
|
|
68
68
|
assert len(results) == 1
|
|
69
69
|
assert results[0].seed == 646428
|
|
70
70
|
|
|
71
|
+
def test_from_response_dict_extracts_display_name_from_workflows(self) -> None:
|
|
72
|
+
"""The Flow-assigned display name lives in the response's ``workflows[]``
|
|
73
|
+
array (keyed by workflow id), not in the media item — extract it onto
|
|
74
|
+
the GeneratedImage so the catalog records a searchable name. (Original
|
|
75
|
+
find by @C1ph3r404, PR #253.)"""
|
|
76
|
+
sample = _load("06_batchGenerateImages.json")
|
|
77
|
+
body = sample["response_body_parsed"]
|
|
78
|
+
results = GeneratedImage.from_response_dict(body)
|
|
79
|
+
assert results[0].display_name == "Warrior Zelda in dungeon"
|
|
80
|
+
|
|
81
|
+
def test_from_response_dict_display_name_none_when_no_workflows(self) -> None:
|
|
82
|
+
"""A response without a matching workflow leaves display_name as None
|
|
83
|
+
(graceful — the field is optional)."""
|
|
84
|
+
item = {
|
|
85
|
+
"name": "m",
|
|
86
|
+
"workflowId": "w-1",
|
|
87
|
+
"image": {
|
|
88
|
+
"generatedImage": {
|
|
89
|
+
"seed": "1",
|
|
90
|
+
"prompt": "p",
|
|
91
|
+
"modelNameType": "NARWHAL",
|
|
92
|
+
"aspectRatio": "IMAGE_ASPECT_RATIO_PORTRAIT",
|
|
93
|
+
"fifeUrl": "https://flow-content.google/image/x",
|
|
94
|
+
},
|
|
95
|
+
"dimensions": {"width": 1, "height": 1},
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
results = GeneratedImage.from_response_dict({"media": [item]})
|
|
99
|
+
assert results[0].display_name is None
|
|
100
|
+
|
|
71
101
|
def test_parse_seeded_landscape_response(self) -> None:
|
|
72
102
|
"""Seeded I2I + 4:3 landscape capture parses to expected dimensions/aspect."""
|
|
73
103
|
sample = _load("07_batchGenerateImages_seeded.json")
|
|
@@ -158,9 +158,10 @@ class _LocalFile:
|
|
|
158
158
|
|
|
159
159
|
|
|
160
160
|
class _Asset:
|
|
161
|
-
def __init__(self, local_files, flow_media_id="m"):
|
|
161
|
+
def __init__(self, local_files, flow_media_id="m", metadata_json=None):
|
|
162
162
|
self.local_files = list(local_files)
|
|
163
163
|
self.flow_media_id = flow_media_id
|
|
164
|
+
self.metadata_json = metadata_json or {}
|
|
164
165
|
|
|
165
166
|
|
|
166
167
|
class _FakeRepo:
|
|
@@ -264,9 +265,9 @@ def test_resolve_payload_refs_r2v_refs_merge_into_reference_images(tmp_path: Pat
|
|
|
264
265
|
assert "ref_names" not in payload
|
|
265
266
|
|
|
266
267
|
|
|
267
|
-
def
|
|
268
|
-
"""PR #245
|
|
269
|
-
|
|
268
|
+
def test_resolve_payload_refs_image_uncatalogued_uuid_passes_through() -> None:
|
|
269
|
+
"""PR #245: an image 'refs' UUID not in the local catalog must pass through
|
|
270
|
+
without error (still a valid media id to attach in place); video errors."""
|
|
270
271
|
from gflow_cli.mcp.tools import _resolve_payload_refs
|
|
271
272
|
|
|
272
273
|
repo = _FakeRepo(asset=None) # UUID not in local catalog
|
|
@@ -274,9 +275,39 @@ def test_resolve_payload_refs_leaves_image_refs_untouched() -> None:
|
|
|
274
275
|
# video task type: resolves (and would error on the missing UUID)
|
|
275
276
|
err_video = _resolve_payload_refs(repo, "default", dict(payload), task_type="r2v")
|
|
276
277
|
assert err_video is not None
|
|
277
|
-
# image task type: passes through
|
|
278
|
+
# image task type: passes through, no error, no ref_meta (nothing to enrich)
|
|
278
279
|
p_image = dict(payload)
|
|
279
280
|
err_image = _resolve_payload_refs(repo, "default", p_image, task_type="i2i")
|
|
280
281
|
assert err_image is None
|
|
281
282
|
assert p_image["refs"] == [_A_UUID]
|
|
282
283
|
assert "reference_images" not in p_image
|
|
284
|
+
assert "ref_meta" not in p_image
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def test_resolve_payload_refs_image_enriches_found_ref(tmp_path: Path) -> None:
|
|
288
|
+
"""An image 'refs' UUID that IS in the catalog is enriched with its
|
|
289
|
+
display_name and on-disk local_path (ref_meta) so the transport can select
|
|
290
|
+
the existing asset in the picker (prefer-existing), refs left in place."""
|
|
291
|
+
from gflow_cli.mcp.tools import _resolve_payload_refs
|
|
292
|
+
|
|
293
|
+
img = tmp_path / "gen.png"
|
|
294
|
+
img.write_bytes(b"\x89PNG\r\n\x1a\n")
|
|
295
|
+
repo = _FakeRepo(asset=_Asset([_LocalFile(img)], metadata_json={"display_name": "Cozy cabin"}))
|
|
296
|
+
payload = {"refs": [_A_UUID]}
|
|
297
|
+
err = _resolve_payload_refs(repo, "default", payload, task_type="i2i")
|
|
298
|
+
assert err is None
|
|
299
|
+
assert payload["refs"] == [_A_UUID] # not rewritten
|
|
300
|
+
assert payload["ref_meta"] == {_A_UUID: {"display_name": "Cozy cabin", "local_path": str(img)}}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def test_resolve_payload_refs_image_enrich_partial_meta_only() -> None:
|
|
304
|
+
"""Enrichment records only what's available — a catalogued asset with no
|
|
305
|
+
on-disk file contributes just its display_name (picker-select still works;
|
|
306
|
+
no local-upload fallback for that ref)."""
|
|
307
|
+
from gflow_cli.mcp.tools import _resolve_payload_refs
|
|
308
|
+
|
|
309
|
+
repo = _FakeRepo(asset=_Asset([], metadata_json={"display_name": "Named but pruned"}))
|
|
310
|
+
payload = {"refs": [_A_UUID]}
|
|
311
|
+
err = _resolve_payload_refs(repo, "default", payload, task_type="i2i")
|
|
312
|
+
assert err is None
|
|
313
|
+
assert payload["ref_meta"] == {_A_UUID: {"display_name": "Named but pruned"}}
|
|
@@ -25,6 +25,7 @@ class FakeGeneratedImage:
|
|
|
25
25
|
aspect_ratio: str = "1:1"
|
|
26
26
|
seed: int = 12345
|
|
27
27
|
fife_url: str = "http://fake"
|
|
28
|
+
display_name: str | None = None # mirrors GeneratedImage.display_name
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def _completed_video_result(media_id: str = "media-vid-123") -> VideoResult:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|