gflow-cli 0.2.0a1__tar.gz → 0.4.0a2__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.2.0a1 → gflow_cli-0.4.0a2}/.claude/README.md +2 -2
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/.claude/commands/release.md +5 -5
- gflow_cli-0.4.0a2/.env.template +75 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/.github/workflows/ci.yml +3 -1
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/.github/workflows/release.yml +2 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/.gitignore +3 -0
- gflow_cli-0.4.0a2/CHANGELOG.md +315 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/CLAUDE.md +27 -13
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/CONTRIBUTING.md +19 -18
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/DISCLAIMER.md +11 -11
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/KNOWN_ISSUES.md +35 -23
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/PKG-INFO +86 -63
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/PLAN.md +125 -52
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/README.md +84 -62
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/docs/ARCHITECTURE.md +57 -19
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/docs/AUTHENTICATION.md +33 -33
- gflow_cli-0.4.0a2/docs/CONFIGURATION.md +190 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/docs/INDEX.md +13 -3
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/docs/SECURITY.md +19 -17
- gflow_cli-0.4.0a2/docs/USAGE.md +397 -0
- gflow_cli-0.4.0a2/docs/USER_GUIDE.md +625 -0
- gflow_cli-0.4.0a2/docs/superpowers/plans/2026-05-09-image-mvp-orchestration.md +319 -0
- gflow_cli-0.4.0a2/docs/superpowers/plans/2026-05-09-image-mvp.md +430 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-video-mvp-orchestration.md +3 -3
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-video-mvp.md +116 -116
- gflow_cli-0.4.0a2/docs/superpowers/plans/2026-05-10-phase-4-hardening-orchestration.md +358 -0
- gflow_cli-0.4.0a2/docs/superpowers/plans/2026-05-10-phase-4-hardening.md +2736 -0
- gflow_cli-0.4.0a2/docs/superpowers/specs/2026-05-10-phase-4-hardening-design.md +484 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/pyproject.toml +7 -5
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/samples/README.md +6 -4
- gflow_cli-0.4.0a2/samples/captured/06_batchGenerateImages.json +103 -0
- gflow_cli-0.4.0a2/samples/captured/07_batchGenerateImages_seeded.json +121 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/scripts/smoke_e2e.py +6 -6
- gflow_cli-0.4.0a2/scripts/smoke_image.py +147 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/skills/README.md +6 -5
- gflow_cli-0.4.0a2/skills/gflow-cli/SKILL.md +158 -0
- gflow_cli-0.4.0a2/src/gflow_cli/__init__.py +3 -0
- gflow_cli-0.4.0a2/src/gflow_cli/__main__.py +6 -0
- gflow_cli-0.4.0a2/src/gflow_cli/_cli_helpers.py +185 -0
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/__init__.py +3 -3
- gflow_cli-0.4.0a2/src/gflow_cli/api/_retry.py +138 -0
- gflow_cli-0.4.0a2/src/gflow_cli/api/client.py +897 -0
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/dto.py +96 -1
- gflow_cli-0.4.0a2/src/gflow_cli/api/image.py +236 -0
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/recaptcha.py +2 -2
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/routes.py +27 -1
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/auth.py +14 -10
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/cli.py +30 -8
- gflow_cli-0.4.0a2/src/gflow_cli/cli_image.py +535 -0
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/cli_video.py +77 -59
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/config.py +41 -7
- gflow_cli-0.4.0a2/src/gflow_cli/errors.py +216 -0
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/manifest.py +1 -1
- gflow_cli-0.4.0a2/src/gflow_cli/observability.py +160 -0
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/paths.py +14 -14
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/profile_store.py +7 -7
- gflow_cli-0.4.0a2/tasks/lessons.md +195 -0
- gflow_cli-0.4.0a2/tests/api/test_client.py +177 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/api/test_client_generate_video.py +44 -28
- gflow_cli-0.4.0a2/tests/api/test_client_image.py +799 -0
- gflow_cli-0.4.0a2/tests/api/test_concurrency.py +199 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/api/test_dto.py +1 -1
- gflow_cli-0.4.0a2/tests/api/test_image.py +253 -0
- gflow_cli-0.4.0a2/tests/api/test_image_dto.py +122 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/api/test_recaptcha.py +1 -1
- gflow_cli-0.4.0a2/tests/api/test_retry.py +243 -0
- gflow_cli-0.4.0a2/tests/api/test_routes.py +93 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/api/test_video.py +1 -1
- gflow_cli-0.4.0a2/tests/cli/__init__.py +0 -0
- gflow_cli-0.4.0a2/tests/cli/test_cli_image.py +459 -0
- gflow_cli-0.4.0a2/tests/cli/test_error_handling.py +323 -0
- gflow_cli-0.4.0a2/tests/cli/test_helpers.py +158 -0
- gflow_cli-0.4.0a2/tests/conftest.py +38 -0
- gflow_cli-0.4.0a2/tests/features/__init__.py +0 -0
- gflow_cli-0.4.0a2/tests/features/auth.feature +28 -0
- gflow_cli-0.4.0a2/tests/features/conftest.py +89 -0
- gflow_cli-0.4.0a2/tests/features/image.feature +25 -0
- gflow_cli-0.4.0a2/tests/features/test_auth_steps.py +186 -0
- gflow_cli-0.4.0a2/tests/features/test_image_steps.py +191 -0
- gflow_cli-0.4.0a2/tests/features/test_step_collision_guard.py +16 -0
- gflow_cli-0.4.0a2/tests/features/test_video_steps.py +253 -0
- gflow_cli-0.4.0a2/tests/features/video.feature +27 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/test_auth.py +6 -6
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/test_cli_video.py +83 -13
- gflow_cli-0.4.0a2/tests/test_config.py +161 -0
- gflow_cli-0.4.0a2/tests/test_errors.py +276 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/test_manifest.py +2 -2
- gflow_cli-0.4.0a2/tests/test_observability.py +165 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/test_paths.py +8 -8
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/test_profile_store.py +7 -7
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/test_smoke.py +7 -7
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/uv.lock +162 -3
- gflow_cli-0.2.0a1/.env.template +0 -59
- gflow_cli-0.2.0a1/CHANGELOG.md +0 -59
- gflow_cli-0.2.0a1/docs/CONFIGURATION.md +0 -182
- gflow_cli-0.2.0a1/docs/USAGE.md +0 -262
- gflow_cli-0.2.0a1/skills/gflow-cli/SKILL.md +0 -156
- gflow_cli-0.2.0a1/src/flow_cli/__init__.py +0 -3
- gflow_cli-0.2.0a1/src/flow_cli/__main__.py +0 -6
- gflow_cli-0.2.0a1/src/flow_cli/api/client.py +0 -246
- gflow_cli-0.2.0a1/tests/api/test_client.py +0 -44
- gflow_cli-0.2.0a1/tests/api/test_routes.py +0 -36
- gflow_cli-0.2.0a1/tests/test_config.py +0 -104
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/.gitattributes +0 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/LICENSE +0 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/samples/captured/01_upload_image.json +0 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/samples/captured/04_archive_workflow.json +0 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/samples/captured/05_createProject.json +0 -0
- {gflow_cli-0.2.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/video.py +0 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/__init__.py +0 -0
- {gflow_cli-0.2.0a1 → gflow_cli-0.4.0a2}/tests/api/__init__.py +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This directory holds **internal maintainer workflows** that are bundled with the repo so any contributor running Claude Code in this checkout gets the same environment.
|
|
4
4
|
|
|
5
|
-
> Distinct from `skills/
|
|
5
|
+
> Distinct from `skills/gflow-cli/SKILL.md` at the repo root, which is the **published** Skill end users install into their own Claude Code to learn how to use `gflow` from agent contexts. `.claude/` is for *us*, the maintainers; `skills/` is for *them*, the users.
|
|
6
6
|
|
|
7
7
|
## Layout
|
|
8
8
|
|
|
@@ -31,7 +31,7 @@ Example: `commands/foo.md` → invoked as `/foo`.
|
|
|
31
31
|
|
|
32
32
|
## Adding an internal skill
|
|
33
33
|
|
|
34
|
-
Same shape as the published one in `skills/
|
|
34
|
+
Same shape as the published one in `skills/gflow-cli/`:
|
|
35
35
|
|
|
36
36
|
```markdown
|
|
37
37
|
---
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Cut a new
|
|
2
|
+
description: Cut a new gflow-cli release — bump version, update CHANGELOG, tag, push.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
# `/release` — Cut a new
|
|
5
|
+
# `/release` — Cut a new gflow-cli release
|
|
6
6
|
|
|
7
|
-
You are about to release a new version of `
|
|
7
|
+
You are about to release a new version of `gflow-cli`. Follow this sequence verbatim — every step matters.
|
|
8
8
|
|
|
9
9
|
## Inputs
|
|
10
10
|
|
|
@@ -32,7 +32,7 @@ Ask the user (if not already provided):
|
|
|
32
32
|
uv run ruff check src tests
|
|
33
33
|
uv run ruff format --check src tests
|
|
34
34
|
uv run pyright src
|
|
35
|
-
uv run pytest -q --cov=
|
|
35
|
+
uv run pytest -q --cov=gflow_cli --cov-fail-under=80
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
4. **Bump version** in `pyproject.toml`:
|
|
@@ -83,4 +83,4 @@ Ask the user (if not already provided):
|
|
|
83
83
|
## See also
|
|
84
84
|
|
|
85
85
|
- [README § Releases](../../README.md#releases) — full release policy & cadence
|
|
86
|
-
- [PLAN § Phase 5](../../PLAN.md#phase-5--public-alpha-release) — first-release exit criteria
|
|
86
|
+
- [PLAN § Phase 5](../../PLAN.md#phase-5--public-alpha-release-on-pypi) — first-release exit criteria
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# gflow-cli — example environment variables.
|
|
2
|
+
# Copy to `.env` in the directory where you invoke `gflow` and uncomment what
|
|
3
|
+
# you want to override. ($GFLOW_CLI_HOME is NOT a .env search path — only the
|
|
4
|
+
# current working directory is loaded.)
|
|
5
|
+
# All variables are OPTIONAL — defaults work out of the box for most users.
|
|
6
|
+
# Lines below are commented to show the SHIPPED DEFAULT — uncomment to override.
|
|
7
|
+
|
|
8
|
+
# -----------------------------------------------------------------------------
|
|
9
|
+
# Auth & profiles
|
|
10
|
+
# -----------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
# Root directory for Playwright persistent contexts (signed-in Google sessions).
|
|
13
|
+
# Default (via `platformdirs`):
|
|
14
|
+
# Windows: %LOCALAPPDATA%\gflow-cli (e.g. C:\Users\<you>\AppData\Local\gflow-cli)
|
|
15
|
+
# macOS: ~/Library/Application Support/gflow-cli
|
|
16
|
+
# Linux: $XDG_DATA_HOME/gflow-cli (typically ~/.local/share/gflow-cli)
|
|
17
|
+
# GFLOW_CLI_HOME=
|
|
18
|
+
|
|
19
|
+
# Default profile name. When unset, the resolution chain auto-picks (see
|
|
20
|
+
# docs/CONFIGURATION.md § GFLOW_CLI_PROFILE). Override per-call with
|
|
21
|
+
# `--profile <name>`.
|
|
22
|
+
# GFLOW_CLI_PROFILE=default
|
|
23
|
+
|
|
24
|
+
# -----------------------------------------------------------------------------
|
|
25
|
+
# Output paths
|
|
26
|
+
# -----------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
# Where downloaded assets land. Subfolders are created per kind/date:
|
|
29
|
+
# <output_dir>/images/<YYYY-MM-DD>/<job_id>_<index>.png
|
|
30
|
+
# <output_dir>/videos/<YYYY-MM-DD>/<job_id>.mp4
|
|
31
|
+
#
|
|
32
|
+
# Default (via `platformdirs`):
|
|
33
|
+
# Windows: %USERPROFILE%\Downloads\gflow-cli
|
|
34
|
+
# macOS: ~/Downloads/gflow-cli
|
|
35
|
+
# Linux: $XDG_DOWNLOAD_DIR/gflow-cli (falls back to ~/Downloads/gflow-cli)
|
|
36
|
+
# GFLOW_CLI_OUTPUT_DIR=
|
|
37
|
+
|
|
38
|
+
# -----------------------------------------------------------------------------
|
|
39
|
+
# Provider selection
|
|
40
|
+
# -----------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
# Backend to use for generations.
|
|
43
|
+
# flow - reverse-engineered Flow REST API (default, requires Ultra/Pro)
|
|
44
|
+
# official - official Veo SDK (planned for v0.5+, requires Gemini API key)
|
|
45
|
+
# GFLOW_CLI_PROVIDER=flow
|
|
46
|
+
|
|
47
|
+
# Gemini API key — only required when GFLOW_CLI_PROVIDER=official.
|
|
48
|
+
# Get one at https://aistudio.google.com/apikey
|
|
49
|
+
# GFLOW_CLI_GEMINI_API_KEY=
|
|
50
|
+
|
|
51
|
+
# -----------------------------------------------------------------------------
|
|
52
|
+
# Runtime tuning
|
|
53
|
+
# -----------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
# Per-request HTTP timeout (seconds). Veo videos can take 60-180s each.
|
|
56
|
+
# GFLOW_CLI_TIMEOUT_SECONDS=600
|
|
57
|
+
|
|
58
|
+
# Logging level — DEBUG | INFO | WARNING | ERROR
|
|
59
|
+
# GFLOW_CLI_LOG_LEVEL=INFO
|
|
60
|
+
|
|
61
|
+
# Log format — auto | text | json
|
|
62
|
+
# auto = text on TTY, json when piped (default)
|
|
63
|
+
# GFLOW_CLI_LOG_FORMAT=auto
|
|
64
|
+
|
|
65
|
+
# Per-worker Playwright Page-pool size for batch runs (1-16). FlowApiClient
|
|
66
|
+
# opens N Pages inside one shared persistent BrowserContext; `gflow video batch`
|
|
67
|
+
# fans out via asyncio.gather. ~30-60 MiB memory per Page on Chromium headless;
|
|
68
|
+
# don't exceed 8 without measuring. Shipped in v0.4.0a2.
|
|
69
|
+
# GFLOW_CLI_CONCURRENCY=1
|
|
70
|
+
|
|
71
|
+
# Run Playwright in headless mode for non-`auth login` commands (true|false).
|
|
72
|
+
# Default: true. Set to false if reCAPTCHA Enterprise refuses to mint tokens
|
|
73
|
+
# (Google's bot-detection sometimes refuses headless Chromium but accepts a
|
|
74
|
+
# visible window). The session is still reused from the persistent profile.
|
|
75
|
+
# GFLOW_CLI_HEADLESS=true
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `gflow-cli` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.4.0a2] — 2026-05-11
|
|
11
|
+
|
|
12
|
+
> **Documentation polish.** Same release surface as v0.4.0a1; this tag fixes
|
|
13
|
+
> a doc-council pass: four broken Python snippets in the README, a shell
|
|
14
|
+
> exit-code branching example that silently dropped failures, a stale anchor
|
|
15
|
+
> link in `AUTHENTICATION.md`, three USER_GUIDE journeys the target audience
|
|
16
|
+
> needs (credit budgeting, pipeline wiring, error recovery), and a sweep of
|
|
17
|
+
> "planned v0.3 / v0.4" callouts across 9 files that had been overtaken by
|
|
18
|
+
> the Phase 4 release. No code changes. No tests changed.
|
|
19
|
+
|
|
20
|
+
### Fixed (docs)
|
|
21
|
+
|
|
22
|
+
- **`README.md` Python quick-start snippet rewritten** — the prior block had
|
|
23
|
+
four real bugs (`from gflow_cli.paths import profile_dir` → import error,
|
|
24
|
+
`upload_image(path, project_id)` args reversed, `generate_video(prompt=,
|
|
25
|
+
start_asset=, aspect=)` wrong kwargs, `poll_video_status` method does not
|
|
26
|
+
exist). Snippet now uses the same invocation pattern as
|
|
27
|
+
`gflow_cli.cli_video._run_i2v` and would actually run.
|
|
28
|
+
- **`docs/USAGE.md` exit-code branching example** — `if ! cmd; then case $?`
|
|
29
|
+
always saw `0` because the `if` consumed the exit code; rewritten to
|
|
30
|
+
capture `rc=$?` first. Exit code `2` re-labelled "Bad CLI usage" (auth is
|
|
31
|
+
exit `3`).
|
|
32
|
+
- **`docs/AUTHENTICATION.md` anchor link** to the Phase 4 PLAN heading
|
|
33
|
+
fixed (was `#phase-4--hardening--post-v030a1`, did not exist).
|
|
34
|
+
- **`CHANGELOG.md` footer** — added `[0.4.0a1]:` and `[0.4.0a2]:` compare
|
|
35
|
+
links; reset `[Unreleased]` to compare from v0.4.0a2.
|
|
36
|
+
- **`docs/USER_GUIDE.md` Journey 2** endpoint name `flowMedia:batchGenerateVeoVideo`
|
|
37
|
+
→ real route `/v1/video:batchAsyncGenerateVideoText`.
|
|
38
|
+
- **`docs/USER_GUIDE.md` Journey 5.2** invalid placeholder UUID
|
|
39
|
+
(`media-uuid-abc-...`) → canonical hex shape.
|
|
40
|
+
- **`docs/USER_GUIDE.md` Journey 7.1** `echo $?` placement — previously
|
|
41
|
+
captured the exit code of an intermediate command, not the failing batch.
|
|
42
|
+
- **`docs/USER_GUIDE.md` Journey 7.3** softened the "Flow doesn't re-bill"
|
|
43
|
+
claim — billing is a private-API contract we cannot assert.
|
|
44
|
+
- **`KNOWN_ISSUES.md` same-profile examples** swapped `gflow image batch`
|
|
45
|
+
(does not exist) → `gflow video batch`.
|
|
46
|
+
|
|
47
|
+
### Added (docs)
|
|
48
|
+
|
|
49
|
+
- **`docs/USER_GUIDE.md` Journey on credit budgeting** — rule-of-thumb credit
|
|
50
|
+
cost per `video t2v` / `video i2v` / `image t2i` / `image i2i` call, links
|
|
51
|
+
to Flow's credit-balance UI, batch-cost math example.
|
|
52
|
+
- **`docs/USER_GUIDE.md` Journey on wiring outputs into a pipeline** —
|
|
53
|
+
deterministic output-dir layout, `find` (POSIX) + `Get-ChildItem`
|
|
54
|
+
(PowerShell) recipes, `ffmpeg` consumer example.
|
|
55
|
+
- **`docs/USER_GUIDE.md` Journey on `ContentPolicyError` / `RateLimitError`
|
|
56
|
+
recovery** — what each error means, how long to wait, prompt-rewrite
|
|
57
|
+
pattern, when retry is futile.
|
|
58
|
+
- **`README.md` doc-nav** now links `docs/USER_GUIDE.md` (was missing).
|
|
59
|
+
- **`README.md` Stack table** lists `tenacity` and `structlog` (were
|
|
60
|
+
shipped in v0.4.0a1 but not in the stack overview).
|
|
61
|
+
|
|
62
|
+
### Changed (docs)
|
|
63
|
+
|
|
64
|
+
- **`CHANGELOG.md` [0.4.0a1] section reordered** — `Added — Phase 4
|
|
65
|
+
hardening` now appears before `Breaking`. The hardening release was
|
|
66
|
+
user-visible value; the env-var rename was a one-line update for most
|
|
67
|
+
users.
|
|
68
|
+
- **Per-class exit codes 3–7** promoted from a bullet to its own "Migration
|
|
69
|
+
notes" subsection in the [0.4.0a1] block.
|
|
70
|
+
- **Version-time-warp sweep across 9 files** — every `(planned v0.3)`,
|
|
71
|
+
`(planned v0.4)`, `v0.3+ will add`, `v0.4 will add`, and `current scaffold
|
|
72
|
+
ignores this` line either describes shipped behaviour or points at v0.5+.
|
|
73
|
+
Files touched: `README.md`, `CHANGELOG.md`, `CLAUDE.md`, `PLAN.md`,
|
|
74
|
+
`KNOWN_ISSUES.md`, `CONFIGURATION.md`, `AUTHENTICATION.md`,
|
|
75
|
+
`ARCHITECTURE.md`, `DISCLAIMER.md`, `SECURITY.md`, `CONTRIBUTING.md`,
|
|
76
|
+
`.env.template`.
|
|
77
|
+
- **`docs/ARCHITECTURE.md` Concurrency section** describes the shipped
|
|
78
|
+
`asyncio.Queue` Page pool, not the target-DDD `Semaphore` model.
|
|
79
|
+
- **`docs/ARCHITECTURE.md` Observability section** describes the shipped
|
|
80
|
+
`error_raised` / `error_unhandled` event names, not the target-DDD
|
|
81
|
+
dot-path names.
|
|
82
|
+
- **`docs/ARCHITECTURE.md` DDD error class names** annotated as target —
|
|
83
|
+
shipped Phase 4 names (`AuthExpiredError`, `RateLimitError`,
|
|
84
|
+
`ContentPolicyError`, `NetworkError`, `WireFormatError`) listed alongside.
|
|
85
|
+
- **`docs/CONFIGURATION.md` `GFLOW_CLI_CONCURRENCY`** describes shipped
|
|
86
|
+
behaviour (per-worker Page pool, `asyncio.gather` fan-out).
|
|
87
|
+
|
|
88
|
+
## [0.4.0a1] — 2026-05-11
|
|
89
|
+
|
|
90
|
+
> **Phase 4 hardening release.** Concurrency, retry/backoff, typed errors,
|
|
91
|
+
> and structured logs ship — your existing scripts keep running (the
|
|
92
|
+
> `FLOW_CLI_*` env-var shim is in place until v0.5.0). The user-visible
|
|
93
|
+
> contract that changed: shell scripts can now branch on stable per-class
|
|
94
|
+
> exit codes (3–7) for auth / rate-limit / content-policy / network /
|
|
95
|
+
> wire-format failures.
|
|
96
|
+
|
|
97
|
+
### Added — Phase 4 hardening
|
|
98
|
+
|
|
99
|
+
- **Per-worker Playwright Page pool.** `FlowApiClient.__aenter__` opens
|
|
100
|
+
`Settings.concurrency` Pages inside a single persistent BrowserContext.
|
|
101
|
+
Operations check out a Page via `asyncio.Queue` (FIFO, bounded by
|
|
102
|
+
`maxsize=N`). `GFLOW_CLI_CONCURRENCY=N` (1–16) now actually parallelizes.
|
|
103
|
+
- **`gflow video batch` fans out via `asyncio.gather`** over manifest
|
|
104
|
+
entries — was sequential pre-v0.4.0a1.
|
|
105
|
+
- **`tenacity`-based retry layer** (3 attempts, exponential jittered
|
|
106
|
+
backoff 1s±25% → 2s±25% → 4s±25%) on 5xx / 429 / `playwright.async_api.Error`
|
|
107
|
+
/ `TimeoutError`. `Retry-After` honoured, **capped at 60 s**. `reraise=True`
|
|
108
|
+
so the original exception's `__cause__` chain is preserved. reCAPTCHA
|
|
109
|
+
token re-minted **inside the retry loop, every attempt**, on the worker's
|
|
110
|
+
own Page.
|
|
111
|
+
- **RFC 9457 Problem Details exception hierarchy:**
|
|
112
|
+
`GFlowError → FlowApiError → {AuthExpiredError, RateLimitError,
|
|
113
|
+
ContentPolicyError, NetworkError, WireFormatError}`. `except FlowApiError`
|
|
114
|
+
catches the typed subclasses (back-compat). Each carries
|
|
115
|
+
`problem_type` URI, `title`, `status`, `detail`, `instance`
|
|
116
|
+
(`gflow:error:<correlation_id>`), `remediation_hint`, and `route`.
|
|
117
|
+
`to_problem_details()` serializes to the RFC 9457 JSON shape.
|
|
118
|
+
- **Per-class exit codes**: 3 (auth) / 4 (rate-limit) / 5 (content-policy) /
|
|
119
|
+
6 (network) / 7 (wire-format). Exit 1 = unhandled. Exit 130 = SIGINT.
|
|
120
|
+
- **`WireFormatError` discovery payload** — `route_name`, `http_status`,
|
|
121
|
+
`content_type`, `top_level_keys`, `body_prefix_redacted` so log mining
|
|
122
|
+
can propose new error subclasses for unexpected response shapes.
|
|
123
|
+
- **`structlog` bootstrap** with TTY auto-detection (text on TTY, JSON when
|
|
124
|
+
piped). `show_locals=False` mandatory on the exception renderer so frame
|
|
125
|
+
locals (which may contain auth tokens) NEVER reach the log stream.
|
|
126
|
+
`correlation_id` + `cli_version` bound via `contextvars` at the process
|
|
127
|
+
boundary.
|
|
128
|
+
- **`error_raised` and `error_unhandled` events.** `error_raised` for caught
|
|
129
|
+
`GFlowError`s — carries Problem Details. `error_unhandled` for anything
|
|
130
|
+
else — privacy-safe: hashes message + stack with SHA-256, never logs raw
|
|
131
|
+
payload.
|
|
132
|
+
- **12 `pytest-bdd` scenarios** across `auth.feature`, `video.feature`,
|
|
133
|
+
`image.feature` — all use a mocked `FlowApiClient`. A
|
|
134
|
+
`_forbid_live_playwright` autouse tripwire fails any scenario that
|
|
135
|
+
accidentally tries to start a real browser.
|
|
136
|
+
|
|
137
|
+
### Migration notes — stable exit codes
|
|
138
|
+
|
|
139
|
+
Shell scripts that previously branched on exit code `1` for any failure
|
|
140
|
+
can now distinguish the failure class. The mapping is locked by an
|
|
141
|
+
ordering-invariant test in `tests/test_errors.py`:
|
|
142
|
+
|
|
143
|
+
| Exit | Error class | Meaning | Retry? |
|
|
144
|
+
|------|-----------------------|--------------------------------------|----------------|
|
|
145
|
+
| 0 | — | Success | — |
|
|
146
|
+
| 1 | (unhandled) | Bug. Filed via `error_unhandled` | No |
|
|
147
|
+
| 2 | (Click) | Bad CLI usage / missing arg | Fix the call |
|
|
148
|
+
| 3 | `AuthExpiredError` | Session cookies invalidated | After re-login |
|
|
149
|
+
| 4 | `RateLimitError` | Flow returned 429 | Yes, with wait |
|
|
150
|
+
| 5 | `ContentPolicyError` | Prompt blocked upstream | After rewrite |
|
|
151
|
+
| 6 | `NetworkError` | DNS / TLS / 5xx after retry | Yes |
|
|
152
|
+
| 7 | `WireFormatError` | Response shape changed (Flow update) | File a bug |
|
|
153
|
+
| 130 | (SIGINT) | User Ctrl-C | — |
|
|
154
|
+
|
|
155
|
+
See [`docs/USAGE.md § Exit codes`](docs/USAGE.md#exit-codes) for a
|
|
156
|
+
shell-script template that branches on these codes.
|
|
157
|
+
|
|
158
|
+
### Breaking — package + env-var rename
|
|
159
|
+
|
|
160
|
+
- **Python package renamed: `flow_cli` → `gflow_cli`.** All imports must
|
|
161
|
+
change: `from gflow_cli...` (was `from flow_cli...`). The PyPI distribution
|
|
162
|
+
name (`gflow-cli`), the CLI binary (`gflow`), and the user data directory
|
|
163
|
+
(`gflow-cli/` under `platformdirs`) are unchanged.
|
|
164
|
+
- **Env var prefix renamed: `FLOW_CLI_*` → `GFLOW_CLI_*`.** Affected vars:
|
|
165
|
+
`GFLOW_CLI_HOME`, `GFLOW_CLI_OUTPUT_DIR`, `GFLOW_CLI_PROFILE`,
|
|
166
|
+
`GFLOW_CLI_HEADLESS`, `GFLOW_CLI_LOG_LEVEL`, `GFLOW_CLI_LOG_FORMAT`,
|
|
167
|
+
`GFLOW_CLI_PROVIDER`, `GFLOW_CLI_TIMEOUT_SECONDS`, `GFLOW_CLI_CONCURRENCY`,
|
|
168
|
+
`GFLOW_CLI_GEMINI_API_KEY`.
|
|
169
|
+
- **Backwards-compat shim.** Legacy `FLOW_CLI_*` env vars continue to work
|
|
170
|
+
in v0.4.x; on first encounter the process emits a single
|
|
171
|
+
`DeprecationWarning` to stderr summarising the promoted keys. The shim
|
|
172
|
+
will be removed in v0.5.0 — update your `.env` files and shell exports.
|
|
173
|
+
|
|
174
|
+
### Changed
|
|
175
|
+
|
|
176
|
+
- `FlowApiError` re-parented under `GFlowError`. Legacy positional
|
|
177
|
+
constructor `FlowApiError(status, body, *, route)` preserved (auto-detected
|
|
178
|
+
via `isinstance(args[0], int) and not isinstance(args[0], bool)`).
|
|
179
|
+
- `_resolve_profile` and `_make_provider_dir` deduped — relocated from
|
|
180
|
+
`cli_image.py` + `cli_video.py` to `gflow_cli._cli_helpers`. AST-based
|
|
181
|
+
drift guard in `tests/cli/test_helpers.py` prevents regression.
|
|
182
|
+
- All `logging.*` callsites in `src/` migrated to `structlog`. The
|
|
183
|
+
remaining `print()` in `auth.py` swapped to Rich `console.print()`.
|
|
184
|
+
|
|
185
|
+
### Internal
|
|
186
|
+
|
|
187
|
+
- New module: `gflow_cli.errors` (RFC 9457 hierarchy + `EXIT_CODE_MAP`).
|
|
188
|
+
- New module: `gflow_cli.observability` (structlog bootstrap + event
|
|
189
|
+
emitters; `show_locals=False` via
|
|
190
|
+
`ExceptionRenderer(ExceptionDictTransformer(show_locals=False))`).
|
|
191
|
+
- New module: `gflow_cli.api._retry` (tenacity `AsyncRetrying` +
|
|
192
|
+
`Retry-After` parser, capped at 60 s).
|
|
193
|
+
- New module: `gflow_cli._cli_helpers` (shared CLI-boundary handlers +
|
|
194
|
+
profile/provider helpers).
|
|
195
|
+
|
|
196
|
+
## [0.3.0a1] — 2026-05-10
|
|
197
|
+
|
|
198
|
+
### Added
|
|
199
|
+
- **`gflow image upload PATH`** — upload a single local image (PNG/JPEG) into a
|
|
200
|
+
fresh Flow project and print the asset UUID + dimensions Flow inferred. The
|
|
201
|
+
UUID is reusable as a starting frame for `gflow image i2i --ref` and
|
|
202
|
+
`gflow video i2v`.
|
|
203
|
+
- **`gflow image t2i PROMPT`** — text-to-image generation (1–4 images per call)
|
|
204
|
+
via Google Flow's Imagen / Nano Banana models.
|
|
205
|
+
Flags: `--model {nano2|nano-pro|image4}`, `--aspect {9:16|16:9|1:1|4:3|3:4}`,
|
|
206
|
+
`-n/--count` (1–4), `--seed` (single-image only), `--out DIR`, `--profile`.
|
|
207
|
+
Files land date-partitioned under `$GFLOW_CLI_OUTPUT_DIR/images/<YYYY-MM-DD>/`
|
|
208
|
+
by default; `--out DIR` writes flat as `<DIR>/<media_name>_<n>.png`.
|
|
209
|
+
- **`gflow image i2i PROMPT --ref PATH_OR_UUID`** — image-to-image generation
|
|
210
|
+
with one or more reference images. Each `--ref` is classified at the CLI
|
|
211
|
+
boundary: case-insensitive 8-4-4-4-12 hex UUIDs are passed through verbatim
|
|
212
|
+
(no upload), anything else is canonicalized (symlinks resolved at validation
|
|
213
|
+
time) and uploaded before use. `--ref` is repeatable; UUIDs and paths can mix
|
|
214
|
+
freely on the same call. Same flag set as `t2i` otherwise.
|
|
215
|
+
- **Multi-image fan-out** — `t2i` / `i2i` with `-n {2..4}` mint a single shared
|
|
216
|
+
`batch_id` and issue N parallel POSTs (one per shot, each with its own random
|
|
217
|
+
seed). Same-batch images share the prompt + refs; per-shot variation comes
|
|
218
|
+
from independent seeds.
|
|
219
|
+
- **Three image models** wired behind CLI aliases:
|
|
220
|
+
`nano2` → `NARWHAL` (Nano Banana 2; default, fast/balanced),
|
|
221
|
+
`nano-pro` → `GEM_PIX_2` (Nano Banana Pro; higher quality),
|
|
222
|
+
`image4` → `IMAGEN_3_5` (Imagen 4; photoreal-leaning).
|
|
223
|
+
- **Five aspect ratios** for image generation: `9:16`, `16:9`, `1:1`, `4:3`,
|
|
224
|
+
`3:4` (default `9:16`, matching the Flow web UI).
|
|
225
|
+
- `download_image()` on `FlowApiClient` — direct download of a generated
|
|
226
|
+
image's signed `fifeUrl` to disk. Streams to a temp file and atomically
|
|
227
|
+
renames on success; enforces an SSRF host allowlist (only Google-controlled
|
|
228
|
+
CDNs accepted).
|
|
229
|
+
- `scripts/smoke_image.py` — live single-image E2E smoke script (image
|
|
230
|
+
counterpart of `scripts/smoke_e2e.py` for video). Run after
|
|
231
|
+
`gflow auth login` to exercise the full happy path: project create →
|
|
232
|
+
`batchGenerateImages` → fifeUrl download.
|
|
233
|
+
|
|
234
|
+
### Changed
|
|
235
|
+
- `FlowApiClient.upload_image` now validates **PNG/JPEG/WebP/GIF magic
|
|
236
|
+
bytes** and rejects files larger than **20 MB** before issuing the upload
|
|
237
|
+
request. Existing callers (`gflow video i2v`, `gflow video batch`) inherit
|
|
238
|
+
the stricter validation; previously-undocumented use of `upload_image` for
|
|
239
|
+
non-image payloads no longer works (was never officially supported).
|
|
240
|
+
- Project renamed `flow-cli` → `gflow-cli` across all docs and source. The
|
|
241
|
+
PyPI package and GitHub repo were already at the new name in v0.2.0a1;
|
|
242
|
+
this commit completes the in-source rename. Local clones may want to
|
|
243
|
+
rename their working directory to match `gh clone https://github.com/ffroliva/gflow-cli`
|
|
244
|
+
behavior.
|
|
245
|
+
|
|
246
|
+
### Security
|
|
247
|
+
- DEBUG-level body logs now redact reCAPTCHA Enterprise tokens and other
|
|
248
|
+
bearer-style fields before emission, eliminating a token-leak vector when
|
|
249
|
+
users share verbose logs while filing bug reports.
|
|
250
|
+
- `download_image()` enforces an **SSRF host allowlist** on the signed
|
|
251
|
+
`fifeUrl` returned by Flow — only Google-controlled image CDNs
|
|
252
|
+
(`*.googleusercontent.com`, etc.) are followed; any other host raises
|
|
253
|
+
before the GET is issued. Defends against a Flow-side bug or compromise
|
|
254
|
+
redirecting downloads to an attacker-controlled origin.
|
|
255
|
+
- `project_id` allowlist regex `^[A-Za-z0-9-]{1,128}$` on
|
|
256
|
+
`batch_generate_images_url` — closes percent-encoded slash (`%2F`),
|
|
257
|
+
Unicode-lookalike (U+FF0F / U+2215 / U+29F8), and CRLF/NUL injection
|
|
258
|
+
bypasses that the previous denylist guard let through.
|
|
259
|
+
|
|
260
|
+
### CI
|
|
261
|
+
- Test matrix now includes Python 3.13 alongside 3.11 and 3.12.
|
|
262
|
+
|
|
263
|
+
## [0.2.0a1] — 2026-05-09
|
|
264
|
+
|
|
265
|
+
### Added
|
|
266
|
+
- **`gflow video t2v`** — generate a video from a text prompt via Veo 3.1.
|
|
267
|
+
Flags: `--aspect 9:16|16:9|1:1`, `--seed`, `--output`, `--profile`, `--poll-interval`.
|
|
268
|
+
- **`gflow video i2v`** — generate a video from a start image + text prompt (Veo 3.1 I2V).
|
|
269
|
+
- **`gflow video batch`** — run a TSV manifest of video generations against one shared project.
|
|
270
|
+
- `gflow_cli.api` package — low-level REST client (`FlowApiClient`) + value objects
|
|
271
|
+
(`GenerateVideoRequest`, `VideoOperation`, `VideoStatus`) for video generation.
|
|
272
|
+
- `gflow_cli.api.recaptcha` — reCAPTCHA Enterprise token minting via Playwright `page.evaluate`.
|
|
273
|
+
`TokenMinter` caches the discovered site key per session; `mint(action)` is called immediately
|
|
274
|
+
before each generation request.
|
|
275
|
+
- `gflow_cli.manifest` — TSV manifest parser for `gflow video batch`. Supports optional
|
|
276
|
+
`start_image`, `end_image`, `aspect`, `output_path` columns; skips `# `-prefixed comments.
|
|
277
|
+
- `GFLOW_CLI_HEADLESS` env var (`bool`, default `true`). Set to `false` if reCAPTCHA refuses
|
|
278
|
+
to mint tokens in headless mode (Google bot detection fallback).
|
|
279
|
+
- `scripts/smoke_e2e.py` — one-shot live T2V smoke test; run after `gflow auth login` to
|
|
280
|
+
verify the full happy path (create project → generate_video → poll → download).
|
|
281
|
+
- **`CLAUDE.md`** at repo root — project memory hub for AI coding agents
|
|
282
|
+
(Claude Code reads natively; Cursor/Codex/Gemini/Aider can read as reference).
|
|
283
|
+
- **`.claude/`** directory — repo-local Claude Code surface for maintainers.
|
|
284
|
+
- `.claude/README.md` — what goes here, how to extend.
|
|
285
|
+
- `.claude/commands/release.md` — `/release` slash command that automates
|
|
286
|
+
version bump + CHANGELOG migration + tag + push, with quality gates.
|
|
287
|
+
- `gflow_cli.profile_store` — profile inventory + default-profile persistence
|
|
288
|
+
in `$GFLOW_CLI_HOME/config.toml`. Five-step resolution chain (CLI flag > env >
|
|
289
|
+
config > auto-select > raise) with named exceptions
|
|
290
|
+
(`NoProfilesError`, `NoDefaultProfileError`).
|
|
291
|
+
- New auth subcommands: bare `gflow auth`, `gflow auth list`, `gflow auth use <name>`,
|
|
292
|
+
`gflow auth logout [--profile NAME] [-y]`. First login auto-sets default profile.
|
|
293
|
+
- `KNOWN_ISSUES.md` at repo root — open/mitigated/resolved issues with workarounds.
|
|
294
|
+
- `docs/` tree (INDEX, AUTHENTICATION, CONFIGURATION, ARCHITECTURE, USAGE, SECURITY).
|
|
295
|
+
- `.env.template` documenting every supported env var.
|
|
296
|
+
- GitHub Actions CI: ruff, pyright, pytest on Python 3.11 and 3.12.
|
|
297
|
+
- GitHub Actions release workflow: tag-triggered PyPI publish via Trusted Publishing.
|
|
298
|
+
- MIT license, comprehensive README, [`DISCLAIMER.md`](DISCLAIMER.md), [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
299
|
+
- [`skills/gflow-cli/SKILL.md`](skills/gflow-cli/SKILL.md) — installable Claude Code Skill.
|
|
300
|
+
|
|
301
|
+
### Removed
|
|
302
|
+
- `gflow_cli.providers.FlowProvider` and `gflow_cli.models` — superseded by `gflow_cli.api`.
|
|
303
|
+
- Legacy CLI stubs: `gflow upload`, `gflow generate`, `gflow status`, `gflow download`,
|
|
304
|
+
`gflow i2v`. Replaced by the wired `gflow video` subgroup.
|
|
305
|
+
|
|
306
|
+
## [0.1.0] — _unreleased_
|
|
307
|
+
|
|
308
|
+
First skeleton. Not functional end-to-end yet.
|
|
309
|
+
|
|
310
|
+
[Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.4.0a2...HEAD
|
|
311
|
+
[0.4.0a2]: https://github.com/ffroliva/gflow-cli/compare/v0.4.0a1...v0.4.0a2
|
|
312
|
+
[0.4.0a1]: https://github.com/ffroliva/gflow-cli/compare/v0.3.0a1...v0.4.0a1
|
|
313
|
+
[0.3.0a1]: https://github.com/ffroliva/gflow-cli/releases/tag/v0.3.0a1
|
|
314
|
+
[0.2.0a1]: https://github.com/ffroliva/gflow-cli/releases/tag/v0.2.0a1
|
|
315
|
+
[0.1.0]: https://github.com/ffroliva/gflow-cli/releases/tag/v0.1.0
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## What this project is
|
|
6
6
|
|
|
7
|
-
`
|
|
7
|
+
`gflow-cli` is an unofficial Python CLI that drives [Google Flow](https://labs.google/fx/tools/flow) (Veo image-to-video, Imagen text-to-image) from the terminal by reverse-engineering Flow's private REST API at `aisandbox-pa.googleapis.com`. Built for Google AI Ultra/Pro subscribers who want to spend their Flow credits via batch automation rather than the web UI.
|
|
8
8
|
|
|
9
9
|
**Same pattern as `edge-tts`:** a community SDK over a private cloud API.
|
|
10
10
|
|
|
@@ -17,11 +17,23 @@
|
|
|
17
17
|
|
|
18
18
|
## Active phase
|
|
19
19
|
|
|
20
|
-
**Phase
|
|
20
|
+
**Phase 4 — Hardening DONE (v0.4.0a2).** Per-worker Page pool, tenacity
|
|
21
|
+
retry/backoff with reCAPTCHA re-mint, RFC 9457 Problem Details error
|
|
22
|
+
taxonomy with per-class exit codes 3–7, structlog observability with
|
|
23
|
+
`error_raised` / `error_unhandled` events, and 12 pytest-bdd scenarios all
|
|
24
|
+
shipped. Documentation polish landed in v0.4.0a2. Next phase TBD — likely
|
|
25
|
+
Phase 5 public alpha on PyPI followed by Phase 6 operations history
|
|
26
|
+
(see `PLAN.md`).
|
|
21
27
|
|
|
22
28
|
## Architecture (skim)
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
> Note: the layered diagram below describes the **target** architecture
|
|
31
|
+
> (deferred per [PLAN.md ADR #2](PLAN.md#5-decision-log-adrs-in-miniature)).
|
|
32
|
+
> The current package layout is the simpler
|
|
33
|
+
> `src/gflow_cli/{api/, cli.py, cli_image.py, cli_video.py, _cli_helpers.py,
|
|
34
|
+
> auth.py, config.py, errors.py, observability.py, paths.py,
|
|
35
|
+
> profile_store.py}`. See
|
|
36
|
+
> [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full target shape.
|
|
25
37
|
|
|
26
38
|
```text
|
|
27
39
|
interfaces/ → application/ → domain/ ← infrastructure/
|
|
@@ -37,19 +49,19 @@ Dependency rule: **`domain/` depends on nothing**. `application/` depends on `do
|
|
|
37
49
|
|
|
38
50
|
- **Never commit secrets.** `.gitignore` excludes `auth/`, `profile_*/`, `*.cookies.json`, `storage_state.json`, `secrets.json`, `.env`, `.env.local`, `.env.*.local`. If you see one of these staged, abort and tell the user.
|
|
39
51
|
- **Never add `Co-Authored-By: Claude` (or any AI co-author) to commit messages.** Author attribution is the human user's only.
|
|
40
|
-
- **Sessions belong outside the repo.** Default location is `$LOCALAPPDATA/
|
|
41
|
-
- **No `print()` in `src/`** — use `structlog`
|
|
42
|
-
- **Domain layer is pure** — `src/
|
|
52
|
+
- **Sessions belong outside the repo.** Default location is `$LOCALAPPDATA/gflow-cli/profile_*` (Windows) or `~/.local/share/gflow-cli/profile_*` (POSIX) via [`platformdirs`](https://github.com/platformdirs/platformdirs). Never store sessions in `/tmp`, `%TEMP%`, or anywhere the OS auto-reaps.
|
|
53
|
+
- **No raw `print()` and no `import logging` in `src/`** — use `structlog` for structured events, or Rich `console.print(...)` for user-facing output.
|
|
54
|
+
- **Domain layer is pure** — `src/gflow_cli/domain/*` must not import `application/`, `infrastructure/`, or `interfaces/`. No I/O, no frameworks.
|
|
43
55
|
- **Frozen dataclasses for value objects.** Aggregates may have controlled mutation methods, but VOs (AspectRatio, Prompt, JobId, ...) are immutable.
|
|
44
56
|
- **Async all the way down.** Handlers and providers are `async def`. CLI is the only place that calls `asyncio.run(...)`.
|
|
45
57
|
- **TDD is non-negotiable.** Red → Green → Refactor → Commit. Coverage floor: **80% overall**, **90% on `domain/` and `application/`**. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
46
58
|
|
|
47
59
|
## Coding conventions
|
|
48
60
|
|
|
49
|
-
- **Python 3.11+**, strict typing (`pyright --strict` on `src/
|
|
61
|
+
- **Python 3.11+**, strict typing (`pyright --strict` on `src/gflow_cli/`), `from __future__ import annotations` at the top of every module.
|
|
50
62
|
- **`@dataclass(frozen=True)`** for value objects and DTOs. `Protocol` for ports.
|
|
51
63
|
- **`pathlib.Path`** everywhere — never raw strings for filesystem paths.
|
|
52
|
-
- **Click + Rich** for the CLI, **
|
|
64
|
+
- **Click + Rich** for the CLI, **Playwright `page.request`** as the HTTP transport (auto-attaches Google session cookies), **`tenacity`** for retry/backoff, **`structlog`** for structured logs, **`pytest + pytest-bdd`** for tests.
|
|
53
65
|
- **Conventional Commits** for messages (`feat:`, `fix:`, `docs:`, `test:`, `chore:`, `refactor:`, etc.). See examples in `git log`.
|
|
54
66
|
- **Short files, single responsibility.** ~200-400 lines typical, 800 max. Split into `package/<topic>.py` if growing.
|
|
55
67
|
|
|
@@ -61,7 +73,7 @@ Run all four BEFORE asking to commit:
|
|
|
61
73
|
uv run ruff check src tests # lint
|
|
62
74
|
uv run ruff format --check src tests # formatting
|
|
63
75
|
uv run pyright src # types
|
|
64
|
-
uv run pytest -q --cov=
|
|
76
|
+
uv run pytest -q --cov=gflow_cli # tests + coverage
|
|
65
77
|
```
|
|
66
78
|
|
|
67
79
|
CI runs the same four on every push (see `.github/workflows/ci.yml`).
|
|
@@ -71,10 +83,10 @@ CI runs the same four on every push (see `.github/workflows/ci.yml`).
|
|
|
71
83
|
| I need to… | Read this |
|
|
72
84
|
|---|---|
|
|
73
85
|
| Understand the architecture | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
|
|
74
|
-
| Add a CLI command | [docs/USAGE.md](docs/USAGE.md) + existing patterns in `src/
|
|
75
|
-
| Add
|
|
86
|
+
| Add a CLI command | [docs/USAGE.md](docs/USAGE.md) + existing patterns in `src/gflow_cli/cli.py` |
|
|
87
|
+
| Add an API route | [PLAN.md § 4 Phase status](PLAN.md#4-phase-status) + existing client in `src/gflow_cli/api/client.py` + capture data under `samples/captured/` |
|
|
76
88
|
| Touch auth | [docs/AUTHENTICATION.md](docs/AUTHENTICATION.md) |
|
|
77
|
-
| Add a config knob | [docs/CONFIGURATION.md](docs/CONFIGURATION.md) + `.env.template` + (later) `src/
|
|
89
|
+
| Add a config knob | [docs/CONFIGURATION.md](docs/CONFIGURATION.md) + `.env.template` + (later) `src/gflow_cli/shared/config.py` |
|
|
78
90
|
| Write a test | [CONTRIBUTING.md § TDD](CONTRIBUTING.md#test-driven-development-mandatory) + existing patterns in `tests/` |
|
|
79
91
|
| Cut a release | [README § Releases](README.md#releases) — bump version in `pyproject.toml`, update CHANGELOG, `git tag vX.Y.Z`, push |
|
|
80
92
|
| Track a known issue | [KNOWN_ISSUES.md](KNOWN_ISSUES.md) |
|
|
@@ -97,7 +109,7 @@ uv run gflow auth login --profile experiments # named profile
|
|
|
97
109
|
2. `uv run pytest tests/<area>/test_<thing>.py` — verify it fails for the right reason.
|
|
98
110
|
3. Implement the minimum production code to pass.
|
|
99
111
|
4. Refactor.
|
|
100
|
-
5. `uv run pytest -q --cov=
|
|
112
|
+
5. `uv run pytest -q --cov=gflow_cli` — verify nothing else broke and coverage didn't regress.
|
|
101
113
|
|
|
102
114
|
### Update a doc
|
|
103
115
|
|
|
@@ -121,6 +133,8 @@ uv run gflow auth login --profile experiments # named profile
|
|
|
121
133
|
- **Question scope creep.** If the user asks for X and you're tempted to also fix Y, ask first.
|
|
122
134
|
- **Plan before implementing** for anything > 1 file or > 50 LoC. Write the plan into PLAN.md or a comment, then execute.
|
|
123
135
|
- **Show your work.** When you change something non-obvious, leave a one-line comment with the WHY (not the WHAT — the diff already shows the what).
|
|
136
|
+
- **Fix bugs autonomously.** Given a bug report, reproduce it, trace the root cause, fix it, and verify — no hand-holding needed. Point at logs/errors/failing tests, then resolve them.
|
|
137
|
+
- **Learn from corrections.** After any user correction, append the pattern to `tasks/lessons.md` (create if absent) as a rule that prevents the same mistake. Review it at session start for relevant context.
|
|
124
138
|
|
|
125
139
|
## See also
|
|
126
140
|
|