Skip to content

Latest commit

 

History

History
855 lines (636 loc) · 35.1 KB

File metadata and controls

855 lines (636 loc) · 35.1 KB

n8n-cli Spec

This document describes the current 0.1.x shape of n8nc.

It is intentionally narrower than the original brainstorm. The tool is now specified as a same-instance workflow sync and development CLI, not as a multi-environment deployment system.

1. Product Boundary

n8nc is for:

  • listing workflows from a configured n8n instance
  • listing recent executions and fetching one execution by ID
  • fetching one or all workflows into canonical local artifacts
  • creating local workflow drafts and editing local workflow JSON structurally
  • creating a remote workflow from a local file and converting it into a tracked artifact
  • executing non-webhook workflows, through a configured external backend or n8n's internal REST API
  • validating and formatting local workflow files
  • pushing a tracked workflow back safely
  • activating and deactivating workflows
  • calling webhook trigger URLs during development

n8nc is not yet for:

  • promoting a workflow across multiple environments
  • remapping credential IDs or project bindings between instances
  • non-webhook execution through a stable public n8n API (none exists; the internal REST route is used instead)

2. Command Surface

Top-level commands:

n8nc
├── init
├── doctor
├── auth add
├── auth test
├── auth session add
├── auth session test
├── auth session remove
├── auth list
├── auth remove
├── ls
├── get
├── runs ls
├── runs get
├── runs watch
├── pull [--all [--active|--inactive]]
├── push
├── workflow new
├── workflow create
├── workflow execute
├── workflow show
├── workflow rm
├── node ls
├── node add
├── node set
├── node rename
├── node rm
├── conn add
├── conn rm
├── expr set
├── credential ls
├── credential schema
├── credential set
├── status
├── diff
├── activate
├── deactivate
├── trigger
├── fmt
└── validate

3. Repository Model

init creates:

.
├── n8n.toml
├── workflows/
└── .n8n/
    └── cache/

n8n.toml stores:

  • schema_version
  • default_instance
  • workflow_dir
  • instance aliases and base URLs
  • optional per-instance workflow execute backend config

Example:

schema_version = 1
default_instance = "prod"
workflow_dir = "workflows"

[instances.prod]
base_url = "https://your-instance.app.n8n.cloud"
api_version = "v1"

[instances.prod.execute]
backend = "command"
program = "uvx"
args = ["your-mcp-runner", "execute_workflow", "{workflow_id}", "{instance_alias}"]
stdin_json = true
cwd = "."

Tracked files:

workflows/<slug>--<workflow_id>.workflow.json
workflows/<slug>--<workflow_id>.meta.json

The path is intentionally environment-neutral, but the sidecar binds the file to the instance it came from. That keeps the Git history of a single instance clean while making the current scope explicit.

workflow new creates a local .workflow.json draft without a sidecar. workflow create takes a sidecar-free local file, creates it remotely, writes the tracked workflow file plus sidecar, and removes the original in-repo draft when the tracked target path changes.

New drafts and create payloads fill in these workflow settings when they are missing:

  • executionOrder = "v1"
  • saveDataSuccessExecution = "all"
  • saveDataErrorExecution = "all"
  • saveManualExecutions = true
  • saveExecutionProgress = true

The cache stores one canonical base snapshot per tracked workflow:

.n8n/cache/<instance>--<workflow_id>.workflow.json

That snapshot is refreshed on pull and successful push.

pull --all batch-pulls every workflow from the remote instance. It fetches each workflow, canonicalizes and hashes the payload, and skips the disk write when the local sidecar already records a matching remote_hash. Optional --active and --inactive filters restrict which workflows are listed. Individual fetch failures are collected and reported without aborting the batch. When any workflow fails, pull --all returns exit code 6 with the full results in the JSON data field.

4. Credentials

Credentials are resolved in this order:

  1. N8NC_TOKEN_<ALIAS>
  2. OS keychain entry stored by auth add

Browser-session auth for the internal REST fallback is resolved in this order:

  1. N8NC_SESSION_COOKIE_<ALIAS> plus N8NC_BROWSER_ID_<ALIAS>
  2. OS keychain entries stored by auth session add

Example:

  • N8NC_TOKEN_PROD
  • N8NC_TOKEN_STAGING
  • N8NC_SESSION_COOKIE_PROD
  • N8NC_BROWSER_ID_PROD

auth add is non-interactive in v0.1. You must provide --token or --stdin.

auth session add stores both the session cookie and browser ID for one alias. It accepts --cookie or --cookie-stdin, plus --browser-id.

Doctor

doctor is a setup and reachability check for humans and agents.

Supported options:

  • --instance <alias>
  • --skip-network

Checks currently include:

  • repo config_file
  • repo workflow_dir
  • repo cache_dir
  • repo sensitive_data
  • repo instances
  • repo default_instance
  • per-instance config
  • per-instance token
  • per-instance api
  • per-instance credential_inventory
  • per-instance workflow_execute

Failure behavior:

  • returns exit code 13 when any check fails
  • in JSON mode, returns an error envelope with the full doctor report attached under data
  • in human mode, prints the report before returning the failure summary

repo.sensitive_data scans tracked .workflow.json files and fails when it finds likely inline secrets. It is skipped when the workflow directory is missing.

instance.credential_inventory reports whether full credential inventory is available through the public API, available only through the opt-in internal REST fallback, or limited to workflow-reference coverage.

instance.workflow_execute reports whether non-webhook execution is available: through a configured backend, or through the internal REST session fallback when session auth is stored. It is skipped only when neither is available.

5. API Assumptions

The implementation targets the public n8n API with:

  • header: X-N8N-API-KEY
  • default base path: /api/v1

The CLI currently assumes these workflow endpoints exist and are reachable through the public API:

  • GET /workflows
  • GET /workflows/{id}
  • PUT /workflows/{id}
  • POST /workflows/{id}/activate
  • POST /workflows/{id}/deactivate

The execution commands currently assume these endpoints exist and are reachable through the public API:

  • GET /executions
  • GET /executions/{id}

runs get uses includeData=true when fetching a single execution for --details, --summary, --node or --raw.

run_data is the canonical location for the run payload. --details strips execution.data, which carried the same bytes: on a real 27-node execution that duplicate was 1.42 MB, and --details fell from 4.0 MB to 1.5 MB. The two fields that only lived there, lastNodeExecuted and the error message, are surfaced as last_node_executed and error. --raw returns the execution exactly as n8n sent it, including n8n's internal executionData, with no derived keys.

trigger does not use the public API. It makes a direct HTTP request to a full URL or a path resolved against the configured instance base URL.

workflow execute resolves the workflow through the public API, then runs it one of two ways:

  1. Through a configured local backend ([instances.<alias>.execute]), such as an MCP runner or other adapter command.
  2. Otherwise through n8n's internal REST API, POST /rest/workflows/:id/run, authenticated with the stored session cookie and browser id. This is the same call the editor's Execute Workflow button makes, because the public API has no run endpoint (POST /api/v1/workflows/:id/run returns 405).

The internal route is not part of n8n's public API surface and carries no stability guarantee: its request shape changed between n8n 1.x and 2.x. n8nc sends workflowData plus triggerToStartFrom, which both accept. The workflow must have a Manual Trigger node; anything else is reported as such, pointing at n8nc trigger for webhook workflows.

n8n registers the run and returns an executionId before the workflow finishes, so workflow execute polls the public executions API until it settles. --no-wait returns as soon as the run is registered, and --timeout <SECONDS> bounds the wait (default 300). A run that finishes with status error or crashed exits non-zero.

6. Canonical Workflow Artifact

Tracked workflow files are canonical JSON.

Current canonicalization rules:

  • top-level payload must be a JSON object
  • top-level volatile fields are removed:
    • createdAt
    • updatedAt
    • versionId
  • top-level keys are emitted in this order when present:
    • id
    • name
    • active
    • tags
    • settings
    • nodes
    • connections
  • nested object keys are sorted
  • array order is preserved
  • output is pretty JSON with 2-space indentation and trailing newline

This canonicalization is versioned.

Current values:

  • canonical_version = 1
  • hash_algorithm = "sha256"

7. Metadata Sidecar

Each pulled workflow has a committed sidecar:

{
  "schema_version": 1,
  "canonical_version": 1,
  "hash_algorithm": "sha256",
  "instance": "prod",
  "workflow_id": "abc123",
  "local_relpath": "workflows/order-alert--abc123.workflow.json",
  "pulled_at": "2026-03-26T10:31:54Z",
  "remote_updated_at": "2026-03-26T10:30:10Z",
  "remote_hash": "sha256:..."
}

The important field is remote_hash. It is the lease token used by push.

8. Status Model

status is local by default in 0.1.x.

Base local states:

  • clean: workflow file and sidecar are valid, and the local canonical hash matches the recorded remote_hash
  • modified: workflow file and sidecar are valid, and the local canonical hash differs from the recorded remote_hash
  • untracked: workflow file exists without a sidecar
  • invalid: workflow file or sidecar cannot be used safely
  • orphaned_meta: sidecar exists without a matching workflow file

invalid currently covers cases like:

  • workflow JSON parse failure
  • sidecar parse failure
  • metadata workflow_id mismatch
  • unsupported canonical_version
  • unsupported hash_algorithm
  • validation errors such as missing node targets

status --refresh adds live remote sync classification for entries that are already clean or modified.

Remote sync states:

  • clean: local file still matches the remote lease recorded in the sidecar
  • modified: local file changed, but the remote still matches the recorded lease
  • drifted: local file is unchanged, but the remote no longer matches the recorded lease
  • conflict: both local file and remote changed since the last pull or successful push
  • missing_remote: the tracked workflow no longer exists remotely

If remote refresh fails for a tracked workflow, the CLI still returns the local row, leaves sync_state unset, and records the reason in remote_detail. untracked, invalid, and orphaned_meta entries remain visible with their local state but do not contribute to sync_summary.

9. Push Safety Model

push is update-only in 0.1.x.

Algorithm:

  1. Read the local workflow file.
  2. Canonicalize it and hash it.
  3. Read the sidecar.
  4. Fetch the current remote workflow by ID.
  5. Canonicalize the remote payload and hash it.
  6. Compare remote hash to meta.remote_hash.

Outcomes:

  • if remote_hash != meta.remote_hash, refuse the push with exit code 12
  • if local_hash == meta.remote_hash, report no-op
  • otherwise, update the workflow with PUT /workflows/{id} using only name, nodes, connections, and settings

If local edits also changed unsupported top-level fields, push fails explicitly instead of silently dropping them.

After a successful push, the CLI re-fetches the workflow and re-writes the workflow and sidecar from that remote snapshot so local state stays canonical.

10. Diff Model

diff is local by default in 0.1.x.

It compares:

  • the current canonical local workflow file
  • the cached base snapshot from .n8n/cache

If a cache snapshot is unavailable, diff falls back to hash and state reporting only and tells the user to re-pull the workflow to seed local diff data.

The human output includes:

  • status summary
  • file path
  • workflow ID
  • local, recorded, and base hashes when available
  • changed top-level sections
  • unified patch when a base snapshot exists and content changed

The JSON output includes:

  • the local status object
  • base_hash
  • base_snapshot_available
  • changed_sections
  • optional patch

diff --refresh keeps the base snapshot comparison and also fetches the current remote workflow by ID.

Additional human output in refresh mode:

  • remote sync state
  • remote hash
  • remote update timestamp when present
  • changed top-level sections between the current remote workflow and the local file
  • unified remote vs local patch when both sides are available and differ

If remote refresh fails, the command still returns the local base snapshot diff, leaves the remote comparison unavailable, and records the reason in status.remote_detail.

Additional JSON fields in refresh mode:

  • status.sync_state
  • status.remote_hash
  • status.remote_updated_at
  • status.remote_detail
  • remote_comparison_available
  • remote_changed_sections
  • optional remote_patch

11. Local Authoring

The local authoring surface in 0.1.x is intentionally narrow and file-based.

Current commands:

  • workflow new <name> [--path <path>] [--id <id>] [--active]
  • workflow create <file> --instance <alias> [--activate]
  • workflow execute <id-or-name> --instance <alias> [--input <value>|--input-file <path>|--stdin]
  • workflow show <file> [--instance <alias>]
  • workflow rm <target> [--instance <alias>] [--local-only|--keep-local]
  • node ls <file>
  • node add <file> --name <name> --type <node_type> [--type-version <number>] [--x <int>] [--y <int>] [--disabled]
  • node set <file> <node> <path> [value] [--json-value|--number|--bool|--null]
  • node rename <file> <current_name> <new_name>
  • node rm <file> <node>
  • expr set <file> <node> <path> <expression>
  • credential ls [--instance <alias>] [--workflow <id-or-name>] [--type <credential_type>] [--source auto|public|rest-session|workflow-refs]
  • credential schema [--instance <alias>] <credential_type>
  • credential set <file> <node> --type <credential_type> --id <credential_id> [--name <credential_name>]
  • conn add <file> --from <node> --to <node> [--kind <type>] [--target-kind <type>] [--output-index <n>] [--input-index <n>]
  • conn rm <file> --from <node> --to <node> [--kind <type>] [--target-kind <type>] [--output-index <n>] [--input-index <n>]

Behavior:

  • all edit commands operate on local workflow files only
  • edit commands rewrite the file in canonical JSON form after each successful mutation
  • tracked sidecars are left untouched, so tracked files become locally modified until they are pushed
  • edit commands also run the sensitive-data scanner after write and include warning_count in JSON output
  • workflow show summarizes local nodes, edges, credential references, and webhook URLs, using the explicit --instance, the tracked sidecar instance, or the repo default instance for local drafts
  • workflow create requires a repo because it writes the new tracked file and sidecar into the configured workflow directory
  • workflow create refuses files that already have a sidecar and expects you to use push for tracked workflows
  • workflow create removes local id and active before the create request, ensures execution-saving settings defaults exist, normalizes webhook nodes for remote creation, and re-fetches the created workflow before storing the new tracked state
  • workflow execute requires [instances.<alias>.execute] config for a local backend and intentionally fails with a config error when none is present
  • workflow execute supports inline input, file input, or stdin; input that parses as JSON is preserved structurally, otherwise it is passed through as a plain string
  • workflow execute resolves the workflow first, then expands {workflow_id}, {workflow_name}, {instance_alias}, and {base_url} placeholders in the configured backend arguments
  • workflow execute exports workflow context through environment variables and, when stdin_json = true, writes a JSON request envelope to stdin for adapter-style runners
  • auth list reports token source plus browser-session readiness for each configured instance
  • auth session test verifies that the internal REST credential-inventory path is reachable with the configured session cookie and browser ID
  • credential ls defaults to --source auto
  • credential ls --source auto resolves inventory in this order:
    • public API inventory via GET /api/v1/credentials
    • internal REST inventory via GET /rest/credentials only if browser-session auth is configured through auth session add or both N8NC_SESSION_COOKIE_<ALIAS> and N8NC_BROWSER_ID_<ALIAS>
    • workflow-reference discovery from fetched workflows
  • credential ls --source public requires the public credential inventory endpoint to work and fails explicitly if it does not
  • credential ls --source rest-session requires browser-session auth and uses the internal browser-session route intentionally as an opt-in fallback, not a default dependency
  • credential ls --source workflow-refs only reports credentials referenced by workflows
  • credential ls --workflow <id-or-name> is workflow-reference scoped and therefore only works with --source auto or --source workflow-refs
  • full-inventory sources still enrich results with workflow usage counts by scanning current workflow references; unused credentials show usage_count = 0
  • credential schema returns the official schema payload from credentials/schema/{credentialTypeName}
  • credential set requires an existing credential ID, which can come from credential ls, the n8n UI, or another trusted source
  • workflow rm accepts a workflow file path, workflow ID, or exact workflow name
  • workflow rm <file> removes a local draft directly; for tracked files it also deletes the remote workflow unless --local-only is set
  • workflow rm <id-or-name> deletes the remote workflow and removes matching tracked local artifacts unless --keep-local is set

Webhook-specific behavior:

  • node add --type n8n-nodes-base.webhook defaults typeVersion to 2
  • webhook nodes get an auto-derived webhookId
  • setting path normalizes leading and trailing slashes
  • when webhookId still uses the auto-derived value, changing path updates webhookId too
  • workflow create, workflow show, and activate return resolved production and test webhook URLs when a base URL is available

Path rules for node set and expr set:

  • url means parameters.url
  • options.timeout means parameters.options.timeout
  • explicit top-level node fields such as position, disabled, typeVersion, notes, alwaysOutputData, and retry-related fields are supported directly
  • id, name, type, and credentials are intentionally blocked from node set

Expression rules:

  • if the input already looks like ={{ ... }}, it is preserved
  • if the input looks like {{ ... }}, the CLI prefixes =
  • otherwise the CLI wraps the value as ={{...}}

Connection rules:

  • source and target node names must already exist
  • duplicate connection edges are deduplicated
  • the default source output type is main
  • the default target input type is the same as --kind
  • node rename rewrites the node name, outbound connection key, and inbound edge targets
  • node rm removes the node, its outbound key, and inbound edges pointing at it
  • conn rm removes matching edges without disturbing other edges in the same branch

12. Validation

validate currently checks:

  • file parses as JSON
  • workflow payload is an object
  • id exists
  • nodes is an array
  • connections is an object
  • node names are unique
  • connection targets point to existing node names
  • if a sidecar exists, workflow_id matches the workflow file

validate also emits non-fatal warnings for likely sensitive literals in tracked workflow files, including:

  • inline private key material
  • URLs with embedded basic-auth credentials
  • token-like literal prefixes such as sk-, ghp_, github_pat_, xoxb-, xoxp-, and Bearer ...
  • literal values stored under field names like password, token, secret, clientSecret, or apiKey

The scanner intentionally ignores obvious placeholders and common n8n dynamic references such as ={{ ... }} and $env.*.

Warnings do not fail validate, but they are returned in human output, JSON output, and the post-write summaries from pull and successful push.

11a. Remote Inspection

get <id-or-name> prints the canonical workflow JSON. A real workflow is tens of kilobytes, and the usual question is about one node, so three projections answer it directly without shipping the rest:

  • get <id> --node "<NAME>" — one node's definition, matched by display name then by id, the same resolution node set uses. An unknown name is a not_found error (exit 11), never an empty result.
  • get <id> --nodes — a summary row per node: name, type, typeVersion, disabled.
  • get <id> --connections — the wiring, so main[0] (true) and main[1] (false) branches are readable directly.

The three are mutually exclusive. On a 52 KB production workflow, --node returns 662 bytes.

These mirror runs get --summary / --node, which do the same for executions.

11b. Remote One-Shot Edits

node set-remote <workflow> <node> <path> <value> edits one node on a remote workflow without making it a tracked artifact. It takes the same node, path and value syntax as node set, including --value-file for multiline bodies, and runs the identical in-memory mutation, so the two cannot drift apart.

The write is guarded:

  • Lease. The workflow is re-read immediately before writing. If it changed on the remote since it was read, the command exits 12 (conflict) rather than overwriting a concurrent edit.
  • Mutable fields only. The payload goes through the same workflow_update_payload that push uses, so id, active and timestamps are never sent.
  • Active is preserved. n8n's update endpoint can return the workflow with active cleared. When that happens the workflow is reactivated and reactivated: true is reported.
  • No-op writes nothing. If the value already matches, no PUT is issued and changed: false is reported.
  • --dry-run reports the change without writing.

This exists because the safe path (pull -> node set -> push --verify) requires tracking the workflow in a repo. For a one-off fix to a workflow you do not want to track, that ceremony pushes people into hand-rolled PUT /api/v1/workflows/:id calls, which is exactly where active gets silently dropped.

13a. Execution Statistics

runs stats [workflow] aggregates every execution in the window. It never samples: pagination walks each page, so the totals cannot be computed from a truncated set.

  • --status <STATUS> filters server-side, so --status error aggregates only failures.
  • --by workflow breaks the window down per workflow, ranked by failures first. That answers "which workflow is failing" in one call rather than by paginating executions by hand.
  • Cadence is always reported: first, last, and the largest interval between consecutive executions as max_gap_ms with its max_gap_from / max_gap_to endpoints.

Cadence turns a monitoring workflow into an outage detector. A heartbeat running every five minutes, with a gap of a hundred, means the instance was down:

$ n8nc runs stats <heartbeat-id> --last 7d
Cadence:
  First: 2026-07-03T14:30:19.000Z
  Last:  2026-07-10T14:25:54.000Z
  Largest gap: 1h 40m (2026-07-05T09:35:19.000Z -> 2026-07-05T11:15:54.000Z)

With no executions in the window, first, last and max_gap_ms are absent rather than zero: nothing ran and a gap of zero are different facts.

12a. Lint Rules

lint checks tracked workflow files against configurable rules. Severities are off, warn, or error, set per rule in n8n.toml. Any diagnostic at error fails the command.

rule default what it catches
no-hardcoded-urls warn HTTP Request nodes with a literal URL instead of an expression
no-disabled-nodes warn nodes left disabled in a tracked workflow
require-error-handler off nodes without an error-handling path
no-default-names warn nodes still carrying their default name
no-empty-expressions warn expressions that resolve to nothing
params-match-type-version warn parameters written for a different typeVersion than the node declares

params-match-type-version exists because n8n resolves a node's parameter schema from its typeVersion and then reads keys that may not be there, without warning. An IF at typeVersion: 2 whose parameters still use the v1 conditions.boolean shape evaluates zero conditions, so every item takes the true branch and the guard is silently inert. The mirror case is equally quiet: Set v3 assignments on a typeVersion: 1 node assigns nothing.

Only migrations confirmed against real workflows are encoded (if, filter, set). An unrecognised node type is never flagged, so the rule cannot invent a false positive. Raise it to error in n8n.toml if a silently-inert guard should fail your build.

13. Execution Inspection

runs ls returns recent executions from the remote instance.

Current supported options:

  • --limit
  • --workflow <id-or-exact-name>
  • --status <value>
  • --since <RFC3339>
  • --last <window>

Time filtering is client-side in 0.1.x. The CLI pages through recent executions until it collects the requested number of matching rows or exhausts the remote result set.

Because execution listings are treated as recent-first, the CLI stops paging once a page has crossed below the active --since cutoff.

--since includes executions at or after the given timestamp.

--last computes a rolling window from the current time and accepts these suffixes:

  • s seconds
  • m minutes
  • h hours
  • d days

List rows currently include:

  • execution ID
  • workflow ID
  • workflow name when it can be resolved
  • status
  • mode
  • started and stopped timestamps
  • computed duration in milliseconds when both timestamps exist

If runs ls --workflow ... returns zero rows for an active workflow whose settings do not explicitly save successful production executions, the CLI includes a note explaining that successful runs may not appear in history.

runs get <execution-id> returns the execution summary.

runs get <execution-id> --details fetches the detailed execution payload and, in human output, summarizes:

  • workflow name and ID
  • status and mode
  • start and stop timestamps
  • computed duration
  • node-level execution status
  • node execution time
  • output item counts per node based on data.resultData.runData

In JSON output, runs get --details returns:

  • execution: the raw detailed execution payload from n8n
  • run_data: the extracted data.resultData.runData object when present
  • node_executions: a stable per-node summary array with status, execution time, and output item counts

runs watch polls the execution list repeatedly and is intended for active debugging sessions.

Current supported options:

  • --workflow <id-or-exact-name>
  • --status <value>
  • --since <RFC3339>
  • --last <window>
  • --limit
  • --interval <seconds>
  • --iterations <count>

Human output behavior:

  • first poll prints the current execution window
  • prints the active workflow, status, and time-window filters when present
  • later polls print only newly seen executions
  • no output is emitted for unchanged polls after the initial snapshot

JSON output behavior:

  • emits one compact JSON envelope per poll
  • first poll uses event = "snapshot"
  • later polls use event = "update" when new executions appear
  • later polls use event = "heartbeat" when no new execution IDs appear

Each JSON watch event currently includes:

  • poll
  • interval_seconds
  • count
  • new_count
  • executions
  • new_executions

The current diagnostics model is intentionally simple:

  • severity
  • code
  • message
  • file
  • optional JSON path
  • optional suggestion

14. Triggering And Execution

The user concern that started this implementation was valid: developers need more than pull and push.

The current answer is:

  • use ls and get for fast inspection
  • use activate and deactivate for workflow state changes; they wait for the remote state to converge and refresh tracked local artifacts when available
  • use trigger for webhook-based development flows
  • use workflow execute for non-webhook workflows when you have a configured local backend

trigger supports:

  • full URLs
  • instance-relative paths
  • custom method
  • repeated --header key:value
  • repeated --query key=value
  • request body from --data, --data-file, or --stdin

If the request body looks like JSON and no Content-Type header was provided explicitly, trigger sends Content-Type: application/json.

Webhook-specific error handling:

  • non-2xx responses include the resolved request path and a summarized response body in the error message
  • 404 responses for /webhook-test/... explain that test listeners must be active in the n8n editor
  • 404 responses for /webhook/... explain that the path may be wrong, the workflow may be inactive, or n8n may not have registered the webhook yet

This avoids pretending there is a stable public “run workflow by ID” endpoint when that has not been verified in the implementation.

workflow execute supports:

  • workflow ID or exact workflow name resolution through the public API
  • optional input from --input, --input-file, or --stdin
  • adapter commands configured per instance in n8n.toml
  • placeholder expansion in configured args:
    • {workflow_id}
    • {workflow_name}
    • {instance_alias}
    • {base_url}
  • execution context in environment variables:
    • N8NC_EXECUTE_INSTANCE_ALIAS
    • N8NC_EXECUTE_BASE_URL
    • N8NC_EXECUTE_WORKFLOW_ID
    • N8NC_EXECUTE_WORKFLOW_NAME
    • N8NC_EXECUTE_WORKFLOW_ACTIVE
    • N8NC_EXECUTE_INPUT_JSON
  • optional stdin JSON request envelopes when stdin_json = true

Example adapter request body:

{
  "tool": "execute_workflow",
  "instance_alias": "prod",
  "base_url": "https://example.n8n.cloud",
  "workflow": {
    "id": "abc123",
    "name": "Nightly Digest",
    "active": true
  },
  "input": {
    "dryRun": true
  }
}

This keeps the semantics honest:

  • trigger means “call a webhook URL”
  • workflow execute means “ask a configured local adapter to run this workflow”

15. JSON Contract

Every command supports --json.

Success envelope:

{
  "ok": true,
  "command": "ls",
  "version": "0.1.0",
  "contract_version": 1,
  "data": {}
}

Error envelope:

{
  "ok": false,
  "command": "push",
  "version": "0.1.0",
  "contract_version": 1,
  "error": {
    "code": "conflict.remote_changed",
    "message": "Remote workflow changed since the last pull."
  }
}

Validation failures and doctor failures may also include a data object with diagnostics or the full doctor report.

validate success and failure payloads include both error_count and warning_count. pull and successful push also include warning_count, plus diagnostics when warnings are present.

Local edit command success payloads include:

  • workflow_path
  • changed
  • warning_count
  • command-specific fields such as workflow_id, node, path, from, to, or credential_type

workflow create success payloads also include:

  • instance
  • source_path
  • source_removed
  • meta_path
  • optional active
  • optional webhooks

workflow rm success payloads include:

  • target
  • optional workflow_id
  • optional workflow_name
  • optional instance
  • remote_removed
  • local_removed
  • removed_paths

workflow execute success payloads include:

  • action = "execute"
  • instance
  • workflow_id
  • workflow_name
  • optional active
  • execution.backend
  • execution.program
  • optional execution.args
  • optional execution.cwd
  • optional execution.output
  • optional execution.stderr

16. Exit Codes

  • 0: success
  • 2: usage error
  • 3: config error
  • 4: auth error
  • 5: network error
  • 6: API error
  • 10: validation error
  • 11: not found
  • 12: conflict refusal
  • 13: doctor failures

16a. Pagination and truncation

The n8n public API caps a page at 250 rows on both /workflows and /executions, and returns 400 above it. That cap is a page size, not a limit on what a caller may request.

  • --limit is the number of results wanted, not the page size. n8nc runs ls --limit 600 walks nextCursor across three pages of 250 and returns 600 rows.
  • Commands that need the whole set (runs stats, pull --all, the credential inventory) walk every page, so their aggregates are never computed from a truncated sample.
  • truncated: true appears in the JSON envelope when --limit stopped the walk while rows remained. It errs toward true: a nextCursor proves the source has more rows, not that any of them still match a client-side filter such as --name. Where exhaustion can be proven it is, so a page that already crossed a --since cutoff reports false. The error is a needless "there may be more", never a silent "this is everything". In text mode the same condition prints a note on stderr, for both ls and runs ls.
  • --offset is applied in-band, after the fetch. Commands therefore fetch offset + limit results, so --offset 20 --limit 20 returns the second page of twenty rather than nothing. Pages are contiguous and non-overlapping.
  • runs ls --explain issues one GET /executions/<id>?includeData=true per returned row. That response carries the whole run payload, so pair --explain with a small --limit and a --status filter.

17. Known Limits

  • The tool is currently strongest when a repo mirrors one n8n instance.
  • workflow create depends on the public workflow-create endpoint and still assumes the returned payload can be stored with the same canonicalization rules as pulled workflows.
  • tags are preserved structurally, not normalized semantically.
  • ls assumes a paginated workflow list response with data and optional nextCursor.
  • remote drift and API health remain opt-in via status --refresh, diff --refresh, and doctor.
  • doctor uses a cheap workflow-list probe and does not verify every endpoint.
  • diff is best after a fresh pull, because older repos may not have cached base snapshots yet.
  • sensitive-data scanning is heuristic. It is tuned to catch likely mistakes, not to prove a workflow is secret-free.
  • non-webhook execution currently depends on a local adapter command you configure yourself; n8nc does not ship an MCP runner or other backend implementation.
  • archive support is still outside the CLI until a stable public endpoint is verified.

18. Next Likely Steps

The next improvements that fit the current design are:

  1. shell completions and packaging
  2. more contract snapshot coverage for agent-facing JSON
  3. richer workflow inspection or graph rendering in human output
  4. optional archive support if a stable public endpoint exists
  5. only after that: a real environment-promotion model with explicit mappings and lock files