Skip to content

feat(cli): image gen / video gen bridge verbs that infer the mode (#1193) - #1211

Open
ffroliva wants to merge 6 commits into
developfrom
feature/1193-gen-bridge-verbs
Open

ffroliva wants to merge 6 commits into
developfrom
feature/1193-gen-bridge-verbs

Conversation

@ffroliva

@ffroliva ffroliva commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Closes #1193
Part of #1191

Summary

  • gflow image gen resolves to image t2i, or image i2i when --ref is given.

  • gflow video gen resolves to video t2v, video i2v with --initial-frame (and optional --end-frame), or video r2v with --ref.

  • A bridge verb is the mode verb it resolves to: after picking the mode it hands its raw arguments to that verb (mode.make_context(...) + mode.invoke(...)), so the mode verb's own parser, envvars, callbacks, required checks, parameter sources, defaults, output and exit codes apply exactly as on a direct call. --json keeps command as the mode verb ("image t2i") and adds a top-level "mode": "<resolved>" on success and failure payloads; it is added once, in json_output.emit, from a ctx.meta key the bridge sets.

  • Usage errors (exit 2), never a guess: a frame and --ref names video i2v and video r2v; --end-frame without --initial-frame says "--end-frame needs --initial-frame"; an option the resolved verb doesn't take is that verb's own Click error plus a line naming the verb the bridge picked:

    $ gflow image gen "a cat" --ref a.png --stdin
    Error: No such option '--stdin'.
    `gflow image gen` resolved to `gflow image i2i` (--ref was given).
    
  • Mode verbs unchanged, no deprecation. MCP unchanged: gflow_generate_image/gflow_generate_video already take the mode. The parity gate maps image gen / video gen to those tools; image gen:prompts / :continue_on_error carry the same exemption reason as image t2i.

Routing table

Inputs Resolves to
image gen PROMPT... image t2i
image gen PROMPT --ref IMG image i2i
video gen PROMPT video t2v
video gen PROMPT --initial-frame F [--end-frame E] video i2v
video gen PROMPT --ref IMG video r2v
a frame and --ref exit 2, names video i2v + video r2v
--end-frame without --initial-frame exit 2, "--end-frame needs --initial-frame"

Spec deviation (disclosed)

#1193 says --ref/--character → r2v. The CLI has no --character, and CLI video r2v requires --ref, so video gen --reference-entity X without --ref is passed to video t2v unchanged (on an account Flow serves from flow.google.com, a character on t2v exits 36). The migrated composer can serve r2v with only a character (migrated_composer.py), so this is follow-up #1213: let CLI r2v take a character without --ref, then route gen --reference-entity to it. r2v's required --ref is unchanged here.

Review

Two independent reviews (code-review xhigh, ponytail-review xhigh) found no wrong route and no dropped option. Fixed on this branch:

  1. --end-frame alone used to reach i2v and print advice that then failed; it is now a bridge usage error.
  2. The two usage-error examples in docs/USAGE.md omitted PROMPT. They now include it and show the real output; test_documented_usage_errors_say_what_the_docs_say runs each documented example and asserts the documented message (and that the example is still in the doc).
  3. --help: both bridges say the options' defaults and limits are the resolved verb's (see its --help). Help written for one verb is rewritten on the bridge's copy: --ref, --reference-entity, --ui-mode on image gen, --model on video gen.
  4. Re-parse through the mode itself replaced the hand-built invoke (default fill, type cast, copied ParameterSource, private click.core.UNSET, sub.invoke(callback)). The env-sourced --project-name test (test_video_gen_keeps_where_project_name_came_from) passes through the re-parse, and options are now rejected by the mode's own parser. test_same_named_options_agree_across_bridged_verbs guards the one place the bridge still parses with a single copy of an option (to read the routing inputs).
  5. "mode" is added at one chokepoint (json_output.emit), replacing five hand-wrapped emit sites. That file is touched by feat(errors): add charged, next_action and operation_id to the problem details #1208 too, but in a separate hunk (emit, not unexpected_payload).
  6. test_mode_verb_json_has_no_top_level_mode asserts exit 0.
  7. resolve_image_mode / resolve_video_mode return (command, why); the parallel dicts are gone.
  8. The shared fakes (_make_generated_image, _make_t2i_client, FakeVideoRecorder) moved to tests/cli/fakes.py; their old modules re-import them, so existing importers keep working.

Not changed: CLI r2v's required --ref (out of scope, #1213).

Gates (Impeccable Routine, on HEAD, PYTHONUTF8=1, UV_LINK_MODE=copy)

  • check_repo_hygiene, check_doc_links, check_website_docs_pii, generate_website_docs --check, check_council_memory, generate_plugin_skills --check: all green
  • ruff check / ruff format --check src tests: clean
  • pyright src: 0 errors (run with --pythonpath .venv/Scripts/python.exe; bare pyright on this machine resolves the system Python 3.13 and reports unrelated mcp/ui errors)
  • pytest -m "not live and not e2e and not smoke" -n 8 --dist worksteal --cov=gflow_cli: 6106 passed, 30 skipped, coverage 93%
  • Step 1b: A. execution paths: MCP direct/queued unchanged (no new tool params). B. MCP truth: no docstring claims affected; parity rows added. C. agent surfaces: skill, AGENTS.md command surface, README, INDEX, llms.txt. D. exit codes: none added (exit 2 = Click usage error). E. templates/models/doctor: N/A. F. USAGE section + JSON section, website mirror regenerated, CHANGELOG [Unreleased] / Added.

E2E

  • Image, live (zero credits, daily image quota): tests/e2e/test_json_output_e2e.py::test_e2e_image_t2i_json_shape[gen] runs the real gflow image gen ... --json against Flow and asserts command == "image t2i" and mode == "t2i" (the [t2i] variant asserts the mode verb has no mode). Result on a verified profile: 1 passed, 5 deselected in 63.53s (--collect-only first: 1/6 selected).
  • Video: tests/e2e/test_json_output_e2e.py::test_e2e_video_t2v_json_shape[gen] is added, not run. Blocker: it spends Veo credits and no paid generation is authorized tonight. Run with GFLOW_CLI_E2E_RUN_VIDEO=1 GFLOW_CLI_E2E_PROFILE=<p> uv run pytest -m e2e_video -k "test_e2e_video_t2v_json_shape and gen" tests/e2e/test_json_output_e2e.py.
  • $0 offline: tests/cli/test_gen_bridge.py runs the real python -m gflow_cli entrypoint in a subprocess for the exit-2 errors, and asserts with the runners stubbed that _run_t2i/_run_i2i/_run_t2v/_run_i2v/_run_r2v receive identical arguments from the bridge and from the mode verb.

Siblings

#1210 adds a @spends_credits marker per command and #1206 adds capability entries per command: whichever lands second marks image gen / video gen (both spend, via t2i/i2i/t2v/i2v/r2v).

)

image gen resolves to t2i, or i2i with --ref; video gen resolves to t2v,
i2v with a frame, or r2v with --ref. The bridge reuses the mode verbs' own
options and runs the resolved verb's callback with the user-supplied values,
so options, output and exit codes are the mode verb's. An option the resolved
verb does not take, or a frame together with --ref, is a usage error (exit 2)
naming the mode verb. --json adds a top-level "mode". Parameter sources are
carried into the mode verb's context, so default_project still sees an
env-sourced --project-name. MCP parity maps both leaves to the existing
generate tools.
@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 3 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6c3ece09-b59e-4522-aa71-e6fe4ec3c1ed
Commits

Reviewing files that changed from the base of the PR and between ca069fc and b275d2d.

Files selected for processing: 20

  • AGENTS.md
  • CHANGELOG.md
  • README.md
  • docs/INDEX.md
  • docs/USAGE.md
  • llms.txt
  • plugins/gflow/skills/gflow-cli/SKILL.md
  • skills/gflow-cli/SKILL.md
  • src/gflow_cli/_cli_helpers.py
  • src/gflow_cli/cli_image.py
  • src/gflow_cli/cli_video.py
  • src/gflow_cli/json_output.py
  • tests/cli/fakes.py
  • tests/cli/test_cli_image.py
  • tests/cli/test_cli_video.py
  • tests/cli/test_gen_bridge.py
  • tests/e2e/test_json_output_e2e.py
  • tests/mcp/test_cli_parity.py
  • tests/test_telemetry_notice.py
  • website/docs/USAGE.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@ffroliva

Copy link
Copy Markdown
Owner Author

Sibling note: #1210 (--help --json tree) adds a @spends_credits marker per command. Whichever of #1210 / #1211 lands second must mark image gen and video gen @spends_credits(True) (they run billed t2i/i2i/t2v/i2v/r2v). Likewise if #1206 lands first, both need capability entries.

…1193)

Review follow-ups on #1211:
- the resolved verb parses the raw arguments itself (make_context + invoke),
  so envvars, callbacks, required checks, parameter sources and Click's own
  'No such option' apply as on a direct call; the hand-built invoke and the
  private UNSET sentinel are gone
- --end-frame without --initial-frame is a bridge usage error
- json_output.emit adds the bridge's mode at one chokepoint
- bridge help says defaults and limits are the resolved verb's, and rewrites
  --ref/--reference-entity/--ui-mode/--model help that only fit one verb
- resolvers return (command, why); shared CLI fakes move to tests/cli/fakes.py
- tests: documented usage-error examples, same-named option agreement guard,
  e2e gen variants in test_json_output_e2e.py
@ffroliva

Copy link
Copy Markdown
Owner Author

CI note: the only failing test is tests/test_telemetry_notice.py::test_consent_region_from_iana_timezone[Atlantic/Faeroe-False], which this PR does not touch. #1210 fails the same way, and a fix is in progress on test/telemetry-tz-legacy-aliases. Because the test job fails, no coverage report reaches SonarCloud, so the gate shows new_coverage 0.0. SonarCloud reports 0 issues and 0 hotspots on this PR now that S3776 in bridge_params is fixed (f54e37a). Once the tz fix lands on develop, I'll merge develop in and re-check the gate.

…ne database

Ubuntu 24.04's tzdata 2026e moves backward links (Atlantic/Faeroe, Portugal,
Eire, Iceland, Poland) into tzdata-legacy, which the CI runners do not install.
ZoneInfo then refuses the alias, consent_region() fails closed, and the
Atlantic/Faeroe row (expected False) went red on every PR, develop included;
the three legacy rows expecting True passed for the wrong reason.

The mapping test now stubs the zone lookup, so every row checks the
name-to-region mapping on any machine. A separate test pins the fail-closed
behaviour for an alias the installed zone database cannot load.

(cherry picked from commit 9f356c9)
@ffroliva

Copy link
Copy Markdown
Owner Author

CI note: this branch now carries a cherry-pick (-x) of #1215's test-only commit 9f356c9d. CI on every PR, develop included, was failing on test_consent_region_from_iana_timezone[Atlantic/Faeroe-False], because Ubuntu 24.04's tzdata 2026e moved legacy zone links into tzdata-legacy. That failure also left SonarCloud's new-code coverage at 0%. The commit is identical to #1215's, so git drops it as a duplicate when #1215 merges first.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI: image gen / video gen bridge verbs that infer the mode from inputs

1 participant