@synthforgeio/cli
v0.1.0
Published
SynthForge IO command line: generate relational schemas from a description, generate multi-table synthetic datasets, and download them
Maintainers
Readme
SynthForge IO CLI
synthforge (alias sf) drives SynthForge IO from a terminal or a CI job: generate a relational schema from a plain-language description, generate a multi-table dataset from that schema, and download it as CSV, Parquet, JSON or JSONL.
npm install -g @synthforgeio/cli
synthforge --helpOne run, no install. The package ships two commands, so name the one you want:
npx -p @synthforgeio/cli synthforge whoamiNeeds Node 20 or newer.
Before you start: an API key
Every command talks to the API as you, so you need a personal API key. Create one in the app under Settings. The key is shown once, at creation. Treat it like a password.
Two ways to give the CLI that key. Pick either.
Put it in the environment. Nothing is written to disk, which is what you want in CI:
export SF_API_KEY='sfk_live_your_key'
synthforge whoamiOr sign in and let the CLI keep a key for you:
synthforge loginIt asks for your email and password (the password is not echoed on a terminal), then mints an API key and stores it. What it prints:
Logged in as [email protected]
API key sfk_live_abcd… stored at /home/you/.config/synthforge/credentials.jsonlogin names the key it mints cli, and before minting it revokes any active key of yours that already has that name. That is deliberate: signing in from five machines leaves you with one key, not five. Use --name to keep them apart if you want one per machine:
synthforge login --email [email protected] --name laptopThe full key is never printed again after that first line, and only its short prefix is shown.
The credentials file
~/.config/synthforge/credentials.jsonWritten with mode 0600 in a directory created with mode 0700, and it holds three fields:
{
"apiKey": "sfk_live_…",
"baseUrl": "https://api.synthforge.io",
"createdAt": "2026-09-13T10:04:11.812Z"
}synthforge logout deletes that file. It does not revoke the key on the server: the key keeps working until you revoke it in Settings, or until the next synthforge login under the same name replaces it. On a shared machine, do both.
Commands
Most commands print their result as JSON on stdout, so jq and friends work as you expect. Progress notes go to stderr, which keeps a pipeline clean.
synthforge whoami
Who the current credentials belong to. The quickest way to tell whether a key is live.
synthforge whoamisynthforge schemas list
Your schemas. --limit (default 50) and --offset page through them.
synthforge schemas list --limit 10synthforge schemas get <id>
One schema in full, including its tables, columns and foreign keys.
synthforge schemas get 5f3c1b7a-...synthforge schemas create --file <path>
Create a schema from a JSON file you already have. --name overrides the name in the file.
synthforge schemas create --file ./orders-schema.json --name "Orders demo"synthforge schemas generate -d "<description>"
Describe what you want in plain language and get a schema back. The command creates the job, waits for it, and prints the new schema id on stdout and nothing else, which is what makes it usable in a shell variable.
synthforge schemas generate -d "customers and orders, each customer places 1 to 5 orders" --name demo--name suggests a name for the result. --timeout-ms (default 600000, ten minutes) and --poll-ms (default 2000) control the wait.
synthforge generate --schema <id> --rows <pairs>
Generate a dataset. The command creates the job, waits for it to finish, and prints the id, the final status and the row count. Add -o and it downloads the artifact as well.
synthforge generate --schema 5f3c1b7a-... --rows customers=500,orders=2000 --format csv,parquet -o ./demo.zip| Option | Default | What it does |
|---|---|---|
| --schema <id> | required | which schema to generate from |
| --rows <pairs> | required | row counts as table=count, comma separated |
| --format <list> | csv | comma-separated export formats |
| --seed <n> | allocated for you | fixes the random seed, so the same run gives the same data |
| --name <name> | none | names the dataset |
| -o, --output <path> | none | download the finished artifact to this path |
| --timeout-ms <n> | 600000 | give up waiting after this long |
| --poll-ms <n> | 2000 | how often to check |
Leave --seed out and the API picks one and tells you, on stderr, so you can reproduce the run later. The artifact is a ZIP holding every format you asked for.
synthforge status <dataset-id>
Where a dataset job has got to. Useful when you did not wait for it.
synthforge status 8b21e4d9-...synthforge cancel <dataset-id>
Stop a job that is still running.
synthforge cancel 8b21e4d9-...synthforge download <dataset-id> -o <path>
Download a finished dataset. Prints the path it wrote.
synthforge download 8b21e4d9-... -o ./demo.zipEnvironment variables
| Variable | What it does |
|---|---|
| SF_API_KEY | the API key to use |
| SF_API_BASE | the API to talk to. Defaults to https://api.synthforge.io |
| SF_PASSWORD | password for synthforge login, so it never reaches your shell history or the process list |
| SYNTHFORGE_CONFIG_DIR | somewhere other than ~/.config/synthforge to keep credentials |
The key is resolved in this order, first hit wins:
SF_API_KEY- the credentials file written by
synthforge login
The base URL follows the same order: SF_API_BASE, then whatever login recorded, then https://api.synthforge.io. The two are resolved separately, so SF_API_BASE can point a stored key somewhere else without you logging in again.
When no key is found anywhere, the command stops before making a request and says so.
Scripting and CI
Non-interactive login, with the password out of argv:
export SF_PASSWORD="$MY_PASSWORD"
synthforge login --email [email protected]login will not prompt when both the email and a password are supplied, so it fails fast in a job rather than hanging on a prompt that nobody can answer. Better still in CI, skip login and set SF_API_KEY: nothing is written to disk and there is no key to clean up.
Schema to data in two lines, with jq nowhere in sight because schemas generate prints only the id:
SCHEMA_ID=$(synthforge schemas generate -d "customers and orders with a foreign key")
synthforge generate --schema "$SCHEMA_ID" --rows customers=500,orders=2000 -o ./data.zipEvery command exits 0 on success and 1 on failure, so set -e does the right thing. Failures print one line on stderr:
error: row_counts has no entry for table(s): line_items (invalid_config) [HTTP 400]The code in parentheses is stable enough to branch on. Waiting commands fail the same way when the job fails, is cancelled, or runs past --timeout-ms.
Reproducible fixtures, with the seed pinned so every run draws the same values:
synthforge generate --schema "$SCHEMA_ID" --rows customers=100 --seed 42 -o ./fixtures.zipHelp
- Guides and examples: synthforge.io/docs/cli
- Stuck, or found a bug: [email protected]. We read that inbox.
- Suspected vulnerability: [email protected], not a public tracker.
License
MIT
