gflow-cli 0.15.1__tar.gz → 0.16.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.15.1 → gflow_cli-0.16.0}/CHANGELOG.md +12 -1
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/KNOWN_ISSUES.md +31 -1
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/PKG-INFO +1 -1
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/CHARACTER.md +2 -1
- gflow_cli-0.16.0/docs/IMAGE_UPSCALE_RECON.md +115 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/INDEX.md +1 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/MOVIE.md +7 -4
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/USAGE.md +38 -0
- gflow_cli-0.16.0/docs/superpowers/plans/2026-06-11-image-upscale/PLAN.md +206 -0
- gflow_cli-0.16.0/docs/superpowers/plans/2026-06-11-image-upscale/SCENARIO.md +79 -0
- gflow_cli-0.16.0/docs/superpowers/plans/2026-06-11-issue-170-locale-selectors/PLAN.md +218 -0
- gflow_cli-0.16.0/docs/superpowers/plans/2026-06-11-issue-170-locale-selectors/SCENARIO.md +101 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/pyproject.toml +1 -1
- gflow_cli-0.16.0/scripts/dev/spike_image_upscale_capture.py +382 -0
- gflow_cli-0.16.0/scripts/dev/spike_image_upscale_drive.py +383 -0
- gflow_cli-0.16.0/scripts/dev/spike_image_upscale_recaptcha_action.py +188 -0
- gflow_cli-0.16.0/scripts/dev/spike_image_upscale_rest_probe.py +277 -0
- gflow_cli-0.16.0/scripts/dev/spike_issue170_picker_locale_recon.py +234 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/spike_movie_attach_payload.py +13 -1
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/__init__.py +1 -1
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/client.py +137 -0
- gflow_cli-0.16.0/src/gflow_cli/api/image_upscale.py +159 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/routes.py +4 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/ui_automation.py +91 -3
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/ui_automation_video.py +106 -18
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/cli_image.py +176 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/errors.py +31 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/exceptions.py +1 -0
- gflow_cli-0.16.0/tests/api/test_client_upscale.py +169 -0
- gflow_cli-0.16.0/tests/api/test_image_upscale.py +153 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_ui_automation.py +143 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_ui_automation_video.py +159 -13
- gflow_cli-0.16.0/tests/cli/test_cli_image_upscale.py +140 -0
- gflow_cli-0.16.0/tests/features/image_upscale.feature +29 -0
- gflow_cli-0.16.0/tests/features/locale_picker_include.feature +25 -0
- gflow_cli-0.16.0/tests/features/test_image_upscale_steps.py +130 -0
- gflow_cli-0.16.0/tests/features/test_locale_picker_include_steps.py +204 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_errors.py +14 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/uv.lock +1 -1
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/README.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/active.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/branch-review.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/changelog.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/check.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/doc-review.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/known-issues.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/next.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/plan.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/pr-council-review.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/predict.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/release.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/scenario.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.claude/commands/gflow/status.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.continue-here.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.env.template +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.gitattributes +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.github/CODEOWNERS +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.github/copilot-instructions.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.github/dependabot.yml +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.github/workflows/ci.yml +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.github/workflows/external-pr-triage.yml +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.github/workflows/governance-advisory.yml +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.github/workflows/governance-benchmark.yml +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.github/workflows/release.yml +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.gitignore +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.gitleaks.toml +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.pre-commit-config.yaml +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/.secrets.baseline +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/AGENTS.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/CLAUDE.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/CONFIGURATION.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/CONTRIBUTING.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/DISCLAIMER.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/GEMINI.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/LICENSE +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/PLAN.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/PR162_MOVIE_CHARACTER_REVIEW.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/README.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/RELEASE.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/ROADMAP.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/conftest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docker-compose.yml +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/AGENT_GUIDE.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/ARCHITECTURE.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/AUTHENTICATION.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/CHARACTER_RECON.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/CONFIGURATION.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/DATA_LAYER.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/DEBUGGING.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/DEMOS.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/DEVELOPMENT.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/E2E_TESTING.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/EXTERNAL_STORAGE.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/GITHUB.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/GOVERNANCE_BENCHMARK.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_data_layer.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_image_batch.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_v0.10.0.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_v0.11.0.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_v0.12.0.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_v0.13.0.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_v0.7.0.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_v0.8.1.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_v0.9.0.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_v0.9.1.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/LIVE_VERIFICATION_video_download.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/PROJECT_STATUS.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/SECURITY.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/USER_GUIDE.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/assets/demo-split-pf.gif +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/assets/example-run.gif +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/schemas/movie-handoff.schema.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/superpowers/2026-05-17-issue-15-handover.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/superpowers/character-scenario.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/superpowers/plans/2026-06-01-governance-enforcement-advisory/PLAN.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/superpowers/plans/2026-06-04-character-create-recording.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/superpowers/plans/2026-06-06-movie-p0-asyncio-hotfix.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/superpowers/plans/2026-06-06-movie-p1-composition-manifest.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/superpowers/plans/2026-06-06-movie-p2-native-identity-voice.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/superpowers/specs/2026-06-06-movie-consistency-design.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/docs/superpowers/verifications/2026-05-11-phase-4-stage-g.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/examples/README.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/examples/batch_from_config.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/examples/multi_prompt_t2i.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/examples/sample_config.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/examples/sample_prompts.txt +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/examples/single_image_t2i.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/examples/workflow_chain.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/llms.txt +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/README.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/01_upload_image.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/04_archive_workflow.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/05_createProject.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/06_batchGenerateImages.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/08_batchAsyncGenerateVideoStartAndEndImage.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/09_batchAsyncGenerateVideoReferenceImages.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/10_batchCheckAsyncVideoGenerationStatus_successful.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/11_batchCheckAsyncVideoGenerationStatus_failed.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/12_create_scene.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/13_sceneWorkflows_update.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/14_get_scene_workflows.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/samples/captured/15_commit_flowWorkflow.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/ci/check_doc_links.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/ci/check_materiality.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/ci/check_repo_hygiene.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/debug_editor.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/debug_gen_settings.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/debug_settings.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/_recording_client.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/_spike_common.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/active_plan.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/capture_i2v_frame_slots_dom.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/capture_i2v_intercept_submit.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/capture_i2v_model_select_repro.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/capture_i2v_post_bind_state.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/capture_image_add_media_dom.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/capture_locale_invariants.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/cdp_drive_and_probe.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/character_create_spike.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/character_create_spike_v2.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/dump_character_selectors.js +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/make_project.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/materiality_backtest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/monitor_pr_38.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/patch_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/record_flow_capture.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/skillopt/README.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/skillopt/harness.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/skillopt/tasks.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/spike_char_editor_dom.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/spike_char_gen_capture.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/spike_movie_entity_recon.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/spike_movie_gen_capture.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/spike_movie_picker_select.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/spike_movie_voice_list.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/dev/spike_patch_entity.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/diag/README.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/diag/capture_flow_traffic.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/diag/memory_profile.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/diag/recaptcha_mint.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/record_demo.ps1 +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/smoke_image.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/smoke_real_chrome_image.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/smoke_video_editor.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/smoke_worker_style.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/scripts/verify_chrome_auth_viability.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/skills/README.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/skills/gflow-cli/SKILL.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/skills/plan/SKILL.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/skills/pr-council-review/SKILL.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/skills/predict/SKILL.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/skills/scenario/SKILL.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/skills/status/SKILL.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/sonar-project.properties +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/__main__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/_cli_helpers.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/_retry.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/_sapisidhash.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/dto.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/image.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/recaptcha.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/scene.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/_common.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/_fingerprint.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/base.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/experimental/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/experimental/bearer.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/experimental/evaluate_fetch.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/transports/experimental/sapisidhash.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/api/video.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/auth/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/auth/base.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/auth/factory.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/auth/internal_chromium.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/auth/real_chrome.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/auth/strategies.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/auth/verification.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/browser_manager.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/chain.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/chain_manifest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/cli.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/cli_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/cli_data.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/cli_models.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/cli_movie.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/cli_run.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/cli_scene.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/cli_video.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/composition.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/config.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/chain_repo.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/migrations/0001_initial.sql +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/migrations/0002_add_cloud_storage.sql +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/migrations/0003_add_scene_tables.sql +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/migrations/0004_add_scene_output_path.sql +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/migrations/0005_add_chain_links.sql +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/migrations/0006_add_operations_metadata.sql +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/migrations/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/models.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/queries.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/recorder.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/redaction.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/repository.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/data/store.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/image_batch.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/json_output.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/manifest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/media.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/movie_manifest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/observability.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/paths.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/profile_store.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/services/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/services/character_create.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/src/gflow_cli/storage.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tasks/lessons.md +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/test_assets/sample_batch.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/test_assets/sample_batch.tsv +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/test_assets/sample_batch_invalid.tsv +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/fixtures/character_gen_response.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/fixtures/patch_entity_response.json +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_aisandbox_auth_error.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_aisandbox_auth_headers.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_bearer_redaction.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_client.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_client_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_client_delete_characters.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_client_generate_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_client_image.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_client_launch_kwargs.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_client_patch_entity.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_client_scene.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_concurrency.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_dto.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_image.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_image_dto.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_post_json_aisandbox_auth.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_recaptcha.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_retry.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_routes.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_routes_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_routes_scene.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_sapisidhash_helper.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_scene_models.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_video.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/test_video_request.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_base.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_bearer.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_common.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_evaluate_fetch.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_factory.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_fingerprint.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_sapisidhash.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_transport_timeout.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_ui_automation_batch.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_ui_automation_image_mode.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/api/transports/test_ui_character_editor.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/auth/strategies/test_factory.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/auth/strategies/test_strategies.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/auth/test_verification.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_auth_list.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_character_create.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_data.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_image.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_image_seed_removed.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_models.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_movie.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_run.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_scene.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_video.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_cli_video_chain.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_error_handling.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_helpers.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_movie_manifest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/cli/test_t2i_multi_prompt.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/composition/test_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/composition/test_compose_prompt.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/composition/test_handoff.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/conftest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_chain_repo.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_find_incomplete_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_models.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_packaging.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_recorder.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_recorder_character.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_redaction.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_repository.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_scene_persistence.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_settings_and_errors.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/data/test_store_migrations.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/dev/test_record_flow_capture.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/dev/test_recording_client.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/conftest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_aisandbox_auth_live.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_auth_verification_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_chain_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_character_create_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_data_layer_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_i2v_flags_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_image_batch_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_image_i2i_ref_cap_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_json_output_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_locale_selectors_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_scene_compose_live.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_transports_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_video_r2v_ref_cap_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/e2e/test_video_t2v_e2e.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/auth.feature +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/auth_login.feature +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/character_create.feature +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/character_read.feature +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/conftest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/image.feature +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/test_auth_login_steps.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/test_auth_steps.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/test_character_create_steps.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/test_character_read_steps.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/test_image_steps.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/test_step_collision_guard.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/test_video_chain_steps.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/features/video_chain.feature +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/fixtures/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/fixtures/seeded_catalog.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/image_batch/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/image_batch/test_image_manifest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/image_batch/test_observability_events.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/integration/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/integration/conftest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/integration/constants.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/integration/test_storage_gcs.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/integration/test_storage_s3.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/scripts/test_capture_locale_invariants.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/scripts/test_check_materiality.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/scripts/test_check_repo_hygiene.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/scripts/test_materiality_backtest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/services/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/services/test_character_create_redaction.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/services/test_character_create_saga.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/services/test_character_gen_no_direct_post.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/smoke/__init__.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/smoke/test_profile_account_smoke.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/smoke/test_real_flow.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_auth.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_browser_manager.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_chain.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_chain_manifest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_cli_data.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_config.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_conftest_isolation.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_data_queries.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_documentation_gate.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_errors_403.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_json_output.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_manifest.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_marker_registry.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_media.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_observability.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_paths.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_profile_store.py +0 -0
- {gflow_cli-0.15.1 → gflow_cli-0.16.0}/tests/test_smoke.py +0 -0
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.16.0] — 2026-06-12
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`--reference-entity` no longer fails on non-Portuguese Flow accounts** ([#170](https://github.com/ffroliva/gflow-cli/issues/170)) — the resource-picker include selectors hardcoded the pt-BR caption "Incluir no comando"; they are now locale-free tier cascades (context-menu `add`-ligature anchor + pt/ru/en text fallback), fixing `gflow image t2i --reference-entity`, movie R2V entity attach, and Vozes voice attach for every account language. Failures are now typed (`TransportTimeoutError`, exit 9) with a locale-neutral remediation hint, the matched selector tier is logged (`include_selector_tier`) for drift telemetry, and a new image-side submit backstop raises `WireFormatError` (exit 7) if a staged character never reaches the wire instead of silently returning a text-only generation. Thanks @papushin7987 for the live-verified report
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`gflow image upscale <mediaId> --scale 2k|4k`** — upscale a platform-generated image to 2K or 4K (Flow's download-menu 1K/2K/4K options) and save it locally; credit-free. The owning project is resolved from the local catalog (or pass `--project` for images gflow didn't record). 4K requires a Flow Ultra subscription — a non-Ultra 4K request fails fast with exit code 22 (`UpscaleUnavailableError`) rather than a generic 403. Reverse-engineered wire documented in [docs/IMAGE_UPSCALE_RECON.md](docs/IMAGE_UPSCALE_RECON.md); live-verified end-to-end (issue #171)
|
|
19
|
+
|
|
10
20
|
## [0.15.1] — 2026-06-10
|
|
11
21
|
|
|
12
22
|
### Changed
|
|
@@ -1484,7 +1494,8 @@ shell-script template that branches on these codes.
|
|
|
1484
1494
|
|
|
1485
1495
|
First skeleton. Not functional end-to-end yet.
|
|
1486
1496
|
|
|
1487
|
-
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.
|
|
1497
|
+
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.16.0...HEAD
|
|
1498
|
+
[0.16.0]: https://github.com/ffroliva/gflow-cli/compare/v0.15.1...v0.16.0
|
|
1488
1499
|
[0.15.1]: https://github.com/ffroliva/gflow-cli/compare/v0.15.0...v0.15.1
|
|
1489
1500
|
[0.15.0]: https://github.com/ffroliva/gflow-cli/compare/v0.14.0...v0.15.0
|
|
1490
1501
|
[0.14.0]: https://github.com/ffroliva/gflow-cli/compare/v0.13.0...v0.14.0
|
|
@@ -14,6 +14,22 @@ Living list of behaviour that's broken, surprising, or limited by design — alo
|
|
|
14
14
|
|
|
15
15
|
## Open
|
|
16
16
|
|
|
17
|
+
### 4K image upscale requires a Flow Ultra subscription
|
|
18
|
+
|
|
19
|
+
- **Status:** **Open** (by design — a Flow platform limit, not a gflow bug)
|
|
20
|
+
- **Severity:** Low · **Affects:** `gflow image upscale --scale 4k` on non-Ultra accounts
|
|
21
|
+
|
|
22
|
+
`gflow image upscale <mediaId> --scale 4k` returns **exit code 22**
|
|
23
|
+
(`UpscaleUnavailableError`) on accounts below the Ultra tier — Flow gates 4K
|
|
24
|
+
upscaling behind Ultra (the web UI shows an "Upgrade" button instead of a 4K
|
|
25
|
+
option). The account tier is reported on the wire as `userPaygateTier` but is
|
|
26
|
+
enforced server-side, so gflow cannot grant 4K locally.
|
|
27
|
+
|
|
28
|
+
**Workaround:** use `--scale 2k` (available on all tiers), or upgrade the Flow
|
|
29
|
+
account to Ultra. If you just upgraded, re-run `gflow auth login --profile <name>`
|
|
30
|
+
to refresh the session before retrying 4K. Wire detail:
|
|
31
|
+
[docs/IMAGE_UPSCALE_RECON.md](docs/IMAGE_UPSCALE_RECON.md) (#171).
|
|
32
|
+
|
|
17
33
|
### Image generation returns HTTP 401 — `aisandbox-pa` generation endpoint
|
|
18
34
|
|
|
19
35
|
- **Status:** **RESOLVED in v0.7.0** — moved to [Resolved](#resolved) section
|
|
@@ -332,7 +348,7 @@ issue and not blocked by any code change in this repo.
|
|
|
332
348
|
|
|
333
349
|
### `UiAutomationTransport` selectors locale-agnostic — issue #24 Phase 5 complete
|
|
334
350
|
|
|
335
|
-
- **Status:** Resolved (pending owner live e2e on non-EN profile) · **Severity:** Low · **Tracking:** [issue #24](https://github.com/ffroliva/gflow-cli/issues/24), [issue #94](https://github.com/ffroliva/gflow-cli/issues/94)
|
|
351
|
+
- **Status:** Resolved (pending owner live e2e on non-EN profile) · **Severity:** Low · **Tracking:** [issue #24](https://github.com/ffroliva/gflow-cli/issues/24), [issue #94](https://github.com/ffroliva/gflow-cli/issues/94), [issue #170](https://github.com/ffroliva/gflow-cli/issues/170)
|
|
336
352
|
|
|
337
353
|
`--lang=en-US` removed in PR #127 (2026-05-30). All selector groups now use
|
|
338
354
|
locale-stable anchors: `IMAGE_MODEL_OPTION_SELECTORS` and
|
|
@@ -343,6 +359,20 @@ issue and not blocked by any code change in this repo.
|
|
|
343
359
|
Playwright kwarg (persists across all in-session navigations). Full resolution
|
|
344
360
|
gate: live e2e with `gflow image t2i` (each model) on a non-EN Chrome profile.
|
|
345
361
|
|
|
362
|
+
**2026-06-12 correction (issue #170):** the "all selector groups" claim above
|
|
363
|
+
had two stragglers — `PICKER_INCLUDE_BUTTON` and `PICKER_CONTEXT_INCLUDE`
|
|
364
|
+
hardcoded the pt-BR caption "Incluir no comando", breaking
|
|
365
|
+
`--reference-entity` (image t2i), movie R2V entity attach, and Vozes voice
|
|
366
|
+
attach on every non-Portuguese account (Flow renders in the ACCOUNT language;
|
|
367
|
+
`?hl=en` cannot override it). Fixed by converting both constants to sequential
|
|
368
|
+
tier cascades — context-menu Tier 1 is the locale-free `add`-ligature menuitem
|
|
369
|
+
scoped to the open menu; the Vozes button has no ligature, so pt/ru/en text
|
|
370
|
+
leads with a lone-iconless-dialog-button structural fallback. The matched tier
|
|
371
|
+
is emitted as `ui_automation_video.include_selector_tier` (drift telemetry),
|
|
372
|
+
exhaustion raises typed `TransportTimeoutError` (exit 9) with a locale-neutral
|
|
373
|
+
remediation hint, and an image-side submit backstop now raises
|
|
374
|
+
`WireFormatError` when a requested entity never rode the wire.
|
|
375
|
+
|
|
346
376
|
**Phase 2 progress (2026-05-25, develop / post-v0.8.1, unreleased):**
|
|
347
377
|
|
|
348
378
|
- **`ONBOARDING_SELECTORS` restructured** — replaced the original 9 English/PT-BR
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gflow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.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
|
|
@@ -375,7 +375,8 @@ convenience later.
|
|
|
375
375
|
generate) so Flow's JS sets `entityContext` and passes the WAF; gflow captures `media[]`/`workflows[]`
|
|
376
376
|
from the response. Reuse gflow's prompt-submit + response-capture machinery (`ui_automation.py`), but in
|
|
377
377
|
the character-editor context (NOT the new-project flow). Video `--character` reuse = resource picker
|
|
378
|
-
(
|
|
378
|
+
(search resources → Personagens → *include-in-prompt* context-menu action; captions are localized,
|
|
379
|
+
selectors locale-free since issue #170) → generate; mirrors #123 `_attach_likeness`.
|
|
379
380
|
All selectors **ligature/structural only** (language-agnostic).
|
|
380
381
|
- **Cost reality:** the feature is UI-automation-heavy (new character-editor + picker selectors), not the
|
|
381
382
|
"mostly REST" earlier framing. Structural metadata is REST; the value-generating steps are UI.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Image Upscale — Reverse-Engineering Recon
|
|
2
|
+
|
|
3
|
+
> Status: **recon complete, pre-implementation**. Wire empirically captured 2026-06-11
|
|
4
|
+
> (denon82 Pro account, project `ffb768fb…`). Spikes: `scripts/dev/spike_image_upscale_drive.py`
|
|
5
|
+
> (UI drive + capture) and `scripts/dev/spike_image_upscale_rest_probe.py` (schema + REST probe).
|
|
6
|
+
|
|
7
|
+
## Feature
|
|
8
|
+
|
|
9
|
+
Flow's image editor download menu offers **1K Original / 2K Upscaled / 4K Upscaled**. gflow
|
|
10
|
+
today saves only the 1K original (the pre-signed `fifeUrl` from the generation response). This
|
|
11
|
+
feature adds `gflow image upscale <mediaId> --scale 2k|4k` to fetch the upscaled variant.
|
|
12
|
+
|
|
13
|
+
Scope: **platform-generated images only** (referenced by `mediaId`). Uploaded images are not
|
|
14
|
+
supported by this endpoint. **4K is Ultra-tier-gated**; Pro accounts see "Upgrade" in the UI.
|
|
15
|
+
|
|
16
|
+
## Wire protocol
|
|
17
|
+
|
|
18
|
+
### Endpoint
|
|
19
|
+
```
|
|
20
|
+
POST https://aisandbox-pa.googleapis.com/v1/flow/upsampleImage
|
|
21
|
+
```
|
|
22
|
+
Same host + auth class as image generation (`batchGenerateImages`).
|
|
23
|
+
|
|
24
|
+
### Request body
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mediaId": "<source-image-uuid>",
|
|
28
|
+
"targetResolution": "UPSAMPLE_IMAGE_RESOLUTION_2K",
|
|
29
|
+
"clientContext": {
|
|
30
|
+
"recaptchaContext": {
|
|
31
|
+
"token": "<reCAPTCHA-Enterprise-token>",
|
|
32
|
+
"applicationType": "RECAPTCHA_APPLICATION_TYPE_WEB"
|
|
33
|
+
},
|
|
34
|
+
"projectId": "<project-that-owns-the-media>",
|
|
35
|
+
"sessionId": ";<epoch_ms>",
|
|
36
|
+
"tool": "PINHOLE",
|
|
37
|
+
"userPaygateTier": "PAYGATE_TIER_ONE"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
- `targetResolution` enum: `UPSAMPLE_IMAGE_RESOLUTION_2K` | `UPSAMPLE_IMAGE_RESOLUTION_4K`.
|
|
42
|
+
1K = original (no API call — gflow already has it).
|
|
43
|
+
- **The FULL `clientContext` is required.** A minimal `{recaptchaContext}` body returns
|
|
44
|
+
**403 even with a valid token** (confirmed by live smoke). All five fields are needed;
|
|
45
|
+
`projectId` (the project owning the media) is load-bearing.
|
|
46
|
+
- `sessionId` format is `;<epoch_ms>` (e.g. `;1781190457842`), same as image generation.
|
|
47
|
+
- `userPaygateTier` is **where the account tier surfaces on the wire** (`PAYGATE_TIER_ONE`
|
|
48
|
+
on a Pro account). It is client-reported telemetry — the server enforces the real tier
|
|
49
|
+
independently (a non-Ultra 4K request 403s regardless), so it is not a security control.
|
|
50
|
+
|
|
51
|
+
### reCAPTCHA action — `IMAGE_GENERATION`
|
|
52
|
+
The token MUST be minted with action **`IMAGE_GENERATION`** (uppercase). reCAPTCHA Enterprise
|
|
53
|
+
scores by action; the guessed `"upsampleImage"` scored low and 403'd. Captured live by hooking
|
|
54
|
+
`grecaptcha.enterprise.execute` (`scripts/dev/spike_image_upscale_recaptcha_action.py`). The
|
|
55
|
+
site key is page-discovered, so it auto-matches; minting on the bootstrap page is fine — the
|
|
56
|
+
action was the only gap. **Live-verified 2026-06-11**: a 2K upscale wrote a 3.8 MB JPEG.
|
|
57
|
+
|
|
58
|
+
### Response
|
|
59
|
+
```json
|
|
60
|
+
{ "encodedImage": "<base64>" }
|
|
61
|
+
```
|
|
62
|
+
- Single field. ~3.8–5.1 MB base64 observed (varies by source image). Decode → write image bytes.
|
|
63
|
+
- Synchronous: one call returns the result. **No async poll/status loop** (unlike video gen).
|
|
64
|
+
|
|
65
|
+
## Transport decision — browser required, REST is dead
|
|
66
|
+
|
|
67
|
+
| Approach | Viable? | Evidence |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Pure REST / Bearer-only | ❌ No | Bearer `ya29` + **no** reCAPTCHA token → **403** (probed with and without `clientContext`) |
|
|
70
|
+
| Browser reCAPTCHA-mint + POST | ✅ Yes | UI drive returns 200; reuses the existing `batchGenerateImages` transport |
|
|
71
|
+
| Hybrid (browser submit + Bearer poll) | ⚠️ N/A | No poll/download phase exists — response is synchronous inline base64 |
|
|
72
|
+
|
|
73
|
+
**Conclusion:** reCAPTCHA token is **mandatory**. Reuse the existing image-generation reCAPTCHA
|
|
74
|
+
minting path; there is no Bearer-only slice to peel off. See [REST-path capability matrix] — a
|
|
75
|
+
generative-class op stays reCAPTCHA-gated.
|
|
76
|
+
|
|
77
|
+
## Tier gating (4K = Ultra)
|
|
78
|
+
|
|
79
|
+
- 4K is Ultra-only. On Pro, the UI renders "Fazer upgrade" (Upgrade) instead of a clickable 4K.
|
|
80
|
+
- Account tier is **NOT** in `GET /fx/api/auth/session` (only `user` / `expires` / `access_token`).
|
|
81
|
+
It surfaces via the upsell recommendations (`fetchUserRecommendations` onramp list) and is
|
|
82
|
+
enforced server-side. A 4K request on a non-Ultra account is expected to 403 (permission), which
|
|
83
|
+
must be distinguished from a WAF/reCAPTCHA 403.
|
|
84
|
+
- Session staleness is real: an idle session returns `error: ACCESS_TOKEN_REFRESH_NEEDED` and
|
|
85
|
+
re-auths before the call succeeds — so a freshly-upgraded account may need a session refresh
|
|
86
|
+
before 4K unlocks.
|
|
87
|
+
|
|
88
|
+
## UI navigation facts (for the spike/transport)
|
|
89
|
+
|
|
90
|
+
- **Cold-loading `/project/<pid>/edit/<mediaId>` 500s** ("Algo deu errado"). Must land on the
|
|
91
|
+
gallery `/project/<pid>` then click the `a[href*=<mediaId>]` tile (in-app SPA routing).
|
|
92
|
+
- A "What's new" changelog modal overlays the gallery but does **not** block the href tile click.
|
|
93
|
+
- Working selectors (locale leaks into the UI — match language-neutral tokens):
|
|
94
|
+
- Download button: `button:has(i.google-symbols:text-is('download'))`
|
|
95
|
+
- Scale menu item: `[role='menuitem']:has-text('2K')` (and `'4K'`)
|
|
96
|
+
- pt labels are 1K="Tamanho original", 2K/4K="Aumentada" — **never** match on "Aumentada".
|
|
97
|
+
|
|
98
|
+
## Implementation sketch (from `/gflow:predict` GO, 7.5/10)
|
|
99
|
+
|
|
100
|
+
- `api/routes.py`: `UPSAMPLE_IMAGE = f"{FLOW_API_BASE}/flow/upsampleImage"`.
|
|
101
|
+
- `api/image.py`: frozen `UpsampleImageRequest(media_id, target_resolution, recaptcha_token="")`;
|
|
102
|
+
`TargetResolution` enum with `from_cli("2k"|"4k")`.
|
|
103
|
+
- `api/client.py`: `upsample_image()` — mint reCAPTCHA (existing path), POST, validate + decode
|
|
104
|
+
`encodedImage` with `MAX_UPSAMPLE_B64_LEN ≈ 50 MB` cap + `del` + PNG/JPEG magic-byte check
|
|
105
|
+
(mirror `concatenate_scene`).
|
|
106
|
+
- `cli_image.py`: `gflow image upscale <mediaId> --scale 2k|4k [--out]`.
|
|
107
|
+
- `errors.py`: `UpscaleUnavailableError` (exit 22), distinct from WAF/reCAPTCHA 403; **no auto-retry**
|
|
108
|
+
on a 4K 403.
|
|
109
|
+
- Never log `encodedImage`. Record the output via `OperationRecorder` honoring the redaction gate.
|
|
110
|
+
- Discover mediaIds via the existing `gflow data list images`.
|
|
111
|
+
|
|
112
|
+
## Credit cost
|
|
113
|
+
|
|
114
|
+
Image operation → expected **credit-free** (only video generation spends credits). Not yet
|
|
115
|
+
stress-confirmed; treat as free but do not batch aggressively (per-profile WAF heat).
|
|
@@ -34,6 +34,7 @@ Welcome to the `gflow-cli` documentation. This index is the routing layer: it te
|
|
|
34
34
|
| **[docs/DATA_LAYER.md](DATA_LAYER.md)** | Local SQLite catalog: goals, schema, recording flow, redaction, `gflow data` CLI, migrations, extension guide | Anything touching `gflow_cli.data`, debugging missing rows, building I2V/repair tooling, auditing what is stored |
|
|
35
35
|
| **[docs/CHARACTER.md](CHARACTER.md)** | Characters feature spec & system design: domain model, endpoint/cost matrix, sequence diagrams, JSON payloads (I/O), CLI surface, reuse via `referenceEntities` (#145) | Working on `gflow character`, reusing a character in generations, or understanding Flow's character wire protocol |
|
|
36
36
|
| **[docs/MOVIE.md](MOVIE.md)** | `gflow movie` — multi-scene character-consistent films: manifest format, run lifecycle (browser stays open through generate→poll→download), entity-attach mechanism (Personagens right-click), resume/handoff, credits | Working on `gflow movie`, debugging scene generation, or understanding the character-entity attach + consistency model |
|
|
37
|
+
| **[docs/IMAGE_UPSCALE_RECON.md](IMAGE_UPSCALE_RECON.md)** | `gflow image upscale` wire protocol: `upsampleImage` endpoint, full clientContext (projectId/sessionId/tool/userPaygateTier), reCAPTCHA action `IMAGE_GENERATION`, inline base64 response, 4K Ultra-gating, REST-is-dead proof | Working on `gflow image upscale`, debugging the 403/reCAPTCHA path, or understanding Flow's image-upscale wire protocol (#171) |
|
|
37
38
|
| **[tasks/lessons.md](../tasks/lessons.md)** | Running notebook of patterns + reviewer findings, dated and traced to commits | Starting a new phase; debugging "why did the council flag this?" |
|
|
38
39
|
| **[skills/README.md](../skills/README.md)** | Installable agent skill docs (gflow-cli, predict, pr-council-review, scenario) — cross-tool portable Markdown consumed by Claude Code, Cursor, Codex, Gemini CLI, Aider, etc. | Any agent wanting to use gflow-cli correctly |
|
|
39
40
|
| **[scripts/dev/skillopt/README.md](../scripts/dev/skillopt/README.md)** | SkillOpt mock harness — rollout→score loop for measuring and improving skill doc accuracy across multiple LLM providers | Measuring a skill edit's impact; comparing Claude vs GPT-4o vs Gemini on gflow tasks |
|
|
@@ -100,10 +100,13 @@ attached by driving Flow's resource picker:
|
|
|
100
100
|
1. Click **Add Media** in the composer (references sub-mode).
|
|
101
101
|
2. Switch to the **Personagens** (Characters) tab.
|
|
102
102
|
3. **Right-click** the entity tile — addressed by entity id as
|
|
103
|
-
`data-tile-id="fe_id_<entityId>"` — and choose
|
|
104
|
-
the context menu
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
`data-tile-id="fe_id_<entityId>"` — and choose the **include-in-prompt**
|
|
104
|
+
action from the context menu (the `add`-ligature menu item; its caption is
|
|
105
|
+
localized per account language, e.g. "Incluir no comando" on pt-BR or
|
|
106
|
+
"Добавить в запрос" on ru — selectors are locale-free since issue #170).
|
|
107
|
+
*(A plain left-click navigates into the character editor; the inline include
|
|
108
|
+
button on the **Tudo** tab attaches the character's thumbnail as a plain
|
|
109
|
+
image, not the entity.)*
|
|
107
110
|
|
|
108
111
|
This puts `referenceEntities:[{entityId}]` on the
|
|
109
112
|
`video:batchAsyncGenerateVideoReferenceImages` **request**. Flow's **response**
|
|
@@ -105,6 +105,44 @@ UUID=$(gflow image upload hero.png | awk '/Asset UUID:/ {print $3}')
|
|
|
105
105
|
gflow image i2i "make it cinematic" --ref "$UUID"
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
## `gflow image upscale`
|
|
109
|
+
|
|
110
|
+
Upscale a **platform-generated** image to 2K or 4K (the same 1K/2K/4K options Flow's
|
|
111
|
+
download menu offers) and save it locally. Uploaded images are not supported.
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
gflow image upscale MEDIA_ID --scale 2k|4k [OPTIONS]
|
|
115
|
+
|
|
116
|
+
Arguments:
|
|
117
|
+
MEDIA_ID UUID of a Flow-generated image (find one with
|
|
118
|
+
`gflow data list images`).
|
|
119
|
+
|
|
120
|
+
Options:
|
|
121
|
+
--scale [2k|4k] Target resolution. 4k requires a Flow Ultra
|
|
122
|
+
subscription; 1k is the original (no upscale).
|
|
123
|
+
--project ID Project that owns the image. Resolved from the local
|
|
124
|
+
catalog when omitted; pass it explicitly for images
|
|
125
|
+
gflow didn't record (e.g. generated in the web UI).
|
|
126
|
+
--out PATH Output directory (see "Output paths" below).
|
|
127
|
+
--profile NAME Profile name (overrides default).
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Upscale a previously generated image to 2K (project auto-resolved from the catalog)
|
|
132
|
+
gflow image upscale 3a56bb5e-92a2-44f4-9992-3c6a9bf0cd14 --scale 2k
|
|
133
|
+
|
|
134
|
+
# Upscale an image generated in the Flow web UI (not in the local catalog)
|
|
135
|
+
gflow image upscale <mediaId> --scale 2k --project <projectId>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Notes:
|
|
139
|
+
|
|
140
|
+
- **Credit-free** — upscaling is an image operation and spends no credits.
|
|
141
|
+
- **4K is Ultra-only.** On a non-Ultra account a 4K request fails with exit code 22
|
|
142
|
+
(`UpscaleUnavailableError`) and a hint to use `--scale 2k` or upgrade.
|
|
143
|
+
- The result is saved as `<output_dir>/images/<YYYY-MM-DD>/<mediaId>_<scale>.<ext>`
|
|
144
|
+
(extension matches the returned format — usually `.jpg`).
|
|
145
|
+
|
|
108
146
|
## `gflow image t2i`
|
|
109
147
|
|
|
110
148
|
Generate 1–4 images from one text prompt, or run a shell-friendly batch of 1–50
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Image Upscale Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** Run `/gflow:status --feature image-upscale` to find the next
|
|
4
|
+
> unchecked task. Implement one task at a time. Run `/gflow:check` before every commit.
|
|
5
|
+
|
|
6
|
+
**Goal:** Add `gflow image upscale <mediaId> --scale 2k|4k` so a user can fetch Flow's
|
|
7
|
+
server-side 2K/4K upscaled variant of a platform-generated image and save it locally.
|
|
8
|
+
|
|
9
|
+
**Architecture:** Reuses the existing image-generation reCAPTCHA browser transport — same host
|
|
10
|
+
(`aisandbox-pa.googleapis.com`) and `recaptchaContext` shape as `batchGenerateImages`. New work is
|
|
11
|
+
glue: a route constant, a frozen request value object, a synchronous `FlowApiClient.upsample_image()`
|
|
12
|
+
that mints a reCAPTCHA token, POSTs, and decodes the inline `{encodedImage: base64}` (mirroring
|
|
13
|
+
`concatenate_scene`'s cap + `del` + magic-byte pattern), a new exit-coded error, and a Click
|
|
14
|
+
subcommand. No transport-Protocol change (it's a client-level op like `concatenate_scene`). No new
|
|
15
|
+
auth, no new persistent state, no async poll loop.
|
|
16
|
+
|
|
17
|
+
**Tracking issue:** [#171](https://github.com/ffroliva/gflow-cli/issues/171)
|
|
18
|
+
|
|
19
|
+
**Predict verdict:** GO — confidence 7.5/10. Recon: `docs/IMAGE_UPSCALE_RECON.md`. Scenario matrix
|
|
20
|
+
complete (20 scenarios; 12 Critical/High are must-cover).
|
|
21
|
+
|
|
22
|
+
**Risk register:**
|
|
23
|
+
| Severity | Risk | Mitigation |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Critical | 403 ambiguity: tier-gate (4K/Pro) vs WAF heat vs expired token all return 403 | Disambiguate by response context; `UpscaleUnavailableError` (exit 22) for tier; **never auto-retry** a 4K 403 |
|
|
26
|
+
| Critical | ~5 MB `encodedImage` base64 leaks into logs / error context | Never log the field; decode then `del`; structlog carries size only |
|
|
27
|
+
| High | Pathological oversized base64 (4K PNG) → OOM | `MAX_UPSAMPLE_B64_LEN` (~50 MB) cap **before** decode; typed error |
|
|
28
|
+
| High | Response missing `encodedImage` / decodes to non-image | `WireFormatError` (redacted discovery payload); PNG/JPEG magic-byte check |
|
|
29
|
+
| High | Per-profile reCAPTCHA/WAF heat (a 4th gated op) | One upscale per invocation; no implicit batch; document the heat |
|
|
30
|
+
| High | mediaId injection / malformed input | UUID allowlist; reject before any request fires |
|
|
31
|
+
| Medium | Selector drift (download button / scale menu) on Flow UI update | Structural-first, locale-neutral selectors (`download`, `2K`/`4K`); re-capture via spike |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## File structure
|
|
36
|
+
|
|
37
|
+
### New files
|
|
38
|
+
```
|
|
39
|
+
src/gflow_cli/api/image_upscale.py (or extend api/image.py)
|
|
40
|
+
UpsampleImageRequest frozen dataclass + TargetResolution enum (from_cli "2k"/"4k")
|
|
41
|
+
tests/api/test_image_upscale.py
|
|
42
|
+
Unit tests: request build, response parse (cap/magic-byte/missing-field), 403 disambiguation
|
|
43
|
+
tests/features/image_upscale.feature
|
|
44
|
+
BDD scenarios from /gflow:scenario (happy 2K, 4K-on-Pro, oversized, malformed mediaId)
|
|
45
|
+
tests/features/test_image_upscale_steps.py
|
|
46
|
+
Step defs mirroring runtime signatures
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Modified files
|
|
50
|
+
```
|
|
51
|
+
src/gflow_cli/api/routes.py
|
|
52
|
+
+ UPSAMPLE_IMAGE = f"{FLOW_API_BASE}/flow/upsampleImage"
|
|
53
|
+
src/gflow_cli/api/client.py
|
|
54
|
+
+ MAX_UPSAMPLE_B64_LEN; + async def upsample_image(media_id, target_resolution, out_path)
|
|
55
|
+
(mint reCAPTCHA via _mint_recaptcha_token; POST; cap+decode+magic-byte; write via image_output_path)
|
|
56
|
+
src/gflow_cli/errors.py
|
|
57
|
+
+ UpscaleUnavailableError (exit 22) registered in EXIT_CODE_MAP; distinct from WAF/reCAPTCHA 403
|
|
58
|
+
src/gflow_cli/cli_image.py
|
|
59
|
+
+ `upscale` Click command: <mediaId> arg, --scale Choice(['2k','4k']), --out, --profile
|
|
60
|
+
src/gflow_cli/paths.py
|
|
61
|
+
(reuse image_output_path; add <mediaId>_<scale> naming if needed)
|
|
62
|
+
docs/USAGE.md, README.md, docs/INDEX.md, CHANGELOG.md
|
|
63
|
+
document the command + the platform-only / Ultra-only constraints
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Task 1 — Error type + exit code (test-first)
|
|
69
|
+
|
|
70
|
+
**What:** Add `UpscaleUnavailableError` (exit 22) for tier-gated/permission failures, distinct from WAF/reCAPTCHA 403.
|
|
71
|
+
|
|
72
|
+
**Files:**
|
|
73
|
+
- `tests/test_errors.py` — assert exit code 22, EXIT_CODE_MAP registration, ordering invariant
|
|
74
|
+
- `src/gflow_cli/errors.py` — new class + map entry
|
|
75
|
+
|
|
76
|
+
**Steps:**
|
|
77
|
+
- [x] Red test: `UpscaleUnavailableError` maps to 22; not equal to WAF 403 code
|
|
78
|
+
- [x] Add the class (RFC 9457 fields: type/title/status/detail/remediation_hint) + register in `EXIT_CODE_MAP`
|
|
79
|
+
- [x] Verify `test_exit_code_map_ordering_invariant` still passes (inheritance trap — see memory)
|
|
80
|
+
|
|
81
|
+
**Tests created (red):**
|
|
82
|
+
- [x] `test_upscale_unavailable_exit_code` — exit 22, distinct from WAF/reCAPTCHA
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Task 2 — Value objects (test-first)
|
|
87
|
+
|
|
88
|
+
**What:** `TargetResolution` enum (`2K`/`4K` ↔ wire `UPSAMPLE_IMAGE_RESOLUTION_*`, `from_cli`) and frozen `UpsampleImageRequest`.
|
|
89
|
+
|
|
90
|
+
**Files:**
|
|
91
|
+
- `tests/api/test_image_upscale.py` — enum mapping, `from_cli` rejects bad values, request immutability
|
|
92
|
+
- `src/gflow_cli/api/image.py` (or new `image_upscale.py`) — enum + dataclass + body builder
|
|
93
|
+
|
|
94
|
+
**Steps:**
|
|
95
|
+
- [x] Red tests: `TargetResolution.from_cli("2k")` → enum; `"1k"`/`"8k"` rejected; request frozen
|
|
96
|
+
- [x] Add enum + `UpsampleImageRequest(media_id, target_resolution, recaptcha_token="")`
|
|
97
|
+
- [x] Body builder → `{mediaId, targetResolution, clientContext:{recaptchaContext:{token}}}`
|
|
98
|
+
- [x] mediaId UUID-allowlist validation (reject malformed before request)
|
|
99
|
+
|
|
100
|
+
**Tests created (red):**
|
|
101
|
+
- [x] `test_target_resolution_from_cli` / `test_target_resolution_rejects_1k_and_unknown`
|
|
102
|
+
- [x] `test_upsample_request_body_shape`
|
|
103
|
+
- [x] `test_media_id_uuid_validation`
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Task 3 — Client `upsample_image()` (test-first)
|
|
108
|
+
|
|
109
|
+
**What:** Synchronous client method: mint reCAPTCHA, POST `upsampleImage`, parse `{encodedImage}` with cap + magic-byte, write file. Route constant added.
|
|
110
|
+
|
|
111
|
+
**Files:**
|
|
112
|
+
- `tests/api/test_image_upscale.py` — mocked transport/HTTP: happy path, oversized cap, missing field, non-image bytes, 403→typed error, no-retry on 4K 403, `encodedImage` never logged
|
|
113
|
+
- `src/gflow_cli/api/routes.py` — `UPSAMPLE_IMAGE`
|
|
114
|
+
- `src/gflow_cli/api/client.py` — `MAX_UPSAMPLE_B64_LEN = 50 * 1024 * 1024`; `upsample_image(...)`
|
|
115
|
+
|
|
116
|
+
**Steps:**
|
|
117
|
+
- [x] Red tests for: 200 happy (decode → PNG/JPEG bytes written); response > cap → typed error before decode; missing `encodedImage` → `WireFormatError` (redacted); decoded bytes fail magic-byte → typed error
|
|
118
|
+
- [x] Red tests for 403 disambiguation: tier 403 → `UpscaleUnavailableError` (no retry); WAF 403 → existing `WafRejectionError`; expired-token 401 → existing refresh+single-retry
|
|
119
|
+
- [x] Red test: structlog capture asserts `encodedImage` value never present in any event
|
|
120
|
+
- [x] Add route + method; reuse `_mint_recaptcha_token("upscaleImage" or correct action)`; single Page checkout (no nested checkout — use `context.request` if aux call needed)
|
|
121
|
+
- [x] Decode mirrors `concatenate_scene` (cap → `b64decode` → magic-byte → `del encoded`)
|
|
122
|
+
- [x] Write via `paths.image_output_path` as `<mediaId>_<scale>.<ext>` (ext from magic byte)
|
|
123
|
+
|
|
124
|
+
**Tests created (red):**
|
|
125
|
+
- [x] `test_upsample_happy_2k`, `test_upsample_oversized_rejected`, `test_upsample_missing_field`,
|
|
126
|
+
`test_upsample_non_image_bytes`, `test_upsample_4k_pro_403_no_retry`,
|
|
127
|
+
`test_upsample_waf_403_distinct`, `test_upsample_encoded_image_never_logged`
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Task 4 — CLI surface (test-first)
|
|
132
|
+
|
|
133
|
+
**What:** `gflow image upscale <mediaId> --scale 2k|4k [--out] [--profile]` with self-contained `--help`.
|
|
134
|
+
|
|
135
|
+
**Files:**
|
|
136
|
+
- `tests/test_cli_image.py` — Click invocation: arg/flag parsing, `--scale` Choice, exit codes, 4K/Pro hint, malformed mediaId usage error (no request), structlog `upscale_started/completed/unavailable`
|
|
137
|
+
- `src/gflow_cli/cli_image.py` — `upscale` command
|
|
138
|
+
|
|
139
|
+
**Steps:**
|
|
140
|
+
- [x] Red tests: happy invocation calls `upsample_image` with mapped enum; `--scale 8k` rejected by Click; `--scale 1k` rejected with "1k is the original" hint; malformed mediaId → usage error, no HTTP
|
|
141
|
+
- [x] Red test: 4K-on-Pro path surfaces exit 22 + Ultra-only remediation; no auto-retry
|
|
142
|
+
- [x] Implement command; emit `upscale_started/completed/unavailable` (stable keys); `--help` states platform-only + Ultra-only constraints + "find mediaId via `gflow data list images`"
|
|
143
|
+
- [x] Record the output asset via `OperationRecorder` honoring the redaction gate; wrap callback in try/except `DataStoreError`
|
|
144
|
+
|
|
145
|
+
**Tests created (red):**
|
|
146
|
+
- [x] `test_upscale_cli_happy`, `test_upscale_cli_scale_choice`, `test_upscale_cli_1k_rejected`,
|
|
147
|
+
`test_upscale_cli_bad_media_id`, `test_upscale_cli_4k_pro_exit22`
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Task 5 — BDD scaffold + green
|
|
152
|
+
|
|
153
|
+
**What:** Gherkin feature covering all Critical+High scenarios; step defs mirror runtime signatures.
|
|
154
|
+
|
|
155
|
+
**Files:**
|
|
156
|
+
- `tests/features/image_upscale.feature` — happy 2K, 4K-on-Pro, oversized, malformed mediaId
|
|
157
|
+
- `tests/features/test_image_upscale_steps.py` — typed step defs (mirror signatures — see memory)
|
|
158
|
+
|
|
159
|
+
**Steps:**
|
|
160
|
+
- [x] Add the four `/gflow:scenario` BDD scenarios as a feature file
|
|
161
|
+
- [x] Implement step defs against the real command; assert exit codes + redaction
|
|
162
|
+
- [x] All BDD green
|
|
163
|
+
|
|
164
|
+
**Tests created:**
|
|
165
|
+
- [x] BDD: 2K happy, 4K/Pro exit 22, oversized rejected, malformed mediaId usage error
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Task 6 — Docs + recon finalize
|
|
170
|
+
|
|
171
|
+
**What:** User-facing docs + cross-references; recon already written.
|
|
172
|
+
|
|
173
|
+
**Files:**
|
|
174
|
+
- `docs/USAGE.md` — `gflow image upscale` section (workflow: `data list images` → `upscale`)
|
|
175
|
+
- `README.md` command table; `docs/INDEX.md` route to `IMAGE_UPSCALE_RECON.md`
|
|
176
|
+
- `docs/KNOWN_ISSUES.md` — note 4K Ultra-gating + per-profile WAF heat from the extra gated op
|
|
177
|
+
- `CHANGELOG.md` `[Unreleased]` — `feat(image): upscale to 2K/4K (#171)`
|
|
178
|
+
|
|
179
|
+
**Steps:**
|
|
180
|
+
- [x] Write usage + constraints (platform-only, Ultra-only, credit-free expectation)
|
|
181
|
+
- [x] Index the recon doc; cross-link spikes
|
|
182
|
+
- [x] CHANGELOG entry referencing #171
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Task 7 — Full gates + PR
|
|
187
|
+
|
|
188
|
+
**What:** Green `/gflow:check`; open PR to `develop`.
|
|
189
|
+
|
|
190
|
+
**Steps:**
|
|
191
|
+
- [x] `/gflow:check` green (ruff + format + `pyright src` + pytest ≥ 80% on changed dirs)
|
|
192
|
+
- [x] Live smoke (opt-in, supervised): one real 2K upscale on a known mediaId (credit-free) confirms wire still matches
|
|
193
|
+
- [x] PR `feature/image-upscale` → `develop`, `Closes #171`
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Definition of done
|
|
198
|
+
|
|
199
|
+
- [x] All task steps checked off
|
|
200
|
+
- [x] `/gflow:check` green (ruff / format / pyright / pytest ≥ 80% coverage)
|
|
201
|
+
- [x] `CHANGELOG.md` `[Unreleased]` updated (`#171`)
|
|
202
|
+
- [x] Docs updated (`USAGE.md`, `README.md`, `INDEX.md`, `KNOWN_ISSUES.md`)
|
|
203
|
+
- [x] BDD feature covers all Critical + High scenarios from `/gflow:scenario`
|
|
204
|
+
- [x] `encodedImage` proven absent from logs (test) — Critical mitigation verified
|
|
205
|
+
- [x] 4K/Pro 403 → exit 22, no auto-retry — Critical mitigation verified
|
|
206
|
+
- [x] No `# TODO` in diff without a tracked issue link
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Scenario: `gflow image upscale <mediaId> --scale 2k|4k`
|
|
2
|
+
|
|
3
|
+
> `/gflow:scenario` output for issue #171. Produced pre-implementation, then
|
|
4
|
+
> revised against the **live-verified** wire (see `docs/IMAGE_UPSCALE_RECON.md`).
|
|
5
|
+
> Feeds the test matrix in `tests/api/test_client_upscale.py`,
|
|
6
|
+
> `tests/cli/test_cli_image_upscale.py`, and `tests/features/image_upscale.feature`.
|
|
7
|
+
|
|
8
|
+
## Coverage map
|
|
9
|
+
|
|
10
|
+
**Active dimensions:**
|
|
11
|
+
- **D1 Auth & session lifecycle** — Bearer/session staleness observed live
|
|
12
|
+
(`ACCESS_TOKEN_REFRESH_NEEDED`); the existing refresh-on-401 path applies.
|
|
13
|
+
- **D2 WAF / reCAPTCHA** — the gating mechanism. Action `IMAGE_GENERATION` is
|
|
14
|
+
mandatory; per-profile heat is the dominant operational risk.
|
|
15
|
+
- **D3 Selector drift** — download button + 1K/2K/4K menu (locale-neutral).
|
|
16
|
+
- **D6 Data layer** — the catalog resolves the owning `projectId` from `mediaId`.
|
|
17
|
+
- **D7 Error propagation & exit codes** — 403 disambiguation, base64 redaction.
|
|
18
|
+
- **D8 Cross-platform paths** — `<mediaId>_<scale>.<ext>` output, Windows.
|
|
19
|
+
- **D9 Transport edge cases** — `encodedImage` parse/cap/magic-byte.
|
|
20
|
+
- **D11 Input validation** — mediaId/projectId UUID, `--scale`, 4K/Pro, uploads.
|
|
21
|
+
- **D12 Observability** — `image.upscale_{started,completed}` events, RFC 9457.
|
|
22
|
+
|
|
23
|
+
**Skipped:**
|
|
24
|
+
- **D4 Batch manifest** — v1 is single-image; no batch runner.
|
|
25
|
+
- **D5 Concurrency / Page pool** — one synchronous op, single Page checkout, no
|
|
26
|
+
fan-out (no `asyncio.gather`, no nested checkout).
|
|
27
|
+
- **D10 Headless/CI** — the browser + reCAPTCHA requirement is identical to the
|
|
28
|
+
existing image-generation path; inherited, not new.
|
|
29
|
+
|
|
30
|
+
## Scenario table
|
|
31
|
+
|
|
32
|
+
| # | Dimension | Scenario | Severity | Expected behaviour | Test |
|
|
33
|
+
|---|---|---|---|---|---|
|
|
34
|
+
| 1 | D2 | reCAPTCHA action mismatch (e.g. wrong/guessed action) | **Critical** | Token scores low → 403. Action MUST be `IMAGE_GENERATION`. *(Root-caused live; locked by the action constant.)* | Live + recon spike |
|
|
35
|
+
| 2 | D9 | Incomplete `clientContext` (missing projectId/sessionId/tool/tier) | **Critical** | Server 403s even with a valid token. Full 5-field clientContext is sent. *(Root-caused live.)* | Unit (`test_body_shape`) |
|
|
36
|
+
| 3 | D2/D7 | 4K requested on a non-Ultra account → server 403 | **Critical** | `UpscaleUnavailableError` (exit 22), "Ultra-only" hint; **no auto-retry** | Unit + CLI + BDD |
|
|
37
|
+
| 4 | D7 | 403 disambiguation: 4K-tier vs WAF vs expired token | **Critical** | 4K-403 → exit 22; 2K-403 → `WafRejectionError` (10); 401 → refresh+retry | Unit (`test_upsample_4k_403…`, `…2k_403_stays_waf`) |
|
|
38
|
+
| 5 | D7/D12 | The ~4 MB `encodedImage` base64 leaks into logs | **Critical** | Never logged; event carries `bytes` int only | Unit (`…never_logged`, `capture_logs`) |
|
|
39
|
+
| 6 | D9 | Oversized response (pathological 4K base64 > cap) | **High** | Reject before decode at `MAX_UPSAMPLE_B64_LEN` (50 MB); no write | Unit (`…oversized…`) |
|
|
40
|
+
| 7 | D9 | Response missing `encodedImage` / unexpected shape | **High** | `WireFormatError` + discovery payload, not a crash | Unit (`…missing…`) |
|
|
41
|
+
| 8 | D9 | `encodedImage` decodes but isn't PNG/JPEG | **High** | Magic-byte check → `WireFormatError`; no garbage written | Unit (`…non_image…`, `…undecodable…`) |
|
|
42
|
+
| 9 | D11/D6 | `projectId` unresolvable (not in catalog, no `--project`) | **High** | Fail fast exit 2 + "pass --project" hint; no browser/mint | CLI + BDD (`…unresolvable…`) |
|
|
43
|
+
| 10 | D11 | Malformed `mediaId` / `--project` (non-UUID) | **High** | Rejected before any work (exit 2) | Unit + CLI + BDD (`…bad_media_id…`) |
|
|
44
|
+
| 11 | D6 | mediaId owned by a different account/profile | **High** | Catalog lookup is profile-scoped (ownership proof); server is final arbiter | CLI (catalog resolve path) |
|
|
45
|
+
| 12 | D1 | Bearer/session stale at call time (`ACCESS_TOKEN_REFRESH_NEEDED`) | **High** | Existing refresh-on-401 mints fresh token + single retry | Live-observed |
|
|
46
|
+
| 13 | D11 | `--scale 1k` (the original, no upscale) | **Medium** | Rejected with "1k is the original" hint (exit 2) | Unit + CLI |
|
|
47
|
+
| 14 | D11 | `--scale` outside {2k,4k} | **Medium** | Rejected before I/O (exit 2) | Unit + CLI |
|
|
48
|
+
| 15 | D3 | Flow renames the download ligature / restructures the menu | **Medium** | Structural, locale-neutral selectors (`download`, `2K`/`4K`); re-capture via spike | Spike (manual) |
|
|
49
|
+
| 16 | D8 | Output filename on Windows; large binary write; PYTHONUTF8 unset | **Medium** | `write_bytes` (encoding-agnostic); platformdirs; `<mediaId>_<scale>.<ext>` | Unit (extension) |
|
|
50
|
+
| 17 | D12 | New structlog events stable + documented | **Medium** | `image.upscale_started/completed`; RFC 9457 on error | Covered by event asserts |
|
|
51
|
+
| 18 | D6 | `--project` disagrees with catalog's project for the mediaId | **Low** | Warn (non-fatal); use `--project` as given | CLI (resolve path) |
|
|
52
|
+
|
|
53
|
+
## Must-cover before merge (Critical + High) — all covered
|
|
54
|
+
|
|
55
|
+
1. **reCAPTCHA action = `IMAGE_GENERATION` + full clientContext** (#1, #2) — root-caused
|
|
56
|
+
by live smoke; locked by the action constant and `build_upsample_image_body`.
|
|
57
|
+
2. **403 disambiguation, no auto-retry on 4K** (#3, #4) — `UpscaleUnavailableError` (22).
|
|
58
|
+
3. **base64 never logged + capped + magic-byte validated** (#5, #6, #7, #8).
|
|
59
|
+
4. **projectId resolution + fail-fast + UUID validation** (#9, #10, #11).
|
|
60
|
+
5. **Session refresh** (#12) — reuses the existing 401 path.
|
|
61
|
+
|
|
62
|
+
## Deferred (Medium + Low — follow-ups, not blockers)
|
|
63
|
+
|
|
64
|
+
- Record the upscaled output in the data catalog (D6) — future follow-up.
|
|
65
|
+
- `gflow image t2i --upscale` convenience flow (one reCAPTCHA mint) — future follow-up.
|
|
66
|
+
- Selector-drift re-capture is manual via `scripts/dev/spike_image_upscale_drive.py`.
|
|
67
|
+
|
|
68
|
+
## BDD scenarios (`tests/features/image_upscale.feature`)
|
|
69
|
+
|
|
70
|
+
Implemented: 2K happy (explicit project), catalog-resolved project, 4K→exit 22,
|
|
71
|
+
unresolvable project→exit 2, malformed mediaId→exit 2.
|
|
72
|
+
|
|
73
|
+
## Known-issues cross-reference
|
|
74
|
+
|
|
75
|
+
- **reCAPTCHA Enterprise 403 / per-profile WAF heat** (KNOWN_ISSUES) — #1, #12 map
|
|
76
|
+
here. Upscale reuses the gated transport and adds a 4th gated op; mitigated by
|
|
77
|
+
one-at-a-time + no-auto-retry. Not resolved (inherent to the platform).
|
|
78
|
+
- **4K requires Ultra** — now a documented Open limitation in KNOWN_ISSUES (#171).
|
|
79
|
+
- **Locale leak / icon ligatures** — #15; selectors stay language-neutral.
|