init
n8n-decanter init [dir] [--force]
n8n-decanter init [dir] --reauth # re-consent (spent token)
n8n-decanter init [dir] --host <url> [--token <mcp-token>] [--api-key <key>] # non-interactive
Interactive setup for a new (or existing) sync dir:
- Prompts for the n8n host. If you leave off the scheme, local addresses
(
localhost, loopback, private LAN ranges,*.local) default tohttp://and everything else tohttps://— type the scheme yourself to override. Then connects via OAuth: your browser opens n8n’s consent page, and the resulting refresh token lands in a gitignored.decanter-auth.json(rotated automatically on every refresh). No browser or piped run? Paste an MCP token instead (minted in n8n → Settings → MCP → API key; stored asN8N_MCP_TOKENin.env). - Offers the optional public API key (
N8N_API_KEY) — only needed for executions, data-tables, and backup. - When credentials already exist they are reused — edit or delete
.env/.decanter-auth.jsonto change them, or pass--reauthto re-consent without touching a file. A best-effort connection check runs at the end (it also reports how many workflows are already “Available in MCP”), and if that check finds the stored refresh token spent, it offers to re-authorize on the spot. - Copies the starter template. Files named
X.examplein the template land asXin the target, and a copy-time baseline is recorded in.decanter-template.json(see Re-running init). - Scaffolds
decanter.config.jsonand a.gitignore(which covers.envand.decanter-auth.json). - Closes by pointing at n8n’s official skills pack — see The n8n skills pointer below.
The instance needs MCP access enabled once (n8n → Settings → MCP; ~2.20+), and each workflow you sync needs its “Available in MCP” flag — see configuration.
Non-interactive setup (--host / --token / --api-key)
Run
init— don’t hand-write.env. No browser (a headless box, a CI job, a coding agent)? That is not a reason to skipinitand dropN8N_HOST/N8N_MCP_TOKENinto a file yourself:init --host … --token …takes exactly those two values as flags and needs no prompt, no TTY and no browser. A hand-written.envleaves the rest of the setup silently missing — nodecanter.config.json, no starter template, no.gitignore(so.envis onegit addaway from being committed), noAGENTS.md/.mcp.jsonagent wiring, no.decanter-template.jsonbaseline — and most verbs then fail with “decanter.config.json not found”. Mint the token in n8n (Settings → MCP → API key), then hand it toinit.
Passing any of --host, --token, or --api-key runs init
non-interactively — values come from the flags plus any existing .env, and
no prompt is ever issued (so it drives cleanly from a script or a coding
agent, with no stdin dance):
n8n-decanter init --host http://localhost:5678 --token "$N8N_MCP_TOKEN"
n8n-decanter init ./flows --host n8n.example.com --token "$TOK" --api-key "$KEY"
--host <url>— the n8n origin. Normalized like a typed host (a scheme-less local address getshttp://, everything elsehttps://; a scheme you write is kept). Required in this mode — omit it andiniterrors instead of prompting.--token <mcp-token>— the MCP bearer token (N8N_MCP_TOKEN), the same one the paste path uses.--mcp-tokenis an accepted alias for it. Omit it andinitwrites the rest and warns that sync won’t work until credentials are set (there is no headless OAuth — the browser consent flow needs a terminal).--api-key <key>— the optional public API key (N8N_API_KEY). Omit it and it’s simply skipped.--auth upstream— a proxy in front of n8n attaches the credentials, so decanter holds and sends none. See below.
An explicit flag wins over an existing .env value; the end-of-init connection
checks run exactly as they do interactively. --force composes with all four.
Auth handled by a proxy (--auth upstream)
When a gateway sits in front of n8n and attaches the n8n credentials itself,
decanter must send none of its own. A placeholder does not work: the proxy
appends its key to whatever the client sent, and n8n 401s the pair.
n8n-decanter init . --host <proxy-url> --auth upstream
That writes N8N_HOST and N8N_DECANTER_AUTH=upstream to .env and nothing
else — no token to paste, no browser, no .decanter-auth.json. Both halves are
then verified: the MCP connection check, and a REST probe through the proxy.
- It refuses
--token,--api-keyand--reauth, before writing anything. Those configure a credential this mode does not send, and quietly storing one is how a value nothing uses ends up in a credential file awaiting rotation. - Credentials already in
.env(or.decanter-auth.json) are kept and ignored, andinitwarns which ones. Remove them yourself if you are not switching back.
Configuration → credentials has what changes at request level, and what still needs the proxy’s key to be scoped correctly.
Re-authorizing (--reauth)
OAuth refresh tokens rotate on every use, so one can be spent for good — a
crash at the wrong moment, a concurrent run, or a stray second copy of
.decanter-auth.json. Decanter then reports:
MCP session expired (invalid_grant — the stored refresh token is spent or was
revoked) — re-run: n8n-decanter init --reauth
--reauth is the one command that fixes it:
n8n-decanter init --reauth
It skips the credential-reuse step, opens the browser consent page again,
and replaces .decanter-auth.json — but only if consent succeeds, so a
cancelled re-auth leaves you exactly where you were. A plain init cannot do
this: it reuses the auth file whenever the host matches, so it would re-probe
with the same dead token.
Two refusals, both up front rather than after a pointless browser trip:
- No terminal — the consent flow needs a browser. Use
init --token <mcp-token>instead (n8n → Settings → MCP → API key). N8N_MCP_TOKENis set — a bearer token always wins over OAuth credentials, so freshly minted ones would be ignored. Remove it from.envfirst, or replace the token withinit --token <mcp-token>.
Not every failure is a spent token, and only this one warrants --reauth.
A 429 means n8n is rate-limiting (the CLI already retried; your credentials are
fine), and any other error is reported without a diagnosis. See
Troubleshooting.
The n8n skills pointer
decanter owns Code-node source; n8n’s official skills pack
teaches your agent everything else. A first init closes by naming it and
printing the install commands for the agent it detects:
Recommended: n8n's official skills pack (n8n-io/skills) — it teaches your agent to
build workflow structure over MCP while decanter keeps every Code node a file.
Claude Code (detected)
claude plugin marketplace add n8n-io/skills
claude plugin install n8n-skills@n8n-io
then /reload-plugins (or restart Claude Code)
Codex
codex plugin marketplace add n8n-io/skills
codex plugin add n8n-skills@n8n-io
then restart Codex and approve the plugin's hooks (needs Codex >= 0.142.0)
other agents (skills.sh)
npx skills add n8n-io/skills -y
no plugin hooks on this route — the scaffolded AGENTS.md carries the routing cue it needs
guide: /docs/agents/n8n-skills/
The (detected) marker comes from your environment (running inside an agent,
its binary on PATH, or a ~/.claude / ~/.codex marker) and only decides
which route is listed first — every route is always shown.
init prints; it never installs. Running claude/codex/npx skills for
you would mean decanter spawning three third-party CLIs with their own version
floors, mutating agent state that lives outside the sync dir, at the most
fragile moment of setup — and a plugin installed mid-session isn’t active until
the agent reloads anyway, so the subprocess buys nothing the printed command
doesn’t. It is printed once, on a first init (before
.decanter-template.json exists); every re-run stays quiet, so there is no
flag to turn it off. Piped and --host-driven runs get it too — an agent
bootstrapping a sync dir should learn the pack exists as much as a human does.
TypeScript tooling
init also scaffolds what a sync dir needs to type-check and run nodes
locally: a package.json (with a typecheck script and the typescript
devDependency), tsconfig.json, and n8n-globals.d.ts with types for the
Code-node globals ($input, $('…'), DateTime, …).
Verification routes through the CLI, so n8n-decanter must be on the sync
dir’s PATH: install it globally, add it to the sync dir’s devDependencies,
or npm link a git checkout (build it first — Node won’t type-strip .mts
under node_modules).
Agent configs
The template includes an AGENTS.md contract for coding agents plus
per-agent configs (Claude Code, Cursor, Codex, opencode), including a hook
that runs preflight --offline after node edits — see
Agents. The scaffolded MCP config (.mcp.json /
opencode.json) wires two servers out of the box: n8n-instance — your
instance’s full MCP surface through the mcp connect
guard (structure and lifecycle acts pass; Code-node jsCode writes are
blocked toward the file + push flow) — and n8n-docs, n8n’s official
read-only docs MCP.
When the sync dir is nested in a bigger repo
Everything in that scaffold assumes the agent is started in the sync dir —
which is exactly what stops being true when the sync dir is a subfolder of a
bigger project. So when a directory above the sync dir looks like a project
root (it holds a .git or a package.json), init prints — in place of the
restart reminder, on the same trigger: the run that first scaffolds those agent
files — the two shapes that actually work. (In place of, because a restart is
not the fix here: the wiring would sit below whatever dir the agent is started
in, and startup discovery only ever walks up.)
- Option A, the recommendation: start the agent in the sync dir
(
cd flows && claude). Nothing needs configuring; the only thing you give up is the parent repo’s own root.claude/settings.json(its.mcp.jsonstill loads, since that one is found by walking up). - Option B: wire the repo root, for setups where Option A isn’t possible. A
paste-ready
<repo-root>/.mcp.jsonentry (plus its opencode equivalent) carryingN8N_DECANTER_DIRand a command that resolves from the root, and a<repo-root>/.claude/settings.jsonhooks block with each script path prefixed by the sync dir. Permission rules you hoist need that same prefix — above allRead(<syncdir>/.env)/Edit(<syncdir>/.env), which stop protecting the credentials file if they are copied verbatim.
Only strict ancestors count, so the sync dir’s own scaffolded package.json
— and a git init run inside it, the shape these docs teach — never make it
look nested.
init prints the note; it never writes into a parent directory. That
parent’s .mcp.json usually carries other servers, and it is not guaranteed to
be the directory you start the agent in — neither is a merge decanter can make
on your behalf. A standalone sync dir sees none of this: with no project above
it, the note stays silent.
Why any of it is necessary — which file loads from where — is the matrix in Working with coding agents; the root MCP entry is spelled out in mcp connect.
Running init from the repo root is refused
The reverse mistake is the costly one. init started above an existing sync
dir used to scaffold a second one — decanter.config.json, the template,
workflows/, shared/, tsconfig.json, the agent configs — into a directory
you never meant to set up, and the report that prompted this fix came from a
user who had only asked for init --help. So before writing anything, init
looks a few levels down for a decanter.config.json, and refuses when it
finds one:
✗ refusing to scaffold a sync dir here
a sync dir already sits BELOW this directory: /repo/n8n
scaffolding here would put a SECOND one on top of a working setup
to use the existing one from here, name it instead of re-initing:
n8n-decanter <verb> --dir=n8n
or set N8N_DECANTER_DIR=n8n (agents: the `env` block of the decanter MCP server entry)
to set up a different sync dir, give init its own target: n8n-decanter init <dir>
In a terminal the same text comes as a question, and y scaffolds anyway. A
piped or flag-driven run refuses outright (exit 1) and leaves the directory
exactly as it found it — an unattended caller cannot consent to a scaffold.
Re-running init inside an existing sync dir is unaffected: the search
never counts the target directory itself, only what is below it.
Re-running init
init is safe to re-run — for example to pick up template improvements after
upgrading the CLI. It’s modification-aware (like dpkg conffiles): at first
init it records the hash of every template file in a git-tracked
.decanter-template.json manifest, then compares that baseline against your
working copy and the current template on each re-run:
-
Files you haven’t touched whose template version changed →
initlists them and offers to update (a singley/Nconfirm). Non-interactive runs report that updates are available and apply nothing — re-run interactively or use--force. -
Files you’ve edited locally → left untouched; reported as
left unchanged (modified locally): …. -
Files changed in both the template and your copy → left untouched; flagged as a conflict to resolve manually (or
--forceto take the template version). -
Files new to the template → copied in.
-
Files the template renamed → migrated, never silently doubled. Your copy is removed and re-scaffolded under the new name if you hadn’t touched it; if you had, it is left exactly where it is and the new name is not written (two overlapping settings files would fire their hooks twice) —
inittells you to move it, and picks up where you left off next run. A fileinitnever wrote is always left alone.--forceresolves a pending rename by removing the old file, per its “reset everything” contract.The one rename so far:
.claude/settings.local.json→.claude/settings.json(it holds shared project policy, not per-machine preferences — see Agents).
Commit .decanter-template.json — it’s the shared baseline, so a teammate who
clones and re-inits sees the same drift picture. .env is never tracked in it.
Restart your agent after init. Nothing init scaffolds for an agent
hot-loads: MCP servers (.mcp.json / opencode.json — including the guarded
n8n-instance server), permission rules (.claude/settings.json), and session
hooks are all read at agent startup. Since init is normally run from
inside the session it configures, that session keeps running unconfigured — it
cannot see the n8n-instance MCP tools, and the deny rules that keep it off
.decanter.json, .env and push --force are inert. Restart the agent (or
/reload) before working in the sync dir; init says so when it first writes
those files. Until then the scaffolded AGENTS.md is the only thing holding the
line, and it asks rather than blocks.
A restart is the fix only when the sync dir is where you start the agent.
Missing n8n-instance tools have a second cause with the opposite answer: the
sync dir is nested and the
agent was started at the repo root, so the .mcp.json init wrote sits below
the launch dir — where startup never looks. Restarting reruns the same discovery
and finds nothing again. Tell the two apart by asking whether the .mcp.json is
below the directory the agent was started in; if it is, the fix is Option A or B
above, not a restart. init knows which case you are in when it scaffolds and
says so, printing the nested options in place of the plain restart line.
Flags
--auth upstream— a proxy in front of n8n attaches the credentials (above). Refuses--token,--api-keyand--reauth.--force— the escape hatch: overwrites every template file with its template version, including ones you edited (each such file is flagged(had local changes)), then re-records the baseline..envis never touched.
The global --dir / N8N_DECANTER_DIR
(configuration)
is the one flag init does not take: it points the config search at an
existing sync dir, while init creates one and takes its target as the
positional [dir]. init --dir flows is refused rather than silently
scaffolding the working directory.