gflow-cli 0.19.0__tar.gz → 0.20.1__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.19.0 → gflow_cli-0.20.1}/.env.template +6 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/CHANGELOG.md +19 -1
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/PKG-INFO +1 -1
- gflow_cli-0.20.1/docs/AGENT_UI_E2E.md +108 -0
- gflow_cli-0.20.1/docs/AGENT_UI_RECON.md +192 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/CONFIGURATION.md +8 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/INDEX.md +2 -1
- gflow_cli-0.20.1/docs/LIVE_VERIFICATION_v0.20.0.md +76 -0
- gflow_cli-0.20.1/docs/LIVE_VERIFICATION_v0.20.1.md +19 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/pyproject.toml +1 -1
- gflow_cli-0.20.1/scripts/dev/analyze_agent_ui_capture.py +146 -0
- gflow_cli-0.20.1/scripts/e2e/agentic_image_e2e.ps1 +74 -0
- gflow_cli-0.20.1/scripts/e2e/capture_agent_toggle.py +101 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/__init__.py +1 -1
- gflow_cli-0.20.1/src/gflow_cli/api/transports/drivers/__init__.py +32 -0
- gflow_cli-0.20.1/src/gflow_cli/api/transports/drivers/agentic.py +465 -0
- gflow_cli-0.20.1/src/gflow_cli/api/transports/drivers/base.py +102 -0
- gflow_cli-0.20.1/src/gflow_cli/api/transports/drivers/classic.py +224 -0
- gflow_cli-0.20.1/src/gflow_cli/api/transports/drivers/factory.py +147 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/ui_automation.py +135 -16
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/ui_automation_video.py +49 -22
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/config.py +10 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/errors.py +22 -0
- gflow_cli-0.20.1/tests/api/transports/conftest.py +22 -0
- gflow_cli-0.20.1/tests/api/transports/drivers/test_agentic.py +500 -0
- gflow_cli-0.20.1/tests/api/transports/drivers/test_factory.py +181 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_ui_automation.py +52 -5
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_ui_automation_batch.py +86 -20
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_ui_automation_image_mode.py +55 -16
- gflow_cli-0.20.1/tests/features/test_video_agent_ui_steps.py +447 -0
- gflow_cli-0.20.1/tests/features/video_agent_ui.feature +34 -0
- gflow_cli-0.20.1/tests/scripts/test_analyze_agent_ui_capture.py +113 -0
- gflow_cli-0.20.1/tests/smoke/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_config.py +9 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_errors.py +20 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/uv.lock +1 -1
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/README.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/active.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/branch-review.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/changelog.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/check.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/doc-review.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/known-issues.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/next.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/plan.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/pr-council-review.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/predict.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/release.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/scenario.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.claude/commands/gflow/status.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.gitattributes +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.github/CODEOWNERS +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.github/copilot-instructions.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.github/dependabot.yml +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.github/workflows/ci.yml +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.github/workflows/external-pr-triage.yml +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.github/workflows/governance-advisory.yml +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.github/workflows/governance-benchmark.yml +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.github/workflows/release.yml +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.gitignore +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.gitleaks.toml +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.pre-commit-config.yaml +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/.secrets.baseline +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/AGENTS.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/CLAUDE.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/CONFIGURATION.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/CONTRIBUTING.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/DISCLAIMER.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/GEMINI.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/KNOWN_ISSUES.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/LICENSE +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/PLAN.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/README.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/RELEASE.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/ROADMAP.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/conftest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docker-compose.yml +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/AGENT_GUIDE.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/ARCHITECTURE.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/AUTHENTICATION.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/CHARACTER.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/CHARACTER_RECON.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/DATA_LAYER.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/DEBUGGING.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/DEMOS.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/DEVELOPMENT.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/E2E_TESTING.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/EXTERNAL_STORAGE.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/GITHUB.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/GOVERNANCE_BENCHMARK.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/IMAGE_UPSCALE_RECON.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_data_layer.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_image_batch.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.10.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.11.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.12.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.13.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.16.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.17.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.18.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.19.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.7.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.8.1.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.9.0.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_v0.9.1.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/LIVE_VERIFICATION_video_download.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/MOVIE.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/PROJECT_STATUS.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/SECURITY.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/USAGE.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/USER_GUIDE.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/assets/demo-split-pf.gif +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/assets/example-run.gif +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/schemas/movie-handoff.schema.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/docs/superpowers/plans/2026-06-12-issue-174-library-ui-attach/PLAN.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/examples/README.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/examples/batch_from_config.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/examples/multi_prompt_t2i.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/examples/sample_config.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/examples/sample_prompts.txt +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/examples/single_image_t2i.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/examples/workflow_chain.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/llms.txt +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/README.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/01_upload_image.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/04_archive_workflow.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/05_createProject.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/06_batchGenerateImages.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/08_batchAsyncGenerateVideoStartAndEndImage.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/09_batchAsyncGenerateVideoReferenceImages.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/10_batchCheckAsyncVideoGenerationStatus_successful.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/11_batchCheckAsyncVideoGenerationStatus_failed.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/12_create_scene.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/13_sceneWorkflows_update.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/14_get_scene_workflows.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/samples/captured/15_commit_flowWorkflow.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/ci/check_doc_links.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/ci/check_materiality.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/ci/check_repo_hygiene.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/debug_editor.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/debug_gen_settings.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/debug_settings.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/_recording_client.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/_spike_common.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/active_plan.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/capture_i2v_frame_slots_dom.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/capture_i2v_intercept_submit.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/capture_i2v_model_select_repro.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/capture_i2v_post_bind_state.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/capture_image_add_media_dom.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/capture_locale_invariants.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/cdp_drive_and_probe.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/character_create_spike.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/character_create_spike_v2.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/dump_character_selectors.js +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/make_project.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/materiality_backtest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/monitor_pr_38.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/patch_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/record_flow_capture.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/skillopt/README.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/skillopt/harness.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/skillopt/tasks.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_char_editor_dom.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_char_gen_capture.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_image_upscale_capture.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_image_upscale_drive.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_image_upscale_recaptcha_action.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_image_upscale_rest_probe.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_issue170_picker_locale_recon.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_issue174_library_ui_recon.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_movie_attach_payload.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_movie_entity_recon.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_movie_gen_capture.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_movie_picker_select.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_movie_voice_list.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_patch_entity.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/dev/spike_patchright.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/diag/README.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/diag/capture_flow_traffic.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/diag/memory_profile.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/diag/recaptcha_mint.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/record_demo.ps1 +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/smoke_image.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/smoke_real_chrome_image.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/smoke_video_editor.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/smoke_worker_style.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/scripts/verify_chrome_auth_viability.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/skills/README.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/skills/gflow-cli/SKILL.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/skills/plan/SKILL.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/skills/pr-council-review/SKILL.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/skills/predict/SKILL.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/skills/scenario/SKILL.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/skills/status/SKILL.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/sonar-project.properties +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/__main__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/_cli_helpers.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/_engine.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/_retry.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/_sapisidhash.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/client.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/dto.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/image.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/image_upscale.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/recaptcha.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/routes.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/scene.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/_common.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/_fingerprint.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/base.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/experimental/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/experimental/bearer.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/experimental/evaluate_fetch.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/transports/experimental/sapisidhash.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/api/video.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/auth/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/auth/base.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/auth/cookies.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/auth/factory.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/auth/internal_chromium.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/auth/real_chrome.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/auth/strategies.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/auth/verification.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/browser_manager.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/chain.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/chain_manifest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/cli.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/cli_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/cli_data.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/cli_image.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/cli_models.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/cli_movie.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/cli_run.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/cli_scene.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/cli_video.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/composition.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/chain_repo.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/migrations/0001_initial.sql +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/migrations/0002_add_cloud_storage.sql +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/migrations/0003_add_scene_tables.sql +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/migrations/0004_add_scene_output_path.sql +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/migrations/0005_add_chain_links.sql +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/migrations/0006_add_operations_metadata.sql +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/migrations/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/models.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/queries.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/recorder.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/redaction.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/repository.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/data/store.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/exceptions.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/image_batch.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/json_output.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/manifest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/media.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/movie_manifest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/observability.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/paths.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/profile_store.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/services/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/services/character_create.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/src/gflow_cli/storage.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tasks/lessons.md +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/test_assets/sample_batch.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/test_assets/sample_batch.tsv +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/test_assets/sample_batch_invalid.tsv +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/fixtures/character_gen_response.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/fixtures/patch_entity_response.json +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_aisandbox_auth_error.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_aisandbox_auth_headers.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_bearer_redaction.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_client.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_client_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_client_delete_characters.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_client_generate_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_client_image.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_client_launch_kwargs.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_client_patch_entity.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_client_scene.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_client_upscale.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_concurrency.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_dto.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_engine.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_image.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_image_dto.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_image_upscale.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_post_json_aisandbox_auth.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_recaptcha.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_retry.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_routes.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_routes_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_routes_scene.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_sapisidhash_helper.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_scene_models.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_video.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/test_video_request.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/__init__.py +0 -0
- {gflow_cli-0.19.0/tests/cli → gflow_cli-0.20.1/tests/api/transports/drivers}/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_base.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_bearer.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_common.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_evaluate_fetch.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_factory.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_fingerprint.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_sapisidhash.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_transport_timeout.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_ui_automation_video.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/api/transports/test_ui_character_editor.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/auth/strategies/test_factory.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/auth/strategies/test_strategies.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/auth/test_verification.py +0 -0
- {gflow_cli-0.19.0/tests/data → gflow_cli-0.20.1/tests/cli}/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_auth_list.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_character_create.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_data.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_image.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_image_seed_removed.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_image_selector_drift.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_image_upscale.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_models.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_movie.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_run.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_scene.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_video.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_cli_video_chain.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_error_handling.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_helpers.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_movie_manifest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_settings_validation.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/cli/test_t2i_multi_prompt.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/composition/test_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/composition/test_compose_prompt.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/composition/test_handoff.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/conftest.py +0 -0
- {gflow_cli-0.19.0/tests/e2e → gflow_cli-0.20.1/tests/data}/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_chain_repo.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_find_incomplete_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_models.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_packaging.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_recorder.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_recorder_character.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_redaction.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_repository.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_scene_persistence.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_settings_and_errors.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/data/test_store_migrations.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/dev/test_record_flow_capture.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/dev/test_recording_client.py +0 -0
- {gflow_cli-0.19.0/tests/features → gflow_cli-0.20.1/tests/e2e}/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/conftest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_aisandbox_auth_live.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_auth_verification_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_chain_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_character_create_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_data_layer_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_i2v_flags_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_image_batch_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_image_i2i_ref_cap_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_json_output_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_locale_selectors_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_scene_compose_live.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_transports_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_video_r2v_ref_cap_e2e.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/e2e/test_video_t2v_e2e.py +0 -0
- {gflow_cli-0.19.0/tests/fixtures → gflow_cli-0.20.1/tests/features}/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/auth.feature +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/auth_login.feature +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/character_create.feature +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/character_read.feature +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/conftest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/image.feature +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/image_upscale.feature +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/locale_picker_include.feature +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/test_auth_login_steps.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/test_auth_steps.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/test_character_create_steps.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/test_character_read_steps.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/test_image_steps.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/test_image_upscale_steps.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/test_locale_picker_include_steps.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/test_step_collision_guard.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/test_video_chain_steps.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/features/video_chain.feature +0 -0
- {gflow_cli-0.19.0/tests/image_batch → gflow_cli-0.20.1/tests/fixtures}/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/fixtures/seeded_catalog.py +0 -0
- {gflow_cli-0.19.0/tests/integration → gflow_cli-0.20.1/tests/image_batch}/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/image_batch/test_image_manifest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/image_batch/test_observability_events.py +0 -0
- {gflow_cli-0.19.0/tests/services → gflow_cli-0.20.1/tests/integration}/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/integration/conftest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/integration/constants.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/integration/test_storage_gcs.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/integration/test_storage_s3.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/scripts/test_capture_locale_invariants.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/scripts/test_check_materiality.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/scripts/test_check_repo_hygiene.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/scripts/test_materiality_backtest.py +0 -0
- {gflow_cli-0.19.0/tests/smoke → gflow_cli-0.20.1/tests/services}/__init__.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/services/test_character_create_redaction.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/services/test_character_create_saga.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/services/test_character_gen_no_direct_post.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/smoke/test_profile_account_smoke.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/smoke/test_real_flow.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_auth.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_browser_manager.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_chain.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_chain_manifest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_cli_data.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_conftest_isolation.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_data_queries.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_documentation_gate.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_errors_403.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_json_output.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_manifest.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_marker_registry.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_media.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_observability.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_paths.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_profile_store.py +0 -0
- {gflow_cli-0.19.0 → gflow_cli-0.20.1}/tests/test_smoke.py +0 -0
|
@@ -115,6 +115,12 @@
|
|
|
115
115
|
# `patchright install chromium`. Unset to revert to the default playwright engine.
|
|
116
116
|
# GFLOW_CLI_BROWSER_ENGINE=playwright
|
|
117
117
|
|
|
118
|
+
# Try to force the Classic Flow UI mode for image generation by clicking the Agent toggle pill
|
|
119
|
+
# if the page mounts in Agentic mode. This ensures deterministic aspect ratio controls.
|
|
120
|
+
# If the page cannot be switched back to Classic, falls back to Agentic mode.
|
|
121
|
+
# GFLOW_CLI_PREFER_CLASSIC=false
|
|
122
|
+
|
|
123
|
+
|
|
118
124
|
# BCP-47 locale tag passed to Playwright's launch `locale=` parameter
|
|
119
125
|
# (controls Accept-Language only — Chrome's UI language is forced to
|
|
120
126
|
# en-US via the `--lang=en-US` launch arg regardless of this setting, so
|
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.20.1] — 2026-06-16
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Aspect ratio overrides in Agentic & Classic cohorts** ([#193](https://github.com/ffroliva/gflow-cli/issues/193)): Fixed a bug where aspect ratio settings were ignored under the pluggable `FlowUiDriver` strategies (both single and batch image generation paths).
|
|
15
|
+
- **`GFLOW_CLI_PREFER_CLASSIC`** (or `prefer_classic` setting): Added a configuration setting to force the classic UI driver, allowing users to bypass the Agentic UI cohort if preferred.
|
|
16
|
+
|
|
17
|
+
## [0.20.0] — 2026-06-15
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Forced Agentic UI detection** (exit code 25): dynamically detects if the account's Flow editor page has been placed in Google Flow's new Agentic UI A/B cohort. Raises `FlowAgentUiError` and captures a diagnostic viewport screenshot (`debug_forced_agent_ui.png`) to exit cleanly and prevent CLI hangs.
|
|
22
|
+
- **Pluggable Flow UI driver strategy** (`FlowUiDriver`): the UI-automation transport now probes the editor DOM **per generation** (the cohort flaps per page load) and binds a `ClassicFlowUiDriver` or `AgenticFlowUiDriver`, so the two cohorts' selectors never share code. Classic generation is unchanged.
|
|
23
|
+
- **Agentic-cohort image generation** (validated live): when the editor is served the Agentic UI, `gflow image` drives it by encoding settings (count / aspect) into the conversational prompt directive and scraping generated assets directly from the DOM — **deduplicated by media UUID** — because the Agentic UI routes generation through a background Web Worker that defeats page-level network capture. Content-policy blocks (detected in alert/dialog regions) fail fast with `ContentPolicyError` (exit 5). Agentic **video** still raises `FlowAgentUiError` (exit 25) pending a validated scraping path.
|
|
24
|
+
- **`GFLOW_CLI_FORCE_AGENT_UI`** (testing/diagnostic opt-in): forces the agentic composer by clicking the in-input "Agent" toggle after entering the editor, so the agentic path can be exercised deterministically regardless of the server-assigned A/B cohort (which has no client-readable flag). Unset by default — the cohort is auto-detected per generation. See `docs/AGENT_UI_E2E.md`.
|
|
25
|
+
|
|
10
26
|
## [0.19.0] — 2026-06-12
|
|
11
27
|
|
|
12
28
|
### Added
|
|
@@ -1549,7 +1565,9 @@ shell-script template that branches on these codes.
|
|
|
1549
1565
|
|
|
1550
1566
|
First skeleton. Not functional end-to-end yet.
|
|
1551
1567
|
|
|
1552
|
-
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.
|
|
1568
|
+
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.20.1...HEAD
|
|
1569
|
+
[0.20.1]: https://github.com/ffroliva/gflow-cli/compare/v0.20.0...v0.20.1
|
|
1570
|
+
[0.20.0]: https://github.com/ffroliva/gflow-cli/compare/v0.19.0...v0.20.0
|
|
1553
1571
|
[0.19.0]: https://github.com/ffroliva/gflow-cli/compare/v0.18.0...v0.19.0
|
|
1554
1572
|
[0.18.0]: https://github.com/ffroliva/gflow-cli/compare/v0.17.0...v0.18.0
|
|
1555
1573
|
[0.17.0]: https://github.com/ffroliva/gflow-cli/compare/v0.16.0...v0.17.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gflow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.1
|
|
4
4
|
Summary: Unofficial CLI for Google Flow — drive Veo image-to-video generations from the terminal.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ffroliva/gflow-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/ffroliva/gflow-cli/issues
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Agentic UI — End-to-End Testing Runbook
|
|
2
|
+
|
|
3
|
+
> How to **deterministically** drive and validate `gflow`'s **Agentic UI** image
|
|
4
|
+
> path against live Google Flow. Companion to
|
|
5
|
+
> [AGENT_UI_RECON.md](AGENT_UI_RECON.md) (the reverse-engineered wire/DOM spec)
|
|
6
|
+
> and [LIVE_VERIFICATION_v0.20.0.md](LIVE_VERIFICATION_v0.20.0.md) (the shipped
|
|
7
|
+
> evidence).
|
|
8
|
+
|
|
9
|
+
## The problem, and the way in
|
|
10
|
+
|
|
11
|
+
Google Flow serves the composer via a **server-assigned A/B cohort** that flaps
|
|
12
|
+
per page load and has **no client-readable flag** — so the agentic UI cannot be
|
|
13
|
+
*requested*. A naive "retry until the dice land agentic" e2e is unreliable (we
|
|
14
|
+
observed 7 classic loads in a row).
|
|
15
|
+
|
|
16
|
+
**The deterministic trigger:** the *classic* composer exposes an in-input
|
|
17
|
+
**"Agent" toggle**. Clicking it switches the composer into the **same** agentic
|
|
18
|
+
layout — confirmed live (`scripts/e2e/capture_agent_toggle.py`, 2026-06-14):
|
|
19
|
+
|
|
20
|
+
| Signal | Before toggle | After toggle |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `crop_*` media trigger | present | **gone** |
|
|
23
|
+
| `tune` settings gear | absent | **present** |
|
|
24
|
+
| Slate composer | present | present |
|
|
25
|
+
|
|
26
|
+
So `tune` appears and `crop_*` disappears → `detect_ui_mode` then binds the
|
|
27
|
+
agentic driver. An **`expand_content`** button opens the full agent side panel.
|
|
28
|
+
|
|
29
|
+
## Repeatable procedure (deterministic)
|
|
30
|
+
|
|
31
|
+
Set **`GFLOW_CLI_FORCE_AGENT_UI=1`** and the transport clicks the Agent toggle
|
|
32
|
+
after entering the editor, forcing the agentic path on any load. The harness
|
|
33
|
+
runs that and verifies the whole chain:
|
|
34
|
+
|
|
35
|
+
```powershell
|
|
36
|
+
# From repo root. Image generation is free — safe to run repeatedly.
|
|
37
|
+
.\scripts\e2e\agentic_image_e2e.ps1 -Profile denon82 -Count 1
|
|
38
|
+
.\scripts\e2e\agentic_image_e2e.ps1 -Profile denon82 -Count 3 # also exercises dedup
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
It PASSes only when all four hold (else it prints which stage failed):
|
|
42
|
+
|
|
43
|
+
1. `agent_mode_forced activated=true` — the toggle flipped the composer to agentic;
|
|
44
|
+
2. `ui_driver.bound mode=agentic` — the agentic driver was bound;
|
|
45
|
+
3. exit 0;
|
|
46
|
+
4. exactly `-Count` `.jpg` files written (scrape → dedup-by-UUID → download).
|
|
47
|
+
|
|
48
|
+
Run it directly without the harness, too:
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
$env:GFLOW_CLI_FORCE_AGENT_UI="1"
|
|
52
|
+
uv run gflow image t2i "a ripe banana on a white plate" --profile denon82 --json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> `GFLOW_CLI_FORCE_AGENT_UI` is a **testing/diagnostic opt-in**, not a user
|
|
56
|
+
> feature: it forces the agentic path even when the server served classic.
|
|
57
|
+
> Leave it unset for normal use (the cohort is then auto-detected per
|
|
58
|
+
> generation).
|
|
59
|
+
|
|
60
|
+
## Validation result (2026-06-14, profile denon82)
|
|
61
|
+
|
|
62
|
+
Forced-agentic runs validated the three assumptions that mocked tests cannot:
|
|
63
|
+
|
|
64
|
+
- **`--count 1`** → exit 0, one **420 KB JPG**. Scrape found the asset, built the
|
|
65
|
+
full-res redirect URL, the `labs.google` download resolved with session
|
|
66
|
+
cookies, and the synthetic `GeneratedImage` fields (`workflow_id=""`, `seed=0`,
|
|
67
|
+
`dimensions=(0,0)`) flowed through the save path without error.
|
|
68
|
+
- **`--count 3`** → exit 0, **3 distinct JPGs** for 3 distinct media UUIDs —
|
|
69
|
+
confirming dedup: the agentic UI's multiple `<img>` nodes per asset collapse to
|
|
70
|
+
the correct count.
|
|
71
|
+
|
|
72
|
+
Classic remains unaffected: live `gflow image t2i` and `gflow video t2v` both
|
|
73
|
+
succeed when the cohort/loads serve classic.
|
|
74
|
+
|
|
75
|
+
## Knowledge extracted (this cost real e2e runs — keep it)
|
|
76
|
+
|
|
77
|
+
Three render-timing / detection bugs were found **only** by live e2e (mocked
|
|
78
|
+
tests passed throughout); each is now fixed with a regression test:
|
|
79
|
+
|
|
80
|
+
1. **Cohort detection raced the render.** `get_ui_driver` probed instantly after
|
|
81
|
+
navigation and missed the agentic `tune` indicator (it appeared ~1.25 s
|
|
82
|
+
later), defaulting to classic → spurious `FlowAgentUiError` (exit 25). Fix:
|
|
83
|
+
`detect_ui_mode` polls (8 s window). Test: `test_detect_agentic_after_delayed_render`.
|
|
84
|
+
2. **The force-toggle raced the render too.** `_force_agent_mode` probed for the
|
|
85
|
+
Agent toggle ~0.8 s after entering the editor, before the composer rendered →
|
|
86
|
+
`agent_toggle_not_found`, silently staying classic. Fix: `wait_for` the toggle
|
|
87
|
+
(8 s) before clicking.
|
|
88
|
+
3. **Content-policy detection false-positived on static chrome.** Scanning the
|
|
89
|
+
whole `document.body` for "content policy" matched a benign page's footer/menu
|
|
90
|
+
link → every agentic generation died with a spurious `ContentPolicyError`
|
|
91
|
+
(exit 5) in ~6 s. Fix: scope the scan to `[role=alert]`/`[role=dialog]`/
|
|
92
|
+
`[aria-live=assertive]` regions only (a real block surfaces there; a
|
|
93
|
+
chat-message-only refusal is missed, which is the safe trade — a miss →
|
|
94
|
+
timeout beats a false positive that breaks every generation). Tests:
|
|
95
|
+
`test_await_images_raises_content_policy_on_explicit_text`,
|
|
96
|
+
`test_await_images_ignores_body_chrome_policy_text`.
|
|
97
|
+
|
|
98
|
+
General lesson: **any agentic-composer probe must wait for the render** — instant
|
|
99
|
+
DOM checks race Flow's deferred composer mount.
|
|
100
|
+
|
|
101
|
+
## Still owed
|
|
102
|
+
|
|
103
|
+
- A **captured positive content-policy refusal** in the agentic cohort, to widen
|
|
104
|
+
detection safely beyond alert/dialog regions (chat-message refusals are
|
|
105
|
+
currently missed → timeout). Trigger a disallowed prompt under
|
|
106
|
+
`GFLOW_CLI_FORCE_AGENT_UI=1` and capture how the refusal surfaces.
|
|
107
|
+
- **Agentic video** — only the image path is validated; video stays on the
|
|
108
|
+
classic→`FlowAgentUiError` path pending a video scraping capture.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Agentic Flow UI — Recon Findings
|
|
2
|
+
|
|
3
|
+
> Reverse-engineered 2026-06-14 via `gflow-agent-browser-spike` (CDP-attached
|
|
4
|
+
> real Chrome, `agent-browser@0.27.0`) against the live Flow editor on two
|
|
5
|
+
> Chrome profiles. Account-specific values (emails, project UUIDs) are redacted.
|
|
6
|
+
> Relates to issues #183 (image `t2i` exit 23) and #174 (library-UI entity drop).
|
|
7
|
+
|
|
8
|
+
## TL;DR
|
|
9
|
+
|
|
10
|
+
- Flow runs a **server-side A/B cohort** that swaps the project composer between
|
|
11
|
+
the **classic** media UI and a new **agentic** UI (chat-style "Agent").
|
|
12
|
+
- It is **volatile**: the *same* Chrome profile rendered the classic composer at
|
|
13
|
+
one point and the agentic composer later on the *same* project — confirming the
|
|
14
|
+
#174 flapping (denon82 reverting within 12h).
|
|
15
|
+
- **gflow's own dedicated automation profiles DO land in the agentic cohort**
|
|
16
|
+
(not only the user's primary profile), so #183/#174 are reproducible through
|
|
17
|
+
gflow — the feature must handle it.
|
|
18
|
+
- The cohort is **NOT discoverable from client-side state**: localStorage keys and
|
|
19
|
+
JS-visible cookies are byte-identical across both UIs, and the prime-suspect key
|
|
20
|
+
`FLOW_MAIN_PROMPT_BOX_STATE` holds only generation settings, not a mode flag.
|
|
21
|
+
→ **Detection must be runtime DOM-based**; there is no pre-navigation cookie/flag
|
|
22
|
+
to read or flip.
|
|
23
|
+
- `navigator.webdriver` is **`false`** under CDP-attached real Chrome.
|
|
24
|
+
|
|
25
|
+
## How it was captured
|
|
26
|
+
|
|
27
|
+
`gflow-agent-browser-spike` launches real Chrome with `--remote-debugging-port`
|
|
28
|
+
on a chosen profile and drives `agent-browser` over CDP. JS is passed as
|
|
29
|
+
**base64-wrapped `eval`** (`eval(atob('…'))`) — multi-line eval args get mangled
|
|
30
|
+
through `npx`, so base64 is the reliable path (this also applies to
|
|
31
|
+
`capture-agent-ui.ps1`, which must minify/encode its eval expressions). Capturing
|
|
32
|
+
the user's **primary** profile required relaunching it with the debug flag (only
|
|
33
|
+
applied on a fresh Chrome start, so all windows must be closed first), which
|
|
34
|
+
triggered a one-time Google 2-Step Verification.
|
|
35
|
+
|
|
36
|
+
## The two UIs — DOM signature
|
|
37
|
+
|
|
38
|
+
| Signal | Classic | Agentic |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `crop_*` inline aspect/mode trigger | **present** (`crop_9_16` etc.) | **absent** |
|
|
41
|
+
| Agent pill (`/\bAgent\b/` + `div[role='textbox']`) | no | **yes** |
|
|
42
|
+
| Chat ligatures `edit_square`, `thumb_up`, `thumb_down`, `flag` | no | **yes** |
|
|
43
|
+
| Agentic ligatures `article_spark`, `apps_spark_2`, `tune` | no | **yes** |
|
|
44
|
+
| "What do you want to create?" placeholder | no | **yes** |
|
|
45
|
+
| Aspect / model / upscale controls | inline `crop_*` dropdown | moved into **"Agent settings"** (`tune`) |
|
|
46
|
+
|
|
47
|
+
Agentic ligature inventory (captured): `accessibility_new, add, add_2,
|
|
48
|
+
apps_spark_2, arrow_back, arrow_forward, arrow_forward_ios, article_spark, close,
|
|
49
|
+
content_copy, dashboard, delete, edit_square, filter_list, flag, help, image,
|
|
50
|
+
left_panel_close, menu, more_vert, movie, search, settings_2, thumb_down,
|
|
51
|
+
thumb_up, tune, warning` — and **no `crop_*`**.
|
|
52
|
+
|
|
53
|
+
## Gating mechanism (the cohort key)
|
|
54
|
+
|
|
55
|
+
Captured on agentic profiles (both the user's primary and a gflow dedicated
|
|
56
|
+
profile), the client-visible state is **identical**:
|
|
57
|
+
|
|
58
|
+
- **localStorage keys**: `FLOW_MAIN_PROMPT_BOX_STATE`, `FLOW_QUICK_SEARCH_MODE`,
|
|
59
|
+
`_grecaptcha`, `glue.CookieNotificationBar`, `nextauth.message`.
|
|
60
|
+
- **JS-visible cookies**: `EMAIL`, `_ga`, `_ga_X2GNH8R5NS` (any cohort cookie
|
|
61
|
+
would be `httpOnly`, i.e. invisible to JS — recoverable only from HAR request
|
|
62
|
+
headers, not yet captured).
|
|
63
|
+
- **`FLOW_MAIN_PROMPT_BOX_STATE`** value (agentic):
|
|
64
|
+
`{"aspectRatio":"PORTRAIT","selectedImageModelFamily":"narwhal_display","selectedVideoModelFamily":"abra","imageOrVideoMode":"IMAGE","outputsPerPrompt":1,"selectedVideoDuration":4}`
|
|
65
|
+
— generation settings only, **no cohort/mode flag**.
|
|
66
|
+
|
|
67
|
+
Because (a) the key set is identical across classic and agentic, (b) the one
|
|
68
|
+
plausible key holds only settings, and (c) the same profile flaps between states,
|
|
69
|
+
the cohort is concluded to be **server-assigned per page load**, not a readable
|
|
70
|
+
client signal. Pre-navigation detection (read a cookie before driving) is **not
|
|
71
|
+
viable**; runtime DOM classification is the only reliable detector.
|
|
72
|
+
|
|
73
|
+
## Why the existing handling fails (#183)
|
|
74
|
+
|
|
75
|
+
`_exit_agent_mode` (`ui_automation_video.py`) assumes the classic composer is
|
|
76
|
+
recoverable and keys on `_media_panel_present` (the `crop_*` trigger). In the
|
|
77
|
+
agentic cohort there is **no `crop_*`** at all (aspect moved into Agent settings),
|
|
78
|
+
so the probe never recovers, the selector cascade exhausts, and the caller raises
|
|
79
|
+
`UiSelectorDriftError` (exit 23). The recovery has nothing to recover *to*.
|
|
80
|
+
|
|
81
|
+
## Recommendation for the feature plan
|
|
82
|
+
|
|
83
|
+
1. **Detection = runtime DOM classification.** Agentic if *no `crop_*` mode
|
|
84
|
+
trigger* **and** (*Agent pill present* **or** *chat-panel ligatures present*).
|
|
85
|
+
This is what `classify_composer` / a refreshed `_exit_agent_mode` should key
|
|
86
|
+
on. Run it every generation — the cohort flaps, so no caching.
|
|
87
|
+
2. **No pre-flight cookie/flag gate** — there is no client-readable cohort signal.
|
|
88
|
+
3. **Response (decide in the feature plan, evidence now supports either):**
|
|
89
|
+
- **Detect + fail cleanly** with a typed `FlowAgentUiError` (own exit code) +
|
|
90
|
+
screenshot — low risk, immediately diagnosable. *Recommended first step.*
|
|
91
|
+
- **Drive the agentic surface** — type into the chat composer, set aspect/model
|
|
92
|
+
via the Agent-settings (`tune`) panel, handle the confirm-before-generating
|
|
93
|
+
gate. Larger; intermittently needed because the cohort flaps.
|
|
94
|
+
|
|
95
|
+
## DOM scraping validation (2026-06-14, live capture)
|
|
96
|
+
|
|
97
|
+
> Captured via `scripts/capture-media-scrape.ps1` (CDP-attached real Chrome,
|
|
98
|
+
> profile `default`, agentic cohort confirmed: `crop=false, agentPill=true,
|
|
99
|
+
> chatPanel=true`). One image generation, before/after media-DOM snapshot.
|
|
100
|
+
> Artifact: `media-scrape-agentic-20260614-202739.json`.
|
|
101
|
+
|
|
102
|
+
This resolves the open question blocking `AgenticFlowUiDriver.await_images`: **how do
|
|
103
|
+
generated assets surface in the agentic DOM, and is count-delta scraping viable?**
|
|
104
|
+
|
|
105
|
+
- **Assets render as plain remote `<img>` nodes.** After the generation: `blob=0`,
|
|
106
|
+
`data=0`, `canvas=0`, `background-image=0`. The full-res asset is a normal
|
|
107
|
+
`<img src="https://…">`. Count-delta DOM scraping **is viable** — network
|
|
108
|
+
interception is not (see HAR note below).
|
|
109
|
+
- **The src is a same-origin tRPC redirect carrying a stable media id:**
|
|
110
|
+
```
|
|
111
|
+
https://labs.google/fx/api/trpc/media.getMediaUrlRedirect?name=<uuid>[&mediaUrlType=MEDIA_URL_TYPE_THUMBNAIL]
|
|
112
|
+
```
|
|
113
|
+
It 302-redirects to the CDN blob; same-origin (`labs.google`) means the session
|
|
114
|
+
cookies authorize the download. The `name=<uuid>` is the **stable backend media id**
|
|
115
|
+
— scraped assets correlate to media identity (usable for the batch ledger / dedup).
|
|
116
|
+
- **Dedupe by `name=<uuid>`, NOT by `<img>` node count.** One generated asset surfaces
|
|
117
|
+
as **multiple** `<img>` nodes (canvas full-res + filmstrip thumbnail + chat preview,
|
|
118
|
+
each in bare and `&mediaUrlType=MEDIA_URL_TYPE_THUMBNAIL` variants). The single
|
|
119
|
+
capture produced **9 new `<img>` nodes for only 3 distinct assets** (~3× inflation).
|
|
120
|
+
`await_images` must extract the `name` query param and **count distinct UUIDs** — a
|
|
121
|
+
raw `initial_count + expected_count` node check would massively over-count.
|
|
122
|
+
- **Page-level HAR captured 0 entries.** Confirms the Web-Worker delegation: the
|
|
123
|
+
`streamChat` + media requests bypass page-level network capture entirely. **DOM
|
|
124
|
+
scraping is the only viable capture path** in the agentic cohort.
|
|
125
|
+
- **`flag` is NOT a content-policy signal.** The warn-symbol probe matched the `flag`
|
|
126
|
+
ligature 11× on a *successful* generation — `flag`/`thumb_up`/`thumb_down` are the
|
|
127
|
+
normal per-message chat affordances. Fail-fast detection must key on `warning` /
|
|
128
|
+
`error` / `block` or specific dialog/stream text, **never `flag`**. *(A deliberate
|
|
129
|
+
content-policy-refusal capture is still outstanding — no positive block sample yet.)*
|
|
130
|
+
|
|
131
|
+
## Settings via prompt, not the `tune` popover (agentic acts MCP-like)
|
|
132
|
+
|
|
133
|
+
The agentic composer behaves like a conversational agent: generation parameters that the
|
|
134
|
+
classic UI exposes as discrete controls — **output count** (1 / 4 images), **video
|
|
135
|
+
duration** (4/6/8/10 s), and likely **aspect ratio** and **model** — can be expressed in
|
|
136
|
+
**natural language inside the prompt** (e.g. `Generate 4 images of <prompt> in 16:9`), and
|
|
137
|
+
the agent resolves the selection itself.
|
|
138
|
+
|
|
139
|
+
Implications for `AgenticFlowUiDriver`:
|
|
140
|
+
- **`configure_settings` becomes optional.** Encoding settings as a directive prompt
|
|
141
|
+
avoids driving the fragile `tune` → Radix-popover dropdowns — the most drift-prone
|
|
142
|
+
surface on a volatile A/B UI. Prefer prompt-encoding; treat popover automation as a
|
|
143
|
+
fallback only if prompt-steering proves unreliable.
|
|
144
|
+
- **The agent *interprets* the request → verify, don't trust.** It may produce a
|
|
145
|
+
different count or ignore a duration. This is precisely why scrape-and-dedup-by-UUID
|
|
146
|
+
is load-bearing: request N → poll until N distinct new media UUIDs → if the produced
|
|
147
|
+
count differs, raise a typed mismatch rather than silently returning the wrong set.
|
|
148
|
+
- **Compose carefully** so settings directives aren't read as subject text (e.g. a
|
|
149
|
+
`Generate {n} image(s){aspect}: {prompt}` template).
|
|
150
|
+
|
|
151
|
+
## Open follow-ups
|
|
152
|
+
|
|
153
|
+
- **Content-policy block sample (outstanding):** capture a deliberate-refusal generation
|
|
154
|
+
in the agentic cohort to learn how a block surfaces (chat message vs. dialog vs.
|
|
155
|
+
symbol) — needed to design `await_images` fail-fast. The `flag` symbol is ruled out.
|
|
156
|
+
|
|
157
|
+
## The Wire & API Endpoint (Resolved via Issue #183 Reporter)
|
|
158
|
+
|
|
159
|
+
The network request logs from the agentic UI have been resolved. The new UX bypasses the direct media generation endpoints (such as `batchGenerateImages`) and routes all generation requests through Google's conversational flow creation agent.
|
|
160
|
+
|
|
161
|
+
- **Endpoint**: `POST https://aisandbox-pa.googleapis.com/v1/flowCreationAgent:streamChat?alt=sse`
|
|
162
|
+
- **Transport**: Server-Sent Events (SSE) stream, preceded by a CORS OPTIONS preflight.
|
|
163
|
+
- **Request Payload Structure**:
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"agentSessionId": "<uuid>",
|
|
167
|
+
"agentClientContext": {
|
|
168
|
+
"projectId": "projects/<projectUuid>",
|
|
169
|
+
"clientSessionId": ";<epochMillis>",
|
|
170
|
+
"recaptchaContext": {
|
|
171
|
+
"token": "<recaptcha_token>",
|
|
172
|
+
"applicationType": "RECAPTCHA_APPLICATION_TYPE_WEB"
|
|
173
|
+
},
|
|
174
|
+
"turnNumber": 2
|
|
175
|
+
},
|
|
176
|
+
"userMessage": {
|
|
177
|
+
"userPrompt": {
|
|
178
|
+
"parts": [
|
|
179
|
+
{
|
|
180
|
+
"text": "a red apple"
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Why page-level CDP/Playwright instrumentation recorded 0 entries
|
|
189
|
+
During local capture sessions, page-level request event listeners repeatedly recorded `0` API request entries. This occurred because the agentic UI delegates all `streamChat` network requests to a background **Web/Service Worker** target (`type: worker`). Worker-initiated requests completely bypass page-level network event registration in Playwright/CDP and require worker-level target attachments to capture.
|
|
190
|
+
|
|
191
|
+
### Impact on issue #174 (referenceEntities)
|
|
192
|
+
In the agentic UI, the prompt box is a conversational Slate.js editor. Because generation is driven by the conversation agent stream rather than direct REST inputs, staging library entities (e.g. characters) behaves differently. For the initial implementation phase, driving or injecting into `flowCreationAgent:streamChat` is out of scope. The recommended direction is **Runtime DOM detection + Fail Cleanly** with `FlowAgentUiError` (exit code 23) to prevent automation lockups while the cohort is active.
|
|
@@ -200,6 +200,14 @@ GFLOW_CLI_HISTORY_PROMPTS=redacted gflow image t2i "confidential brief"
|
|
|
200
200
|
**Reverting:** unset the variable (or set `playwright`) — the default path is byte-identical to a build without this feature, with no profile migration.
|
|
201
201
|
**Security note:** the `patchright` extra ships a *patched Chromium driver* that handles your live Google session cookies; it is exact-pinned and treated as a security-review-required dependency. See [SECURITY.md § Dependencies](SECURITY.md).
|
|
202
202
|
|
|
203
|
+
### `GFLOW_CLI_PREFER_CLASSIC`
|
|
204
|
+
|
|
205
|
+
**What:** Try to force the Classic Flow UI mode for image generation by clicking the Agent toggle pill if the page mounts in Agentic mode.
|
|
206
|
+
**Values:** `true` | `false`
|
|
207
|
+
**Default:** `false`
|
|
208
|
+
**Why use it:** The Agentic UI cohort (conversational chat) treats requested aspect ratios as a soft prompt hint, which can result in inconsistent aspect ratios (e.g. producing landscape when portrait 9:16 was requested). Exposing this setting allows callers to opt into switching the UI back to Classic mode where aspect ratios are enforced deterministically (via hard Flow crop controls).
|
|
209
|
+
**Fallback:** If the page cannot be switched back to Classic (e.g., in a forced A/B cohort where the toggle is not available), the driver gracefully falls back to Agentic mode rather than blocking execution.
|
|
210
|
+
|
|
203
211
|
### `GFLOW_CLI_LOCALE`
|
|
204
212
|
|
|
205
213
|
**What:** BCP-47 locale tag passed to Playwright's `launch_persistent_context(locale=...)` — controls the `Accept-Language` HTTP header only.
|
|
@@ -35,6 +35,7 @@ Welcome to the `gflow-cli` documentation. This index is the routing layer: it te
|
|
|
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
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) |
|
|
38
|
+
| **[docs/AGENT_UI_RECON.md](AGENT_UI_RECON.md)** | Agentic Flow UI cohort: classic-vs-agentic DOM signature, server-side + volatile (flapping) A/B gating (no client-readable flag), why `_exit_agent_mode` fails, runtime-DOM-detection recommendation | Touching `_exit_agent_mode` / composer mode-switch, diagnosing exit 23 selector drift, or understanding Flow's agentic-UI A/B (#183/#174) |
|
|
38
39
|
| **[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?" |
|
|
39
40
|
| **[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 |
|
|
40
41
|
| **[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 |
|
|
@@ -111,7 +112,7 @@ Slash commands for Claude Code, stored in `.claude/commands/gflow/`. All prefixe
|
|
|
111
112
|
**"A gflow command hangs / fails — where do I start?"** → [DEBUGGING § Quick reference](DEBUGGING.md#quick-reference)
|
|
112
113
|
**"Flow's UI broke a selector — how do I diagnose it?"** → [DEBUGGING § Inspecting Flow's live UI](DEBUGGING.md#inspecting-flows-live-ui)
|
|
113
114
|
**"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.
|
|
115
|
+
**"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.20.1](LIVE_VERIFICATION_v0.20.1.md) (Aspect ratio overrides in Agentic & Classic cohorts; skipped honestly due to sandbox execution). Prior: [v0.20.0](LIVE_VERIFICATION_v0.20.0.md) · [v0.19.0](LIVE_VERIFICATION_v0.19.0.md) · [v0.18.0](LIVE_VERIFICATION_v0.18.0.md) · [v0.17.0](LIVE_VERIFICATION_v0.17.0.md) · [v0.16.0](LIVE_VERIFICATION_v0.16.0.md) · [v0.13.0](LIVE_VERIFICATION_v0.13.0.md) · [v0.12.0](LIVE_VERIFICATION_v0.12.0.md) · [v0.11.0](LIVE_VERIFICATION_v0.11.0.md) · [v0.10.0](LIVE_VERIFICATION_v0.10.0.md) · [v0.9.1](LIVE_VERIFICATION_v0.9.1.md) · [v0.9.0](LIVE_VERIFICATION_v0.9.0.md) · [v0.8.1](LIVE_VERIFICATION_v0.8.1.md) · [v0.7.0](LIVE_VERIFICATION_v0.7.0.md)
|
|
115
116
|
**"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
117
|
**"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
118
|
**"What was live-verified for the video-download feature (#29)?"** → [LIVE_VERIFICATION_video_download](LIVE_VERIFICATION_video_download.md)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Live verification — v0.20.0 (Agentic UI driver)
|
|
2
|
+
|
|
3
|
+
> Hand-run against the real Google Flow editor on profile `denon82`. v0.20.0
|
|
4
|
+
> ships the pluggable `FlowUiDriver` strategy and **Agentic-cohort image
|
|
5
|
+
> generation**. The agentic cohort is server-assigned and cannot be forced from
|
|
6
|
+
> the client, so the deterministic trigger `GFLOW_CLI_FORCE_AGENT_UI=1` (clicks
|
|
7
|
+
> the in-input "Agent" toggle) is used to exercise the agentic path. Image
|
|
8
|
+
> generation is credit-free.
|
|
9
|
+
|
|
10
|
+
## Environment
|
|
11
|
+
|
|
12
|
+
| | |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Branch | `feature/agentic-ui-driver` → `develop` (PR [#189](https://github.com/ffroliva/gflow-cli/pull/189)) |
|
|
15
|
+
| Version | `0.19.0` working tree (pre-v0.20.0 bump) |
|
|
16
|
+
| Profile | `denon82` (real-browser Chrome) |
|
|
17
|
+
| Profile dir | `C:\Users\ffrol\AppData\Local\ffroliva\gflow-cli\profile_denon82` |
|
|
18
|
+
| Date | 2026-06-14 |
|
|
19
|
+
| Transport | `ui_automation` |
|
|
20
|
+
| Engine | `playwright` |
|
|
21
|
+
|
|
22
|
+
## How to reproduce
|
|
23
|
+
|
|
24
|
+
```pwsh
|
|
25
|
+
$env:PYTHONUTF8=1
|
|
26
|
+
# Classic cohort (auto-detected):
|
|
27
|
+
uv run gflow image t2i "a red apple on a rustic wooden table" --profile denon82 --aspect 16:9 --json
|
|
28
|
+
uv run gflow video t2v "a golden sunset over calm ocean waves" --profile denon82 --aspect 16:9 --duration 4 --json
|
|
29
|
+
|
|
30
|
+
# Agentic cohort, forced deterministically (clicks the Agent toggle):
|
|
31
|
+
$env:GFLOW_CLI_FORCE_AGENT_UI="1"
|
|
32
|
+
uv run gflow image t2i "a single ripe banana on a white plate" --profile denon82 --aspect 16:9 --json
|
|
33
|
+
uv run gflow image t2i "three colorful macarons on a marble surface" --profile denon82 --count 3 --json
|
|
34
|
+
|
|
35
|
+
# Deterministic harness (verifies forced→agentic→scrape→download, asserts file count):
|
|
36
|
+
.\scripts\e2e\agentic_image_e2e.ps1 -Profile denon82 -Count 3
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Evidence ledger (5-layer)
|
|
40
|
+
|
|
41
|
+
Each run: **file written** + **magic bytes (JPEG/MP4)** + **dimensions/size** +
|
|
42
|
+
**structlog invariants** + **user-confirmable artifact (media UUID + path)**.
|
|
43
|
+
|
|
44
|
+
| # | Path | Cohort | Result | Media UUID(s) | Bytes | Key structlog invariants |
|
|
45
|
+
|---|---|---|---|---|---|---|
|
|
46
|
+
| 1 | image t2i (classic) | classic | exit 0, 1 JPG | `f3e529ca…` | 647 KB | `ui_driver.bound mode=classic`; `crop_16_9` matched; `batchGenerateImages` 200 captured |
|
|
47
|
+
| 2 | video t2v (classic) | classic | exit 0, 1 MP4 | `433a92d5…` | 6.33 MB | `video_mode_entered`; `poll_terminal STATUS_SUCCESSFUL`; `video_saved` |
|
|
48
|
+
| 3 | image t2i (forced agentic, count 1) | agentic | exit 0, 1 JPG | `3722b716…` | 420 KB | `agent_mode_forced activated=true`; `ui_driver.bound mode=agentic`; full-res `getMediaUrlRedirect` URL |
|
|
49
|
+
| 4 | image t2i (forced agentic, count 3) | agentic | exit 0, **3 distinct** JPGs | `0bfb4127…`, `2f5c5f9e…`, `cd8f4781…` | 596 / 590 / 629 KB | `mode=agentic`; **dedup**: multiple `<img>` nodes → 3 distinct media UUIDs |
|
|
50
|
+
|
|
51
|
+
## What this proves
|
|
52
|
+
|
|
53
|
+
- **Classic path unaffected** by the driver refactor (rows 1–2): image + video
|
|
54
|
+
generate and download end-to-end through the new `ClassicFlowUiDriver`.
|
|
55
|
+
- **Agentic image generation works end-to-end** (rows 3–4): the Agent toggle
|
|
56
|
+
flips the composer to agentic, the agentic driver encodes settings into the
|
|
57
|
+
prompt, types into the Slate composer, **scrapes** the generated assets from
|
|
58
|
+
the DOM (page-level network capture is dead in this cohort — Web-Worker
|
|
59
|
+
delegated), **deduplicates by media UUID**, and downloads via the same-origin
|
|
60
|
+
`labs.google/.../getMediaUrlRedirect` redirect (allow-listed for download).
|
|
61
|
+
- **Synthetic `GeneratedImage` fields** (`seed=0`, `workflow_id=""`,
|
|
62
|
+
`dimensions=(0,0)`) flow through the save/report path without error.
|
|
63
|
+
|
|
64
|
+
## Not verified this cycle (recorded honestly)
|
|
65
|
+
|
|
66
|
+
- **A natural (non-forced) agentic load end-to-end:** the cohort flapped to
|
|
67
|
+
classic during the windows tested; the forced path exercises identical driver
|
|
68
|
+
code, but a naturally-served agentic load was not captured generating.
|
|
69
|
+
- **A positive content-policy refusal in the agentic cohort:** detection is
|
|
70
|
+
scoped to alert/dialog regions pending a captured sample (a chat-message-only
|
|
71
|
+
refusal currently misses → timeout). See `docs/AGENT_UI_E2E.md`.
|
|
72
|
+
- **Agentic video:** out of scope — raises `FlowAgentUiError` (exit 25) pending a
|
|
73
|
+
video scraping capture.
|
|
74
|
+
|
|
75
|
+
See [docs/AGENT_UI_E2E.md](AGENT_UI_E2E.md) for the repeatable procedure and the
|
|
76
|
+
three render-race / detection bugs this live testing surfaced and fixed.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Live Verification — v0.20.1
|
|
2
|
+
|
|
3
|
+
Feature under test:
|
|
4
|
+
- **Aspect ratio overrides in Agentic & Classic cohorts** ([#193](https://github.com/ffroliva/gflow-cli/issues/193)).
|
|
5
|
+
- **`GFLOW_CLI_PREFER_CLASSIC`** config option (`prefer_classic` setting).
|
|
6
|
+
|
|
7
|
+
## Live Verification Status
|
|
8
|
+
|
|
9
|
+
Live verification was **not run this cycle** (skipped honestly per the release protocol).
|
|
10
|
+
|
|
11
|
+
### Reason
|
|
12
|
+
The release prep was executed in an automated agent sandbox environment where live Google Flow credentials (`GFLOW_LIVE` and `GFLOW_CLI_E2E_PROFILE`) are not configured.
|
|
13
|
+
|
|
14
|
+
### Local Verification
|
|
15
|
+
To ensure no regressions, the following local verification checks were executed:
|
|
16
|
+
1. **Local Test Suite**: 1,765 local tests passed successfully (`pytest -m "not e2e and not live and not smoke"`).
|
|
17
|
+
2. **Type Checking**: `pyright src` completed with 0 errors and 0 warnings.
|
|
18
|
+
3. **Lint & Formatting**: `ruff check` and `ruff format --check` passed successfully with no violations.
|
|
19
|
+
4. **Repository Hygiene**: `check_repo_hygiene.py` and `check_doc_links.py` resolved all files and links with no violations.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
r"""Offline analyzer for gflow-agent-browser-spike captures.
|
|
3
|
+
|
|
4
|
+
Reads the sandbox's agentui-capture-*.json files, classifies each composer
|
|
5
|
+
state, redacts + fingerprints the gating signals, diffs them across runs
|
|
6
|
+
(account / locale / engine), and emits a consolidated, redacted findings JSON.
|
|
7
|
+
No browser driving — pure file processing. Raw captures stay in the sandbox.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
.venv\Scripts\python.exe scripts\dev\analyze_agent_ui_capture.py \
|
|
11
|
+
path\to\agentui-capture-ffroliva-en-*.json \
|
|
12
|
+
path\to\agentui-capture-denon82-pt-*.json --out findings.json
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import hashlib
|
|
19
|
+
import json
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from enum import StrEnum
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ComposerState(StrEnum):
|
|
27
|
+
CLASSIC_MEDIA = "classic_media"
|
|
28
|
+
AGENT_OVER_CLASSIC = "agent_over_classic"
|
|
29
|
+
FORCED_AGENT = "forced_agent"
|
|
30
|
+
UNKNOWN = "unknown"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ComposerSignals:
|
|
35
|
+
crop_present: bool
|
|
36
|
+
agent_pill_present: bool
|
|
37
|
+
agent_chat_panel_present: bool
|
|
38
|
+
crop_recoverable: bool | None # None = recovery not attempted
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def classify_composer(s: ComposerSignals) -> ComposerState:
|
|
42
|
+
if s.crop_present:
|
|
43
|
+
return ComposerState.CLASSIC_MEDIA
|
|
44
|
+
if not (s.agent_pill_present or s.agent_chat_panel_present):
|
|
45
|
+
return ComposerState.UNKNOWN
|
|
46
|
+
if s.crop_recoverable is True:
|
|
47
|
+
return ComposerState.AGENT_OVER_CLASSIC
|
|
48
|
+
if s.crop_recoverable is False:
|
|
49
|
+
return ComposerState.FORCED_AGENT
|
|
50
|
+
return ComposerState.UNKNOWN
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _sha8(value: str) -> str:
|
|
54
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()[:8] if value else ""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def fingerprint_map(kv: dict[str, Any]) -> dict[str, str]:
|
|
58
|
+
"""Reduce a {key: value} map (localStorage etc.) to {key: sha8(value)} —
|
|
59
|
+
lets us diff by presence + value-hash without persisting secrets."""
|
|
60
|
+
return {k: _sha8(str(v)) for k, v in kv.items()}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def diff_signal_sets(a: dict[str, str], b: dict[str, str]) -> dict[str, list[str]]:
|
|
64
|
+
ka, kb = set(a), set(b)
|
|
65
|
+
return {
|
|
66
|
+
"onlyInA": sorted(ka - kb),
|
|
67
|
+
"onlyInB": sorted(kb - ka),
|
|
68
|
+
"changed": sorted(k for k in ka & kb if a[k] != b[k]),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def load_capture(path: Path) -> dict[str, Any]:
|
|
73
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _signals_from_capture(capture: dict[str, Any]) -> ComposerSignals:
|
|
77
|
+
s = capture.get("signals", {})
|
|
78
|
+
rec = s.get("cropRecoverable")
|
|
79
|
+
return ComposerSignals(
|
|
80
|
+
crop_present=bool(s.get("cropPresent")),
|
|
81
|
+
agent_pill_present=bool(s.get("agentPill")),
|
|
82
|
+
agent_chat_panel_present=bool(s.get("chatPanel")),
|
|
83
|
+
crop_recoverable=None if rec is None else bool(rec),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def summarize_capture(capture: dict[str, Any]) -> dict[str, Any]:
|
|
88
|
+
gating = capture.get("gating", {})
|
|
89
|
+
return {
|
|
90
|
+
"key": f"{capture.get('profile')}/{capture.get('locale')}",
|
|
91
|
+
"profile": capture.get("profile"),
|
|
92
|
+
"locale": capture.get("locale"),
|
|
93
|
+
"engine": capture.get("engine"),
|
|
94
|
+
"navigatorWebdriver": capture.get("navigatorWebdriver"),
|
|
95
|
+
"state": classify_composer(_signals_from_capture(capture)).value,
|
|
96
|
+
"localStorageFp": fingerprint_map(gating.get("localStorage", {})),
|
|
97
|
+
"sessionStorageFp": fingerprint_map(gating.get("sessionStorage", {})),
|
|
98
|
+
"cookieNames": sorted(gating.get("documentCookieNames", [])),
|
|
99
|
+
"nextDataPagePropKeys": sorted(gating.get("nextDataPagePropKeys", [])),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def build_findings(summaries: list[dict[str, Any]]) -> dict[str, Any]:
|
|
104
|
+
"""Consolidate per-run summaries; diff the first agentic run against the
|
|
105
|
+
first classic run to surface candidate gating signals."""
|
|
106
|
+
states = {s["key"]: s["state"] for s in summaries}
|
|
107
|
+
agentic = next(
|
|
108
|
+
(s for s in summaries if s["state"] in ("forced_agent", "agent_over_classic")), None
|
|
109
|
+
)
|
|
110
|
+
classic = next((s for s in summaries if s["state"] == "classic_media"), None)
|
|
111
|
+
out: dict[str, Any] = {"runs": summaries, "states": states}
|
|
112
|
+
if agentic and classic:
|
|
113
|
+
out["localStorageDiff"] = diff_signal_sets(
|
|
114
|
+
agentic["localStorageFp"], classic["localStorageFp"]
|
|
115
|
+
)
|
|
116
|
+
out["sessionStorageDiff"] = diff_signal_sets(
|
|
117
|
+
agentic["sessionStorageFp"], classic["sessionStorageFp"]
|
|
118
|
+
)
|
|
119
|
+
out["cookieNameDiff"] = diff_signal_sets(
|
|
120
|
+
{k: "1" for k in agentic["cookieNames"]}, {k: "1" for k in classic["cookieNames"]}
|
|
121
|
+
)
|
|
122
|
+
out["nextDataKeyDiff"] = diff_signal_sets(
|
|
123
|
+
{k: "1" for k in agentic["nextDataPagePropKeys"]},
|
|
124
|
+
{k: "1" for k in classic["nextDataPagePropKeys"]},
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
out["note"] = "need at least one agentic and one classic capture to diff gating signals"
|
|
128
|
+
return out
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main(argv: list[str] | None = None) -> int:
|
|
132
|
+
p = argparse.ArgumentParser(description="Analyze agentic-UI captures")
|
|
133
|
+
p.add_argument("captures", nargs="+", help="paths to agentui-capture-*.json")
|
|
134
|
+
p.add_argument("--out", default=None)
|
|
135
|
+
args = p.parse_args(argv)
|
|
136
|
+
summaries = [summarize_capture(load_capture(Path(c))) for c in args.captures]
|
|
137
|
+
findings = build_findings(summaries)
|
|
138
|
+
text = json.dumps(findings, indent=2)
|
|
139
|
+
if args.out:
|
|
140
|
+
Path(args.out).write_text(text, encoding="utf-8")
|
|
141
|
+
print(text)
|
|
142
|
+
return 0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
raise SystemExit(main())
|