Concepts
Mental model for gform: contracts, live metadata, execution, and artifacts.
Form contract
A form is a YAML file (name, url, options, steps) validated by Zod. Unknown keys are rejected.
- Library home:
$GFORM_HOME/forms/<name>.yaml - Optional cwd override:
./forms/when that directory exists - Scaffold:
gform init· edit:gform edit· check:gform verify
The contract is portable — not tied to one machine’s hard-coded script. Full field map: schema.
Resolved configuration
What actually runs is the merged result of:
- Explicit CLI flags (highest)
- Named
--policy(safe/demo/load/watch) - Form YAML
optionsstarters (headed,repeat, …) - Engine defaults
Run intro shows provenance so you can see which layer set headed/repeat/concurrency. Details: policies · commands/run.
Live form metadata
gform inspect (and the preflight on run unless --skip-inspect) reads public Forms HTML / FB_PUBLIC_LOAD_DATA_:
- title, questions, types, options, required
- risk flags: login wall, closed, already-responded, captcha, file upload, collect-email, …
- branching / grid metadata (fill still follows your YAML
nextsteps)
Authenticated inspect (--auth / --profile) is available when the public fetch is gated (common for file-upload forms). See inspect & verify.
Steps
Ordered actions: text, radio, checkbox, select, file, next, back, submit, pause, wait.
Questions match by exact visible labels (as inspected). File steps use nested source: — file-uploads.
Pools and variant modes
Pools supply alternate answers across --repeat runs.
options.variantMode | Behavior |
|---|---|
rotate (default) | pool[i % length] by run index |
random | Pick randomly each run |
zip | Shared index across equal-length pools (persona rows) |
Without pools, --repeat + --skip-duplicates mostly skips identical fingerprints. See repeat.
Repeat and concurrency
--repeat N/-r— N child runs (batch parent +r01… children)--concurrency N/-C— parallel workers, capped at 16 and atrepeat--delay/-D— pause between sequential runs only (-C 1; ignored when parallel). Formats:500ms,2s,1m; bare number = seconds--continue-on-error— keep going after child failures
Live TTY shows a worker panel; --no-live / -nl is print-and-go.
Uniqueness and fingerprints
| Mechanism | Scope |
|---|---|
--once | Skip if this payload fingerprint already succeeded |
--skip-duplicates / -sd | Same, across a repeat wave using resolved answers |
options.unique | Field-level: scope: history or batch on named questions |
Skipped runs still get a small manifest (status: skipped) so history and unique ledgers stay honest.
Policies
Named presets that only set run behavior — they do not rewrite YAML answers:
| Policy | Intent |
|---|---|
safe | 1× headless, once + skip-duplicates + strict |
demo | 1× headed (watch / assist) |
load | 10× headless, concurrency 5, skip-duplicates, continue-on-error |
watch | 1× headed + keep-open |
List: gform policies (pol).
Auth profiles
Optional Google sessions under $GFORM_HOME/auth/profiles/<name>/. Opt-in per run with --auth / -A or --profile / -pr. No flag → no session loaded. auth.
Browser targets
Playwright launches a discovered Chromium-family browser. Prefer system Chrome for auth create. Preference: $GFORM_HOME/browsers/default.json. browsers.
Artifacts
Every run/batch gets a 16-hex id under $GFORM_HOME/artifacts/:
manifest.json/batch.jsonconfig.yamlsnapshotrun.log, screenshots, optionalpw-trace.zip,content/- Stable
reason+messageon finish/skip
Browse: gform run list · run view · run last. Restore YAML: gform restore. artifacts.
Batches and child runs
A batch is a parent directory with child rNN/ runs. List labels single vs batch. Parent rollup shows ✓ / ✗ / ⊘ / ■ counts; child statuses and reasons stay inspectable.
Human assist
Soft walls (CAPTCHA, login, validation, some file UIs, pause) need headed + TTY. You complete the hard bit in the window, press Enter, continue. Headless never pauses — it fails with a tip (assist-headed-required, …). Disable pauses with --no-assist / -na. limitations.
Core SDK
The same engine powers the CLI:
import { /* … */ } from "@zamdevio/gform-core";
// or from the root package:
import { /* … */ } from "gform/core";Host supplies adapters; CLI stays thin. architecture.