gflow-cli 0.17.0__tar.gz → 0.18.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.17.0 → gflow_cli-0.18.0}/CHANGELOG.md +20 -1
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/PKG-INFO +1 -1
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/INDEX.md +1 -1
- gflow_cli-0.18.0/docs/LIVE_VERIFICATION_v0.18.0.md +72 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/USAGE.md +6 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/pyproject.toml +1 -1
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/__init__.py +1 -1
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/ui_automation.py +15 -5
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/ui_automation_video.py +34 -8
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_image.py +4 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/errors.py +31 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/exceptions.py +1 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_automation_image_mode.py +24 -2
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_automation_video.py +16 -3
- gflow_cli-0.18.0/tests/cli/test_cli_image_selector_drift.py +74 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_errors.py +37 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/uv.lock +1 -1
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/README.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/active.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/branch-review.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/changelog.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/check.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/doc-review.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/known-issues.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/next.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/plan.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/pr-council-review.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/predict.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/release.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/scenario.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/status.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.env.template +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.gitattributes +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/CODEOWNERS +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/copilot-instructions.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/dependabot.yml +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/ci.yml +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/external-pr-triage.yml +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/governance-advisory.yml +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/governance-benchmark.yml +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/release.yml +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.gitignore +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.gitleaks.toml +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.pre-commit-config.yaml +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.secrets.baseline +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/AGENTS.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/CLAUDE.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/CONFIGURATION.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/CONTRIBUTING.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/DISCLAIMER.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/GEMINI.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/KNOWN_ISSUES.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/LICENSE +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/PLAN.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/README.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/RELEASE.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/ROADMAP.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/conftest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docker-compose.yml +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/AGENT_GUIDE.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/ARCHITECTURE.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/AUTHENTICATION.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/CHARACTER.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/CHARACTER_RECON.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/CONFIGURATION.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/DATA_LAYER.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/DEBUGGING.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/DEMOS.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/DEVELOPMENT.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/E2E_TESTING.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/EXTERNAL_STORAGE.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/GITHUB.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/GOVERNANCE_BENCHMARK.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/IMAGE_UPSCALE_RECON.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_data_layer.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_image_batch.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.10.0.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.11.0.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.12.0.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.13.0.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.16.0.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.17.0.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.7.0.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.8.1.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.9.0.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.9.1.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_video_download.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/MOVIE.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/PROJECT_STATUS.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/SECURITY.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/USER_GUIDE.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/assets/demo-split-pf.gif +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/assets/example-run.gif +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/schemas/movie-handoff.schema.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/superpowers/plans/2026-06-12-issue-174-library-ui-attach/PLAN.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/README.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/batch_from_config.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/multi_prompt_t2i.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/sample_config.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/sample_prompts.txt +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/single_image_t2i.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/workflow_chain.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/llms.txt +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/README.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/01_upload_image.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/04_archive_workflow.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/05_createProject.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/06_batchGenerateImages.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/08_batchAsyncGenerateVideoStartAndEndImage.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/09_batchAsyncGenerateVideoReferenceImages.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/10_batchCheckAsyncVideoGenerationStatus_successful.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/11_batchCheckAsyncVideoGenerationStatus_failed.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/12_create_scene.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/13_sceneWorkflows_update.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/14_get_scene_workflows.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/15_commit_flowWorkflow.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/ci/check_doc_links.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/ci/check_materiality.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/ci/check_repo_hygiene.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/debug_editor.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/debug_gen_settings.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/debug_settings.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/_recording_client.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/_spike_common.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/active_plan.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_i2v_frame_slots_dom.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_i2v_intercept_submit.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_i2v_model_select_repro.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_i2v_post_bind_state.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_image_add_media_dom.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_locale_invariants.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/cdp_drive_and_probe.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/character_create_spike.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/character_create_spike_v2.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/dump_character_selectors.js +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/make_project.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/materiality_backtest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/monitor_pr_38.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/patch_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/record_flow_capture.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/skillopt/README.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/skillopt/harness.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/skillopt/tasks.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_char_editor_dom.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_char_gen_capture.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_image_upscale_capture.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_image_upscale_drive.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_image_upscale_recaptcha_action.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_image_upscale_rest_probe.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_issue170_picker_locale_recon.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_issue174_library_ui_recon.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_attach_payload.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_entity_recon.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_gen_capture.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_picker_select.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_voice_list.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_patch_entity.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/diag/README.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/diag/capture_flow_traffic.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/diag/memory_profile.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/diag/recaptcha_mint.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/record_demo.ps1 +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/smoke_image.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/smoke_real_chrome_image.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/smoke_video_editor.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/smoke_worker_style.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/verify_chrome_auth_viability.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/README.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/gflow-cli/SKILL.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/plan/SKILL.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/pr-council-review/SKILL.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/predict/SKILL.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/scenario/SKILL.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/status/SKILL.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/sonar-project.properties +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/__main__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/_cli_helpers.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/_retry.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/_sapisidhash.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/client.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/dto.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/image.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/image_upscale.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/recaptcha.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/routes.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/scene.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/_common.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/_fingerprint.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/base.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/experimental/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/experimental/bearer.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/experimental/evaluate_fetch.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/experimental/sapisidhash.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/video.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/base.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/cookies.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/factory.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/internal_chromium.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/real_chrome.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/strategies.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/verification.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/browser_manager.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/chain.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/chain_manifest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_data.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_models.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_movie.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_run.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_scene.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_video.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/composition.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/config.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/chain_repo.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0001_initial.sql +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0002_add_cloud_storage.sql +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0003_add_scene_tables.sql +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0004_add_scene_output_path.sql +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0005_add_chain_links.sql +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0006_add_operations_metadata.sql +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/models.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/queries.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/recorder.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/redaction.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/repository.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/store.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/image_batch.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/json_output.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/manifest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/media.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/movie_manifest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/observability.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/paths.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/profile_store.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/services/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/services/character_create.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/storage.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tasks/lessons.md +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/test_assets/sample_batch.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/test_assets/sample_batch.tsv +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/test_assets/sample_batch_invalid.tsv +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/fixtures/character_gen_response.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/fixtures/patch_entity_response.json +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_aisandbox_auth_error.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_aisandbox_auth_headers.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_bearer_redaction.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_delete_characters.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_generate_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_image.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_launch_kwargs.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_patch_entity.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_scene.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_upscale.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_concurrency.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_dto.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_image.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_image_dto.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_image_upscale.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_post_json_aisandbox_auth.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_recaptcha.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_retry.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_routes.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_routes_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_routes_scene.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_sapisidhash_helper.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_scene_models.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_video.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_video_request.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_base.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_bearer.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_common.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_evaluate_fetch.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_factory.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_fingerprint.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_sapisidhash.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_transport_timeout.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_automation.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_automation_batch.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_character_editor.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/auth/strategies/test_factory.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/auth/strategies/test_strategies.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/auth/test_verification.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_auth_list.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_character_create.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_data.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_image.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_image_seed_removed.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_image_upscale.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_models.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_movie.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_run.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_scene.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_video.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_video_chain.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_error_handling.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_helpers.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_movie_manifest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_t2i_multi_prompt.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/composition/test_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/composition/test_compose_prompt.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/composition/test_handoff.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/conftest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_chain_repo.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_find_incomplete_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_models.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_packaging.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_recorder.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_recorder_character.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_redaction.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_repository.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_scene_persistence.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_settings_and_errors.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_store_migrations.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/dev/test_record_flow_capture.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/dev/test_recording_client.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/conftest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_aisandbox_auth_live.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_auth_verification_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_chain_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_character_create_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_data_layer_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_i2v_flags_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_image_batch_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_image_i2i_ref_cap_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_json_output_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_locale_selectors_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_scene_compose_live.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_transports_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_video_r2v_ref_cap_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_video_t2v_e2e.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/auth.feature +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/auth_login.feature +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/character_create.feature +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/character_read.feature +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/conftest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/image.feature +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/image_upscale.feature +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/locale_picker_include.feature +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_auth_login_steps.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_auth_steps.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_character_create_steps.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_character_read_steps.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_image_steps.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_image_upscale_steps.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_locale_picker_include_steps.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_step_collision_guard.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_video_chain_steps.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/video_chain.feature +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/fixtures/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/fixtures/seeded_catalog.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/image_batch/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/image_batch/test_image_manifest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/image_batch/test_observability_events.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/conftest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/constants.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/test_storage_gcs.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/test_storage_s3.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/scripts/test_capture_locale_invariants.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/scripts/test_check_materiality.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/scripts/test_check_repo_hygiene.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/scripts/test_materiality_backtest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/services/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/services/test_character_create_redaction.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/services/test_character_create_saga.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/services/test_character_gen_no_direct_post.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/smoke/__init__.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/smoke/test_profile_account_smoke.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/smoke/test_real_flow.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_auth.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_browser_manager.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_chain.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_chain_manifest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_cli_data.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_config.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_conftest_isolation.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_data_queries.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_documentation_gate.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_errors_403.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_json_output.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_manifest.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_marker_registry.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_media.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_observability.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_paths.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_profile_store.py +0 -0
- {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_smoke.py +0 -0
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.18.0] — 2026-06-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `UiSelectorDriftError` (exit code 23): UI-automation selector-probe failures — e.g. the
|
|
15
|
+
mode-switch `crop_*` trigger missing from the Flow editor (issue #183) — now raise a typed
|
|
16
|
+
error carrying the probe name, the debug-screenshot path, and a remediation hint, instead
|
|
17
|
+
of an opaque "Unexpected error" (exit 1) whose message was hashed away in logs. Converted
|
|
18
|
+
probes: mode-switch trigger, Image/Video mode tabs, and video sub-mode tabs, in both the
|
|
19
|
+
image and video transports.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `gflow image` commands (`t2i` / `i2i` / `upscale` / `upload`) now plumb their output
|
|
24
|
+
directory into the API client, so debug screenshots are actually captured on
|
|
25
|
+
UI-automation failures. Previously the transport's screenshot directory was never set on
|
|
26
|
+
the image path and drift errors reported `Screenshot: None` even with `--verbose`.
|
|
27
|
+
|
|
10
28
|
## [0.17.0] — 2026-06-12
|
|
11
29
|
|
|
12
30
|
### Added
|
|
@@ -1509,7 +1527,8 @@ shell-script template that branches on these codes.
|
|
|
1509
1527
|
|
|
1510
1528
|
First skeleton. Not functional end-to-end yet.
|
|
1511
1529
|
|
|
1512
|
-
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.
|
|
1530
|
+
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.18.0...HEAD
|
|
1531
|
+
[0.18.0]: https://github.com/ffroliva/gflow-cli/compare/v0.17.0...v0.18.0
|
|
1513
1532
|
[0.17.0]: https://github.com/ffroliva/gflow-cli/compare/v0.16.0...v0.17.0
|
|
1514
1533
|
[0.16.0]: https://github.com/ffroliva/gflow-cli/compare/v0.15.1...v0.16.0
|
|
1515
1534
|
[0.15.1]: https://github.com/ffroliva/gflow-cli/compare/v0.15.0...v0.15.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gflow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.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
|
|
@@ -111,7 +111,7 @@ Slash commands for Claude Code, stored in `.claude/commands/gflow/`. All prefixe
|
|
|
111
111
|
**"A gflow command hangs / fails — where do I start?"** → [DEBUGGING § Quick reference](DEBUGGING.md#quick-reference)
|
|
112
112
|
**"Flow's UI broke a selector — how do I diagnose it?"** → [DEBUGGING § Inspecting Flow's live UI](DEBUGGING.md#inspecting-flows-live-ui)
|
|
113
113
|
**"What does each `ui_automation.*` log event mean?"** → [DEBUGGING § Listener & HTTP-layer debugging](DEBUGGING.md#listener--http-layer-debugging)
|
|
114
|
-
**"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.
|
|
114
|
+
**"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.18.0](LIVE_VERIFICATION_v0.18.0.md) (#183 exit-23 selector-drift error: live drift simulation + happy path, credit-free). v0.14.0 / v0.15.0 / v0.15.1 shipped without a dedicated doc (CI + the v0.13.0 baseline). Prior: [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)
|
|
115
115
|
**"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.
|
|
116
116
|
**"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)
|
|
117
117
|
**"What was live-verified for the video-download feature (#29)?"** → [LIVE_VERIFICATION_video_download](LIVE_VERIFICATION_video_download.md)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Live verification — v0.18.0
|
|
2
|
+
|
|
3
|
+
> Evidence record for the v0.18.0 release. v0.18.0 ships one feature — typed
|
|
4
|
+
> `UiSelectorDriftError` (exit code 23) for UI-automation selector-probe failures
|
|
5
|
+
> (issue #183, PR #184) — and one fix: the `gflow image` command family now wires
|
|
6
|
+
> its output directory into the API client so debug screenshots are actually
|
|
7
|
+
> captured on UI-automation failures.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
- **Verified by:** ffroliva (Claude Code)
|
|
12
|
+
- **Date:** 2026-06-12 (live runs on the PR #184 head `45ae442`, merged unchanged
|
|
13
|
+
into `develop @ 9f1d80b`)
|
|
14
|
+
- **gflow-cli version:** 0.18.0 (pre-tag verification)
|
|
15
|
+
- **Status:** 🟢 Green — both the failure path (drift simulation) and the happy
|
|
16
|
+
path live-verified, **credit-free** (image generation spends no Veo credits)
|
|
17
|
+
|
|
18
|
+
## 1. Pre-tag gates
|
|
19
|
+
|
|
20
|
+
- `ruff check src tests` — clean; `ruff format --check` — 220 files formatted
|
|
21
|
+
- `pyright src` — 0 errors (7 pre-existing `browser_cookie3` resolution errors in
|
|
22
|
+
the local env only, untouched `auth/cookies.py`; CI green)
|
|
23
|
+
- Scoped pytest (errors + transports + cli surfaces) — 475 passed, 1 skipped
|
|
24
|
+
- PR #184 CI — 7/7 checks green on `45ae442` (test matrix 3.11/3.12/3.13,
|
|
25
|
+
gitleaks, SonarCloud)
|
|
26
|
+
|
|
27
|
+
## 2. Failure path — live drift simulation (the issue-#183 condition)
|
|
28
|
+
|
|
29
|
+
`MODE_SWITCH_TRIGGER_SELECTORS` was patched (in both transport module namespaces)
|
|
30
|
+
to a guaranteed-miss selector, reproducing exactly the reporter's DOM condition —
|
|
31
|
+
the crop_* mode-switch trigger absent from the Flow editor. The real CLI t2i path
|
|
32
|
+
was then run against live Flow (profile `denon82`, project created, $0 credits —
|
|
33
|
+
the failure fires before any generation).
|
|
34
|
+
|
|
35
|
+
| Ledger layer | Evidence |
|
|
36
|
+
|---|---|
|
|
37
|
+
| Exit code | **23** (`UiSelectorDriftError` via `EXIT_CODE_MAP`) |
|
|
38
|
+
| File count | 1 — `debug_no_mode_trigger.png` written to the `--out` dir |
|
|
39
|
+
| Magic bytes | `\x89PNG\r\n\x1a\n` ✅ (valid PNG) |
|
|
40
|
+
| Dimensions | 1280×720 (Pillow) |
|
|
41
|
+
| structlog invariants | `selector_miss` ×1 (bogus selector) → `selector_probe_failed` → `error_raised` with full RFC 9457 problem payload; **zero** `error_unhandled` / `message_hash` events |
|
|
42
|
+
| User-visible artifact | `Flow UI selector drift: probe=mode_switch_trigger: no matching element found on the Flow editor. Screenshot: <out-dir>/debug_no_mode_trigger.png` + remediation hint (no `--verbose` claim, PII warning present) |
|
|
43
|
+
|
|
44
|
+
This is the user-facing contract the release exists for: the issue-#183 reporter
|
|
45
|
+
saw `Unexpected error` (exit 1) with the real message hashed away; on v0.18.0 the
|
|
46
|
+
same condition produces an actionable, screenshot-carrying exit-23 error.
|
|
47
|
+
|
|
48
|
+
## 3. Happy path — mode-switch unregressed
|
|
49
|
+
|
|
50
|
+
`gflow --verbose image t2i "<prompt>" --profile denon82 --aspect 9:16 --out <dir>`
|
|
51
|
+
against live Flow:
|
|
52
|
+
|
|
53
|
+
| Ledger layer | Evidence |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Exit code | 0 |
|
|
56
|
+
| File count | 1 (`ec262f4c-….jpg`) |
|
|
57
|
+
| Magic bytes | `\xff\xd8\xff` ✅ (valid JPEG) |
|
|
58
|
+
| Dimensions | 768×1376 (portrait — matches `--aspect 9:16`) |
|
|
59
|
+
| structlog invariants | `image_mode_entered` fired (mode-switch dropdown found and used); no `UiSelectorDriftError` in the run |
|
|
60
|
+
| User gallery | image present in the Flow project gallery (denon82) |
|
|
61
|
+
|
|
62
|
+
## 4. Not verified this cycle (recorded, not omitted)
|
|
63
|
+
|
|
64
|
+
- **The drift error on a genuinely affected account:** the issue-#183 reporter's
|
|
65
|
+
account is in the (suspected) issue-#174 new-UI A/B cohort; both maintainer
|
|
66
|
+
accounts probed as the old dialog UI on 2026-06-12, so the organic condition is
|
|
67
|
+
not reproducible on our side. The simulation in § 2 exercises the identical
|
|
68
|
+
code path (selector cascade miss → typed raise → CLI boundary → exit 23).
|
|
69
|
+
Reporter confirmation is requested on #183.
|
|
70
|
+
- **`cli_character.py` screenshot wiring:** intentionally out of scope for this
|
|
71
|
+
release (tracked as a follow-up); its drift errors correctly omit the
|
|
72
|
+
`Screenshot:` clause.
|
|
@@ -1022,9 +1022,15 @@ shell scripts can branch on the failure mode without parsing stderr.
|
|
|
1022
1022
|
| `12` | `AuthLoginTimeoutError` | Browser sign-in was not completed in time | Re-run login or raise `GFLOW_CLI_AUTH_LOGIN_TIMEOUT` |
|
|
1023
1023
|
| `13` | `SecurityError` | Unsafe local profile or secret handling blocked | Follow the error's safety guidance |
|
|
1024
1024
|
| `14` | `AuthBrowserRejectedError` | Google rejected the login browser | `gflow auth login --browser chrome` |
|
|
1025
|
+
| `15` | `BrowserSessionClosedError` | The automation browser window was closed mid-operation | Re-run; keep the browser window open until the command finishes |
|
|
1025
1026
|
| `16` | `DataStoreError` | Local database cannot be opened, a migration failed, or the DB schema is newer than the installed gflow-cli | See below |
|
|
1026
1027
|
| `17` | `ModelModeIncompatibilityError` | The chosen video model can't do the requested mode (e.g. `--model omni-flash` with an `i2v` start/end frame — issue #125) | Use `--model veo-lite` (or veo-fast / veo-quality / veo-lite-lp) for `i2v` |
|
|
1027
1028
|
| `18` | `VideoModelSelectionError` | gflow could not select the requested video model in Flow's editor for an `i2v` run (model-picker option not found) | Usually transient — retry; if it persists, Flow's model-picker UI changed (report referencing #125) |
|
|
1029
|
+
| `19` | `SceneConcatError` | Server-side scene render/concat failed (`gflow scene --output`) | Retry; the recorded compose survives, so re-render is safe |
|
|
1030
|
+
| `20` | `FrameExtractionError` | Could not extract the last frame for a video chain link | Check the source video downloaded intact; retry the link |
|
|
1031
|
+
| `21` | `ChainPartialError` | A video chain stopped mid-way; earlier links completed | Resume from the last completed link shown in the error |
|
|
1032
|
+
| `22` | `UpscaleUnavailableError` | 4K upscale is gated to Flow **Ultra** accounts (HTTP 403) | Use `--scale 2k`, or upgrade the Flow plan |
|
|
1033
|
+
| `23` | `UiSelectorDriftError` | A Flow editor control could not be located — Google changed the frontend (issue #183) | Update gflow-cli; file a bug with the probe name + debug screenshot from the error message |
|
|
1028
1034
|
| `130`| SIGINT | User-interrupted (Ctrl-C) | — |
|
|
1029
1035
|
|
|
1030
1036
|
**Exit code 16 — data store / migration error.** Fires when:
|
|
@@ -32,6 +32,7 @@ from gflow_cli.api.transports.ui_automation_video import (
|
|
|
32
32
|
ENTITY_ATTACH_DRIFT_HINT,
|
|
33
33
|
MODE_SWITCH_TRIGGER_SELECTORS,
|
|
34
34
|
VideoGenerationMixin,
|
|
35
|
+
selector_drift_detail,
|
|
35
36
|
zip_entity_refs,
|
|
36
37
|
)
|
|
37
38
|
from gflow_cli.errors import (
|
|
@@ -39,6 +40,7 @@ from gflow_cli.errors import (
|
|
|
39
40
|
BatchPartialError,
|
|
40
41
|
ContentPolicyError,
|
|
41
42
|
GFlowError,
|
|
43
|
+
UiSelectorDriftError,
|
|
42
44
|
WafRejectionError,
|
|
43
45
|
WireFormatError,
|
|
44
46
|
)
|
|
@@ -989,9 +991,12 @@ class UiAutomationTransport(VideoGenerationMixin):
|
|
|
989
991
|
)
|
|
990
992
|
if trigger is None:
|
|
991
993
|
shot = await _capture_debug_screenshot(page, out_dir, "debug_no_mode_trigger.png")
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
994
|
+
raise UiSelectorDriftError(
|
|
995
|
+
selector_drift_detail(
|
|
996
|
+
"mode_switch_trigger",
|
|
997
|
+
"no matching element found on the Flow editor.",
|
|
998
|
+
shot,
|
|
999
|
+
)
|
|
995
1000
|
)
|
|
996
1001
|
await trigger.click()
|
|
997
1002
|
await page.wait_for_timeout(800)
|
|
@@ -1002,8 +1007,13 @@ class UiAutomationTransport(VideoGenerationMixin):
|
|
|
1002
1007
|
)
|
|
1003
1008
|
if image_tab is None:
|
|
1004
1009
|
shot = await _capture_debug_screenshot(page, out_dir, "debug_no_image_tab.png")
|
|
1005
|
-
|
|
1006
|
-
|
|
1010
|
+
raise UiSelectorDriftError(
|
|
1011
|
+
selector_drift_detail(
|
|
1012
|
+
"image_mode_tab",
|
|
1013
|
+
"Image tab not found in the mode dropdown.",
|
|
1014
|
+
shot,
|
|
1015
|
+
)
|
|
1016
|
+
)
|
|
1007
1017
|
await image_tab.click()
|
|
1008
1018
|
await page.wait_for_timeout(1200)
|
|
1009
1019
|
await page.keyboard.press("Escape")
|
|
@@ -41,6 +41,7 @@ from gflow_cli.errors import (
|
|
|
41
41
|
AuthExpiredError,
|
|
42
42
|
ModelModeIncompatibilityError,
|
|
43
43
|
TransportTimeoutError,
|
|
44
|
+
UiSelectorDriftError,
|
|
44
45
|
VideoModelSelectionError,
|
|
45
46
|
WafRejectionError,
|
|
46
47
|
WireFormatError,
|
|
@@ -396,6 +397,20 @@ async def _capture_debug_screenshot(page: Any, out_dir: Path | None, filename: s
|
|
|
396
397
|
return shot_path
|
|
397
398
|
|
|
398
399
|
|
|
400
|
+
def selector_drift_detail(probe: str, what: str, shot: Path | None) -> str:
|
|
401
|
+
"""Build a :class:`UiSelectorDriftError` detail string for a probe miss.
|
|
402
|
+
|
|
403
|
+
Shared by the image and video transports so the message shape stays
|
|
404
|
+
symmetric, and so the ``Screenshot:`` clause is omitted (rather than
|
|
405
|
+
rendering a literal ``None``) when no debug screenshot was captured —
|
|
406
|
+
the caller had no ``out_dir`` to write into.
|
|
407
|
+
"""
|
|
408
|
+
detail = f"probe={probe}: {what}"
|
|
409
|
+
if shot is not None:
|
|
410
|
+
detail = f"{detail} Screenshot: {shot}"
|
|
411
|
+
return detail
|
|
412
|
+
|
|
413
|
+
|
|
399
414
|
def _summarize_request_image_inputs(request: Any) -> dict[str, Any]:
|
|
400
415
|
"""Privacy-safe summary of the image inputs in a generate request body:
|
|
401
416
|
presence of startImage/endImage + referenceImages count, each as an 8-char
|
|
@@ -811,9 +826,12 @@ class VideoGenerationMixin:
|
|
|
811
826
|
)
|
|
812
827
|
if trigger is None:
|
|
813
828
|
shot = await _capture_debug_screenshot(page, out_dir, "debug_no_mode_trigger.png")
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
829
|
+
raise UiSelectorDriftError(
|
|
830
|
+
selector_drift_detail(
|
|
831
|
+
"mode_switch_trigger",
|
|
832
|
+
"no matching element found on the Flow editor.",
|
|
833
|
+
shot,
|
|
834
|
+
)
|
|
817
835
|
)
|
|
818
836
|
await trigger.click()
|
|
819
837
|
await page.wait_for_timeout(800)
|
|
@@ -824,8 +842,13 @@ class VideoGenerationMixin:
|
|
|
824
842
|
)
|
|
825
843
|
if video_tab is None:
|
|
826
844
|
shot = await _capture_debug_screenshot(page, out_dir, "debug_no_video_tab.png")
|
|
827
|
-
|
|
828
|
-
|
|
845
|
+
raise UiSelectorDriftError(
|
|
846
|
+
selector_drift_detail(
|
|
847
|
+
"video_mode_tab",
|
|
848
|
+
"Video tab not found in the mode dropdown.",
|
|
849
|
+
shot,
|
|
850
|
+
)
|
|
851
|
+
)
|
|
829
852
|
await video_tab.click()
|
|
830
853
|
await page.wait_for_timeout(1200)
|
|
831
854
|
log.info("ui_automation_video.video_mode_entered")
|
|
@@ -996,9 +1019,12 @@ class VideoGenerationMixin:
|
|
|
996
1019
|
)
|
|
997
1020
|
if tab is None:
|
|
998
1021
|
shot = await _capture_debug_screenshot(page, out_dir, f"debug_no_submode_{sub}.png")
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1022
|
+
raise UiSelectorDriftError(
|
|
1023
|
+
selector_drift_detail(
|
|
1024
|
+
f"video_submode_{sub}",
|
|
1025
|
+
f"video sub-mode tab {sub!r} not found on the Flow editor.",
|
|
1026
|
+
shot,
|
|
1027
|
+
)
|
|
1002
1028
|
)
|
|
1003
1029
|
await tab.click()
|
|
1004
1030
|
await page.wait_for_timeout(900)
|
|
@@ -353,6 +353,7 @@ async def _run_upload(
|
|
|
353
353
|
profile_dir=profile_dir,
|
|
354
354
|
headless=headless,
|
|
355
355
|
transport=transport,
|
|
356
|
+
out_dir=settings.output_dir,
|
|
356
357
|
) as client:
|
|
357
358
|
console.print(_CREATING_PROJECT_MSG)
|
|
358
359
|
project = await client.create_project(title="gflow-cli upload")
|
|
@@ -544,6 +545,7 @@ async def _run_upscale(
|
|
|
544
545
|
profile_dir=profile_dir,
|
|
545
546
|
headless=headless,
|
|
546
547
|
transport=transport,
|
|
548
|
+
out_dir=output_root,
|
|
547
549
|
) as client:
|
|
548
550
|
console.print(f"Upscaling [bold]{media_id}[/bold] to {scale_label.upper()}...")
|
|
549
551
|
target = await client.upsample_image(
|
|
@@ -871,6 +873,7 @@ async def _run_t2i(
|
|
|
871
873
|
profile_dir=profile_dir,
|
|
872
874
|
headless=headless,
|
|
873
875
|
transport=transport,
|
|
876
|
+
out_dir=out if out is not None else output_root,
|
|
874
877
|
) as client:
|
|
875
878
|
# Title is a `gflow-cli ...` prefix per project convention (post-rename a02684f).
|
|
876
879
|
# cli_video.py's _run_t2v / _run_i2v don't currently set a title — tracked separately.
|
|
@@ -1264,6 +1267,7 @@ async def _run_i2i(
|
|
|
1264
1267
|
profile_dir=profile_dir,
|
|
1265
1268
|
headless=headless,
|
|
1266
1269
|
transport=transport,
|
|
1270
|
+
out_dir=out if out is not None else output_root,
|
|
1267
1271
|
) as client:
|
|
1268
1272
|
# Title is a `gflow-cli ...` prefix per project convention (post-rename a02684f).
|
|
1269
1273
|
# cli_video.py's _run_t2v / _run_i2v don't currently set a title — tracked separately.
|
|
@@ -28,6 +28,7 @@ __all__ = [
|
|
|
28
28
|
"SceneConcatError",
|
|
29
29
|
"SecurityError",
|
|
30
30
|
"TransportTimeoutError",
|
|
31
|
+
"UiSelectorDriftError",
|
|
31
32
|
"UpscaleUnavailableError",
|
|
32
33
|
"VideoModelSelectionError",
|
|
33
34
|
"WafRejectionError",
|
|
@@ -381,6 +382,32 @@ class UpscaleUnavailableError(GFlowError):
|
|
|
381
382
|
)
|
|
382
383
|
|
|
383
384
|
|
|
385
|
+
class UiSelectorDriftError(GFlowError):
|
|
386
|
+
"""Raised when a UI-automation selector cascade finds no matching element.
|
|
387
|
+
|
|
388
|
+
Indicates that Flow's frontend has changed in a way that invalidates one
|
|
389
|
+
of the selector probes (mode-switch trigger, mode tab, sub-mode tab, etc.).
|
|
390
|
+
The ``detail`` names the probe label and includes the debug screenshot path
|
|
391
|
+
when one was captured.
|
|
392
|
+
|
|
393
|
+
This is a hard failure — gflow cannot safely proceed without the control —
|
|
394
|
+
but it is *diagnosed*, not opaque: the user gets the probe name and the
|
|
395
|
+
screenshot for inspection. Exit code 23 lets scripted callers branch on
|
|
396
|
+
"the UI changed and needs a selector update" versus generic error (1).
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
problem_type = "https://gflow-cli.dev/errors/ui-selector-drift"
|
|
400
|
+
title = "Flow UI selector drift"
|
|
401
|
+
_default_remediation = (
|
|
402
|
+
"A Flow editor UI element could not be located — Google may have updated "
|
|
403
|
+
"their frontend. Check for a newer gflow-cli release, then file a bug at "
|
|
404
|
+
"https://github.com/ffroliva/gflow-cli/issues referencing the probe name "
|
|
405
|
+
"and attaching the debug screenshot from this message, if one was captured "
|
|
406
|
+
"(review it first — the viewport may show your account name/avatar; do NOT "
|
|
407
|
+
"include tokens or signed URLs)."
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
384
411
|
class SecurityError(GFlowError):
|
|
385
412
|
"""Raised when a security boundary is violated (e.g. profile_dir outside HOME)."""
|
|
386
413
|
|
|
@@ -643,6 +670,10 @@ EXIT_CODE_MAP: dict[type[GFlowError], int] = {
|
|
|
643
670
|
# from WafRejectionError's 10 even though both are HTTP 403. Direct GFlowError
|
|
644
671
|
# subclass, so unconstrained by the ordering invariant.
|
|
645
672
|
UpscaleUnavailableError: 22,
|
|
673
|
+
# UiSelectorDriftError (issue #183): Flow UI changed, selector probe failed.
|
|
674
|
+
# Direct GFlowError subclass; exit 23 lets scripts distinguish "UI drifted"
|
|
675
|
+
# from generic error (1) without parsing stderr.
|
|
676
|
+
UiSelectorDriftError: 23,
|
|
646
677
|
# ModelModeIncompatibilityError + VideoModelSelectionError BEFORE
|
|
647
678
|
# ConfigurationError (their parent) so the isinstance walk lands on 17/18,
|
|
648
679
|
# not 11. Per [[exit-code-map-ordering-invariant-test-pitfall]].
|
|
@@ -24,6 +24,7 @@ from gflow_cli.errors import ProblemDetails as ProblemDetails
|
|
|
24
24
|
from gflow_cli.errors import RateLimitError as RateLimitError
|
|
25
25
|
from gflow_cli.errors import SecurityError as SecurityError
|
|
26
26
|
from gflow_cli.errors import TransportTimeoutError as TransportTimeoutError
|
|
27
|
+
from gflow_cli.errors import UiSelectorDriftError as UiSelectorDriftError
|
|
27
28
|
from gflow_cli.errors import UpscaleUnavailableError as UpscaleUnavailableError
|
|
28
29
|
from gflow_cli.errors import WafRejectionError as WafRejectionError
|
|
29
30
|
from gflow_cli.errors import WireFormatError as WireFormatError
|
|
@@ -9,6 +9,7 @@ otherwise an account whose last-used mode was Video silently routes
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
from pathlib import Path
|
|
12
13
|
from unittest.mock import AsyncMock, MagicMock
|
|
13
14
|
|
|
14
15
|
import pytest
|
|
@@ -18,6 +19,7 @@ from gflow_cli.api.transports.ui_automation import UiAutomationTransport
|
|
|
18
19
|
from gflow_cli.api.transports.ui_automation_video import (
|
|
19
20
|
MODE_SWITCH_TRIGGER_SELECTORS,
|
|
20
21
|
)
|
|
22
|
+
from gflow_cli.errors import UiSelectorDriftError
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def _cascade_page(visible: set[str]) -> MagicMock:
|
|
@@ -53,15 +55,35 @@ class TestSwitchToImageMode:
|
|
|
53
55
|
@pytest.mark.asyncio
|
|
54
56
|
async def test_raises_when_trigger_missing(self) -> None:
|
|
55
57
|
page = _cascade_page(set())
|
|
56
|
-
with pytest.raises(
|
|
58
|
+
with pytest.raises(UiSelectorDriftError, match="mode_switch_trigger"):
|
|
57
59
|
await UiAutomationTransport._switch_to_image_mode(page, out_dir=None)
|
|
58
60
|
|
|
59
61
|
@pytest.mark.asyncio
|
|
60
62
|
async def test_raises_when_image_tab_missing(self) -> None:
|
|
61
63
|
page = _cascade_page({MODE_SWITCH_TRIGGER_SELECTORS[0]})
|
|
62
|
-
with pytest.raises(
|
|
64
|
+
with pytest.raises(UiSelectorDriftError, match="Image tab"):
|
|
63
65
|
await UiAutomationTransport._switch_to_image_mode(page, out_dir=None)
|
|
64
66
|
|
|
67
|
+
@pytest.mark.asyncio
|
|
68
|
+
async def test_trigger_miss_detail_carries_screenshot_path(self, tmp_path: Path) -> None:
|
|
69
|
+
# The screenshot path is the diagnostic payload issue-#183 reporters
|
|
70
|
+
# need — assert it survives into the user-visible detail.
|
|
71
|
+
page = _cascade_page(set())
|
|
72
|
+
page.screenshot = AsyncMock()
|
|
73
|
+
with pytest.raises(UiSelectorDriftError, match="debug_no_mode_trigger.png"):
|
|
74
|
+
await UiAutomationTransport._switch_to_image_mode(page, out_dir=tmp_path)
|
|
75
|
+
page.screenshot.assert_awaited()
|
|
76
|
+
|
|
77
|
+
@pytest.mark.asyncio
|
|
78
|
+
async def test_trigger_miss_detail_omits_screenshot_clause_without_out_dir(self) -> None:
|
|
79
|
+
# No out_dir → no capture; the detail must drop the clause entirely
|
|
80
|
+
# rather than render a literal "Screenshot: None".
|
|
81
|
+
page = _cascade_page(set())
|
|
82
|
+
with pytest.raises(UiSelectorDriftError) as excinfo:
|
|
83
|
+
await UiAutomationTransport._switch_to_image_mode(page, out_dir=None)
|
|
84
|
+
assert "Screenshot" not in str(excinfo.value)
|
|
85
|
+
assert "None" not in str(excinfo.value)
|
|
86
|
+
|
|
65
87
|
|
|
66
88
|
class TestModeSwitchCallSite:
|
|
67
89
|
"""Regression guard: assert ``_switch_to_image_mode`` is actually
|
|
@@ -19,7 +19,12 @@ from gflow_cli.api.transports.ui_automation_video import (
|
|
|
19
19
|
VideoGenerationMixin,
|
|
20
20
|
)
|
|
21
21
|
from gflow_cli.api.video import Aspect, GenerateVideoRequest, Mode, VideoModel, VideoStatus
|
|
22
|
-
from gflow_cli.errors import
|
|
22
|
+
from gflow_cli.errors import (
|
|
23
|
+
AuthExpiredError,
|
|
24
|
+
TransportTimeoutError,
|
|
25
|
+
UiSelectorDriftError,
|
|
26
|
+
WireFormatError,
|
|
27
|
+
)
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
def _make_listener_page() -> tuple[MagicMock, list]:
|
|
@@ -227,7 +232,7 @@ class TestSwitchToVideoMode:
|
|
|
227
232
|
@pytest.mark.asyncio
|
|
228
233
|
async def test_raises_when_trigger_missing(self) -> None:
|
|
229
234
|
page = _cascade_page(set())
|
|
230
|
-
with pytest.raises(
|
|
235
|
+
with pytest.raises(UiSelectorDriftError, match="mode_switch_trigger"):
|
|
231
236
|
await VideoGenerationMixin._switch_to_video_mode(page, out_dir=None)
|
|
232
237
|
|
|
233
238
|
@pytest.mark.asyncio
|
|
@@ -235,9 +240,17 @@ class TestSwitchToVideoMode:
|
|
|
235
240
|
from gflow_cli.api.transports import ui_automation_video as mod
|
|
236
241
|
|
|
237
242
|
page = _cascade_page({mod.MODE_SWITCH_TRIGGER_SELECTORS[0]})
|
|
238
|
-
with pytest.raises(
|
|
243
|
+
with pytest.raises(UiSelectorDriftError, match="Video tab"):
|
|
239
244
|
await VideoGenerationMixin._switch_to_video_mode(page, out_dir=None)
|
|
240
245
|
|
|
246
|
+
@pytest.mark.asyncio
|
|
247
|
+
async def test_submode_miss_raises_drift_error(self) -> None:
|
|
248
|
+
# The sub-mode probe is the same selector-cascade pattern as the
|
|
249
|
+
# mode-switch trigger and must carry the same typed-error contract.
|
|
250
|
+
page = _cascade_page(set())
|
|
251
|
+
with pytest.raises(UiSelectorDriftError, match="video_submode_references"):
|
|
252
|
+
await VideoGenerationMixin._switch_video_sub_mode(page, "references", out_dir=None)
|
|
253
|
+
|
|
241
254
|
|
|
242
255
|
class TestWaitVideoEditorReady:
|
|
243
256
|
@pytest.mark.asyncio
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""CLI tests for the ``UiSelectorDriftError`` surface (issue #183).
|
|
2
|
+
|
|
3
|
+
Proves the two user-visible guarantees the typed error exists for:
|
|
4
|
+
|
|
5
|
+
1. exit code 23 with the probe name in the output — instead of the old bare
|
|
6
|
+
``RuntimeError`` whose message was hashed by the unhandled-error handler,
|
|
7
|
+
leaving the user with an opaque "Unexpected error" (exit 1);
|
|
8
|
+
2. the ``out_dir`` wiring on the image surface — the transport can only
|
|
9
|
+
capture debug screenshots when ``FlowApiClient`` receives ``out_dir``,
|
|
10
|
+
and it was silently ``None`` for every ``gflow image`` command.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
from click.testing import CliRunner, Result
|
|
20
|
+
|
|
21
|
+
from gflow_cli.errors import UiSelectorDriftError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def runner() -> CliRunner:
|
|
26
|
+
return CliRunner()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _drifting_client() -> MagicMock:
|
|
30
|
+
"""Stub FlowApiClient whose generate_image hits selector drift."""
|
|
31
|
+
client = MagicMock()
|
|
32
|
+
client.__aenter__ = AsyncMock(return_value=client)
|
|
33
|
+
client.__aexit__ = AsyncMock(return_value=None)
|
|
34
|
+
client.create_project = AsyncMock(
|
|
35
|
+
return_value=MagicMock(project_id="proj-1", title="gflow-cli t2i")
|
|
36
|
+
)
|
|
37
|
+
client.generate_image = AsyncMock(
|
|
38
|
+
side_effect=UiSelectorDriftError(
|
|
39
|
+
"probe=mode_switch_trigger: no matching element found on the Flow editor."
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
return client
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _invoke_t2i(runner: CliRunner, tmp_path: Path, out_dir: Path) -> tuple[Result, MagicMock]:
|
|
46
|
+
client = _drifting_client()
|
|
47
|
+
with (
|
|
48
|
+
patch("gflow_cli.cli_image.FlowApiClient", return_value=client) as client_cls,
|
|
49
|
+
patch("gflow_cli.cli_image._make_provider_dir", return_value=tmp_path / "prof"),
|
|
50
|
+
patch("gflow_cli.cli_image._resolve_profile", return_value="default"),
|
|
51
|
+
):
|
|
52
|
+
from gflow_cli.cli import main
|
|
53
|
+
|
|
54
|
+
result = runner.invoke(
|
|
55
|
+
main,
|
|
56
|
+
["image", "t2i", "a cat", "--out", str(out_dir)],
|
|
57
|
+
catch_exceptions=False,
|
|
58
|
+
)
|
|
59
|
+
return result, client_cls
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_t2i_selector_drift_maps_exit_23_with_probe_name(runner: CliRunner, tmp_path: Path) -> None:
|
|
63
|
+
result, _ = _invoke_t2i(runner, tmp_path, tmp_path / "out")
|
|
64
|
+
assert result.exit_code == 23, result.output
|
|
65
|
+
assert "mode_switch_trigger" in result.output
|
|
66
|
+
# Regression guard for the issue-#183 root cause: the typed error's real
|
|
67
|
+
# message must reach the user, not the hashed "Unexpected error" fallback.
|
|
68
|
+
assert "Unexpected error" not in result.output
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_t2i_wires_out_dir_into_client(runner: CliRunner, tmp_path: Path) -> None:
|
|
72
|
+
out_dir = tmp_path / "out"
|
|
73
|
+
_, client_cls = _invoke_t2i(runner, tmp_path, out_dir)
|
|
74
|
+
assert client_cls.call_args.kwargs["out_dir"] == out_dir
|
|
@@ -22,6 +22,7 @@ from gflow_cli.errors import (
|
|
|
22
22
|
ProblemDetails,
|
|
23
23
|
RateLimitError,
|
|
24
24
|
TransportTimeoutError,
|
|
25
|
+
UiSelectorDriftError,
|
|
25
26
|
UpscaleUnavailableError,
|
|
26
27
|
VideoModelSelectionError,
|
|
27
28
|
WafRejectionError,
|
|
@@ -452,3 +453,39 @@ def test_chain_partial_error_partial_results_defaults_empty() -> None:
|
|
|
452
453
|
(but present) ``partial_results`` list — never None."""
|
|
453
454
|
err = ChainPartialError(detail="first link failed")
|
|
454
455
|
assert err.partial_results == []
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
# ---------- UiSelectorDriftError (issue #183) ----------
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def test_ui_selector_drift_error_exit_code_23() -> None:
|
|
462
|
+
"""UiSelectorDriftError -> exit code 23 (issue #183).
|
|
463
|
+
|
|
464
|
+
Raised when a UI-automation selector cascade finds no matching element,
|
|
465
|
+
indicating that Flow's frontend has changed. Exit 23 lets scripted
|
|
466
|
+
callers distinguish "UI drifted" from generic error (1)."""
|
|
467
|
+
err = UiSelectorDriftError(
|
|
468
|
+
detail="probe=mode_switch_trigger: no matching element found on the Flow editor."
|
|
469
|
+
)
|
|
470
|
+
assert _exit_code_for(err) == 23
|
|
471
|
+
assert EXIT_CODE_MAP[UiSelectorDriftError] == 23
|
|
472
|
+
assert isinstance(err, GFlowError)
|
|
473
|
+
assert err.remediation_hint != ""
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def test_ui_selector_drift_error_problem_details() -> None:
|
|
477
|
+
"""UiSelectorDriftError carries RFC 9457 Problem Details with a stable type URI."""
|
|
478
|
+
err = UiSelectorDriftError(detail="probe=image_mode_tab: Image tab not found.")
|
|
479
|
+
pd = err.to_problem_details()
|
|
480
|
+
assert pd["type"] == "https://gflow-cli.dev/errors/ui-selector-drift"
|
|
481
|
+
assert pd["title"] == "Flow UI selector drift"
|
|
482
|
+
assert "image_mode_tab" in pd.get("detail", "")
|
|
483
|
+
assert "remediation_hint" in pd
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def test_ui_selector_drift_error_not_a_subclass_of_flow_api_error() -> None:
|
|
487
|
+
"""UiSelectorDriftError is a direct GFlowError subclass — it is NOT a
|
|
488
|
+
FlowApiError (it is a UI-automation concern, not a wire-protocol error)."""
|
|
489
|
+
err = UiSelectorDriftError(detail="probe=mode_switch_trigger: selector cascade failed.")
|
|
490
|
+
assert isinstance(err, GFlowError)
|
|
491
|
+
assert not isinstance(err, FlowApiError)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|