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.
Files changed (39) hide show
  1. package/CHANGELOG.md +427 -2
  2. package/README.md +146 -325
  3. package/dist/lib/add.mjs +54 -44
  4. package/dist/lib/api.mjs +36 -55
  5. package/dist/lib/backup.mjs +266 -0
  6. package/dist/lib/config.mjs +26 -7
  7. package/dist/lib/executions.mjs +54 -6
  8. package/dist/lib/git.mjs +10 -0
  9. package/dist/lib/init.mjs +306 -66
  10. package/dist/lib/lifecycle.mjs +16 -109
  11. package/dist/lib/mcp.mjs +636 -0
  12. package/dist/lib/mcpconnect.mjs +174 -0
  13. package/dist/lib/mcpserve.mjs +259 -0
  14. package/dist/lib/mirror.mjs +133 -0
  15. package/dist/lib/picker.mjs +33 -17
  16. package/dist/lib/preflight.mjs +442 -0
  17. package/dist/lib/pull.mjs +56 -36
  18. package/dist/lib/push.mjs +161 -64
  19. package/dist/lib/rename.mjs +34 -103
  20. package/dist/lib/run.mjs +88 -27
  21. package/dist/lib/simulate.mjs +417 -102
  22. package/dist/lib/skills.mjs +126 -0
  23. package/dist/lib/state.mjs +51 -1
  24. package/dist/lib/status.mjs +115 -65
  25. package/dist/lib/testrun.mjs +299 -0
  26. package/dist/lib/util.mjs +1 -1
  27. package/dist/lib/validate.mjs +97 -12
  28. package/dist/lib/watch.mjs +32 -208
  29. package/dist/n8n-decanter.mjs +530 -152
  30. package/{template/n8n-globals.d.ts.example → n8n-globals.d.ts} +16 -3
  31. package/package.json +19 -4
  32. package/template/.claude/hooks/mcp-route-check.mjs.example +72 -0
  33. package/template/.claude/{settings.local.json.example → settings.json.example} +17 -7
  34. package/template/.cursor/rules/n8n-decanter.mdc.example +11 -5
  35. package/template/.env.example +16 -8
  36. package/template/.mcp.json.example +7 -11
  37. package/template/AGENTS.md.example +316 -115
  38. package/template/CLAUDE.md.example +9 -3
  39. 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