gnmyflow 0.3.1__py3-none-any.whl

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.
@@ -0,0 +1,826 @@
1
+ Metadata-Version: 2.5
2
+ Name: gnmyflow
3
+ Version: 0.3.1
4
+ Summary: GnmyFlow: a fast CLI and MCP server for Google Flow and Google Flow Music. Fork of StudioWright; unofficial and not affiliated with Google.
5
+ Project-URL: Repository, https://github.com/anggi-susanto/gnmyflow
6
+ Project-URL: Issues, https://github.com/anggi-susanto/gnmyflow/issues
7
+ Project-URL: Upstream, https://github.com/umutkocak/studiowright
8
+ Author: Umut Koçak
9
+ Maintainer: anggi-susanto
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Requires-Python: >=3.12
13
+ Requires-Dist: click>=8.5.0
14
+ Requires-Dist: filelock>=4.1.0
15
+ Requires-Dist: mcp[cli]>=2.3.0
16
+ Requires-Dist: pillow>=11.0
17
+ Requires-Dist: platformdirs>=4.12.4
18
+ Requires-Dist: playwright>=1.63.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ <div align="center">
22
+
23
+ # GnmyFlow
24
+
25
+ **Google Flow and Google Flow Music, from your terminal and your AI agent.**
26
+
27
+ Images, video clips and songs made in your own signed-in Chrome, on your own subscription.<br>
28
+ No API keys. No cookie exports. No captcha solvers. No browser extension.
29
+
30
+ ![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-3776AB?logo=python&logoColor=white)
31
+ ![License: MIT](https://img.shields.io/badge/license-MIT-green)
32
+ ![MCP server](https://img.shields.io/badge/MCP-server-6E56CF)
33
+ ![Claude Code plugin](https://img.shields.io/badge/Claude%20Code-plugin-D97757)
34
+ ![Tested on Windows 11](https://img.shields.io/badge/tested%20on-Windows%2011-0078D4)
35
+
36
+ </div>
37
+
38
+ > [!IMPORTANT]
39
+ > GnmyFlow is an unofficial fork of [StudioWright](https://github.com/umutkocak/studiowright), originally authored by Umut Koçak. It is not affiliated with, endorsed
40
+ > or sponsored by Google. It drives Flow's and Flow Music's web pages in **your** browser
41
+ > and spends **your** credits. Read the [disclaimer](#disclaimer) before you use it.
42
+
43
+ ```bash
44
+ sw image "a lighthouse keeper with a teal coat, flat textured 2D" --aspect 16:9
45
+ sw video "he turns towards the sea" --start keeper.jpg --duration 4 --resolution 360p
46
+ sw music "moody indie folk, warm male vocal" --lyrics-file chorus.txt --length 1:00
47
+ ```
48
+
49
+ ## Contents
50
+
51
+ - [Showcase](#showcase)
52
+ - [Why GnmyFlow](#why-gnmyflow)
53
+ - [Features](#features)
54
+ - [Install](#install)
55
+ - [Quick start](#quick-start)
56
+ - [Use it from AI agents](#use-it-from-ai-agents)
57
+ - [Command reference](#command-reference)
58
+ - [Batch files](#batch-files)
59
+ - [Costs and safety](#costs-and-safety)
60
+ - [How it works](#how-it-works)
61
+ - [Troubleshooting](#troubleshooting)
62
+ - [Configuration](#configuration)
63
+ - [Development](#development)
64
+ - [Disclaimer](#disclaimer)
65
+
66
+ ## Showcase
67
+
68
+ The showcase below was inherited from StudioWright and made with StudioWright, from one small batch file and two chat
69
+ messages.
70
+
71
+ ```json
72
+ {"id": "keeper", "kind": "image", "prompt": "a lighthouse keeper with a teal coat, standing, flat textured 2D illustration", "refs": ["style.png"]}
73
+ {"id": "keeper-close", "kind": "image", "prompt": "close-up portrait of the same lighthouse keeper, flat textured 2D", "refs": ["keeper"]}
74
+ {"id": "keeper-clip", "kind": "video", "prompt": "the lighthouse keeper turns his head towards the sea, gentle wind", "start": "keeper", "resolution": "360p", "duration": 4}
75
+ ```
76
+
77
+ <table>
78
+ <tr>
79
+ <td width="50%"><img src="docs/media/keeper.jpg" alt="A lighthouse keeper in a teal coat on a cliff at night"><br><sub><b>keeper</b> · Nano Banana 2.1 · style from a reference image · 22 s</sub></td>
80
+ <td width="50%"><img src="docs/media/keeper-closeup.jpg" alt="Close-up of the same lighthouse keeper"><br><sub><b>keeper-close</b> · the same character, by referring to the first job · 21 s</sub></td>
81
+ </tr>
82
+ <tr>
83
+ <td width="50%"><img src="docs/media/keeper-clip.gif" alt="The keeper turns towards the sea"><br><sub><b>keeper-clip</b> · Omni Flash, 4 s, start frame from the first job · 32 s · 4 credits</sub></td>
84
+ <td width="50%"><a href="docs/media/beacon-in-the-dark.mp4"><img src="docs/media/beacon-cover.jpg" alt="Cover art of the song Beacon in the Dark: a lighthouse in a storm"></a><br><sub><b>Beacon in the Dark</b> · written and sung by the Flow Music Producer · 37 s · 5 credits<br><a href="docs/media/beacon-in-the-dark.mp4">▶ listen (MP4)</a> · <a href="docs/media/beacon-in-the-dark.mp3">MP3</a></sub></td>
85
+ </tr>
86
+ </table>
87
+
88
+ The song took two chat turns:
89
+
90
+ ```bash
91
+ sw music chat "Write a four-line chorus about a lighthouse keeper who waits for the ships."
92
+ sw music chat "Moody indie-folk please. Make the song now, about one minute long." --session <id from the first reply>
93
+ ```
94
+
95
+ ## Why GnmyFlow
96
+
97
+ Most ways to automate Google's media models need one of these: a paid API key, cookies
98
+ copied out of your browser, a captcha-solving service, an unpacked browser extension, or a
99
+ third-party relay that holds your session. GnmyFlow needs none of them.
100
+
101
+ | | Paid model APIs | Cookie-based wrappers | Hosted relays | **GnmyFlow** |
102
+ | --- | :---: | :---: | :---: | :---: |
103
+ | Uses the credits of your Flow / Flow Music plan | ✗ | ✓ | ✓ | **✓** |
104
+ | No API key or per-call billing | ✗ | ✓ | ✗ | **✓** |
105
+ | Your session never leaves your machine | ✓ | ✗ | ✗ | **✓** |
106
+ | No captcha solving or token forging | ✓ | ✗ | ? | **✓** |
107
+ | Every option of the web app | partial | partial | partial | **✓** |
108
+ | Images, video **and** music in one tool | ✗ | ✗ | partial | **✓** |
109
+
110
+ What makes it different:
111
+
112
+ - **It acts like you.** GnmyFlow types, clicks and chooses files in Flow's real pages,
113
+ in a Chrome you signed in to yourself. It only *reads* the replies those pages receive,
114
+ to learn ids and download links. It never forges, replays or alters a request.
115
+ - **One package, three front doors.** The same engine is a CLI (`sw`), an MCP server
116
+ (`sw mcp`) and a Claude Code plugin.
117
+ - **Built for real production.** Price checks before you spend (`--dry-run`,
118
+ `--max-credits`), resumable batch files that chain results, sized downloads (2K images,
119
+ 1080p/4K clips), pacing across processes, and a cooldown when Google asks for a break.
120
+ - **Fast on a warm tab.** About 20 s for an image, 30-60 s for a clip, 40 s for a song.
121
+
122
+ ## Features
123
+
124
+ **Images (Google Flow)**
125
+ - Nano Banana 2.1 (default), Nano Banana Pro and Nano Banana 2 Lite.
126
+ - Aspect ratios 16:9, 4:3, 1:1, 3:4, 9:16; one to four images per call.
127
+ - Reference images from files or earlier results; each file is uploaded once per project
128
+ and then reused by content.
129
+ - Downloads at Flow's own sizes: 1K (original), 2K (upscaled by Flow), 4K where the plan
130
+ allows.
131
+
132
+ **Video (Google Flow)**
133
+ - Omni Flash (default), Veo 3.1 Lite, Fast and Quality.
134
+ - Text only, start frame, start and end frames, or ingredients (several reference images).
135
+ - Omni: 360p / 720p and 4, 6, 8 or 10 s. Aspect 16:9 or 9:16.
136
+ - Downloads at 720p, 1080p or 4K (upscaled by Flow), or as a GIF.
137
+ - `--dry-run` shows Flow's price without submitting; `--max-credits` refuses anything
138
+ more expensive.
139
+
140
+ **Music (Google Flow Music)**
141
+ - Songs with your lyrics (any language) or instrumentals; Lyria 3.5 or Lyria 3 Pro;
142
+ BPM, length (1:00-3:00), seed, title, and cover art from a file or a prompt.
143
+ - **Edit existing songs:** extend, cover in another style, replace a section, variation,
144
+ trim.
145
+ - **Stems:** drums, bass, vocals and other, each as its own file.
146
+ - **Downloads:** WAV (48 kHz), M4A or MP3; vertical MP4 videos from Flow Music's
147
+ templates.
148
+ - **The Producer agent:** chat with it to write lyrics, plan songs, remix your own audio
149
+ or make AI music videos; songs it makes are saved automatically.
150
+ - **Your own audio:** upload a file and extend, cover or split it like any other song.
151
+
152
+ **Automation**
153
+ - CLI with `--json` output, an MCP server with 16 tools, and a Claude Code plugin with a
154
+ skill.
155
+ - JSON Lines batch files that resume where they stopped and pass results between jobs.
156
+ - A job history (`sw jobs`) and a screenshot of the page whenever a job fails.
157
+
158
+ ## Install
159
+
160
+ This fork is not published on PyPI yet. Install from the repository only after
161
+ the rename changes have been pushed. The plugin pin `v0.3.1` targets the first fork
162
+ release and will only work after that tag is published. The existing `v0.3.0` tag
163
+ predates the rename and must not be moved or reused for the fork release.
164
+
165
+ Requirements: Python 3.12+, [uv](https://docs.astral.sh/uv/), Google Chrome, and a Google
166
+ account with a Flow plan (images and video) and/or a Flow Music plan (music).
167
+
168
+ ```bash
169
+ uv tool install git+https://github.com/anggi-susanto/gnmyflow
170
+ sw --version
171
+ ```
172
+
173
+ The primary command is `gnmyflow`. Compatibility aliases `sw` and `studiowright`
174
+ run the same CLI. Do not install both forks into the same environment: they share
175
+ the `studiowright` Python namespace and legacy commands.
176
+
177
+ <details>
178
+ <summary>From a local checkout (for development)</summary>
179
+
180
+ ```bash
181
+ git clone https://github.com/anggi-susanto/gnmyflow.git
182
+ cd gnmyflow
183
+ uv sync
184
+ uv tool install --python 3.13 -e .
185
+ ```
186
+
187
+ </details>
188
+
189
+ ### Updating
190
+
191
+ - **Plugin** (MCP server and skill): `claude plugin update gnmyflow@gnmyflow`, or in
192
+ Claude Code `/plugin` → Installed → gnmyflow → Update now; then `/reload-plugins`.
193
+ Each plugin version runs the GnmyFlow release of the same version. To update
194
+ automatically, turn it on once: `/plugin` → Marketplaces → gnmyflow → Enable
195
+ auto-update.
196
+ - **Command line**: `uv tool install --force git+https://github.com/anggi-susanto/gnmyflow@vX.Y.Z`
197
+ with the release you want (see [Releases](https://github.com/anggi-susanto/gnmyflow/releases)).
198
+
199
+ ## Quick start
200
+
201
+ **1. Sign in once.** Chrome opens on a profile that belongs to GnmyFlow only. Sign in
202
+ to Google, open Flow once, then close the window.
203
+
204
+ ```bash
205
+ sw auth login
206
+ sw auth login --url https://www.flowmusic.app/ # only if you will make music
207
+ ```
208
+
209
+ Run `sw stop` first if GnmyFlow's Chrome is already running.
210
+
211
+ **2. Make something.**
212
+
213
+ ```bash
214
+ sw image "a red fox in the snow, flat 2D" --aspect 1:1
215
+ sw video "slow push-in on a quiet harbour at dawn" --model veo-lite --dry-run # price only
216
+ sw music "calm documentary underscore, soft piano, warm strings" --bpm 80 --length 1:30
217
+ ```
218
+
219
+ Files land in `Downloads/studiowright` (change it with `--out`), and each command prints
220
+ their paths. Add `--json` for ids, sizes, timings and credits.
221
+
222
+ **3. Keep Chrome in the background.** `sw window hide` moves the window off-screen. Never
223
+ minimize it: a minimized Chrome stops rendering and Flow stalls.
224
+
225
+ ## Use it from AI agents
226
+
227
+ Every client below runs `gnmyflow mcp` over stdio; `sw mcp` remains compatible.
228
+ If a client cannot find `gnmyflow`, give it the full executable path.
229
+
230
+ Generating takes real time (an image ~20 s, a clip up to a few minutes), so give the
231
+ client a generous tool timeout where it has one.
232
+
233
+ ### Claude Code plugin (recommended)
234
+
235
+ The plugin adds the MCP server and a skill that teaches Claude the options, prices and
236
+ safe habits.
237
+
238
+ ```bash
239
+ claude plugin install gnmyflow --marketplace anggi-susanto/gnmyflow
240
+ ```
241
+
242
+ or inside Claude Code:
243
+
244
+ ```text
245
+ /plugin marketplace add anggi-susanto/gnmyflow
246
+ /plugin install gnmyflow@gnmyflow
247
+ ```
248
+
249
+ The plugin starts its own matching GnmyFlow release with `uvx`, so it needs only `uv`
250
+ and Chrome; installing `sw` separately is for the command line.
251
+
252
+ Then just ask:
253
+
254
+ > Make a 16:9 image of a lighthouse keeper, then a 4-second clip where he turns to the
255
+ > sea. Check the price first.
256
+ >
257
+ > Write a one-minute instrumental bed for a documentary intro and give me the stems.
258
+
259
+ ### Claude Code (MCP server only)
260
+
261
+ ```bash
262
+ claude mcp add --scope user gnmyflow -- gnmyflow mcp
263
+ ```
264
+
265
+ For long video jobs, raise the tool timeout, e.g. `MCP_TOOL_TIMEOUT=900000` in your
266
+ environment.
267
+
268
+ <details>
269
+ <summary><b>Claude Desktop</b></summary>
270
+
271
+ Settings → Developer → Edit Config, then add the server to `claude_desktop_config.json`
272
+ (`%APPDATA%\Claude\` on Windows, `~/Library/Application Support/Claude/` on macOS):
273
+
274
+ ```json
275
+ {
276
+ "mcpServers": {
277
+ "gnmyflow": { "command": "gnmyflow", "args": ["mcp"] }
278
+ }
279
+ }
280
+ ```
281
+
282
+ Quit Claude Desktop completely and start it again.
283
+ </details>
284
+
285
+ <details>
286
+ <summary><b>Cursor</b></summary>
287
+
288
+ `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project):
289
+
290
+ ```json
291
+ {
292
+ "mcpServers": {
293
+ "gnmyflow": { "command": "gnmyflow", "args": ["mcp"] }
294
+ }
295
+ }
296
+ ```
297
+ </details>
298
+
299
+ <details>
300
+ <summary><b>VS Code (GitHub Copilot)</b></summary>
301
+
302
+ `.vscode/mcp.json` (note the `servers` key):
303
+
304
+ ```json
305
+ {
306
+ "servers": {
307
+ "gnmyflow": { "type": "stdio", "command": "gnmyflow", "args": ["mcp"] }
308
+ }
309
+ }
310
+ ```
311
+
312
+ For all workspaces, run **MCP: Open User Configuration** and add the same entry.
313
+ </details>
314
+
315
+ <details>
316
+ <summary><b>Codex CLI</b></summary>
317
+
318
+ ```bash
319
+ codex mcp add gnmyflow -- gnmyflow mcp
320
+ ```
321
+
322
+ or in `~/.codex/config.toml`:
323
+
324
+ ```toml
325
+ [mcp_servers.gnmyflow]
326
+ command = "gnmyflow"
327
+ args = ["mcp"]
328
+ tool_timeout_sec = 900
329
+ ```
330
+ </details>
331
+
332
+ <details>
333
+ <summary><b>Gemini CLI</b></summary>
334
+
335
+ ```bash
336
+ gemini mcp add -s user gnmyflow gnmyflow mcp
337
+ ```
338
+
339
+ or in `~/.gemini/settings.json`:
340
+
341
+ ```json
342
+ {
343
+ "mcpServers": {
344
+ "gnmyflow": { "command": "gnmyflow", "args": ["mcp"], "timeout": 900000 }
345
+ }
346
+ }
347
+ ```
348
+ </details>
349
+
350
+ ### MCP tools
351
+
352
+ | Tool | What it does |
353
+ | --- | --- |
354
+ | `flow_generate_image` | Images with any model, aspect, count, references and download size |
355
+ | `flow_generate_video` | Clips with any model and mode, frames, references, size; `dry_run`, `max_credits` |
356
+ | `flow_upload` / `flow_download` | Upload references once; fetch existing media again at any size |
357
+ | `flow_create_project` / `flow_use_project` | Choose where Flow keeps the work |
358
+ | `flow_options` / `flow_status` | Every model and option; recent jobs, pacing and cooldown |
359
+ | `music_generate` | A song or instrumental, with lyrics, BPM, length, seed, model, title, cover art |
360
+ | `music_edit` | Extend, cover, replace, variation or trim an existing song |
361
+ | `music_stems` | Split a song into drums, bass, vocals and other |
362
+ | `music_video` | A vertical MP4 of a song from Flow Music's templates |
363
+ | `music_download` | Existing songs as WAV, M4A or MP3 |
364
+ | `music_library` | The newest songs, the credit balance and the plan (free) |
365
+ | `music_chat` | Talk to the Producer agent; saves any songs it makes |
366
+ | `music_upload` | Upload your own audio as a private song |
367
+
368
+ ## Command reference
369
+
370
+ `sw --help` and `sw <command> --help` show every option. The most used ones:
371
+
372
+ ### Images and video
373
+
374
+ ```bash
375
+ sw image "PROMPT" [--model nano-banana-2.1|nano-banana-pro|nano-banana-2-lite]
376
+ [--aspect 16:9|4:3|1:1|3:4|9:16] [--count 1-4] [--ref FILE_OR_ID ...]
377
+ [--size 1K|2K|4K] [--project ID] [--out DIR] [--json]
378
+
379
+ sw video "PROMPT" [--model omni-flash|veo-lite|veo-fast|veo-quality] [--mode frames|ingredients]
380
+ [--start FILE_OR_ID] [--end FILE_OR_ID] [--ref FILE_OR_ID ...]
381
+ [--aspect 16:9|9:16] [--resolution 360p|720p] [--duration 4|6|8|10]
382
+ [--size 720p|1080p|4K|270p] [--max-credits N] [--dry-run] [--json]
383
+
384
+ sw upload FILE ... # upload references once, print their media ids
385
+ sw download MEDIA_ID ... [--size 2K|1080p]
386
+ sw project create "TITLE" | use ID | list
387
+ sw run BATCH.jsonl [--keep-going]
388
+ sw credits # Flow (as of the last clip) and Flow Music balances
389
+ sw jobs | sw pace | sw window show|hide | sw stop | sw mcp
390
+ ```
391
+
392
+ ### Music
393
+
394
+ `SONG` is a song id, a unique id prefix, a flowmusic.app song URL, or `last`.
395
+
396
+ ```bash
397
+ sw music "SOUND" [--lyrics TEXT | --lyrics-file FILE] [--bpm N] [--length 1:00-3:00] [--seed N]
398
+ [--model lyria-3.5|lyria-3-pro] [--title T] [--cover IMAGE | --cover-prompt TEXT]
399
+ [--format wav|m4a|mp3] [--out DIR] [--json]
400
+
401
+ sw music extend SONG [--from 0:45] [--until 2:30] [--prompt TEXT] [--lyrics-file FILE]
402
+ sw music cover SONG --prompt "slow jazz trio, brushed drums"
403
+ sw music replace SONG --start 0:20 --end 0:35 [--prompt TEXT] [--lyrics-file FILE]
404
+ sw music variation SONG
405
+ sw music trim SONG --start 0:05 --end 0:35
406
+ sw music stems SONG
407
+ sw music video SONG [--template simple|modern|player]
408
+ sw music download SONG ... [--format wav|m4a|mp3]
409
+ sw music chat "MESSAGE" [--session ID] [--audio FILE] [--image FILE] [--confirm-rights]
410
+ sw music upload FILE --confirm-rights
411
+ sw music list | sw music credits
412
+ ```
413
+
414
+ `sw music "SOUND"` is short for `sw music create "SOUND"`. Flow Music asks you to confirm
415
+ that you have the rights to files you upload; `--confirm-rights` is that confirmation, and
416
+ GnmyFlow never gives it on its own.
417
+
418
+ ## Batch files
419
+
420
+ A batch is JSON Lines (one job per line) or a JSON list. Jobs run in order, and a job can
421
+ use an earlier job's result by its `id`.
422
+
423
+ ```json
424
+ {"id": "keeper", "kind": "image", "prompt": "a lighthouse keeper, flat 2D", "refs": ["style.png"]}
425
+ {"id": "clip", "kind": "video", "prompt": "he turns to the sea", "start": "keeper", "duration": 6, "max_credits": 12}
426
+ {"id": "bed", "kind": "music", "sound": "calm piano underscore", "length": "1:30"}
427
+ {"id": "bed-long", "kind": "music", "action": "extend", "song": "bed", "end": "2:30"}
428
+ {"id": "bed-stems", "kind": "music", "action": "stems", "song": "bed-long"}
429
+ ```
430
+
431
+ ```bash
432
+ sw run shots.jsonl
433
+ ```
434
+
435
+ - `refs`, `start` and `end` take file paths (relative to the batch file), media ids or job
436
+ ids. A music job's `song` takes a song id or a job id.
437
+ - Music `action`s: `create` (default), `extend`, `cover`, `replace`, `variation`, `trim`,
438
+ `stems`, `video`. Extend, replace and trim take their times as `start` / `end`.
439
+ - Results are appended to `shots.jsonl.results.jsonl`. Running the batch again skips jobs
440
+ that already finished; `--keep-going` continues past a failure.
441
+
442
+ ## Costs and safety
443
+
444
+ Prices as Flow and Flow Music showed them in October 2026. Your plan may differ.
445
+
446
+ | Google Flow | Credits |
447
+ | --- | --- |
448
+ | Image, any Nano Banana model | 0 (daily quota) |
449
+ | Omni Flash clip, 360p, 4 s | 4 |
450
+ | Omni Flash clip, 720p, 4 / 6 / 8 / 10 s | 7 / 10 / 12 / 15 |
451
+ | Veo 3.1 Lite / Fast / Quality, 8 s | 10 / 20 / 100 |
452
+
453
+ | Google Flow Music | Credits | Time |
454
+ | --- | --- | --- |
455
+ | New song, extend, cover, replace, variation | 5 | 30-50 s |
456
+ | Trim, stems, video template, chat, upload, downloads | 0 | 10-60 s |
457
+ | Song made by the Producer in chat | 5 | ~40 s |
458
+ | AI music video (through chat) | expensive; check in the app | minutes |
459
+
460
+ Built-in guards:
461
+
462
+ - `--dry-run` / `dry_run` sets up a clip and reports Flow's price without submitting.
463
+ - `--max-credits` / `max_credits` refuses a clip that costs more.
464
+ - Submits are spaced (20 s plus jitter) per profile, across processes.
465
+ - When Google reports unusual activity, the profile cools down for 30 minutes and every
466
+ call says so instead of retrying.
467
+ - One job at a time per profile (and one Flow Music job at a time), queued by a lock.
468
+ Downloads run in a tab of their own beside generations, since they submit nothing.
469
+ - Upscaled downloads (2K/4K, 1080p) are spaced a minute apart. Flow refuses bulk upscaling
470
+ with "unusual activity"; GnmyFlow then pauses upscales for an hour (generations go
471
+ on) and saves new media at their original size meanwhile.
472
+
473
+ ## How it works
474
+
475
+ ```mermaid
476
+ flowchart LR
477
+ A["You, Claude or any MCP client"] -->|"sw … / MCP tools"| B["GnmyFlow"]
478
+ B -->|"CDP on 127.0.0.1"| C["Your Chrome<br/>dedicated profile, signed in by you"]
479
+ C -->|"types, clicks, chooses files"| D["Google Flow"]
480
+ C -->|"types, clicks, chooses files"| E["Google Flow Music"]
481
+ D -. "replies are read, never changed" .-> B
482
+ E -. "replies are read, never changed" .-> B
483
+ B --> F[("Downloads/studiowright")]
484
+ ```
485
+
486
+ - GnmyFlow starts your installed Chrome on its own profile and attaches over the
487
+ DevTools protocol on loopback. Chrome keeps running between commands, so every call finds
488
+ a warm, signed-in tab.
489
+ - Generation uses page controls; selected project and download operations also use
490
+ authenticated browser requests. Replies supply media ids, progress and download links.
491
+ - You sign in manually. Chrome retains authentication state in the dedicated profile.
492
+ Loopback CDP has no application authentication: other local processes may control it.
493
+ - Job records contain prompts and local paths; failures can save page screenshots.
494
+ Diagnostic scripts in `spikes/` may capture session material. These files are sensitive;
495
+ there is no automatic retention cleanup or application-level encryption. Do not share
496
+ profiles, databases, screenshots or captures without reviewing them.
497
+ - No production telemetry destination was identified in the source audit. This is not a
498
+ guarantee about Chrome, dependencies or third-party provider traffic.
499
+
500
+ Technical notes: [Flow](docs/notes/flow.md) · [Flow Music](docs/notes/flow-music.md).
501
+
502
+ ## Troubleshooting
503
+
504
+ <details>
505
+ <summary><b>"not signed in", or a sign-in page appears</b></summary>
506
+
507
+ Run `sw stop`, then `sw auth login` (Flow) or `sw auth login --url https://www.flowmusic.app/`
508
+ (Flow Music), sign in, and close the window.
509
+ </details>
510
+
511
+ <details>
512
+ <summary><b>A job is stuck or very slow</b></summary>
513
+
514
+ Make sure GnmyFlow's Chrome window is not minimized (`sw window hide` is the safe way
515
+ to hide it). Run with `sw -v …` to see each step. A failed job leaves a screenshot in the
516
+ `incidents` folder of the data directory, and `sw jobs` shows the error.
517
+ </details>
518
+
519
+ <details>
520
+ <summary><b>A clip finished in Flow but the command timed out</b></summary>
521
+
522
+ `sw download <media id>` fetches it without generating again. The timeout message prints
523
+ the ids.
524
+ </details>
525
+
526
+ <details>
527
+ <summary><b>"unusual activity" / cooldown</b></summary>
528
+
529
+ Google asked for a break. Wait for the cooldown (`sw pace` shows it), and spread large
530
+ batches out. Do not run several profiles of the same account in parallel.
531
+ </details>
532
+
533
+ <details>
534
+ <summary><b>A large project (hundreds of media)</b></summary>
535
+
536
+ Flow's grid only renders the tiles near the viewport. GnmyFlow finds older references
537
+ by searching the prompt box's picker (uploads keep their `sw-<hash>` name) or by scrolling
538
+ the grid from where the medium should be, and downloads open each medium's own edit view,
539
+ so position in the grid does not matter. Uploaded images download only at their own size;
540
+ Flow upscales generated media only.
541
+ </details>
542
+
543
+ <details>
544
+ <summary><b>2K / 1080p downloads are refused or paused</b></summary>
545
+
546
+ Flow limits how many upscales an account may request in a row and answers further ones
547
+ with "unusual activity". GnmyFlow stops at the first refusal, pauses upscales for an
548
+ hour (`sw pace` shows it) and keeps generating. Download the originals meanwhile, upscale
549
+ fewer media, or upscale locally.
550
+ </details>
551
+
552
+ <details>
553
+ <summary><b>A music video template fails</b></summary>
554
+
555
+ Flow Music sometimes cannot render a template for a song. Try again or pick another
556
+ template (`--template modern|simple|player`).
557
+ </details>
558
+
559
+ <details>
560
+ <summary><b>The MCP client cannot start the server</b></summary>
561
+
562
+ Give the client the full path of `sw`. The Claude Code plugin needs `uvx` (part of uv) on
563
+ the PATH. `sw mcp` run in a terminal should wait silently for input.
564
+ </details>
565
+
566
+ ## Configuration
567
+
568
+ The Python import namespace `studiowright`, `STUDIOWRIGHT_*` environment variables,
569
+ and existing data/output directories are retained for compatibility. No profile or
570
+ database migration is performed. The renamed plugin uses `GNMYFLOW_SOURCE` for
571
+ its source override; update any previous `STUDIOWRIGHT_SOURCE` plugin override.
572
+
573
+
574
+ | Variable | Meaning | Default |
575
+ | --- | --- | --- |
576
+ | `STUDIOWRIGHT_PROFILE` | Chrome profile to use | `default` |
577
+ | `STUDIOWRIGHT_HOME` | data directory: profiles, job database, incidents | `%LOCALAPPDATA%\studiowright` on Windows |
578
+ | `STUDIOWRIGHT_OUTPUT` | where files are saved | `Downloads/studiowright` |
579
+ | `STUDIOWRIGHT_GAP_S`, `STUDIOWRIGHT_JITTER_S` | pacing between submits | 20 s + 0-6 s |
580
+ | `STUDIOWRIGHT_COOLDOWN_S` | pause after an unusual-activity refusal | 1800 s |
581
+ | `STUDIOWRIGHT_UPSCALE_GAP_S`, `STUDIOWRIGHT_UPSCALE_JITTER_S` | spacing of upscaled downloads | 60 s + 0-20 s |
582
+ | `STUDIOWRIGHT_UPSCALE_COOLDOWN_S` | pause of upscales after a refusal | 3600 s |
583
+ | `STUDIOWRIGHT_SOURCE` | what the Claude Code plugin runs with `uvx --from` (e.g. a local checkout) | the plugin's own release |
584
+
585
+ ### Several Google accounts
586
+
587
+ Each profile is one signed-in Google account with its own Chrome, queue and credits.
588
+ Profiles run in parallel; jobs on the same profile take turns.
589
+
590
+ ```bash
591
+ sw --profile account-1 auth login # name it as you like; sign in, close the window
592
+ sw --profile account-1 image "…" # or set STUDIOWRIGHT_PROFILE=account-1
593
+ sw auth list # each profile and the account it is signed in to
594
+ sw auth rename account-1 studio # keeps the sign-in, projects and settings
595
+ sw auth logout studio # removes the profile from this computer
596
+ ```
597
+
598
+ `logout` deletes only the profile's Chrome data and GnmyFlow's records of it.
599
+ Downloaded files stay where they are, and the projects stay in your Google account.
600
+ Both `rename` and `logout` wait until that profile's Chrome is closed (`sw stop`).
601
+
602
+ ## Development
603
+
604
+ ```bash
605
+ uv sync
606
+ uv run pytest # unit tests, no browser needed
607
+ uv run ruff check src tests spikes
608
+ ```
609
+
610
+ ### Manual release procedure (no CI)
611
+
612
+ GitHub Actions is currently disabled because the account has a billing lock, not because
613
+ GitHub Free universally excludes Actions. The previous run stopped before any test step.
614
+ Local validation passed on macOS with Python 3.12; the Linux/Windows and Python 3.13
615
+ matrix has **not** been verified. Live provider/browser behavior is a separate check.
616
+
617
+ Both registered workflows have been disabled on GitHub. Templates are retained under
618
+ `.github/disabled-workflows/*.yml.disabled`, outside the active workflow directory.
619
+ Do not restore them or enable publication without a separate review. Trusted Publishing
620
+ OIDC is deferred; manual uploads use separate TestPyPI/PyPI API tokens.
621
+
622
+ The commands below assume macOS/Linux and a shell opened at the repository root. On
623
+ Windows, use equivalent PowerShell commands and venv executables under `Scripts/`.
624
+ Do not launch Chrome, run diagnostic spikes, sign in, or generate content for these checks.
625
+ Installing dependencies accesses package indexes; the smoke test itself blocks socket
626
+ connections and Chrome startup during the CLI checks. Package installation does not
627
+ install Google Chrome or configure MCP/the Claude Code plugin. No Playwright browser
628
+ binary installation is needed: this application attaches to separately installed Chrome.
629
+
630
+ #### 1. Review and freeze the source
631
+
632
+ Obtain explicit approval for the release version and source commit. Commit/push only
633
+ with permission; build from a clean reviewed checkout, not an uncommitted working tree.
634
+ Check that package, runtime, plugin and plugin source pin all agree on `0.3.1`:
635
+
636
+ ```bash
637
+ git status --short
638
+ git rev-parse HEAD
639
+ uv sync --locked
640
+ uv run --locked pytest
641
+ uv run --locked ruff check src tests spikes scripts
642
+ git diff --check
643
+ ```
644
+
645
+ Stop on any failure or unexpected working-tree change. Record the source commit, Python,
646
+ OS and uv versions outside the package. Do not claim untested platforms are supported.
647
+
648
+ #### 2. Build once, inspect and record hashes
649
+
650
+ Use a new temporary workspace so old artifacts are not mixed into the release. Keep this
651
+ shell open: later commands reuse `RELEASE_WORK` and `RELEASE_DIST`.
652
+
653
+ ```bash
654
+ RELEASE_WORK=$(mktemp -d "${TMPDIR:-/tmp}/gnmyflow-release.XXXXXX")
655
+ RELEASE_DIST="$RELEASE_WORK/dist"
656
+ uv build --out-dir "$RELEASE_DIST"
657
+ uv run --no-project python -c \
658
+ 'import runpy, sys; from pathlib import Path; runpy.run_path("scripts/audit_dist.py")["audit"](Path(sys.argv[1]))' \
659
+ "$RELEASE_DIST"
660
+ uvx --from twine twine check --strict "$RELEASE_DIST"/*
661
+ (cd "$RELEASE_DIST" && shasum -a 256 gnmyflow-0.3.1* > "$RELEASE_WORK/SHA256SUMS")
662
+ cat "$RELEASE_WORK/SHA256SUMS"
663
+ ```
664
+
665
+ The archive allowlist excludes profiles, session captures, incident screenshots, databases,
666
+ `.env`, caches, diagnostic spikes and media showcase files. Inspect the source as well:
667
+ an allowed Python file can still contain hard-coded secrets. Keep MIT copyright and
668
+ upstream author attribution intact. Freeze these exact archives; **do not rebuild between
669
+ TestPyPI and PyPI**. Any source/artifact change invalidates approval and requires revalidation.
670
+
671
+ #### 3. Clean installation of both local archives
672
+
673
+ ```bash
674
+ uv venv "$RELEASE_WORK/wheel" --python 3.12
675
+ uv pip install --python "$RELEASE_WORK/wheel/bin/python" "$RELEASE_DIST"/*.whl
676
+ "$RELEASE_WORK/wheel/bin/python" -I scripts/smoke_install.py
677
+ uv venv "$RELEASE_WORK/sdist" --python 3.12
678
+ uv pip install --python "$RELEASE_WORK/sdist/bin/python" "$RELEASE_DIST"/*.tar.gz
679
+ "$RELEASE_WORK/sdist/bin/python" -I scripts/smoke_install.py
680
+ ```
681
+
682
+ Both must pass metadata/import checks and offline help/version/capabilities for
683
+ `gnmyflow`, `sw` and `studiowright`. Dependency resolution here is fresh, rather than the
684
+ development lockfile. Do not install alongside upstream in the same environment: the
685
+ import namespace and legacy commands overlap. Additional tests on Linux/Windows and
686
+ Python 3.13 require their own machines/environments; these commands do not prove them.
687
+
688
+ #### 4. TestPyPI upload — explicit approval required
689
+
690
+ Create/manage the TestPyPI account and enable required account security (including 2FA)
691
+ through the official website. PyPI is a separate account/index. Create a TestPyPI API
692
+ token there; for the first upload a narrowly scoped project token may not yet be possible.
693
+ If an account-wide token is needed, revoke it after initial project creation and use a
694
+ project-scoped token thereafter. Never publish a placeholder to reserve the name.
695
+
696
+ Enter the token only into Twine's hidden terminal prompt (username `__token__` if asked).
697
+ Never put tokens in chat, commands/arguments, shell history, repository files or a plaintext
698
+ `.pypirc`. Avoid terminal/session recording while entering secrets. The following command
699
+ disables keyring lookup for that invocation without changing the user's global settings:
700
+
701
+ ```bash
702
+ (cd "$RELEASE_DIST" && shasum -a 256 -c "$RELEASE_WORK/SHA256SUMS")
703
+ PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring \
704
+ uvx --from twine twine upload \
705
+ --repository-url https://test.pypi.org/legacy/ "$RELEASE_DIST"/*
706
+ ```
707
+
708
+ Use an interactive terminal; do not use `--non-interactive` without configured credentials.
709
+ If credential environment overrides are already set, clear them in that shell before
710
+ upload so Twine prompts instead; never print their values. Do not use `--skip-existing`
711
+ to hide errors. For a partial upload, inspect the index, compare existing file hashes,
712
+ and upload only a missing file after review. Existing PyPI filenames cannot be overwritten.
713
+
714
+ #### 5. Verify the TestPyPI package, not a mixed-index substitute
715
+
716
+ TestPyPI may lack dependencies. Install the exact package only from TestPyPI with
717
+ `--no-deps`, then install dependencies separately from PyPI. Do not use a mixed
718
+ `--extra-index-url` install as evidence that the target package came from TestPyPI.
719
+
720
+ ```bash
721
+ uv venv "$RELEASE_WORK/testpypi" --python 3.12
722
+ uv pip install --python "$RELEASE_WORK/testpypi/bin/python" --no-deps \
723
+ --index-url https://test.pypi.org/simple/ 'gnmyflow==0.3.1'
724
+ uv pip install --python "$RELEASE_WORK/testpypi/bin/python" \
725
+ --index-url https://pypi.org/simple/ \
726
+ 'click>=8.5.0' 'filelock>=4.1.0' 'mcp[cli]>=2.3.0' 'pillow>=11.0' \
727
+ 'platformdirs>=4.12.4' 'playwright>=1.63.0'
728
+ "$RELEASE_WORK/testpypi/bin/python" -I scripts/smoke_install.py
729
+ ```
730
+
731
+ Review `https://test.pypi.org/pypi/gnmyflow/0.3.1/json`: compare the uploaded filenames
732
+ and `digests.sha256` with the frozen manifest. Read metadata/README/license on the project
733
+ page. TestPyPI success does not reserve the PyPI name or validate provider behavior.
734
+
735
+ #### 6. PyPI upload — separate explicit approval required
736
+
737
+ Confirm the name is still usable, source commit is unchanged, and TestPyPI verification
738
+ passed. Public HTTP errors are not evidence of name availability; a 404 is not a guarantee
739
+ that PyPI permits registration. Use a real release, not a placeholder. Configure 2FA and
740
+ an API token on **PyPI**, separate from TestPyPI; use project scope when available and
741
+ revoke any bootstrap account-wide token afterward.
742
+
743
+ ```bash
744
+ git status --short
745
+ git rev-parse HEAD
746
+ (cd "$RELEASE_DIST" && shasum -a 256 -c "$RELEASE_WORK/SHA256SUMS")
747
+ PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring \
748
+ uvx --from twine twine upload \
749
+ --repository-url https://upload.pypi.org/legacy/ "$RELEASE_DIST"/*
750
+ ```
751
+
752
+ This uploads the **same bytes** already tested. Do not rebuild. Verify filenames and
753
+ SHA-256 values at `https://pypi.org/pypi/gnmyflow/0.3.1/json`, then install from PyPI alone:
754
+
755
+ ```bash
756
+ uv venv "$RELEASE_WORK/pypi" --python 3.12
757
+ uv pip install --python "$RELEASE_WORK/pypi/bin/python" \
758
+ --index-url https://pypi.org/simple/ 'gnmyflow==0.3.1'
759
+ "$RELEASE_WORK/pypi/bin/python" -I scripts/smoke_install.py
760
+ ```
761
+
762
+ #### 7. Tag, GitHub release and updates
763
+
764
+ After verification and separate approval, create/push `v0.3.1` at the exact reviewed source
765
+ commit. Never move the existing `v0.3.0` tag. Create the GitHub release with the source
766
+ commit, artifact hashes, upstream attribution, compatibility details and platform/security
767
+ limitations. The plugin's pinned source becomes usable only once the tag exists. Update
768
+ the installation notice after publication; do not announce success prematurely.
769
+
770
+ Keep the archives and hash manifest as release evidence; remove only the disposable
771
+ venvs when no longer needed. Users may install with `uv tool install gnmyflow` or
772
+ `pipx install gnmyflow`, then update with `uv tool upgrade gnmyflow` or
773
+ `pipx upgrade gnmyflow`. VCS installations use an explicitly selected release tag.
774
+
775
+ For a bad release, stop publication, yank the affected version when appropriate, and
776
+ publish a new fixed version. PyPI releases cannot be overwritten; do not delete/reuse
777
+ filenames or move tags. A yanked version can still be installed with an explicit pin, so
778
+ publish a clear advisory. Reinstall a known-good version to roll back; preserve existing
779
+ profiles/databases. Revoke compromised tokens immediately and rotate them outside chat.
780
+
781
+ References: [Twine](https://twine.readthedocs.io/en/stable/),
782
+ [TestPyPI](https://packaging.python.org/en/latest/guides/using-testpypi/),
783
+ [Trusted Publishing (future option)](https://docs.pypi.org/trusted-publishers/).
784
+
785
+ - `src/studiowright/flow/` drives Flow, `src/studiowright/music/` drives Flow Music, and
786
+ `studio.py` adds locking, pacing, job records and downloads on top.
787
+ - `spikes/` holds the small scripts used to map each page. Most only open menus and read
788
+ replies, at no cost; their docstrings say when one spends credits.
789
+ - Tested on Windows 11. The code also finds Chrome on macOS and Linux, but those platforms
790
+ have not been tested yet.
791
+
792
+ ## Disclaimer
793
+
794
+ - GnmyFlow is not affiliated with, endorsed or sponsored by Google LLC. Google, Flow,
795
+ Flow Music, Veo, Nano Banana and Lyria are trademarks of Google LLC.
796
+ - The MIT license covers this code only. It grants no rights to Google's services or to
797
+ what they generate. Your use of Flow and Flow Music, and of their output, is governed by
798
+ Google's terms and your plan.
799
+ - Automated use of a web app may conflict with its terms. Use your own account, at your
800
+ own risk and at a human pace. GnmyFlow stops when Google asks it to.
801
+ - The web apps can change at any time and break GnmyFlow until it is updated.
802
+
803
+ ## License
804
+
805
+ [MIT](LICENSE) © 2026 Umut Koçak
806
+
807
+ ### Single-submit create contract (version 1)
808
+
809
+ `gnmyflow capabilities --json` is a local, read-only probe: it does not open Chrome, sign in,
810
+ access an account, or generate anything. Its `production_create_contract: 1` describes
811
+ **opt-in** `sw music create "SOUND" --single-submit` behavior (not legacy defaults):
812
+
813
+ - At most one Generate trigger per invocation, including when no start is observed.
814
+ - No heuristic library recovery by title or creation time after an ambiguous failure.
815
+ The original error is propagated; callers must not automatically retry paid creation.
816
+ - `--cover-prompt` is rejected because it can generate additional paid cover art.
817
+ A local `--cover` image is allowed. Refilling a failed compose panel before Generate
818
+ is still allowed.
819
+ - This is not an exactly-once server guarantee, cross-invocation idempotency, a credit
820
+ reservation, or an assertion that an ambiguous submission spent no credits.
821
+
822
+ The JSON fields are `production_create_contract` (integer), `single_submit` (boolean),
823
+ `heuristic_recovery` (boolean), `create_flag` (string), `models` and `formats` (arrays of
824
+ accepted strings), and `min_length` / `max_length` (inclusive seconds). Models, formats,
825
+ and length bounds come from request options. Existing create/edit/chat/batch/MCP callers
826
+ retain legacy behavior unless a `MusicRequest(single_submit=True)` is explicitly used.