falg
A small CLI for generating and editing images on fal.ai. A JSON config sets
up a prompt template, its variables and the models. falg runs every combination in parallel
and downloads the results. Each output gets a .json sidecar describing how it was made, and
each run gets an HTML gallery for comparing the results side by side.
falg run examples/styles.json --only subject=fox --dry-run # preview prompts and cost
falg run examples/styles.json --only subject=fox -y # 2 styles x 3 models, in parallel
open examples/generations/*-styles/index.html # compare the results
Unofficial. falg is a community tool, not affiliated with or endorsed by fal.ai. It uses fal's public queue API with your own API key; generations are billed to your fal account.
Why
falg started in a DTF (direct-to-film) T-shirt printing project. We needed many design
variations: several styles and models, compared side by side, then turned into
print-ready PNGs. Beyond that, the goal is to automate image generation for other projects
too. The same config can be rerun later, each image records how it was made, and scripts
or AI agents can drive it without prompts. cutout is the only print-specific part.
Similar projects
Other tools take different approaches; one of them may suit you better:
- fal CLI (official):
fal apicalls any endpoint from the terminal. It's mainly for deploying your own apps on fal. - falcon: a polished single-image workflow with a terminal UI, presets, upscaling and background removal.
- fal-cli: batches and spending limits, and an MCP server for AI assistants.
- falgen: a conversational tool that picks a model for you, covering images, video and audio.
- gen: a minimal Go CLI for generating and editing.
- llm-fal: fal models inside Simon Willison's
llm.
falg focuses on config-driven batches: prompt × model grids, a sidecar per output, and a comparison gallery. Some of its planned ideas come from these projects (see IDEAS.md).
Install
Requires Python 3.10+.
pipx install 'falg[cutout]'
# or: uv tool install 'falg[cutout]'
To run the latest unreleased version, install from GitHub:
pipx install 'falg[cutout] @ git+https://github.com/lemmon/falg.git'.
The fal part is standard library only. The optional [cutout] extra adds numpy, Pillow and
SciPy for falg cutout; leave it out if you only generate.
For development:
git clone https://github.com/lemmon/falg.git && cd falg
python3 -m venv .venv && .venv/bin/pip install -e '.[cutout]'
ln -sf "$PWD/.venv/bin/falg" ~/.local/bin/falg # optional: put the command on PATH
.venv/bin/python -m unittest discover -s tests # run the tests (no network, no spending)
API key: save it once for all projects:
falg auth # asks for the key with hidden input; keeps it out of shell history
falg auth fal_xxx # or pass it directly (it lands in shell history)
pbpaste | falg auth - # or from stdin / the clipboard
falg auth --status # which key is active here, and from where (masked)
falg auth --remove
The key is stored in ~/.config/falg/.env with mode 600. falg uses the first key it finds:
FAL_KEYin the environment.FAL_KEY=...in a.envin the current directory.- A
.envin the config file's directory. - The global
~/.config/falg/.env.
So a project can still use its own key.
Commands
falg run job.json [--only axis=a,b] [--set var=text] [-m models] [-n 2] [-a 3:2] [--dry-run] [-y]
falg gen "a prompt" [-m nano-banana-2.1 gpt-image-2.5] [-n 2] [-a 3:2] [-p seed:=7 quality=low]
falg edit photo.png "make the cat smile" [-m nano-banana-2.1] [-i extra-ref.png]
falg call any/fal/endpoint prompt="..." num_images:=2 [--args body.json] [--file image_url=a.png]
falg auth # save the API key globally (see Install)
falg models # the registry: names, endpoints, price estimates (* = default)
falg catalog # refresh MODELS.md: dated list of recent models + upgrade check
falg search "flux" # fal's catalog: endpoint ids, category, release date
falg schema google/nano-banana-2.1 # an endpoint's input parameters, types, defaults
falg cutout art.png -o prints/ [--bg black] [--fit 21 14] [--ink "#FF2D95"]
Options shared by run, gen and edit:
--dry-runprints the prompts and cost and sends nothing. With--jsonit prints the plan as JSON: prompts, request count, estimated cost and an example request.- By default falg asks before spending; without a terminal it refuses.
-yapproves any cost.--max-cost 2approves runs estimated up to $2 and aborts above that, which suits scripts and agents. A model without apricecan't be checked against a budget, so--max-costrefuses it; use-yto run it anyway. - When
ntakes several requests (models withoutbatch, or more thanmax_batchimages), a fixedseedinparamsis offset per request by the number of images before it (seed 7 with-n 3on a single-image model gives 7, 8, 9; with batches of 4 it gives 7, 11), so you don't pay for the same image twice. The sidecars record the seed that was sent. - Ctrl+C sends nothing more: queued jobs are cancelled, and so are jobs waiting to retry after a rate limit. It waits for the requests already sent, which fal has already billed. A second Ctrl+C stops waiting. Every request ID is recorded in
run.jsonat submission, so results can still be recovered. --jsonprints the output files and sidecars as JSON, for scripts and agents. Progress goes to stderr.- The exit code is 0 only if every request succeeded. If any failed it is 1, and
--jsonreports how many underfailed. -o DIRsets the parent output folder. The default isgenerations/next to the config, or in the current directory forgen.
call passes values like fal api does: k=v sends a string and k:=json sends a number, bool, list or object, e.g. image_size:='{"width":1024,"height":1024}'. --file KEY=PATH sends a local file as a data URI; repeat it for several files. call sends no cost question because the endpoint may not be in the registry.
Output
generations/20261105-120000-chest-logos/
sloth-firefighter__riso__gpt-image-2.5__01.png
sloth-firefighter__riso__gpt-image-2.5__01.json <- sidecar
run.json config snapshot + every request (ids, args, errors)
index.html grid: prompts (rows) x models (columns); toggle grey/white/black background
Files are named <axis option names>__<model>__<nn>.
Each sidecar holds:
fileandcreated;seconds, the request time;model,mode(generate or edit) andendpoint;vars(axis → option) and the finalprompt;sources, the input images for edits;seedandrequest_id;width,height,content_typeandurl(fal CDN);price_estimate;- the exact
argssent (data URIs elided); - the rest of the model
response; runandconfig.
cutout writes its own sidecar with the measurements and settings. If the source image has a falg sidecar, cutout embeds it as source_sidecar, so a print file keeps its prompt history.
Job config
{
"name": "chest-logos",
"models": ["gpt-image-2.5", "nano-banana-2.1"],
"n": 1,
"aspect": "1:1",
"template": ["{subject}. Style: {style}", "{background} {rules}"],
"vars": {
"subject": { "fox": "A cheerful red fox", "owl": "A tiny owl astronaut" },
"style": {
"riso": "Retro risograph print, coarse halftone dots.",
"ink": {
"text": "Bold ink line art.",
"set": { "background": "On pure black." }
}
},
"background": "On a plain pure white background.",
"rules": ["No text.", "No frame, no border."]
},
"params": {
"*": { "output_format": "png" },
"gpt-image-2.5": { "quality": "medium" }
},
"model_defs": {},
"images": [],
"out": "generations",
"gallery_background": "#888"
}
vars builds the prompt from the template:
- A string is a constant. A list of strings is a constant too, joined with spaces, so long text stays readable.
- An object is an axis of named options. Every combination of axes becomes one prompt; the example above gives 2 subjects × 2 styles = 4 prompts.
- An option can be
{"text": ..., "set": {var: value}}. That overrides other variables for that option only, asinkdoes for the background above. {var}works inside variables too, nested. An unresolved{name}(usually a typo) stops the run before anything is sent;--dry-runshows it. A plainpromptwith novarsortemplatemay contain braces.- Use
--only style=risoto pick a subset, and--set background="..."to override a constant (or--set prompt="..."for a single-prompt config).--setstops with an error for an unknown name or an axis, rather than running without the override.
Other fields:
prompt/prompts: shortcuts with no template.promptscan be a list or a{name: text}object.template: a string, or a list of strings joined with spaces. Default:{prompt}.aspect: each model gets it in its own form (see the registry below).params: raw request arguments merged last.*applies to every model; a model name applies to that model only.images: input images, as paths relative to the config, URLs, or data URIs. Any config withimagesuses the models' edit endpoints.model_defs: add or override models for this config only.
Fields starting with _ are ignored at any level (top level, vars, axis options, prompts, params), so you can leave notes in "_about". See examples/.
Which models exist right now: MODELS.md
MODELS.md is a dated snapshot of fal's catalog. It lists the text-to-image and image-to-image models released in the last 180 days, newest first, with prices from fal's pricing API. Its "Registry check" table flags newer releases in the same family as each registry model. (On 2026-11-05 it caught GPT Image 2.6, released the day before.)
falg catalog # refresh MODELS.md (uses your key; takes ~10 s)
falg catalog --days 60 --categories text-to-image text-to-video image-to-video
Model names go stale fast, so check the file's date before choosing a model, and refresh it
if it is over 30 days old. falg models prints how old the snapshot is. AGENTS.md (also
linked as CLAUDE.md) tells AI assistants the same.
Model registry
src/falg/models.json gives each model a generate and/or edit spec:
endpointandprice: USD per image, a rough guess. Many endpoints bill per megapixel, resolution or quality; the estimate is only meant to flag a run that costs much more than you expected;default, which puts the model in the default set when-mandmodelsare not given;batch, which sends one request withnum_imagesinstead of n separate requests;max_batch, the endpoint'snum_imagesmaximum (seefalg schema). A larger n is split into several requests. Without it, falg assumes 4;params, the default request arguments. Overrides merge key by key, so"params": {"seed": 7}keeps the built-in params; set a key tonullto remove it (this works in a config'sparamsand in-p key:=nulltoo);size, how the aspect ratio is sent:px:image_size {width, height}, computed from the aspect andpixels, rounded to a multiple of 16. For edits with aspectauto, the aspect comes from the first input image (a local file); for a URL, falg leaves the size to the endpoint.aspect: anaspect_ratiostring.enum: a size fromenum_map, keyed by aspect: a preset name, or{width, height}for endpoints that accept only listed sizes. Other aspects are an error before anything is sent.none: no size argument.
To add a new model, check its inputs with falg schema <endpoint>. Then add it to ~/.config/falg/models.json (merged over the built-ins), or to a config's model_defs:
{
"my-model": {
"note": "...",
"generate": {
"endpoint": "vendor/model/text-to-image",
"price": 0.04,
"size": "aspect",
"batch": true,
"max_batch": 4,
"params": { "output_format": "png" }
}
}
}
cutout
This removes a plain white background (art drawn on white) or a black one (light art on black) and writes a print-ready PNG. It is the Photoshop "image as its own mask + Levels" technique, plus a defringe step:
- Alpha: hard by default, 0 or 255 only.
- Defringe: pixels along the cut edge take the colour of the ink next to them, so lines get no light halo. Solid areas keep their colour, pale ones too.
- Size: crops to the artwork. Optionally fits it into
--fit W Hcm at--ppi, and adds the size to the file name. - One ink:
--ink "#RRGGBB"makes everything that prints a single colour. - Checks: reports coverage and how much ink sits in marks thinner than
--min-line-mm. - Previews: the design on a fabric colour, plus a 100% detail crop on the opposite tone, where any fringe shows.
--no-previewskips them.
Run falg cutout -h for the full explanation and tuning flags (--low, --high, --soft, --min-speck).
Prompting tips
What has worked in practice, especially for artwork meant for print or cutouts:
- Generate on plain white or black and remove the background afterwards (
falg cutout). Don't ask the generator for transparency. - Ask for the subject to fill about 90% of the frame. For scenes, ask for a free-standing vignette with no background.
- Don't mention the end product (e.g. "T-shirt", "sticker") in prompts, or some models draw a mockup of it.
- For expression edits, describe the whole new expression ("calm, content, gently smiling, relaxed half-closed eyes"), not just "make it smile". FLUX edits sometimes drop characters; Nano Banana keeps scenes intact best.
Metadata
Release files for falg 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| falg-0.1.0.tar.gz | 57.9 kB | With provenance | Details for falg-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| falg-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for falg-0.1.0-py3-none-any.whl |
Total release size: 100.5 kB
Release files / falg-0.1.0.tar.gz
| Download URL | falg-0.1.0.tar.gz |
|---|---|
| Size | 57.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d0af7bd70bc5d51720f135a28ae7f105e01354d5a314c62b21e3cdb08fc6a903
|
|
BLAKE2b-256 checksum How to use checksums |
201c89e2927f3d805fe940cbbd38db8c1fa6253bbef49b1ce5a8446bfc4e4800
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logRelease files / falg-0.1.0-py3-none-any.whl
| Download URL | falg-0.1.0-py3-none-any.whl |
|---|---|
| Size | 42.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3bd948eb6b4c786249f88ee14e37093607ba197056d36219032349da0a310c1e
|
|
BLAKE2b-256 checksum How to use checksums |
d3821cfe70c18bf31d85acae0cbbff397d9bb03438194a38bd0f655e7566e128
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logSecurity for falg 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for falg-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.