Skip to main content

CLI Reference

Complete reference for the FlowForger command-line interface (flowforger, installed from npm, or node packages/cli/dist/index.js when running from source).

Global behaviour​

  • --config <file> — Path to flowforger.config.json (connection references, generator/parser/parity options, and the auth section). Some commands also pick up flowforger.config.json from the working directory without the flag; Connection Configuration lists which.

  • --config-env <name> — Environment name to overlay from the config's environments block.

  • flowforger --version / -v / flowforger version — Print the installed CLI version.

  • flowforger --help / -h / help — Print the built-in help. Running flowforger with no command, or an unrecognized one, prints it too.

  • Options take two dashes. A misspelled option (--stop-befor) or a single-dash one (-o) is rejected with a suggestion and exit code 2, instead of being silently ignored:

    Unknown option '-o' (did you mean '--out'?) — options take two dashes
    Run 'flowforger --help' for the option list.

    An option that belongs to a different command is accepted and ignored.

Exit codes​

CodeMeaning
0Success: the run Succeeded, validation found no errors, diff found the flows identical.
1The command ran but the result is a failure: a run that did not succeed, validation errors, differences found by diff or parity, push --require-active on a flow that is not Activated, or an unexpected error.
2Usage error: unknown option, missing or malformed argument, a flow that cannot be resolved (no match, ambiguous name), a refused push --expect-clean, or a Power Automate API error.
3run stopped before an action on purpose (--stop-before, the --live guard, or --replay-run safe mode). The run is incomplete, not failed.

compile​

Compile DSL (.ff.ts) or Flow IR (.ir.json) to Flow IR or Logic Apps JSON.

  • Input is auto-detected by extension: .ts/.ff.ts is treated as DSL, anything else as IR JSON.
  • DSL input defaults to emitting IR (flow.ir.json) unless --emit logicapps is passed, in which case the default output is clientdata.json.
  • IR JSON input always emits Logic Apps JSON (default output clientdata.json).
  • A DSL file with TypeScript syntax errors is refused, with each error listed as file:line:col.

Flags:

  • --out <file> — Output file path.
  • --emit logicapps — Only meaningful for .ff.ts input: emit Logic Apps JSON directly instead of IR.
  • --config <file> — Connection reference config, used when emitting Logic Apps JSON. compile reads a config only when this flag is given.
  • --config-env <name> — Environment override within --config.
# DSL -> IR
flowforger compile examples/hello-flow/flow.ff.ts --out flow.ir.json

# DSL -> Logic Apps JSON directly
flowforger compile examples/hello-flow/flow.ff.ts --out clientdata.json --emit logicapps

# IR -> Logic Apps JSON
flowforger compile flow.ir.json --out clientdata.json

# With connection config and environment
flowforger compile flow.ir.json --out clientdata.json --config flowforger.config.json --config-env dev

validate​

Validate a DSL file, Flow IR, or Logic Apps JSON. Format is auto-detected from the file extension/content.

  • .ts/.ff.ts — Runs the same DSL diagnostics as the web editor and the VS Code extension, one line per finding (file:line:col SEVERITY [CODE] message). When the DSL has no errors, the file is also compiled to IR and the IR validator runs on it, so rules that are only checked on the IR (nesting depth, expression length, runAfter reachability, ...) are reported too, including info-level hints. Codes already covered by a DSL diagnostic are not reported twice.
  • .json — Auto-detects IR (nodes present) vs Logic Apps JSON, runs the matching checks, and prints the result as JSON: { "ok": true|false, "issues": [{ "level", "code", "message", "path" }] }.

Exits 0 when there are no errors (warnings and info are allowed), 1 otherwise. See Validation and diagnostics for what each code means.

flowforger validate examples/hello-flow/flow.ff.ts
flowforger validate flow.ir.json
flowforger validate clientdata.json

run​

Execute a flow locally with the FlowForger engine. Accepts .ff.ts (compiled on the fly) or .ir.json.

Output:

  • On an interactive terminal, run prints a human-readable execution trace: one line per action with its status (✓ succeeded, ✗ failed, ↷ skipped, ⊘ cancelled) and a one-line summary of its outputs. Conditions show the evaluated result and branch taken; loops show every iteration, labelled by the item's title-like field, with its nested actions. A footer reports the overall result and action count.
  • When stdout is piped or redirected (scripts, CI, jq), the full run result is printed as JSON: { status, trace, artifacts?, response?, stopped?, stoppedBefore?, replayedRun? }. In the trace, a scope/condition/switch entry comes before its body, which is nested under its children; a loop's body is under iterations[].actions.
  • --pretty / --json — Force either mode regardless of where stdout goes.
  • --verbose — Also print sign-in progress, connector request/response logs in pretty mode (in JSON mode they always go to stderr), and the values of secret-looking environment variables, which are otherwise redacted.
  • Exit code: 0 when the run Succeeded, 1 when it Failed or was Cancelled, 3 when it was stopped before an action (see below).

Input/payload:

  • --in <file> — JSON payload for the trigger. It becomes the trigger body: triggerBody() is the payload, and triggerOutputs() is { headers, body }. A file that already has the outputs shape (a body plus only headers/queries/statusCode/relativePathParameters) is used as is, so you can pass request headers too.
  • --in-content-type <type> — Treat --in as a raw request body of this content type (for example application/x-www-form-urlencoded or multipart/form-data; boundary=...), stored the way the cloud's Request trigger stores it. Requires --in.
  • --vars <file> — JSON file of initial variable values.
  • --var name=value — Set an individual variable (repeatable; value is parsed as JSON if possible, else used as a string).
  • --param name=value — Override a workflow parameter (repeatable; same JSON-or-string parsing as --var).
  • --artifacts-dir <dir> — Directory to write files produced by ctx.saveFile() / the @@ff:saveFile compose convention (default: ff-artifacts).

Environment variables: when a Dataverse connection is available (--dv-url/--dv-token, or --auth with auth.resources.dataverse configured), parameters backed by a Dataverse environment variable resolve to their current value in the environment (the value record first, then the definition's current default) instead of the default baked into the flow. Each resolved value is logged on stderr. An explicit --param wins for its key. If resolution fails, the run continues with the flow's defaults and prints a [WARN].

Safe local testing (details and a walkthrough in Local testing):

  • --stop-before <actionName> — Run everything before that action live, then stop: the action itself does not run. The trace so far and the inputs the action would have been called with are printed; the JSON result has stopped: true and stoppedBefore: { nodeId, name, inputs, reason: 'stop-before' }; status Cancelled, exit code 3. A name that is never reached only warns; a name that is not in the flow is a usage error.
  • --mock <actionName>=<outputs.json> — Repeatable. The action does not run (no token needed for its connector); its outputs are the file's JSON, used as is, so a connector/HTTP/child-flow mock is the full { "statusCode": 200, "body": ... } shape. Its inputs are still resolved and recorded, its status is Succeeded, and its trace entry carries mocked: true. Applies at every depth and on every loop iteration.
  • --mocks <file.json> — One JSON object mapping action names to outputs. Combines with --mock, but an action may be mocked only once.
  • --live <actionName>[,<actionName>] — Repeatable. Makes the run deny-by-default: every connector, HTTP or child-flow action must be mocked or listed here, otherwise the run stops before it (exit 3, stoppedBefore.reason: 'live'). --stop-before takes precedence.

Replaying a cloud run (details in Cloud runs):

  • --replay-run <runId> — Replay a run recorded in the cloud. The run's trigger outputs are the input (unless --in is given), and every connector/HTTP/child-flow action the run recorded is pinned to its recorded outputs, per loop iteration, with a failure replayed as a failure. Composes, variables and conditions recompute locally. Safe mode is implied: an external action with no recorded result stops the run (exit 3). --live X unpins X so it calls out; a --mock/--mocks entry wins over a pin. The JSON result carries replayedRun: { runId, status }.
  • The flow is found like run-show finds it: --id <workflowid>, then the workflowId in @Flow({...}), then the flow name. Needs --auth, or --flow-token plus --url and --token. --environment-id skips the environment lookup.

Connector tokens:

  • --sp-token <token> — SharePoint REST API token (audience https://<tenant>.sharepoint.com, not a Graph token). Enables the SharePoint connector.
  • --dv-url <url> --dv-token <token> — Dataverse base URL + Entra ID token. Enables the Dataverse connector.
  • --graph-token <token> — Microsoft Graph token. Enables the Office 365 Outlook, Office 365 Users, Office 365 Groups and Teams connectors, and Word Online, Excel Online and OneDrive for Business unless they get their own token. Each connector is loaded automatically the moment a usable token is present; there is no separate flag to turn one on.
  • --word-token <token> — Graph token specifically for Word Online (Business); falls back to --graph-token if omitted.
  • --excel-token <token> — Graph token specifically for Excel Online (Business); falls back to --graph-token if omitted.
  • --onedrive-token <token> — Graph token specifically for OneDrive for Business; falls back to --graph-token if omitted.
  • --auth — Acquire the tokens automatically. See below.

Child workflow execution (for flows using ctx.callWorkflow(...)):

  • A .ff.ts parent resolves its children without any flag: ctx.flow.childFlows[name].dslPath (relative to the parent file), then <workflowRef>.ff.ts next to the parent. This is the layout pull --all writes and the one the child-flows example uses.
  • --workflows-config <config.json> — Maps child workflow GUIDs to local IR files: { "workflows": { "<guid>": "./flows/<guid>.ir.json" } }.
  • --workflows-dir <dir> — Convention-based lookup: <dir>/<guid>.ir.json (default ./flows/).
  • --strict-workflows — Fail the run if a referenced child workflow can't be found/loaded (default: warn and continue).
  • --cache-workflows — Cache Dataverse-fetched child workflows to disk (./.workflow-cache).
  • With --dv-url/--dv-token (or --auth), a child that is not found locally is fetched from Dataverse by its GUID.

Deployed-flow context:

  • --environment-id <guid> — Sets workflow().tags.environmentName. Also used for listCallbackUrl() pre-resolution and --replay-run.
  • listCallbackUrl() pre-resolution (only relevant if the flow uses ctx.listCallbackUrl() or @listCallbackUrl()):
    • --flow-id <guid> and --environment-id <guid> — Identify the deployed flow so its trigger's invocation URL can be fetched from the Power Automate service before the run starts.
    • --trigger-name <name> — Trigger to resolve the callback URL for (default: the flow's first trigger).
    • --flow-token <token> — Flow Service token (resource https://service.flow.microsoft.com). If omitted and --auth is set, a token is acquired automatically.
    • If no URL can be resolved, the expression evaluates to '' at run time (non-fatal, with a [WARN]).
flowforger run examples/control-flow/flow.ff.ts --in payload.json
flowforger run flow.ff.ts --json # force the JSON run result on a terminal
flowforger run flow.ir.json --vars examples/control-flow/vars.json
flowforger run flow.ir.json --var name=value --var count=5
flowforger run flow.ir.json --sp-token $SP_TOKEN
flowforger run flow.ir.json --dv-url https://contoso.crm.dynamics.com --dv-token $DV_TOKEN
flowforger run examples/child-flows/order-intake.ff.ts --in examples/child-flows/payloads/order.json # children resolved from childFlows dslPath

# Read live, but stop before the first write
flowforger run flow.ff.ts --auth --stop-before Create_item

# Dry run: nothing calls out except Get_items; Send_an_email is answered by a mock
flowforger run flow.ff.ts --auth --live Get_items --mock Send_an_email=mocks/send.json

# Reproduce a production run locally
flowforger run flow.ff.ts --auth --replay-run <runId>

--auth: automatic token acquisition​

--auth replaces manually obtained --sp-token/--dv-token/--graph-token flags with automatic sign-in, scoped to only the connectors the flow actually uses:

flowforger run flow.ir.json --auth --config flowforger.config.json

How it works:

  1. The CLI scans the flow for every connector action (including those nested in scopes, conditions, switches and loops) and groups the operations used per connector.
  2. Each connector maps to an API: the Office 365, Teams, Office 365 Groups/Users, Word/Excel Online and OneDrive for Business connectors use Microsoft Graph (https://graph.microsoft.com); SharePoint uses auth.resources.sharepoint; Dataverse uses auth.resources.dataverse. A flow that uses a connector whose resource isn't configured fails with an error naming the missing config key.
  3. Only the minimum permissions for the operations present are requested (for example Mail.Read is dropped when Mail.ReadWrite is also needed). auth.additionalScopes.{graph,sharepoint,dataverse} are added on top, for permissions an HttpRequest action needs that the scanner can't infer. When the flow has environment-variable-backed parameters and auth.resources.dataverse is set, Dataverse access is requested too, so their current values can be read.
  4. Sign-in is a device-code login, one per API (not per connector). Cached refresh tokens are tried first; only a cache miss prompts. Silent runs print nothing about authentication (pass --verbose to see it).
  5. Tokens are cached at ~/.flowforger/token-cache.encrypted.json, encrypted for the current OS user with a key kept in the OS credential store (Windows Credential Manager, macOS Keychain, Secret Service on Linux). See App Registration for headless Linux and other cache details.
  6. An explicit --sp-token (etc.) always wins over --auth for that connector, and that connector is left out of the sign-in, so its resource need not be configured.
  7. --auth requires a config file (--config, defaulting to flowforger.config.json in the working directory) containing an auth section; a missing file or missing auth key fails before any network call is made.

Config shape (auth.resources.flowservice is optional and defaults to https://service.flow.microsoft.com):

{
"auth": {
"clientId": "your-entra-app-client-id",
"tenantId": "your-entra-tenant-id",
"resources": {
"sharepoint": "https://contoso.sharepoint.com",
"dataverse": "https://contoso.crm.dynamics.com"
},
"additionalScopes": {
"graph": ["Sites.Read.All"]
}
}
}

--auth is also accepted by pull, push, activate, diff, runs, run-show and mcp. For the Dataverse commands it acquires a single Dataverse user_impersonation token instead of scanning a flow, and the Dataverse URL comes from --url or, when that is omitted, from auth.resources.dataverse. runs, run-show and run --replay-run additionally acquire a Power Automate (Flow Service) token.

pull​

Pull one or more flows from Dataverse and decompile them to DSL (default) or raw Logic Apps JSON.

Mode (exactly one required):

  • --id <workflowid> — Pull a single flow by GUID.
  • --name <flowName> — Pull a single flow by display name.
  • --all — Pull every flow in the environment.
  • --solution <uniqueName> — Pull every flow in a solution.

Connection:

  • --url <dataverseUrl> — Required, unless --auth + config supplies auth.resources.dataverse.
  • --token <token> — Dataverse token. Falls back to the DATAVERSE_TOKEN environment variable.
  • --auth — Acquire a Dataverse token automatically (same config/cache as run --auth).

Output:

  • --out <path> — File path for single-flow mode (default <sanitizedFlowName>.ff.ts in the current directory; missing folders are created); directory for --all/--solution mode (default: current directory).
  • --json — Write the raw clientdata JSON instead of decompiling to DSL.
  • --config <file> / --config-env <name> — Generator/parser config used when decompiling to DSL (falls back to flowforger.config.json in the working directory if present, even without --config). The generator and parser flags of generate-dsl work here too.
  • --no-children — Skip child workflow resolution. In single-flow mode this also skips the recursive fetch of Workflow-action targets; in --all/--solution mode, callWorkflow actions keep raw GUIDs instead of being rewritten to named references with a dslPath.

By default, child flows referenced via the Workflow action are recursively pulled alongside the parent (single-flow mode) or cross-linked by GUID within the same solution/environment pull (--all/--solution mode), and @Flow({ workflowId: ... }) is embedded so a later push can round-trip without --id.

Exits 2 when a required argument is missing, 1 when the flow or solution is not found.

# Pull a single flow by name (writes ./My-Flow.ff.ts)
flowforger pull --name "My Flow" --url https://contoso.crm.dynamics.com --auth

# Pull by GUID to a specific file
flowforger pull --id 11111111-1111-1111-1111-111111111111 --url https://contoso.crm.dynamics.com --token $DV_TOKEN --out flows/my-flow.ff.ts

# Pull every flow in a solution as DSL, resolving child workflow references
flowforger pull --solution MySolution --url https://contoso.crm.dynamics.com --auth --out ./pulled

# Pull raw clientdata JSON instead of DSL
flowforger pull --name "My Flow" --url https://contoso.crm.dynamics.com --auth --json

push​

Deploy a workflow definition (compiled from DSL, or a pre-built clientdata.json) to Dataverse. Updates the flow if it already exists, creates it if it does not. For a safe deployment routine (diff first, refuse to overwrite designer edits, check the flow is on) see Deploying.

  • --file <flow.ff.ts|clientdata.json> — Required. .ts files are compiled to Logic Apps JSON on the fly (using --config, or flowforger.config.json if present); other files are sent as is.
  • --id <workflowid> — Target workflow GUID. Optional when the DSL has workflowId set in @Flow({...}) (e.g. embedded by a prior pull); an explicit --id always overrides the decorator value.
  • --name <flowName> — Flow name to look up or create under. Only meaningful for clientdata.json input, which carries no flow name; for .ff.ts the name in @Flow({...}) always wins.
  • --solution <uniqueName> — Place a newly created flow in this solution. Ignored (with a warning) when updating.
  • --create — Skip the name lookup and always create. Errors if a workflow id is also available.
  • --no-create — Never create: a missing flow is an error. Use this in CI.
  • --expect-clean — Before updating, compare the live flow with the local file (the same comparison as diff) and refuse when the live flow has actions, triggers or parameters the local file lacks, which usually means someone added them in the designer since you pulled. Nothing is pushed; the message lists what is missing. A changed action cannot be told apart from your own local edit, so it is not checked.
  • --require-active — Exit 1 when the flow is not Activated after the push. A newly created flow is always Draft, so this always fails on a create.
  • --url <dataverseUrl> — Required, unless --auth + config supplies auth.resources.dataverse.
  • --token <token> — Dataverse token. Falls back to the DATAVERSE_TOKEN environment variable.
  • --auth — Acquire a Dataverse token automatically.
  • --config <file> / --config-env <name> — Connection reference config used when compiling .ff.ts input.

push never changes a flow's state. After an update it prints the flow's state, for example State: Activated (statecode 1, statuscode 2) or State: Draft (statecode 0, statuscode 1) — not running! with the activate command to turn it on.

Exit codes: 0 pushed; 1 --require-active and the flow is not Activated; 2 usage error, an unresolvable or ambiguous flow, a --no-create miss, or a refused --expect-clean.

How the target flow is resolved​

  1. --id, if given.
  2. workflowId in the @Flow({...}) decorator.
  3. Otherwise, a lookup by flow name. A match is updated; no match creates the flow.

A newly created flow is created as Draft. Run activate when you want it live.

After creating from a .ff.ts source, the new GUID is written back into that file's @Flow decorator, so the next push resolves at step 2 and skips the name lookup. If the decorator cannot be edited (for example @Flow(SOME_CONST)), the create still succeeds and the CLI prints the GUID for you to add by hand.

# Create or update, whichever applies
flowforger push --file flow.ff.ts --url https://contoso.crm.dynamics.com --auth

# Create straight into a solution
flowforger push --file flow.ff.ts --solution MySolution --url https://contoso.crm.dynamics.com --auth

# CI: fail if the flow is missing, was edited in the designer, or is not running
flowforger push --file flow.ff.ts --no-create --expect-clean --require-active --auth

# Update a known flow by id
flowforger push --id 11111111-1111-1111-1111-111111111111 --file clientdata.json --url https://contoso.crm.dynamics.com --token $DV_TOKEN

# JSON input needs --name for anything other than --id
flowforger push --file clientdata.json --name "My Flow" --url https://contoso.crm.dynamics.com --auth

Limits of name resolution​

The name lookup is what makes a first push work without a GUID, but it is weaker than an id — prefer --id or a stamped workflowId whenever you have one.

  • Ambiguous names are refused. Dataverse allows two flows to share a name. When the lookup finds more than one, push stops and asks for --id rather than guessing which to overwrite.
  • push never updates the flow's name. It sends only clientdata. Rename a flow in @Flow({...}) and Dataverse keeps the old name — a later push that falls back to name resolution will miss and create a duplicate. Push by id after renaming.
  • You cannot match a flow you cannot see. Flows owned by someone else and not shared with you are invisible to the lookup, so a push creates a second flow with the same name. The create message says when this is what happened.

diff​

Compare a local flow with the live one in Dataverse: what push would change, per action.

  • --file <flow.ff.ts|clientdata.json> — Required. A .ff.ts file is compiled exactly as push would send it.
  • --id <workflowid> / --name <flowName> — Which live flow to compare against. Resolved like push: --id, then the decorator workflowId, then the name (an ambiguous name is refused).
  • --url <dataverseUrl>, --token <token> (or DATAVERSE_TOKEN), --auth — Dataverse connection, as for push.
  • --config <file> / --config-env <name> — Connection references for compiling, and the comparison tolerance (parity and parser.skipMetadataFields). The parser flags work here too.
  • --pretty / --json — Human-readable report on a terminal, JSON when piped; these force either. Progress goes to stderr.

Each action, trigger and parameter is reported as only-local, only-live, or changed with the kinds of change (type, inputs, expression, runAfter, moved, other), followed by flow-level differences (such as the description). Nested actions are matched by name, so an action moved into or out of a block shows as moved. Differences read live → local. Cosmetic differences are ignored: metadata ids, key order, expression whitespace and function-name casing, the default $connections/$authentication parameters, and empty runAfter. Connection references are compared by key only: a value difference is listed but not counted, because the values in the cloud are what keep a deployed flow's connections attached.

Exit codes: 0 identical, 1 differences, 2 usage or resolution error.

flowforger diff --file flow.ff.ts --auth

runs​

List a flow's recent cloud runs, newest first.

  • --file <flow.ff.ts>, --id <workflowid> or --name <flowName> — Which flow. Resolved like push: --id, then the decorator workflowId of --file, then the name.
  • --top <n> — How many runs (default 20).
  • --status <status> — Only runs with this status (Succeeded, Failed, Cancelled, Running).
  • --auth — Acquire the Dataverse and Power Automate tokens automatically. Without it, pass --url and --token (or DATAVERSE_TOKEN) for Dataverse and --flow-token for Power Automate (resource https://service.flow.microsoft.com).
  • --environment-id <guid> — The Power Platform environment. By default it is found by matching the Dataverse URL against the environments you can see.
  • --pretty / --json — Human-readable list on a terminal, JSON ({ workflowId, flowId, environmentId, runs }) when piped.

The Power Automate token needs the Flow Service User permission on your app registration (see App Registration). Exits 0 on success, 2 on a usage, resolution or API error. More in Cloud runs.

flowforger runs --file flow.ff.ts --auth --status Failed --top 5

run-show​

Print one cloud run in the trace shape of run --json.

  • --run <runId> — Required. The run to show (list them with runs).
  • Flow, connection, --environment-id and --pretty/--json: as for runs.

The JSON output is { runId, status, startTime, endTime, error?, trace }. Every action carries the cloud's own status, inputs, outputs and error; a block's body is nested under children, a loop's under iterations[].actions, and an action the run never recorded is Skipped. The values are the cloud's (a connector action's inputs are { host, parameters }), so only the shape matches a local run. The pretty output uses the same printer as run.

Exit codes: 0 when the run Succeeded, 1 otherwise, 2 on a usage, resolution or API error. To re-run it locally against the recorded results, use run --replay-run.

flowforger run-show --run <runId> --file flow.ff.ts --auth

activate​

Set a Dataverse workflow's state/status codes (e.g. to activate or deactivate a flow).

  • --id <workflowid> — Required.
  • --url <dataverseUrl> — Required, unless --auth + config supplies auth.resources.dataverse.
  • --token <token> — Dataverse token. Falls back to DATAVERSE_TOKEN.
  • --auth — Acquire a Dataverse token automatically.
  • --state <statecode> — Required numeric state code.
  • --status <statuscode> — Required numeric status code.
--state--status
Draft (off)01
Activated12
Suspended23

Exits 2 when a required argument is missing.

# Activate
flowforger activate --id 11111111-1111-1111-1111-111111111111 --auth --state 1 --status 2

# Turn it off again
flowforger activate --id 11111111-1111-1111-1111-111111111111 --auth --state 0 --status 1

generate-dsl​

Reverse-engineer Logic Apps JSON to TypeScript DSL (Logic Apps JSON → IR → DSL).

  • --in <clientdata.json> — Required (a bare positional argument also works).
  • --out <file> — Output DSL file (default: flow.native.ts).
  • --name <flowName> — Flow name to use in the generated @Flow(...) decorator.
  • --config <file> / --config-env <name> — Generator and parser options (see below).

Generator options (also used by pull and parity):

  • --multiline-preserve — Preserve multiline expression formatting (default).
  • --multiline-flatten — Flatten multiline expressions to a single line.
  • --whitespace-spaced — Spaces after commas: func(a, b) (default).
  • --whitespace-compact — No spaces after commas: func(a,b).
  • --expressions-strict — Keep an expression verbatim in ctx.eval(...) when regenerating it as typed DSL would change cosmetic details such as spacing or casing (default).
  • --expressions-relaxed — Generate typed DSL for every expression that can be parsed, normalizing spacing and casing (the re-emitted JSON may then differ textually).
  • --description-comment — Render action descriptions as // line comments (default).
  • --description-jsdoc — Render action descriptions as /** @description ... */ tags.
  • --skip-default-constructor — Omit the constructor when it only contains boilerplate that compile adds back anyway (standard schema metadata, the default $connections/$authentication parameters, empty connection references).

Parser options (also used by pull, parity and diff):

  • --skip-metadata-fields <fields> — Comma-separated metadata fields to omit (e.g. operationMetadataId).
  • --skip-action-names-for-kinds <kinds> — Comma-separated action kinds (lowercase) to skip name emission for (e.g. initializevariable,setvariable).
flowforger generate-dsl --in clientdata.json --out flow.ff.ts --name MyFlow
flowforger generate-dsl --in clientdata.json --out flow.ff.ts --description-jsdoc --whitespace-compact

parity​

Round-trip test: Logic Apps JSON → IR → DSL → IR → Logic Apps JSON, then compares the result with the original.

  • --in <clientdata.json> — Required (a bare positional argument also works).
  • --name <flowName> — Flow name used during the round-trip.
  • --config <file> / --config-env <name> — Same generator/parser options as generate-dsl, plus the parity section of the config.
  • --ignore-whitespace, --ignore-runafter, --ignore-metadata — Accepted for compatibility. Expression whitespace, empty runAfter and operationMetadataId differences are always ignored.

The whole definition is compared, including the actions nested in scopes, conditions, switches and loops. Differences that do not change behaviour are normalized first: key order, number formatting (100.00 vs 100), function-name casing, item() vs items('Loop'), repeated spaces, and the default $connections/$authentication values. Exits 0 with {"ok": true} on a clean round-trip, 1 with a JSON report of the differences otherwise, or 2 if the generated DSL fails to compile back to IR (a transform error rather than a difference).

flowforger parity --in clientdata.json --name MyFlow
flowforger parity --in clientdata.json --ignore-whitespace --ignore-runafter

sp-discover​

Discover SharePoint sites and lists via Microsoft Graph, to find the dataset/table values used by the SharePoint connector.

  • --token <token> — Required. Microsoft Graph token (audience https://graph.microsoft.com). Falls back to the GRAPH_TOKEN environment variable.
  • --site <site-url> — List all lists in this site (omit to list all sites instead).
  • --list <list-name> — Search for a specific list by name within --site.
# List all sites
flowforger sp-discover --token $GRAPH_TOKEN

# List all lists in a site
flowforger sp-discover --token $GRAPH_TOKEN --site https://contoso.sharepoint.com/sites/MySite

# Find a specific list
flowforger sp-discover --token $GRAPH_TOKEN --site https://contoso.sharepoint.com/sites/MySite --list "My List"

optimize​

Rewrite common inefficient DSL patterns into faster equivalents (source-to-source: parses .ff.ts, applies rewrites, emits .ff.ts again).

  • Input is a bare positional argument (.ff.ts).
  • --out <file> — Output file (default: <input>.optimized.ts).
  • --report <file> — Write a JSON report of the changes applied.
  • --no-variable-to-compose — Disable rewriting a single-set (never mutated) variable into a Compose action.
  • --no-loop-variable-to-compose — Disable rewriting a foreach loop that only appends to an array variable into a single Select action.
  • --no-append-to-select — Disable the append-to-array → Select optimization specifically.
  • --no-parallelism-warnings — Disable warnings about patterns that block Power Automate's concurrency.
  • --config <file> / --config-env <name> — Generator options used when re-emitting DSL.
flowforger optimize examples/optimizer/unoptimized.ff.ts \
--out examples/optimizer/optimized.ff.ts \
--report examples/optimizer/report.json

Review optimizer output before deploying, as with any generated code — it's a starting point, not a guarantee of behavioral equivalence.

init​

Bootstrap a flowforger.config.json for a Dataverse environment: discovers the tenant ID and existing connection references, and scaffolds the auth section needed for --auth.

  • --url <dataverseUrl> — Required.
  • --client-id <clientId> — Required. The Entra ID app registration used for both discovery and later --auth logins.
  • --tenant-id <id> — Optional; auto-discovered from --url if omitted.
  • --sp-url <sharepointUrl> — Optional SharePoint root URL, written into auth.resources.sharepoint.
  • --out <file> — Output path (default flowforger.config.json). If the file already exists, writes <file>.new instead rather than overwriting.
  • --skip-discovery — Skip the Dataverse sign-in and connection-reference discovery step; just generate a template config (faster, but connection references are left blank).
flowforger init --url https://contoso.crm.dynamics.com --client-id <client-id> --sp-url https://contoso.sharepoint.com

This performs a device-code sign-in (unless --skip-discovery) to enumerate existing connection references in the environment, then writes a config with global.connections pre-filled where possible and an auth section ready to use with --auth.

scopes​

Print the delegated permissions an app registration needs, so you can add them in the Azure Portal before the first --auth run (or after one fails with AADSTS65001).

  • <input.ff.ts|input.ir.json> — Resolve the permissions this one flow requests under --auth, using the same scanner and rules as run --auth.
  • --all — Print every permission any connector can request, grouped by API, with the connectors that use each.
  • --config <file> — Optional. With a config, SharePoint/Dataverse resource URLs come from auth.resources; without one, placeholders are shown.
  • --json — Emit [{ api, appId, resource, scopes, connectors? }] instead of text.
flowforger scopes my-flow.ff.ts
flowforger scopes --all --json

Each API is printed with the App ID to search for under APIs my organization uses. The full-set output intentionally lists both Read and ReadWrite variants: Entra ID consent is per permission, and a read-only flow requests the narrower one. See App Registration for the portal walkthrough.

mcp​

Run the FlowForger debug server over stdio using the Model Context Protocol, so an AI agent can start, step through and inspect a local debug session. Setup, the tool list and the recording/replay behaviour are described in MCP server.

  • --auth — Acquire connector tokens for each flow the agent starts, from the token cache only: the server never starts a device-code sign-in. Run flowforger run <flow> --auth once in a terminal first so the cache is warm.
  • --budget <n> — Maximum number of live connector calls per debug session (default 200).
  • --sp-token, --dv-url/--dv-token, --graph-token, --word-token, --excel-token, --onedrive-token — Fixed tokens, as for run.
  • --config <file> — Config with the auth section used by --auth.

The server keeps running until the agent closes the connection. Register it with your agent's MCP configuration as the command flowforger mcp --auth, for example:

claude mcp add flowforger -- flowforger mcp --auth

skills install​

Install the FlowForger agent skills (used by Claude Code and other agent harnesses) into a project.

  • --dir <targetDir> — Where to copy the skills (default: .claude/skills). Use --dir skills for the agent-agnostic top-level layout.
  • --bundled — Install the skills bundled with the installed CLI version instead of fetching the latest from GitHub (for offline/air-gapped use).
  • --repo <owner/repo> — GitHub repo to fetch skills from (default: tomdam/flowforger).
  • --ref <branch> — Git ref to fetch (default: main).
  • --path <repoPath> — Folder inside the repo containing the skills (default: skills).
flowforger skills install
flowforger skills install --dir skills
flowforger skills install --bundled

By default this fetches the latest skills from GitHub (so re-running the command refreshes them without needing a CLI upgrade); it falls back to the CLI's bundled copies automatically if the fetch fails, or immediately if --bundled is passed.

Notes​

  • compile accepts .ff.ts (DSL) or .ir.json input; the default output is Flow IR — pass --emit logicapps for Power Automate clientdata.json.
  • validate auto-detects format: .ff.ts/.ts files run DSL diagnostics (plus the IR checks when the DSL is clean), .json files auto-detect IR vs Logic Apps.
  • Running flows with connectors locally requires valid tokens (Graph for Office 365 Outlook/Users/Groups, Teams, Word, Excel and OneDrive; SharePoint-specific for SharePoint; a Dataverse token for Dataverse) — or --auth to acquire them automatically. Approvals is the one connector with no local implementation; see Approvals.
  • See Connection Configuration for the full flowforger.config.json shape (global/environments, generator/parser/parity options, and the auth section).