CLI

Quickstart

The Etlworks CLI is built into the platform — no install, no credential management. Open it, write a statement, hit Ctrl+Enter. Same engine drives POST /v1/cli for automation.

Open the CLI

Sign in to Etlworks. Open CLI from the main navigation panel. The console runs entirely inside the platform under your user identity — no separate auth, no API key to manage.

For programmatic access, skip ahead

If you're driving the CLI from a script, CI job, or external orchestrator, the same commands run via POST /v1/cli. See Driving from REST below — everything else on this page applies identically.

The interface

Keyboard shortcuts

ActionShortcut
Run commandCtrl + Enter (or Ctrl + F2)
Open command paletteCtrl + Shift + O
Open historyCtrl + Shift + H
Clear outputCtrl + L
Find & replaceCmd + Option + F
Go to lineAlt + G
Undo / RedoCmd + Z / Shift + Cmd + Z
Close dropdownEsc

Your first script

Confirm identity, list flows tagged production, and dump one flow's configuration to a file:

Etlworks CLI
whoami;
list-flows where tags = "production";
get-flow 12345 >> backup/flow-12345.json;

Hit Ctrl + Enter. Each statement runs in order; output panel shows three blocks — user info, the filtered flow list, and a confirmation that the file was written under the tenant's data folder.


Statements & comments

The CLI is a statement-based execution engine. A script is one or more statements; each ends with ; when there's more than one. Comments use // for single-line and /* … */ for multi-line.

Etlworks CLI
// Single statement, no semicolon needed
list-connections

// Multiple statements — semicolons required between them
list-connections; list-flows;

/*
  Multi-line comment.
  Useful for documenting longer scripts.
*/
list-tenants;

Parameters & quoting

Every command follows the same shape:

Etlworks CLI
command-name positional1 'positional2 with spaces'
             param1=value param2='value with spaces'
             --modifier1 --modifier2
             select … from … where … order by … limit …
             | capture var=stdout.field
             & >> relative/path/name.json;

Positional args come first, named key=value args follow in any order. Wrap any value containing spaces, special characters, or JSON in quotes — single or double, both work, and inner quotes can be escaped:

Etlworks CLI
print '{ "id": 10, "name": "Sample" }';
print '{ "message": "He said \"hello\"" }';
print "{ 'path': 'c:\\\\data\\\\file.txt' }";

Modifiers

ModifierEffect
--forceAllow destructive operations (delete-flow, delete-tenant, flows-suspend, …) without an interactive confirm.
--silentSuppress normal output. Errors still surface.
--silent-if-emptySuppress output only if stdout is null or an empty array. Errors still surface.
--stop-on-errorStop the entire script (or loop) on the first failure.

Modifiers go after the command name and before any SQL or capture clause:

Etlworks CLI
list-flows --silent select id where flowType like '%cdc%' | capture cdc=stdout;

Capture variables

Pipe a command's JSON output into a variable, then reference it later as {name}. Capture works on whole results, single fields, or arrays.

Etlworks CLI
// Single field
get-connection 120 | capture connId=stdout.id;

// Multiple fields in one capture
get-connection 120 | capture id=stdout.id name=stdout.connection.name;

// Capture an entire array — then iterate
list-tenants | capture tenants=stdout;
for-each (t in tenants) {
  print 't.name';
};

// Use captured values inside JSON
print '{"id":"{id}","name":"{name}"}';

Control structures

if and for-each are built into the language.

Etlworks CLI
// if — full boolean expressions, comparisons, JSON-path access
if ({connId} == 100) {
  print 'Matched';
};

// for-each over arrays
for-each (tenant in tenants) {
  print 'tenant.email';
};

// Nested loops + per-iteration commands
for-each --stop-on-error (agent in agents) {
  update-agent {agent.id} --force;
};

Loops continue on per-iteration errors by default (consistent with the script-wide default). Add --stop-on-error on the loop itself to halt on the first failure.

Parallel execution

Append & to run a command asynchronously — the script keeps going without waiting:

Etlworks CLI
// Fire and forget
run-flow 100 &;

// With timeout
run-flow 200 timeout=15000 &;

// Combined with redirect
get-flow 100 & >> f100.json

Default cap is 10 parallel commands per request (server-side cli.max.parallel).

Redirecting output

Append >> filename at the end of any statement to save its stdout to a file under the tenant's application data directory. Subfolders are auto-created. Absolute paths are not allowed.

Etlworks CLI
// Save all flows
list-flows >> flows.json

// Subfolder
list-connections >> exports/conn-list.json

// Filename from a captured variable
list-flows --silent where name like "%script%" | capture flows=stdout;
for-each (f in flows) {
  get-flow {f.id} >> backup/{f.name}.json;
};

// Combined with inline SQL
list-flows select id,name where flowType like '%cdc%' >> cdc.json

Inline SQL on JSON output

Most commands return JSON. The CLI ships a compact SQL engine that operates directly on that JSON — SELECT, FROM (array flattening), WHERE, ORDER BY, LIMIT, plus scalar & time-helper functions.

Etlworks CLI
list-connections scope=all
  select id, name, connectionType, date(created) as created
  where connectionType like '%mysql%'
  order by created desc
  limit 10;

flow-executions-aggregated offset=5
  select flowId, auditId, started, ended, status
  from executions
  where status != 'success'
    and started > ts("now - 10d")
  order by ended desc
  limit 10;

Time helpers like ts("now - 1d"), addDays(ts, n), parseDate(text, pattern), plus string/aggregate utilities are documented in the SQL reference. The full SQL grammar comes from the Built-in SQL in CLI support article — we mirror the highlights, that page has the exhaustive function list.


Exit codes & output

Each statement returns a CliResponse:

FieldMeaning
commandThe statement that ran.
stdoutResult of the command — JSON for read commands, plain text for status/info, SQL-transformed when an SQL clause was present.
stderrErrors, warnings, prompts.
exitCode0 on success, non-zero on error.

The REST endpoint returns an array of these — one per statement.

Confirmation for destructive operations

Roughly 50 commands require explicit confirmation: every delete-*, every edit-*, flows-suspend, flows-resume, run-flow, agent lifecycle commands. Pass --force to confirm inline; without it, interactive runs prompt and scripted runs return exitCode=1 with a "confirmation required" message.

Etlworks CLI
// In a script:
delete-flow 12345 --force;

// Bulk:
list-flows where name like '%test cdc%' | capture flows=stdout;
for-each (f in flows) {
  delete-flow {f.id} deleteSchedules=true --force;
};

Roles & permissions

Commands run as the calling user. Each command requires at least a particular role; the CLI inherits your role and tenant scope — same boundary as the UI and REST API.

RoleCan doExamples
ViewerRead-only across the platformlist-flows, get-connection, flow-executions
OperatorRun flows + readrun-flow, run-flow-by-name, stop-flow
EditorCRUD on flows, connections, schedules …add-flow, edit-connection, delete-schedule
AdminTenant-level config + user managementadd-user, edit-tenant, flows-suspend
Super AdminCross-tenant + system-leveladd-tenant, set-agent-max-ram, list-app-releases

Driving from REST

Same engine, same commands, programmatic. POST /v1/cli takes a CliRequest with the script as a single string and an optional variable map. Returns an array of CliResponse objects — one per statement.

Shell
curl -s -X POST "$ETLWORKS_URL/v1/cli" \
  -H "Authorization: Bearer $ETLWORKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "command": "list-flows --silent where tags = \"production\" | capture flows=stdout; for-each (f in flows) { get-flow {f.id} >> backup/{f.name}.json; };",
    "vars": {}
  }'
# → [
#   { "command": "list-flows ...", "stdout": "...", "stderr": "", "exitCode": 0 },
#   { "command": "get-flow 12345 >> backup/...", "stdout": "...", "stderr": "", "exitCode": 0 },
#   ...
# ]

The vars map is the REST equivalent of CLI {var} interpolation — the script references {flowName}, the request supplies "vars": {"flowName": "Stripe to Snowflake"}. Auth is the same Bearer token used by the REST API: an API key (long-lived) or a JWT (mint via POST /v1/token-auth).


You're set

From here: