A CLI with SQL built in, and an agent that can drive it
The in-app CLI runs SQL over the JSON that commands return, scripts with loops and captured variables, and doubles as the tool the agent uses to operate the platform.
Etlworks has a command line that lives inside the application. Nothing to install, no credentials to manage, and every command runs with the permissions of whoever is signed in.
That last detail is what makes the rest of this post possible.
SQL over command output
Most CLI commands return JSON, and the CLI ships a small SQL engine that runs over that JSON in the same statement:
list-flows select id,name where flowType like '%cdc%' order by name
You get SELECT with aliases and scalar functions, DISTINCT, WHERE with a real expression language, ORDER BY, LIMIT, and FROM for flattening nested arrays. JSON paths work throughout, including array indexes like items[0].value, and they are case-insensitive.
Flattening is where it earns its keep. Execution history comes back nested, so pull the array out and keep the parent fields you need:
flow-executions-aggregated offset=5
select parent.id, parent.name, started, ended
from executions
where started > ts("now - 10d")
order by ended desc
limit 5
ts() parses relative time, so "everything that started in the last ten days" is a where clause instead of a date calculation.
Worth stealing
Flows that have been running longer than an hour:
list-running-flows select flowId,flowName,tenantId,date(started) as started
where started < ts("now - 1h")
Every failure across every tenant, newest first:
flow-executions-aggregated tenants=all offset=1 includeMetrics=true
select flowId, eventName, tenantId, status, user, exception
from executions
where status != "success"
order by auditId desc
Swap the ordering for order by records desc limit 10 and you have the ten heaviest runs instead.
Capture, loop, redirect
Pipe a result into a variable and iterate over it. Bulk-disable every enabled schedule matching a substring:
list-schedules scope=all where name like "%maintenance%" and enabled = true | capture schedules=stdout;
for-each (s in schedules) {
disable-schedule {s.id} --force;
};
Back up matching flows, one file each:
list-flows --silent where name like "%script%" | capture flows=stdout;
for-each (f in flows) {
get-flow {f.id} >> backup/{f.name}.json;
};
Three lines that would otherwise be a script somebody has to maintain.
The same engine over HTTP
The entire CLI is reachable through a single API endpoint. Same commands, same SQL, same permission model, callable from CI/CD, Jenkins, GitHub Actions, or an Airflow DAG when Etlworks is one step inside someone else's graph.
Why the agent cares
Simba, our AI agent, has no special back door into the platform. It has the CLI. Two of its tools are host_list_commands, which discovers what commands exist, and host_cli, which runs one.
Ask it what failed recently and it composes the same statement you would have typed, runs it, and reads the JSON back.
Three consequences worth stating plainly. The agent can only do what the CLI can do, which is a far smaller surface than arbitrary access. It runs as you, so it cannot reach anything your account cannot. And state-changing actions require your explicit approval before they execute.
Every command it runs lands in history with a timestamp, so you can read exactly what it did rather than trusting a summary of it.
Any MCP client, same path
Both tools are exposed through the MCP server at POST /rest/v1/ai-agent/mcp, so Claude Desktop, Claude Code, Cursor, and Windsurf reach the CLI the way Simba does. Same tool catalog, same API key, same permission model. A request arriving over MCP resolves the same user as one typed into the console.
host_cli is the one tool we mark with destructiveHint, since it can run any command. Clients that honor MCP annotations prompt before calling it instead of firing it silently.
If that is still more surface than you want exposed, one property removes it:
ai.mcp.expose.host_cli=false
The tool then disappears from tools/list, and tools/call answers "Tool not found". The console keeps working; only the MCP path loses it.
Not the other CLI
This is the CLI inside the application, for metadata, flows, and data. A separate external command-line tool lives on the host and handles system administration. Different tool, different job.
Reference: CLI documentation.