n8n-decanter 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +427 -2
- package/README.md +146 -325
- package/dist/lib/add.mjs +54 -44
- package/dist/lib/api.mjs +36 -55
- package/dist/lib/backup.mjs +266 -0
- package/dist/lib/config.mjs +26 -7
- package/dist/lib/executions.mjs +54 -6
- package/dist/lib/git.mjs +10 -0
- package/dist/lib/init.mjs +306 -66
- package/dist/lib/lifecycle.mjs +16 -109
- package/dist/lib/mcp.mjs +636 -0
- package/dist/lib/mcpconnect.mjs +174 -0
- package/dist/lib/mcpserve.mjs +259 -0
- package/dist/lib/mirror.mjs +133 -0
- package/dist/lib/picker.mjs +33 -17
- package/dist/lib/preflight.mjs +442 -0
- package/dist/lib/pull.mjs +56 -36
- package/dist/lib/push.mjs +161 -64
- package/dist/lib/rename.mjs +34 -103
- package/dist/lib/run.mjs +88 -27
- package/dist/lib/simulate.mjs +417 -102
- package/dist/lib/skills.mjs +126 -0
- package/dist/lib/state.mjs +51 -1
- package/dist/lib/status.mjs +115 -65
- package/dist/lib/testrun.mjs +299 -0
- package/dist/lib/util.mjs +1 -1
- package/dist/lib/validate.mjs +97 -12
- package/dist/lib/watch.mjs +32 -208
- package/dist/n8n-decanter.mjs +530 -152
- package/{template/n8n-globals.d.ts.example → n8n-globals.d.ts} +16 -3
- package/package.json +19 -4
- package/template/.claude/hooks/mcp-route-check.mjs.example +72 -0
- package/template/.claude/{settings.local.json.example → settings.json.example} +17 -7
- package/template/.cursor/rules/n8n-decanter.mdc.example +11 -5
- package/template/.env.example +16 -8
- package/template/.mcp.json.example +7 -11
- package/template/AGENTS.md.example +316 -115
- package/template/CLAUDE.md.example +9 -3
- package/template/opencode.json.example +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,433 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.0] - 2026-07-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`check` now warns when local work has not been registered with n8n** — a
|
|
15
|
+
node whose `//@file:` placeholder has moved off what `.decanter.json` records
|
|
16
|
+
(the shape of a `.js`→`.ts` conversion), or whose recorded file is gone from
|
|
17
|
+
disk. It stays a *warning*, not an error: `push` reconciles the file map, and
|
|
18
|
+
the compliance guard runs before that reconcile, so failing here would refuse
|
|
19
|
+
the one command that fixes it.
|
|
20
|
+
|
|
21
|
+
- **`n8n-decanter --version` prints the installed version** (`-v` too), the way
|
|
22
|
+
every CLI is expected to. It answers before any config load or verb dispatch,
|
|
23
|
+
so it works from anywhere — including outside a sync dir. Passed *alongside* a
|
|
24
|
+
verb it is a hard error naming the flag you meant, so a stray `--version`
|
|
25
|
+
can't quietly swallow a command.
|
|
26
|
+
|
|
27
|
+
- **A first `init` points at n8n's official skills pack.** Setup now closes by
|
|
28
|
+
naming [n8n-io/skills](https://github.com/n8n-io/skills) — the knowledge layer
|
|
29
|
+
that makes agentic workflow building work — and printing the install commands
|
|
30
|
+
for **Claude Code**, **Codex**, and **skills.sh**, with the agent it detects
|
|
31
|
+
from your environment listed first and the activation step each one still
|
|
32
|
+
needs. It prints; it does not install: that would mean spawning a third-party
|
|
33
|
+
CLI to mutate agent state outside the sync dir, and a plugin installed
|
|
34
|
+
mid-session isn't active until the agent reloads. Said once per sync dir (no
|
|
35
|
+
re-init repeats it), on every path including piped and `--host`-driven runs,
|
|
36
|
+
and it consumes no input — no existing script's stdin changes. *(Plan 55.)*
|
|
37
|
+
|
|
38
|
+
- **`init` can run non-interactively via `--host` / `--token` / `--api-key`.**
|
|
39
|
+
Passing any of them drives setup purely from the flags plus any existing
|
|
40
|
+
`.env` and issues **no prompt** — so a script or coding agent can bootstrap a
|
|
41
|
+
sync dir without the interactive stdin dance (the field-test agents needed
|
|
42
|
+
20+ tries to drive the old prompt path). `--host` is required in this mode
|
|
43
|
+
(a scheme-less local host is normalized to `http://`, like a typed one) and
|
|
44
|
+
wins over an existing `.env` value; `--token` sets `N8N_MCP_TOKEN` (headless
|
|
45
|
+
OAuth is still terminal-only); `--api-key` sets the optional `N8N_API_KEY`.
|
|
46
|
+
The flag-less path (interactive, or piped answers) is unchanged. *(Plan 35
|
|
47
|
+
field-test finding.)*
|
|
48
|
+
|
|
49
|
+
- **`node run` now emulates `$jmespath`.** A Code node that calls
|
|
50
|
+
`$jmespath(data, expr)` (or the `$jmesPath` alias) runs offline, matching
|
|
51
|
+
n8n's result (backed by `jmespath@0.16.0`, the version n8n pins). It also
|
|
52
|
+
fills in `$items()`/`$node` (views over the fixture's `nodes`), `$vars`/
|
|
53
|
+
`$secrets` (new fixture fields), and `$nodeId`/`$nodeVersion`/`$webhookId`.
|
|
54
|
+
|
|
55
|
+
- **`node run` fixtures gained `vars` and `secrets`** to pin the instance-scoped
|
|
56
|
+
`$vars`/`$secrets` when a node reads them.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **Breaking:** the scaffolded Claude Code settings moved from
|
|
61
|
+
**`.claude/settings.local.json` to `.claude/settings.json`**, and `init` now
|
|
62
|
+
migrates existing sync dirs. The file holds *project policy* — decanter's verb
|
|
63
|
+
permissions plus the `verify.mjs` and `mcp-route-check.mjs` hooks — with
|
|
64
|
+
nothing machine-specific in it; it was already being committed and tracked in
|
|
65
|
+
the shared `.decanter-template.json`, so `local` was the wrong scope, and it
|
|
66
|
+
squatted the one file Claude Code reserves for **your** own overrides. The
|
|
67
|
+
local slot is now yours: permission lists merge across the two files and a
|
|
68
|
+
`deny` beats an `allow`, so a local file can add to the policy but cannot
|
|
69
|
+
unblock what the project denies. On re-init, an untouched copy is moved for
|
|
70
|
+
you; a copy you edited is left exactly where it is and the new file is **not**
|
|
71
|
+
written (both would register their hooks) — `init` says what to move, and
|
|
72
|
+
`--force` resolves it by removing the old file. A `settings.local.json` that
|
|
73
|
+
`init` never wrote is never touched. *(Plan 56.)*
|
|
74
|
+
|
|
75
|
+
- **Breaking: `backup restore` takes the backup as an argument, not a flag —
|
|
76
|
+
`backup restore <workflow> [<backup>]`.** `--version <id>` and `--at <ts>`
|
|
77
|
+
are gone. The argument is a **backup ref** resolved by shape, exactly like a
|
|
78
|
+
`<workflow>` ref: paste a timestamp (or a prefix — a bare date is enough) or a
|
|
79
|
+
`versionId` (short or full), whichever column of `backup list` you have to
|
|
80
|
+
hand. `backup restore order-sync 2026-07-24` and `backup restore order-sync
|
|
81
|
+
a1b2c3d4` both just work; a ref that matches nothing is an error, never a
|
|
82
|
+
silent fall back to the latest. The retired flags fail loudly with the
|
|
83
|
+
replacement. This also un-squats `--version`, which no CLI can spend on a
|
|
84
|
+
verb-scoped meaning (see Added).
|
|
85
|
+
|
|
86
|
+
- **`check`'s success line now states its scope** —
|
|
87
|
+
`OK (local layout — status compares with n8n)` instead of a bare `OK`. `check`
|
|
88
|
+
is offline by definition, so green means "well-formed", never "live in n8n".
|
|
89
|
+
The agent guide gained the matching rule, and the `.js`→`.ts` recipe now ends
|
|
90
|
+
at `push` rather than `check` — it previously told you to stop one step short
|
|
91
|
+
of the conversion actually reaching the instance. *(Surfaced by the Plan 35
|
|
92
|
+
blind field test: three separate sessions authored code, read a green `check`
|
|
93
|
+
as "done", and never pushed.)*
|
|
94
|
+
|
|
95
|
+
- **`scenario create` / `scenario check` with no workflow now open the picker on
|
|
96
|
+
a terminal**, like every other ref-taking verb (`pull`, `push`, `backup …`, …).
|
|
97
|
+
They previously hard-errored with a usage line even on a TTY, which made them
|
|
98
|
+
the odd ones out. Piped / non-TTY runs are unchanged — still the usage error —
|
|
99
|
+
so scripts and agent harnesses never block on a prompt. *(Surfaced by the Plan
|
|
100
|
+
35 blind field test, where an agent tripped the inconsistency twice.)*
|
|
101
|
+
|
|
102
|
+
- **`node run` signposts instead of crashing on instance-scoped globals.** A
|
|
103
|
+
global whose value lives on the running instance (`$vars`/`$secrets` when
|
|
104
|
+
unpinned, `$evaluateExpression`) now throws a friendly message that names the
|
|
105
|
+
global and points to `test` (or the fixture field) — never a bare
|
|
106
|
+
`ReferenceError`. `docs/cli/node-run.md` documents the covered / partial /
|
|
107
|
+
unsupported boundary.
|
|
108
|
+
|
|
109
|
+
### Fixed
|
|
110
|
+
|
|
111
|
+
- **The documented Claude Code skills-install commands are no longer
|
|
112
|
+
copy-paste-broken.** `/plugin marketplace add` / `/plugin install` are
|
|
113
|
+
in-session slash commands, but the docs and the scaffolded `AGENTS.md`
|
|
114
|
+
presented them as shell commands. Both now show the in-session form and the
|
|
115
|
+
real shell equivalents (`claude plugin marketplace add …` /
|
|
116
|
+
`claude plugin install …`) separately, plus the post-install activation step
|
|
117
|
+
each agent needs.
|
|
118
|
+
|
|
119
|
+
- **A `.js`→`.ts` conversion is no longer reverted by a pull that fires before
|
|
120
|
+
the first TS push.** Re-pointing a node's `//@file:` placeholder to a `.ts`
|
|
121
|
+
file and swapping the source is the sanctioned way to convert a node, but a
|
|
122
|
+
`pull` landing in the window before the first TS `push` — notably the
|
|
123
|
+
on-by-default live-mirror background refresh after a structure edit — rewrote
|
|
124
|
+
the placeholder back to `.js` and left `.decanter.json` pointing at the
|
|
125
|
+
deleted `.js` file, so the next push failed with `referenced node file
|
|
126
|
+
missing`. Pull now honors the re-pointed placeholder exactly as push does
|
|
127
|
+
(they share one reconcile step). *(Plan 35 field-test finding.)*
|
|
128
|
+
|
|
129
|
+
- **`init` no longer breaks local `http` instances.** A scheme-less host typed
|
|
130
|
+
at the `n8n host:` prompt now defaults to `http://` for local addresses
|
|
131
|
+
(`localhost`, loopback, private LAN ranges, `*.local`) and `https://`
|
|
132
|
+
otherwise. Previously every scheme-less host got `https://`, so a local n8n
|
|
133
|
+
(plain http) was written to `.env` as a TLS URL and every sync/guard call
|
|
134
|
+
failed with `fetch failed`. A scheme you type is still kept as-is.
|
|
135
|
+
|
|
136
|
+
- **`n8n-globals.d.ts` no longer over-declares `$if`/`$min`/`$max`.** Those are
|
|
137
|
+
n8n *expression-language* helpers (`{{ }}` only), not Code-node globals — they
|
|
138
|
+
throw in a real Code node too — so declaring them wrongly type-checked broken
|
|
139
|
+
code. The declared surface now matches what a Code node actually sees, and is
|
|
140
|
+
single-sourced (init copies the one root file — no duplicate template copy).
|
|
141
|
+
|
|
142
|
+
- The scaffolded agent permission allowlist (`.claude/settings.json` — see the
|
|
143
|
+
move under Changed) now
|
|
144
|
+
pre-approves the read-only **`preflight`** gate, so an agent following the
|
|
145
|
+
template's recommended `edit → check → preflight → push` loop no longer stalls
|
|
146
|
+
on a permission prompt at the gate itself. Also dropped the obsolete
|
|
147
|
+
`*.remote.js` deny rule — those artifacts were removed in the Plan 32 MCP pivot.
|
|
148
|
+
|
|
149
|
+
## [0.6.0] - 2026-07-23
|
|
150
|
+
|
|
151
|
+
### Added
|
|
152
|
+
|
|
153
|
+
- **`backup` — git-native, redeployable disaster recovery.** `n8n-decanter
|
|
154
|
+
backup create <workflow>` captures the workflow's full REST export into a
|
|
155
|
+
committed, versioned `workflows/<slug>/backups/<timestamp>.<versionId>.json`
|
|
156
|
+
store — the fidelity MCP can't give (credential refs + `description` kept;
|
|
157
|
+
`pinData`/`staticData` stripped; each Code node's `jsCode` stays a `//@file:`
|
|
158
|
+
placeholder, so no code is duplicated). It **dedupes** on an unchanged
|
|
159
|
+
`versionId` and **rolling-prunes** the working set to `backupLimit` (config,
|
|
160
|
+
default 20; `0` keeps all). `backup restore <workflow> [--version <id> |
|
|
161
|
+
--at <ts>]` re-inlines the Code from `code/` and REST-POSTs a **new,
|
|
162
|
+
unpublished** workflow with **node ids preserved** — a real second version
|
|
163
|
+
history that survives the instance being lost; it prints credential-rebind
|
|
164
|
+
hints + the editor URL (publish is your next step). `backup list <workflow>`
|
|
165
|
+
shows the retained set. REST-only: needs `N8N_API_KEY`. The backup file is
|
|
166
|
+
**not** auto-committed (it carries credential refs and any embedded
|
|
167
|
+
secrets) — review it, then `git add` deliberately.
|
|
168
|
+
- **Live `workflow.json` mirror — the review snapshot refreshes itself after
|
|
169
|
+
an agent restructures a workflow through the guard.** When a structure edit
|
|
170
|
+
is forwarded through `mcp connect` / `mcp serve` (a non-blocked
|
|
171
|
+
`update_workflow`), decanter now schedules a debounced background `pull` of
|
|
172
|
+
that workflow, so the read-only `workflow.json` (+ code files + state) stays
|
|
173
|
+
fresh with **no manual `pull`**. On by default; set `"liveMirror": false` in
|
|
174
|
+
`decanter.config.json` to disable (CI / deterministic setups). It is
|
|
175
|
+
fire-and-forget (never blocks the agent's next tool call), git-gated
|
|
176
|
+
(safety-commits before pulling; skips with no git), and tracked-only. This
|
|
177
|
+
changes `mcp connect`/`serve` default behavior (additive and disable-able —
|
|
178
|
+
not breaking).
|
|
179
|
+
- **`preflight` — the whole verification ladder as one scored, read-only
|
|
180
|
+
gate.** `n8n-decanter preflight [workflow…]` runs every safe check there
|
|
181
|
+
is — local static (`layout`, `types`) → instance read-only (`connect`,
|
|
182
|
+
`access`, `parity`, `drift`, `snapshot`, `lifecycle`, `history`,
|
|
183
|
+
`capture`) → pinned draft runs (`test`, `simulate`) — ordered fast→slow,
|
|
184
|
+
streaming each result, and condenses them into a **score (0–100)** and a
|
|
185
|
+
**verdict** (`ready` / `caution` / `not ready`, exit 0/1) with per-check
|
|
186
|
+
remediation. Profiles are explicit and deterministic: `--quick` (static +
|
|
187
|
+
sync), default (+ `test`), `--full` (+ `simulate`), `--offline` (static +
|
|
188
|
+
`simulate`, no instance). It brings **executions into the gate** — auto-
|
|
189
|
+
fetching the newest capture when `N8N_API_KEY` is set (`--no-fetch` opts
|
|
190
|
+
out) and reading production run health (`history`, via MCP
|
|
191
|
+
`search_executions` or the REST fallback). Coverage is first-class: every
|
|
192
|
+
skip names its unlock, and `--require=<ids>` turns a skipped check into a
|
|
193
|
+
hard fail; `--fail-on=warn` promotes a caution to exit 1; `--fail-fast`
|
|
194
|
+
stops at the first failure. `--json` emits the full report (stable check
|
|
195
|
+
ids + remediation strings — the agent contract). **`preflight` never
|
|
196
|
+
mutates** in any profile: no push, publish, restore, or draft write —
|
|
197
|
+
`test` runs in a never-mutate mode and `simulate` headless with
|
|
198
|
+
`--network-none` forced on. The single gate to run before `push`/`publish`.
|
|
199
|
+
- **`test` — instance-side pinned test runs (the recommended runtime
|
|
200
|
+
check).** `n8n-decanter test <workflow>` runs the workflow on your
|
|
201
|
+
instance via MCP `test_workflow`: the trigger and network/credentialed
|
|
202
|
+
nodes are pinned from a capture (`--execution`, default newest) or a
|
|
203
|
+
committed scenario (`--scenario`), logic nodes execute for real on the
|
|
204
|
+
instance-exact engine, and each node's output is diffed against the
|
|
205
|
+
capture (exit 1 on divergence; `--trigger` picks the start node,
|
|
206
|
+
`--json` emits the report). The run targets the **draft** — the live
|
|
207
|
+
version is never affected. On a terminal, when local code differs from
|
|
208
|
+
the draft, `test` offers to push it first (drift-guarded, draft-only)
|
|
209
|
+
and afterwards to keep or restore the pre-test draft (n8n version
|
|
210
|
+
history when available, byte-exact write-back below n8n 2.29);
|
|
211
|
+
non-interactive runs never mutate and say when they tested the draft
|
|
212
|
+
instead of local code. `simulate` stays the offline sibling —
|
|
213
|
+
pre-push/CI/isolation/version-rehearsal — and its docs now recommend
|
|
214
|
+
`test` first.
|
|
215
|
+
- **`mcp connect` — the stdio MCP guard, auto-wired by `init`.** The default
|
|
216
|
+
way a coding agent reaches your instance's MCP server: the scaffolded
|
|
217
|
+
`.mcp.json` (and `opencode.json`) carry a static, secret-free
|
|
218
|
+
`n8n-instance` entry (`{"command":"n8n-decanter","args":["mcp","connect"]}`),
|
|
219
|
+
so guarded instance access exists the moment `init` runs — nothing to
|
|
220
|
+
start, no secret to manage (stdio pipes are private). Decanter holds the
|
|
221
|
+
credentials; the same guard rule as `mcp serve` applies (see below).
|
|
222
|
+
Structure and lifecycle acts — creating/renaming/archiving workflows,
|
|
223
|
+
adding/renaming/wiring nodes — pass through; Code-node (`jsCode`) writes
|
|
224
|
+
are blocked toward the file + `push` flow. Fail-closed on unparseable
|
|
225
|
+
input; an unreachable instance answers the agent with a JSON-RPC error
|
|
226
|
+
naming the host; logs go to stderr (stdout is protocol-only).
|
|
227
|
+
- **`mcp serve` — the same guard as a localhost HTTP proxy**, for agents
|
|
228
|
+
configured by URL: decanter holds the credentials (the
|
|
229
|
+
agent gets a per-session secret instead), every read and structure
|
|
230
|
+
operation forwards untouched (SSE included), and exactly one thing is
|
|
231
|
+
blocked — `update_workflow` calls that write Code-node source, via either
|
|
232
|
+
a `jsCode` key or a `setNodeParameter` op whose path targets `jsCode`,
|
|
233
|
+
which get an instructive "edit the file + push" tool error. Fail-closed on unparseable
|
|
234
|
+
bodies, 127.0.0.1-only, body-size cap; the running endpoint + secret land
|
|
235
|
+
in a gitignored `.decanter-proxy.json`. The template gains a
|
|
236
|
+
`mcp-route-check.mjs` session hook that nudges agents whose MCP config
|
|
237
|
+
still points at the instance directly, and the sync-dir `AGENTS.md`
|
|
238
|
+
contract is now guard-first.
|
|
239
|
+
|
|
240
|
+
### Removed
|
|
241
|
+
|
|
242
|
+
- **Breaking: the structure/lifecycle verbs are gone — `rename`, `create`,
|
|
243
|
+
`node create` (and its `--ts` flag), and `node rename`.** Those acts go
|
|
244
|
+
through **n8n itself**: the n8n editor, or n8n's MCP tools reached through
|
|
245
|
+
the new `mcp connect`/`mcp serve` guard (which is exactly what the
|
|
246
|
+
official n8n skills drive). Decanter's job is the reconcile: the next
|
|
247
|
+
`pull` re-caches a renamed workflow's name (folder stays put), renames a
|
|
248
|
+
renamed node's local file, and lands a new Code node as a source file. A
|
|
249
|
+
Code node added over MCP carries **no `jsCode`** (the guard blocks code in
|
|
250
|
+
`addNode`) — it now lands as an **empty file** whose first `push` seeds
|
|
251
|
+
the source, completing the guarded authoring loop. Two behaviors did not
|
|
252
|
+
survive the removal: `$('…')` refs inside local `.ts` sources are no
|
|
253
|
+
longer rewritten on a node rename (n8n never sees `.ts` — update them by
|
|
254
|
+
hand after the pull), and validate-before-create is now the calling
|
|
255
|
+
agent's discipline (`validate_workflow` first, as the n8n skills teach).
|
|
256
|
+
- **Breaking: the `delete` verb is gone.** Decanter no longer offers a hard
|
|
257
|
+
delete; retiring a workflow is an n8n act (archive it over MCP or in the
|
|
258
|
+
UI — reversible there, which is also where permanent deletion lives).
|
|
259
|
+
- **Breaking: the `duplicate` verb is gone.** MCP has no lossless full-JSON
|
|
260
|
+
create, so a faithful clone required the public API — rather than keep the
|
|
261
|
+
API dependency or ship a lossy SDK-code re-expression, the verb was
|
|
262
|
+
dropped. Duplicate workflows from the n8n UI and `pull` the copy.
|
|
263
|
+
- **Breaking: `watch`'s browser-reload proxy is gone — `browserReload` and
|
|
264
|
+
`proxyPort` config keys are no longer honored (silently ignored, not an
|
|
265
|
+
error).** n8n 2.x reflects an MCP draft edit in the open editor natively
|
|
266
|
+
(soft canvas re-render, skipped — with a warning — while the tab has
|
|
267
|
+
unsaved edits), making decanter's injected `<script>`-reload proxy
|
|
268
|
+
redundant and, on that exact dirty-tab path, worse than doing nothing (a
|
|
269
|
+
hard reload would have clobbered the unsaved edits). `watch` now just
|
|
270
|
+
prints the editor deep link with a note to keep the tab open; it updates
|
|
271
|
+
live on every push.
|
|
272
|
+
- **Breaking: `simulate --pin` and per-node `fixtures/` are gone — folded into
|
|
273
|
+
`scenario`.** The per-node `workflows/<folder>/fixtures/<node>.json`
|
|
274
|
+
mechanism and its precedence over captures are removed outright; a scenario
|
|
275
|
+
is now the only committed pin artifact and is always self-contained (no
|
|
276
|
+
fixture-over-capture layering to reason about). `--pin`'s job — "make a
|
|
277
|
+
clean capture reproducible" — is now `scenario create --execution <id>`. A
|
|
278
|
+
leftover `fixtures/` dir is a **hard error** from `simulate`/`check` naming
|
|
279
|
+
the replacement; there is no silent read-path or auto-migration for it
|
|
280
|
+
(unlike a leftover `mocks/` dir, which auto-migrates to `scenarios/` on
|
|
281
|
+
first touch — see the `scenario` namespace under Added).
|
|
282
|
+
|
|
283
|
+
### Fixed
|
|
284
|
+
|
|
285
|
+
- **Verb-first error hints.** Several CLI error/guidance messages suggested
|
|
286
|
+
**verb-last** commands (`n8n-decanter <ref> simulate …`,
|
|
287
|
+
`n8n-decanter <ref> executions`, `n8n-decanter <ref> scenario …`) that the
|
|
288
|
+
verb-first grammar rejects when copy-pasted; every one now prints the
|
|
289
|
+
verb-first form (`n8n-decanter simulate <workflow> …`,
|
|
290
|
+
`n8n-decanter executions <workflow>`, `n8n-decanter scenario … <workflow>`).
|
|
291
|
+
- **Refresh-token race (OAuth):** two concurrent MCP calls — or `watch` plus
|
|
292
|
+
a manual `push` sharing `.decanter-auth.json` — could both redeem the
|
|
293
|
+
single-use refresh token, killing the session for the loser ("re-run
|
|
294
|
+
init"). Concurrent calls now share one redemption, a lost cross-process
|
|
295
|
+
race recovers by re-reading the winner's rotated auth file, and auth-file
|
|
296
|
+
writes are atomic.
|
|
297
|
+
- **MCP client hardening:** a transient handshake failure no longer poisons
|
|
298
|
+
every later call in the same run; a 200-with-HTML answer (captive
|
|
299
|
+
portal/reverse proxy) gets a named error instead of a raw `SyntaxError`;
|
|
300
|
+
body-read timeouts use the friendly timeout message; a rate-limit
|
|
301
|
+
`Retry-After` is honored up to n8n's verified 5-minute window (with a
|
|
302
|
+
visible "waiting Ns" warning) and capped there against bogus-huge
|
|
303
|
+
headers; a dropped MCP session (404 with a session id) re-handshakes once
|
|
304
|
+
transparently; a token-refresh response without a
|
|
305
|
+
rotated refresh token keeps the old one; workflow lists that hit the
|
|
306
|
+
200-row page cap warn about truncation.
|
|
307
|
+
- **`init` appends `.decanter-auth.json` to a pre-existing `.gitignore`**
|
|
308
|
+
instead of only warning — the file holds the MCP refresh token.
|
|
309
|
+
- **Push verifies `.ts` nodes after the write** (marker hash vs. remote
|
|
310
|
+
body — catches server-side normalization), and watch's single-node pushes
|
|
311
|
+
run the same post-push verification as full pushes.
|
|
312
|
+
|
|
313
|
+
### Changed
|
|
314
|
+
|
|
315
|
+
- **`pull` with no argument now opens the picker on a fresh setup.** On a
|
|
316
|
+
terminal, `n8n-decanter pull` (no ref) lists your workflows — **local and
|
|
317
|
+
remote** (over MCP) — so you can pick one to pull without knowing its id or
|
|
318
|
+
pre-listing it in `decanter.config.json`; picking a not-yet-local workflow
|
|
319
|
+
pulls it fresh. Previously its no-ref picker showed only already-pulled
|
|
320
|
+
workflows, so a first-ever `pull` errored with `no workflow ids`. Piped /
|
|
321
|
+
non-interactive runs are unchanged (they pull the config `workflows` set).
|
|
322
|
+
- **The scaffolded MCP config is rebuilt around the guard + n8n's official
|
|
323
|
+
docs MCP.** `init`'s `.mcp.json` (and `opencode.json`) now wire two
|
|
324
|
+
servers: **`n8n-instance`** — the `mcp connect` guard (see Added) — and
|
|
325
|
+
**`n8n-docs`**, n8n's first-party read-only docs MCP
|
|
326
|
+
(`https://docs.n8n.io/~gitbook/mcp`, public, no auth), replacing the
|
|
327
|
+
community `n8n-mcp` server. The docs server can't reach your instance, so
|
|
328
|
+
it can't bypass the guard — live workflow access goes only through
|
|
329
|
+
`n8n-instance`. The scaffolded Claude Code allowlist pre-approves
|
|
330
|
+
`mcp__n8n-docs` plus the offline/read verbs `pull`, `scenario`, and
|
|
331
|
+
`simulate`; instance-mutating verbs still prompt.
|
|
332
|
+
- **A body-equal push now re-registers a missing `@ts-n8n` marker** — when a
|
|
333
|
+
`.ts` node's compiled code already matches the remote but the marker is
|
|
334
|
+
gone (e.g. rewritten in the UI), push writes the node anyway so it is
|
|
335
|
+
recognized as TS-managed again (previously skipped as "in sync").
|
|
336
|
+
- **Converting a `.ts` node back to `.js` is now supported symmetrically:**
|
|
337
|
+
replace the file, re-point its `//@file:` placeholder, and push — the
|
|
338
|
+
push clears the remote `@ts-n8n` marker even when the code is otherwise
|
|
339
|
+
identical, so the node stops being TS-managed (previously the stale
|
|
340
|
+
marker made the next pull resurrect the node as `.ts`).
|
|
341
|
+
- **`scenario create` strips the capture's embedded `workflowData`** — committed
|
|
342
|
+
scenarios no longer duplicate every Code node's source in git; the compliance
|
|
343
|
+
guard warns about legacy scenarios that still embed it, and it now also flags
|
|
344
|
+
Python Code nodes honestly (their `pythonCode` stays inline in
|
|
345
|
+
`workflow.json`; extraction is a planned feature).
|
|
346
|
+
- **Template refresh (from the MCP pivot):** the sync-dir `AGENTS.md`
|
|
347
|
+
contract was rewritten around the MCP boundary (Code-node source = files +
|
|
348
|
+
decanter push; structure = n8n/MCP; knowledge skills recommended) with
|
|
349
|
+
matching `.cursor` rules, and `.env.example` is OAuth-first (MCP
|
|
350
|
+
credentials primary, the API key optional with a minimal scope list).
|
|
351
|
+
- **`N8N_API_KEY` now powers only `executions` and `data-tables`** — the last
|
|
352
|
+
lifecycle verbs left the REST API, so the recommended key scopes shrink to
|
|
353
|
+
`workflow:list`, `execution:read`, `execution:list`, and the `dataTable:*`
|
|
354
|
+
read scopes (`template/.env.example` was rewritten OAuth-first to match).
|
|
355
|
+
|
|
356
|
+
- **Breaking: the workflow code path now syncs over n8n's built-in MCP server —
|
|
357
|
+
decanter is the Code-node code layer, n8n owns structure (Plan 32).**
|
|
358
|
+
`pull`/`push`/`watch`/`status`/`publish`/`unpublish` ride
|
|
359
|
+
`POST /mcp-server/http` instead of the public REST API. What that means in
|
|
360
|
+
practice:
|
|
361
|
+
- **Pushes are draft-first.** `push` writes only each Code node's `jsCode`
|
|
362
|
+
(an atomic `update_workflow` batch with merge semantics) to the workflow's
|
|
363
|
+
**draft**; the live version never changes until an explicit `publish` — or
|
|
364
|
+
the new **`push --publish`**, which combines the two. The API-era
|
|
365
|
+
"auto-publish on push to an active workflow" behavior is gone.
|
|
366
|
+
- **`workflow.json` is now a read-only structure snapshot.** Pull refreshes
|
|
367
|
+
it for review diffs and the offline tooling; nothing pushes it. The
|
|
368
|
+
whole-workflow structural hashing, the structural drift guard, watch's
|
|
369
|
+
structural-conflict prompt (`workflow.remote.json`), and the `.remote.js`
|
|
370
|
+
conflict artifacts are all gone — the only drift guard left is the
|
|
371
|
+
per-node code check (`--force` still overrides it), and remote structure
|
|
372
|
+
changes never block a push (`status` prints a snapshot-stale hint instead).
|
|
373
|
+
- **Structure acts live in n8n.** Renames, new nodes, wiring, and new
|
|
374
|
+
workflows happen in the n8n editor or over n8n's MCP tools (through the
|
|
375
|
+
guard) — n8n rewrites connections and `$('…')` references server-side,
|
|
376
|
+
node ids stay stable, and the next `pull` makes local files follow.
|
|
377
|
+
- **Requires n8n ≥ ~2.20 with MCP access enabled**, plus a per-workflow
|
|
378
|
+
"Available in MCP" opt-in. The picker shows MCP-unavailable workflows as a
|
|
379
|
+
third state (red `⊘`, sorted last) with enable guidance instead of a
|
|
380
|
+
failing pull; `list --remote` marks them (`--json` adds `mcpAvailable`)
|
|
381
|
+
and pull/push errors carry the same guidance.
|
|
382
|
+
- **The public API key becomes optional.** Only the surfaces MCP cannot
|
|
383
|
+
serve still use it: `executions` and `data-tables` fetches. The client
|
|
384
|
+
retries n8n's MCP rate limiting (429) with backoff automatically.
|
|
385
|
+
- **Breaking: `init` is OAuth-first.** `init` now connects to the instance via
|
|
386
|
+
the standard MCP OAuth flow — browser consent, then a refresh token stored
|
|
387
|
+
in a new gitignored **`.decanter-auth.json`** (rotated on every refresh) —
|
|
388
|
+
with a paste-a-token fallback (`N8N_MCP_TOKEN`, minted in n8n → Settings →
|
|
389
|
+
MCP) for piped/headless runs. The public API key prompt is now optional.
|
|
390
|
+
|
|
391
|
+
### Added
|
|
392
|
+
|
|
393
|
+
- **New `scenario` namespace — named, committed pin-data sets, captured and/or
|
|
394
|
+
schema-scaffolded.** A *gap* (a network node reached in the replay with no
|
|
395
|
+
pinned data) used to be a dead end. `scenario create <workflow> ["<slug>"]
|
|
396
|
+
[--execution <id>] [--scaffold]` writes a tracked, self-contained
|
|
397
|
+
**scenario** `workflows/<folder>/scenarios/<slug>.json` (slug defaults to the
|
|
398
|
+
execution id) and flags which nodes to fill: `--execution <id>` promotes a
|
|
399
|
+
gitignored capture and flags each remaining gap; `--scaffold` calls n8n's
|
|
400
|
+
read-only MCP tool `prepare_test_pin_data` and annotates every gap with its
|
|
401
|
+
output **JSON Schema** (no data — the tool is a schema oracle only); the two
|
|
402
|
+
compose, and a bare `--scaffold` with no capture builds a from-scratch set
|
|
403
|
+
where every pinnable node is a fill entry. You (or your IDE agent) add the
|
|
404
|
+
nodes' `runData` — **no API key, the CLI never calls a model or invents
|
|
405
|
+
values** — and replay it with `simulate --scenario <slug>` /
|
|
406
|
+
`test --scenario <slug>`. Each node's pins carry a **provenance**
|
|
407
|
+
(`capture`/`authored`/`scaffolded`); a run on a scenario with any
|
|
408
|
+
non-`capture` node is labeled "**synthetic pins — proves executability, not
|
|
409
|
+
output correctness**" (no per-node diff asserted; `--json` reports gain
|
|
410
|
+
`syntheticPins`/`provenance`), while a capture-only scenario keeps full
|
|
411
|
+
per-node diff and exit-1-on-divergence semantics. `scenario check <workflow>
|
|
412
|
+
["<slug>"]` **structurally validates** a scenario (or all of them)
|
|
413
|
+
**offline** — no Docker — with a node-named error if an item is malformed or
|
|
414
|
+
a flagged node is left empty; `simulate --scenario`/`test --scenario` run the
|
|
415
|
+
same check on load. n8n publishes no execution-data JSON Schema, so decanter
|
|
416
|
+
checks the exact shape it replays. Committed → scenario-based replays are
|
|
417
|
+
reproducible for teammates and CI; `scenario create` warns about PII and
|
|
418
|
+
refuses to overwrite an existing scenario. A `mocks/` dir from an earlier
|
|
419
|
+
unreleased build auto-migrates to `scenarios/` on first touch.
|
|
420
|
+
- **`simulate` previews multi-batch loops in the viewer.** In an interactive
|
|
421
|
+
terminal, a genuine multi-batch loop (previously a hard error) now caps the
|
|
422
|
+
loop to its first batch and opens that single iteration in the browsable
|
|
423
|
+
viewer, clearly labeled *"iteration 1 of N — not a pass/fail check."* Headless
|
|
424
|
+
/ `--json` / `--network-none` runs (scripts, CI) still hard-error, so an exit
|
|
425
|
+
code is never mistaken for a verified pass.
|
|
426
|
+
|
|
427
|
+
### Fixed
|
|
428
|
+
|
|
429
|
+
- **Value-taking flags no longer swallow a following verb.** Writing a
|
|
430
|
+
value flag in its space-separated form without a value — e.g.
|
|
431
|
+
`n8n-decanter --status pull` — used to consume the `pull` verb as the
|
|
432
|
+
flag's value and then fail with a confusing "no verb" error. Such flags
|
|
433
|
+
(`--status`, `--limit`, `--execution`, `--n8n-version`, `--scenario`,
|
|
434
|
+
`--filter`, `--search`, `--sort`) now refuse to eat a known verb and report
|
|
435
|
+
`--status needs a value (e.g. --status=success)` instead.
|
|
436
|
+
|
|
10
437
|
## [0.5.0] - 2026-07-21
|
|
11
438
|
|
|
12
439
|
### Changed
|
|
@@ -55,8 +482,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
55
482
|
single batch — it runs twice (one batch pass + the final "done" pass) while
|
|
56
483
|
every other node ran once — no longer hard-errors. The loop driver executes
|
|
57
484
|
for real to reproduce the loop, and each node's one captured run pins exactly.
|
|
58
|
-
Multi-batch loops (any node ran more than once) stay out of scope, since
|
|
59
|
-
first-run-only pinning can't feed later iterations.
|
|
60
485
|
|
|
61
486
|
### Removed
|
|
62
487
|
|