@pipefy/pipefy-process-coder 0.1.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 (92) hide show
  1. package/.agents/skills/ppc-pipefy-flow-authoring/SKILL.md +262 -0
  2. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-danfe-consulta.json +247 -0
  3. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-webhook-retorno-consulta.json +589 -0
  4. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-4-recebimento-barramento.json +1391 -0
  5. package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-2-danfe-retorno.json +623 -0
  6. package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-4-criacao-operacao.json +636 -0
  7. package/.agents/skills/ppc-pipefy-flow-authoring/examples/03-subflow-4-criacao-titulo.json +3642 -0
  8. package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-cedente.json +863 -0
  9. package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-sacado.json +799 -0
  10. package/.agents/skills/ppc-pipefy-flow-authoring/examples/README.md +42 -0
  11. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-acompanhamento-cobranca.json +581 -0
  12. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-nfe-monitoramento.json +503 -0
  13. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-retorno-bancario.json +562 -0
  14. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-cedente.json +557 -0
  15. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-sacado.json +609 -0
  16. package/.agents/skills/ppc-pipefy-pipe-authoring/SKILL.md +146 -0
  17. package/.agents/skills/ppc-pipefy-process-design/SKILL.md +127 -0
  18. package/.agents/skills/ppc-pipefy-workspace/SKILL.md +91 -0
  19. package/AGENTS.md +456 -0
  20. package/README.md +808 -0
  21. package/bin/pipe.js +13 -0
  22. package/package.json +35 -0
  23. package/src/apply/adopt.ts +150 -0
  24. package/src/apply/agentops.ts +71 -0
  25. package/src/apply/compile.ts +875 -0
  26. package/src/apply/execute.ts +336 -0
  27. package/src/apply/flowops.ts +399 -0
  28. package/src/apply/idmap.ts +241 -0
  29. package/src/apply/mutations.ts +955 -0
  30. package/src/apply/registry.ts +430 -0
  31. package/src/apply/types.ts +134 -0
  32. package/src/cli/args.ts +88 -0
  33. package/src/cli.ts +211 -0
  34. package/src/codec/flow.ts +199 -0
  35. package/src/codec/pack.ts +103 -0
  36. package/src/codec/roundtrip.ts +94 -0
  37. package/src/codec/unpack.ts +198 -0
  38. package/src/commands/agents.ts +184 -0
  39. package/src/commands/apply.ts +1144 -0
  40. package/src/commands/context.ts +119 -0
  41. package/src/commands/create.ts +79 -0
  42. package/src/commands/diff.ts +314 -0
  43. package/src/commands/flows.ts +414 -0
  44. package/src/commands/misc.ts +644 -0
  45. package/src/commands/plan.ts +331 -0
  46. package/src/commands/pull.ts +567 -0
  47. package/src/commands/runs.ts +83 -0
  48. package/src/commands/skills.ts +137 -0
  49. package/src/commands/verify.ts +253 -0
  50. package/src/config.ts +168 -0
  51. package/src/diff/agents.ts +122 -0
  52. package/src/diff/diff.ts +1130 -0
  53. package/src/diff/flow.ts +318 -0
  54. package/src/diff/html.ts +322 -0
  55. package/src/diff/render.ts +101 -0
  56. package/src/model/payload.ts +154 -0
  57. package/src/model/tree.ts +99 -0
  58. package/src/model/volatile.ts +55 -0
  59. package/src/pipefy/agents.ts +165 -0
  60. package/src/pipefy/automations.ts +219 -0
  61. package/src/pipefy/capability.ts +119 -0
  62. package/src/pipefy/client.ts +267 -0
  63. package/src/pipefy/discovery.ts +209 -0
  64. package/src/pipefy/internal.ts +380 -0
  65. package/src/pipefy/ipaas.ts +365 -0
  66. package/src/pipefy/reconstruct.ts +775 -0
  67. package/src/pipefy/reference.ts +251 -0
  68. package/src/pipefy/snapshot.ts +245 -0
  69. package/src/pipefy/toolkit.ts +200 -0
  70. package/src/pipefy/toolkit_bearer.py +137 -0
  71. package/src/report/integrations.ts +231 -0
  72. package/src/report/run.ts +475 -0
  73. package/src/util/fsx.ts +45 -0
  74. package/src/util/git.ts +32 -0
  75. package/src/util/json.ts +55 -0
  76. package/src/util/log.ts +76 -0
  77. package/src/util/pool.ts +48 -0
  78. package/src/util/slug.ts +26 -0
  79. package/src/util/tui.ts +335 -0
  80. package/src/validate/index.ts +123 -0
  81. package/src/validate/integrity.ts +387 -0
  82. package/src/validate/reference.ts +136 -0
  83. package/src/validate/schema.ts +328 -0
  84. package/src/workspace/agents.ts +290 -0
  85. package/src/workspace/docs.ts +407 -0
  86. package/src/workspace/flows.ts +191 -0
  87. package/src/workspace/layout.ts +165 -0
  88. package/src/workspace/lock.ts +148 -0
  89. package/src/workspace/read.ts +165 -0
  90. package/src/workspace/reference.ts +24 -0
  91. package/src/workspace/stamp.ts +301 -0
  92. package/src/workspace/write.ts +225 -0
package/README.md ADDED
@@ -0,0 +1,808 @@
1
+ # Pipefy Process Coder
2
+
3
+ Pull a Pipefy pipe and everything it connects to down to disk as readable JSON,
4
+ edit it with Claude, diff it, push it back.
5
+
6
+ A CLI, and only a CLI. `PLAN.md` describes an Electron app with a CLI seam inside
7
+ it; this build inverts that — `pipe` **is** the product. The desktop shell is
8
+ gone rather than deferred: the one thing a GUI bought was *seeing* a diff, and
9
+ `pipe diff --open` renders the same page in the browser with nothing to install.
10
+
11
+ > **Working on this repo with an agent?** Read [`AGENTS.md`](AGENTS.md) first —
12
+ > what the tool is for, the doctrines that make it trustworthy, the specific ways
13
+ > this API lies, and what is still unbuilt.
14
+
15
+ ```bash
16
+ pipe login # browser sign-in via the Pipefy AI toolkit
17
+ pipe doctor # what can this endpoint actually do?
18
+ pipe pull <pipeId> --out ./my-pipe # snapshot if available, GraphQL if not
19
+ cd my-pipe
20
+ pipe validate # offline, instant
21
+ pipe diff --open # see it
22
+ pipe plan --dry-run # refuses what cannot be applied
23
+ pipe apply # snapshot, apply, verify
24
+ ```
25
+
26
+ ## The two workflows
27
+
28
+ An existing pipe starts with **`pipe pull`**. A new one starts with
29
+ **`pipe create`**, which creates the pipe and then pulls it, so both land in the
30
+ same place: a workspace on disk with a baseline to diff against.
31
+
32
+ ```
33
+ EXISTING PIPE NEW PIPE
34
+ ───────────── ────────
35
+
36
+ pipe pull <pipeId> --out ./my-pipe pipe create "New pipe" --org <orgId>
37
+ │ │
38
+ │ snapshot, or GraphQL │ createPipe, then pulls it —
39
+ │ reconstruction if the org │ a new pipe is not empty, it
40
+ │ has no snapshots │ arrives with a start form
41
+ │ │ and three default phases
42
+ └──────────────┬───────────────────────┘
43
+ ▼
44
+ ./my-pipe/ pipes/<id>--<name>/… the tree you edit
45
+ .ppc/baseline/<id>.json what you diff against
46
+ AGENTS.md · MAPPING.md how to edit it, and what every id means
47
+ │
48
+ ▼
49
+ ┌──── edit the JSON ─────────────────────────────────┐
50
+ │ by hand, or with an agent reading AGENTS.md │
51
+ │ │
52
+ │ pipe validate shape + referential integrity │ offline,
53
+ │ pipe diff against the baseline │ instant,
54
+ │ pipe plan --dry-run is it all applicable? │ no network
55
+ │ │
56
+ └──── both clean? ───────────────────────────────────┘
57
+ │
58
+ ▼
59
+ pipe apply
60
+ 1. snapshot the pipe, labelled pre-apply/<ts> the rollback point
61
+ 2. execute step by step, persisting each status resumable
62
+ 3. adopt created ids from the responses no read-back needed
63
+ 4. verify for free (GraphQL), or ask first before a 2nd snapshot
64
+ │
65
+ ┌────────────┴────────────┐
66
+ ▼ ▼
67
+ clean something failed
68
+ done pipe apply --resume <plan> replay as compiled
69
+ pipe apply recompile and retry
70
+ (offered) revert everything pipe, flows, agents —
71
+ whatever this run
72
+ actually changed
73
+ ```
74
+
75
+ ### `pull` vs `export`
76
+
77
+ **They perform the same read.** Snapshot if the organization has them, GraphQL
78
+ reconstruction if not. The difference is entirely in what they leave on disk.
79
+
80
+ ```
81
+ pipe export <pipeId> --out pipe.json pipe pull <pipeId> --out ./my-pipe
82
+ ─────────────────────────────────── ─────────────────────────────
83
+
84
+ pipe.json 76K ./my-pipe/
85
+ pipes/<pipeId>--<slug>/
86
+ ONE file. The structure exactly as pipe.json
87
+ Pipefy's snapshot format has it: phases/00--start-form.json
88
+ twenty flat arrays named after phases/01--new-request.json
89
+ database tables. …7 more phase files
90
+ labels.json webhooks.json
91
+ payload.relations.fields[0] public-form.json relations.json
92
+ .label = "Some field in a late phase" preferences.json
93
+ .phase_id = <a phase id> _readonly/ _non-snapshot/
94
+ .ppc/baseline/<pipeId>.json
95
+ All 48 fields in one array, in .ppc/lock.json
96
+ whatever order the database gave AGENTS.md MAPPING.md CAPABILITIES.md
97
+ REFERENCE.md
98
+ them, each pointing at its phase .gitignore workspace.json
99
+ by id. 29 files
100
+
101
+ The same 48 fields, but each one inside
102
+ the phase file it belongs to, ordered by
103
+ index:
104
+
105
+ phases/00--start-form.json
106
+ .name = "Start form"
107
+ .fields[0].label = "Request title"
108
+ ```
109
+
110
+ So:
111
+
112
+ | | `pipe export` | `pipe pull` |
113
+ | --- | --- | --- |
114
+ | Writes | one JSON file | a workspace directory |
115
+ | Shape | the raw payload, 20 flat arrays | files organised the way you would edit them |
116
+ | Baseline to diff against | no | yes, `.ppc/baseline/<id>.json` |
117
+ | Docs for agents | no | `AGENTS.md`, `MAPPING.md`, `CAPABILITIES.md`, `REFERENCE.md` |
118
+ | Connected repos | no, one repo only | yes, `--depth` walks them |
119
+ | `validate` / `diff` / `plan` / `apply` work on it | **no** | yes |
120
+
121
+ That last row is the whole distinction. Run `pipe diff` in a directory holding
122
+ only an export and you get *"no pipe workspace found"* — because there is no
123
+ workspace, no baseline, and nothing organised to edit. **`export` gives you the
124
+ data; `pull` gives you somewhere to work.**
125
+
126
+ ### So why does `export` exist?
127
+
128
+ To carry a read from a machine that can reach the pipe to one that cannot.
129
+
130
+ An FDE often gets one session on a client's laptop, or one token that expires, or
131
+ a VPN they will not have tomorrow. `export` captures the structure as a file they
132
+ can take away. On the other side, `pull --from-file` turns that file into a real
133
+ workspace — the same 29 files, no network, no token, no second snapshot:
134
+
135
+ ```bash
136
+ # on the machine with access, once
137
+ pipe export <pipeId> --envelope --pretty --out pipe.json
138
+
139
+ # anywhere, afterwards
140
+ pipe pull --from-file pipe.json --out ./my-pipe
141
+ cd cs && pipe validate && pipe diff # the normal loop, offline
142
+ ```
143
+
144
+ It is a transport format, not a step in the workflow. If you can reach the pipe,
145
+ use `pipe pull` and skip it.
146
+
147
+ **When you do use it, pass `--envelope`.** Without it the file is a bare payload
148
+ with no coverage map, and `--from-file` then has to assume every entity group was
149
+ read in full. On an endpoint with snapshots that is true. On one without, it is
150
+ false in the dangerous direction: a group the reconstruction could not read looks
151
+ *empty*, and empty means delete. Both commands warn about it now, but the fix is
152
+ the flag.
153
+
154
+ ## Does it save tokens?
155
+
156
+ Yes, and the saving grows with the size of the pipe. Measured against the live
157
+ FDE pipe — 8 phases, 58 fields, 14 automations — with
158
+ `research/measure-read-cost.ts`, which issues the same queries the Pipefy MCP
159
+ tools issue and weighs the responses.
160
+
161
+ **Learning the pipe through MCP tool calls:**
162
+
163
+ ```
164
+ minified as the MCP sends it
165
+ get_pipe 1 call ~3,931 tok ~7,432 tok
166
+ get_automations 1 call ~405 tok ~786 tok
167
+ get_automation × each 14 calls ~2,488 tok ~4,218 tok
168
+ get_webhooks 1 call ~87 tok ~164 tok
169
+ get_field_conditions 1 call ~85 tok ~187 tok
170
+ get_pipe_relations 1 call ~75 tok ~142 tok
171
+ get_pipe_members 1 call ~64 tok ~131 tok
172
+ ─────────────────────────────────────────────────────────────
173
+ TOTAL 20 calls ~7,135 tok ~13,060 tok
174
+ ```
175
+
176
+ The right-hand column is the one that matters. An MCP result is pretty-printed
177
+ with a 2-space indent and then embedded as an escaped string inside its own JSON
178
+ envelope, which is roughly **1.8× the minified data**. Both steps are
179
+ deterministic, so that number is measured, not estimated.
180
+
181
+ **Learning the same pipe through the CLI:**
182
+
183
+ ```
184
+ pipe pull <pipeId> --out ./my-pipe ~138 tok its whole terminal output
185
+
186
+ then read only what the task needs:
187
+ MAPPING.md every id → label + phase ~2,179 tok
188
+ AGENTS.md the editing rules ~1,210 tok
189
+ one phase file, to edit its fields ~1,089 tok
190
+ ```
191
+
192
+ A pull moves 115 KB of pipe structure onto disk and puts **~138 tokens** in the
193
+ context — a fifteen-line summary. Nothing else is read until something needs it,
194
+ and then it is one file, not twenty API responses.
195
+
196
+ For a typical structural edit — orient with `MAPPING.md`, open the phase you are
197
+ changing, edit it — that is around **3,300 tokens against ~13,000**. About a
198
+ quarter. Add a second edit in the same session and the gap widens: the CLI
199
+ rereads one file, while the MCP path either re-queries or holds the whole
200
+ structure in context for the rest of the conversation.
201
+
202
+ ### Where the rest of the saving comes from
203
+
204
+ | | |
205
+ | --- | --- |
206
+ | **Diffs instead of dumps** | `pipe diff` on a real 3-field change: **148 tokens**. `pipe plan --dry-run`: **73**. `pipe validate`: **7**. The model sees what changed, never the pipe. |
207
+ | **No tool schemas** | The Pipefy MCP exposes ~250 tools. Their JSON schemas sit in context for the whole session whether used or not. `pipe` is one Bash call. |
208
+ | **Refusals cost nothing** | `pipe plan` says "`start_form_title` has no input" offline, before a single request. The MCP path finds out by sending a mutation that succeeds and silently changes nothing. |
209
+ | **Failures explain themselves** | A failed apply writes its step statuses to the plan file. Resuming reads one line, not a re-derivation of what happened. |
210
+
211
+ The honest counter-case: for **one** small read — "what phases does this pipe
212
+ have?" — the MCP is cheaper, because a pull costs a snapshot (~60s) and writes 29
213
+ files you did not need. Use the MCP to look; use `pipe` to change.
214
+
215
+ ## Does it handle integrations?
216
+
217
+ The ones with a public write path, yes — all of them, exercised live and
218
+ reverted. See `VALIDATION.md`.
219
+
220
+ | Integration | Coverage |
221
+ | --- | --- |
222
+ | **Webhooks** | create, update, delete. The full row: url, actions, headers, filters. |
223
+ | **Pipe relations** (connections) | create, update, delete, including all eight behaviour flags |
224
+ | **Cross-pipe automations** | create and delete, and update by delete+recreate. A `create_card` into another pipe with its field map is what wires the FDE pipe to the CS pipe. |
225
+ | **HTTP request automations** | the whole `send_http_request` surface: url, method, headers, body, and the four authentication params |
226
+ | **Connector fields** | read and diffed; edited inside their phase file |
227
+ | **Public form** | 14 of the 16 `PublicFormSettingsInput` fields; `html` and `css` have no input |
228
+ | **Email templates** | **read-only.** `EmailTemplate` has no Input type on this endpoint at all |
229
+ | **Email inboxes** | **read-only.** No create, update or delete exists |
230
+ | **iPaaS flows** | pulled, diffed, planned and applied by the same commands — see **iPaaS flows** below |
231
+ | **iPaaS connections** | read and referenced, never written. A missing one is created as an inert placeholder so a flow can still be built |
232
+ | **AI agents** | read separately (they are absent from the snapshot payload) and never written back |
233
+
234
+ Every run also writes the integration surface out on its own when `--report` is
235
+ passed — webhooks, relations, outbound automations, connector fields, inbound
236
+ email, and every repo the pipe reaches. See **Runs** below.
237
+
238
+ What makes this trustworthy rather than a claim: the tool refuses at plan time
239
+ when a write path does not exist, per column, rather than sending a mutation that
240
+ reports success and changes nothing. `pipe capabilities` prints the whole
241
+ registry — 29 supported paths, and the reason for each gap.
242
+
243
+ ## iPaaS flows
244
+
245
+ Pipefy's iPaaS — "Advanced Automations" — is **Activepieces** underneath, and it
246
+ is where a process does what the automation engine cannot: reach external
247
+ systems, and read pipes other than the one that triggered.
248
+
249
+ **A flow is an entity of its pipe.** Every pipe has its own iPaaS workspace, and
250
+ a pipe's own webhook URLs carry the ids of its flows — so `pipe pull` reads them
251
+ without a flag, and they land under the pipe:
252
+
253
+ ```
254
+ pipes/<pipeId>--<slug>/flows/
255
+ _meta.json coverage, projectId, counts, read time
256
+ connections.json externalId, pieceName, displayName, status
257
+ pieces.json pieceName -> version this instance offers
258
+ _readonly/flow-runs.json recent run outcomes; never written back
259
+ <flowId>--<slug>/
260
+ flow.json the flow row
261
+ version.json displayName, valid, schemaVersion, state
262
+ nodes/NN--<name>.json one node per file, in execution order
263
+ ```
264
+
265
+ A flow is a linked list on the wire — `nextAction`, `firstLoopAction`,
266
+ `children[]` — and one file per node on disk. That fold is a real
267
+ transformation rather than a move, so `link(unlink(F)) == F` is proven per flow
268
+ on every pull and in `test/flow-codec.test.ts`.
269
+
270
+ Authentication needs no new credential: the internal API mints a pipe-scoped
271
+ token and one exchange turns it into an iPaaS session, which also hands back the
272
+ project id. A pipe whose iPaaS is off gets `coverage: "unknown"` — never read as
273
+ "this pipe has no flows".
274
+
275
+ ### The two rules the flow path enforces
276
+
277
+ **Enabling a flow is refused** unless `--allow-flow-enable` is passed, and
278
+ refused outright while the project holds a placeholder connection. Enabling
279
+ points a flow at live traffic; that is never incidental.
280
+
281
+ **Every datapill is compared against what was sent.** The whole-tree write the
282
+ API also offers, `IMPORT_FLOW`, runs a schema migration on whatever it is given
283
+ — so a flow already in live form comes back with every reference doubled,
284
+ `{{step_1['output'].x}}` becoming `{{step_1['output']['output'].x}}`. That is
285
+ structurally valid, passes Pipefy's own validator, and resolves to nothing at
286
+ runtime. `pipe` never sends it, compiles to the per-node operations instead, and
287
+ reads the stored bytes back to prove nothing was rewritten.
288
+
289
+ ### Flags
290
+
291
+ ```bash
292
+ pipe pull <pipeId> # flows included
293
+ pipe diff # flows diffed offline, against the recorded baseline
294
+ pipe diff --against live # re-read the pipe instead
295
+ pipe apply # flows applied after the pipe, then verified
296
+ pipe apply --allow-flow-enable # permit a status change to ENABLED
297
+ <any of the above> --no-flows # skip the flow half entirely
298
+ ```
299
+
300
+ `PLAN-IPAAS.md` has the design and the evidence behind it; the run logs under
301
+ `workspaces/*/flows/BUILD-LOG.md` record what each build found.
302
+
303
+ ## Install
304
+
305
+ One requirement: **Node 22.18 or newer**. That version strips TypeScript types
306
+ natively, which is why there is no build step and no `dist/`.
307
+
308
+ ```bash
309
+ git clone https://github.com/Guilherme-CA-Dias/pipefy-process-coder
310
+ cd pipefy-process-coder
311
+ npm link # puts `pipe` on PATH, pointing at this folder
312
+ pipe --version # confirms which clone you are running
313
+ pipe doctor # confirms it can reach Pipefy
314
+ pipe skills install # installs this repo's agent skills for every agent you use
315
+ ```
316
+
317
+ The skills under [`.agents/skills/`](.agents/skills) — named `ppc-pipefy-*`, not
318
+ just `pipefy-*`, to stay out of the namespace the official
319
+ [pipefy/ai-toolkit](https://github.com/pipefy/ai-toolkit) already ships skills
320
+ in — are what teaches an agent (Claude Code or otherwise) how to actually use
321
+ this tool: the pull/edit/diff/apply loop, the pipe-authoring rules, the iPaaS
322
+ flow traps, when to reach for which mechanism. They only trigger for a session
323
+ opened inside this repo until installed somewhere every session reads from,
324
+ because a pulled workspace (**Where `pipe` can run**, below) usually lives
325
+ somewhere else entirely.
326
+
327
+ `pipe skills install` doesn't copy them itself — it hands off to
328
+ [`npx skills add`](https://github.com/vercel-labs/skills), which already
329
+ solves "which agent's directory" for 75+ agents (Claude Code, Cursor, Codex,
330
+ …), picking each one's own path and symlinking by default so there is one
331
+ canonical copy. Every flag after `install` is forwarded to it verbatim —
332
+ `pipe skills install -g -a claude-code -y`, `--skill`, `--copy`, and the rest
333
+ of its README apply as-is. `pipe skills list` still shows what ships with this
334
+ install, locally, with no network call.
335
+
336
+ The source it hands `npx skills add` is a **local path** — this package's own
337
+ install location on disk, resolved the same way `pipe --version` locates
338
+ `package.json` — not a GitHub reference. That means `pipe skills install`
339
+ only works for someone who already has this CLI installed some other way
340
+ (the clone above, or `npm install -g` once published); it is not a way to get
341
+ the skills without it, and this repo's visibility (public or private) is
342
+ irrelevant to it. `pipefy/ai-toolkit`'s own `npx skills add pipefy/ai-toolkit`
343
+ is the other pattern — a GitHub reference, installable with no CLI at all —
344
+ which would need this repo to be public (or private with the installer's own
345
+ git credentials already granted access) if we ever want that decoupled path
346
+ too. Nothing here does that yet.
347
+
348
+ ```
349
+ $ pipe --version
350
+ pipefy-process-coder 0.1.0
351
+ node v24.19.0 (needs >=22.18 for native type stripping)
352
+ running C:UsersyouCodepipefy-process-codersrccli.ts
353
+ ```
354
+
355
+ **No `npm install` needed to run it.** Every import in `src/` is a `node:`
356
+ builtin — child_process, crypto, fs, os, path, readline, url, util, zlib and
357
+ nothing else. Verified by deleting `node_modules` entirely and running `pipe
358
+ capabilities`, which worked. The two devDependencies (`typescript`,
359
+ `@types/node`) are only for `npm run typecheck`.
360
+
361
+ That matters for the actual use case: an FDE on a client-issued laptop, behind a
362
+ proxy that blocks the npm registry, can clone and run.
363
+
364
+ ### Where `pipe` can run
365
+
366
+ Anywhere. `npm link` symlinks a launcher into the global npm bin directory, so
367
+ `pipe` is on PATH for every shell and every folder:
368
+
369
+ ```
370
+ C:\Users\you\AppData\Roaming\npm\pipe → <the cloned repo>\bin\pipe.js
371
+ ~/.npm-global/bin/pipe → <the cloned repo>/bin/pipe.js
372
+ ```
373
+
374
+ Two consequences of it being a symlink rather than a copy: moving or deleting the
375
+ clone breaks `pipe`, and `git pull` upgrades it instantly with nothing to rebuild.
376
+ If you would rather have a copy, `npm i -g .` installs one.
377
+
378
+ Commands that need no workspace — `login`, `doctor`, `pull`, `create`, `export`,
379
+ `capabilities` — run from any directory. The rest (`validate`, `diff`, `plan`,
380
+ `apply`, `verify`, `status`, `runs`) work on a workspace, and find it by walking
381
+ **up** from the current directory looking for `workspace.json`. So either `cd`
382
+ into the workspace, or pass its path:
383
+
384
+ ```bash
385
+ cd ./my-pipe && pipe diff # found by walking up
386
+ pipe diff ./my-pipe # or name it from anywhere
387
+ ```
388
+
389
+ ### Other machines, other platforms
390
+
391
+ The package is `private: true`, so it is not on npm — distribution is the git
392
+ clone above. Same three commands on any machine.
393
+
394
+ | | |
395
+ | --- | --- |
396
+ | Node | ≥ 22.18, for native type stripping. Older Node cannot run `.ts` at all. |
397
+ | npm install | not required to run; only for `npm run typecheck` |
398
+ | Auth, easy path | `uv tool install pipefy-cli`, then `pipe login` opens a browser |
399
+ | Auth, no-uv path | `pipe login --token <api token>` — a Pipefy personal token, stored in `~/.ppc/credentials.json` |
400
+
401
+ **Not Windows-only.** Nothing in the tool assumes a shell, and every
402
+ platform-specific branch is explicit: `.exe` suffixes and `Scripts/` vs `bin/`
403
+ when locating the toolkit, `cmd /c start` vs `open` vs `xdg-open` for
404
+ `pipe diff --open`, `USERPROFILE` vs `HOME`, and `~/.ppc` from `os.homedir()`.
405
+ macOS `~/Library/Application Support/uv/tools` is looked in too.
406
+
407
+ Being honest about the limit of that claim: it has been **run** only on Windows
408
+ 11 with Node 24. The code is written for all three platforms and the tests are
409
+ pure Node, but macOS and Linux are untested rather than known-good.
410
+
411
+ ## Authentication
412
+
413
+ `pipe` does not implement Pipefy login. It borrows the session from the official
414
+ [Pipefy AI toolkit](https://github.com/pipefy/ai-toolkit), so one browser
415
+ sign-in serves this tool, the `pipefy` CLI and the Pipefy MCP server.
416
+
417
+ ```bash
418
+ uv tool install pipefy-cli # once
419
+ pipe login # runs `pipefy auth login` for you
420
+ pipe login --status # who am I, and via which source
421
+ ```
422
+
423
+ Resolution order:
424
+
425
+ | | Source | When |
426
+ | --- | --- | --- |
427
+ | 1 | `PIPEFY_TOKEN` / `PPC_TOKEN` | explicit override; CI |
428
+ | 2 | the toolkit | its own precedence: static token → service account → keychain session |
429
+ | 3 | `~/.ppc/credentials.json` | `pipe login --token <token>`, for machines without the toolkit |
430
+
431
+ The toolkit issues **five-minute** access tokens, so the credential is a
432
+ provider, not a string: it is fetched per process and re-fetched once when the
433
+ API answers 401, which is the difference between a long apply finishing and
434
+ failing at step 90. Nothing long-lived is ever written to disk by this tool.
435
+
436
+ [`src/pipefy/toolkit_bearer.py`](src/pipefy/toolkit_bearer.py) runs inside the
437
+ toolkit's own environment and prints one bearer as JSON, so the keychain
438
+ backend, the refresh grant and the credential precedence stay the toolkit's
439
+ rather than being reimplemented against a private store shape.
440
+
441
+ > **Windows note.** `pipefy auth login` fails with `WinError 1783 CredWrite` out
442
+ > of the box: Credential Manager caps a credential blob at ~2.5 KB and a Keycloak
443
+ > session is larger. The toolkit's documented unblock is
444
+ > `keychain_backend = "file"` in `%APPDATA%\pipefy\config.toml`, which stores the
445
+ > session in **plaintext** at `%APPDATA%\pipefy\keyring.cfg`. `pipefy auth logout`
446
+ > clears it.
447
+
448
+ No build step and no runtime dependencies: Node ≥ 22.18 strips the types itself,
449
+ so `node src/cli.ts` works on a clean checkout. That matters for a tool an FDE
450
+ runs on a client's laptop.
451
+
452
+ ## Two read paths, one shape
453
+
454
+ `pipe pull` uses whichever is available, and both produce the identical
455
+ snapshot-shaped payload, so the codec, diff, plan and apply are written once.
456
+
457
+ | | Snapshot | Reconstruction |
458
+ | --- | --- | --- |
459
+ | When | the org has `createRepoSnapshot` | it does not, or `--no-snapshot`, or the repo is a table |
460
+ | Cost | ~63 s and a new S3 object per pull | a handful of queries, no snapshot rows |
461
+ | Coverage | all 20 entity arrays | 12 groups; the rest are marked **unknown** |
462
+
463
+ The reconstruction path is the Change Migrator recipes, generalised. Everything
464
+ it reads, it reads the way they do:
465
+
466
+ - structure from `pipe { phases { fields } start_form_fields }` — with
467
+ `internal_id` as the payload's `id` and the public `id` as the slug, which is
468
+ what every field reference in every automation depends on
469
+ - field conditions from `pipe.fieldConditions`, split back into the four payload
470
+ arrays a snapshot would have produced
471
+ - automations from the internal `getAutomations` plus `loadAutomationToEdit` per
472
+ automation — the only read path that returns `action_params.field_map`,
473
+ `searchFor`, `responseSchema` and `aiParams`
474
+ - phase jumps from `GET /internal_api/settings/phases/:id`
475
+
476
+ **Coverage is tracked per read, and absence is not emptiness.** A reconstructed
477
+ pull cannot tell "this pipe has no webhooks" from "I have no way to read
478
+ webhooks", so absence from an incomplete read is treated as no information at
479
+ three levels:
480
+
481
+ | Level | Measured on a real pipe (8 phases, 58 fields, 14 automations) | Without the rule |
482
+ | --- | --- | --- |
483
+ | group | `visibilities` has no public query | every reconstructed tree "empties" them |
484
+ | column | the public API has no `color_uuid`, `only_admin_can_move_to_previous` | ~10 phantom changes per phase, each proposing null |
485
+ | row | the internal automations list returns **13 of 15** | two live automations proposed for deletion |
486
+ | collection | no `field_map` for `update_card_field` automations | "delete every field mapping" |
487
+
488
+ Suppressions are reported, never silent: they appear under *not diffed* with the
489
+ row named. The rules are the subject of [test/coverage.test.ts](test/coverage.test.ts).
490
+
491
+ ### Verified by reading the same pipe twice
492
+
493
+ The two paths are checked against each other — snapshot vs reconstruction of one
494
+ live pipe. They now agree on the repo row, all 9 phases, all 55 fields, field
495
+ conditions, labels and webhooks. The only remaining difference is real: the
496
+ snapshot payload carries **orphaned `phase_jumps`** pointing at phases that no
497
+ longer exist, and the live endpoint does not. The compiler drops those out of the
498
+ set-replacing write rather than resending them.
499
+
500
+ ## Progress
501
+
502
+ `pull`, `apply` and `apply --dry-run` show a live task list: one line per repo
503
+ or plan step, with a spinner, elapsed time and the current stage. It exists
504
+ because a snapshot takes ~63 s and silence for a minute is indistinguishable from
505
+ a hang.
506
+
507
+ Two renderers, chosen by whether stderr is a terminal. The plain one is not a
508
+ fallback — it is what CI, `2>file` and a backgrounded run get:
509
+
510
+ - **live**: repaints a block in place, capped to a window so a 200-step plan
511
+ cannot scroll the terminal, and restores the cursor on Ctrl-C
512
+ - **plain**: one line per task outcome, plus a heartbeat every 15 s for work that
513
+ is still running, and no cursor control at all
514
+
515
+ While a display is up it owns the screen: `log.ts` routes ordinary output through
516
+ it, so an interleaved write cannot land inside a repaint. Set `PPC_NO_TUI=1` to
517
+ force plain. Everything goes to stderr, so `--json` on stdout stays clean.
518
+
519
+ ## The diff
520
+
521
+ `pipe diff` is modelled on what the recipes do: one change list per entity type,
522
+ each row tagged create / update / delete, walked in dependency order.
523
+
524
+ ```bash
525
+ pipe diff # against the last pull
526
+ pipe diff --against live # re-read the pipe now
527
+ pipe diff --against 302208097 # cross-repo: a dev→prod migration diff
528
+ pipe diff --open # the HTML view
529
+ ```
530
+
531
+ `--against <another pipeId>` is the recipes' actual job. It runs in **cross-repo** mode,
532
+ where matching falls back to the natural key (phase name, phase + field label,
533
+ automation name) exactly as the recipes match, and the resulting id map is what
534
+ lets embedded field references be rewritten on apply — the work the recipes do
535
+ with a Pipefy table keyed `dev_pipe_field_internal_id` and one API call per id.
536
+
537
+ Same-repo mode matches on uuid, then id, which is exact and needs no lookups.
538
+
539
+ ## What it refuses, and why that is the point
540
+
541
+ `pipe plan` classifies every change against a hand-maintained registry
542
+ (`src/apply/registry.ts`) and **blocks** on anything unsupported, naming the
543
+ entity and the reason. The worst failure mode available to this tool is an apply
544
+ that reports success having silently ignored something.
545
+
546
+ | Refused | Why |
547
+ | --- | --- |
548
+ | a field's `type_id` | `updatePhaseField` has no `type_id`; delete + create destroys every value |
549
+ | a field's `phase_id` | same, and the diff reconciles the delete/create pair into one refusal |
550
+ | reordering phases | `createPhase` takes an index, `updatePhase` does not |
551
+ | anything under `_readonly/` | no public write path — rejected on the path alone |
552
+ | deleting the start form | it is a real phase at index 0 |
553
+ | deleting a phase | needs `--allow-card-moves`: cards must move first |
554
+
555
+ `pipe capabilities` prints the whole registry. Each refusal names the platform ask
556
+ that would remove it (`ROUTE-B-V2.md` §8).
557
+
558
+ ## Safety
559
+
560
+ - `pack(unpack(P)) ≡ P` under a canonical form, checked on every pull and
561
+ runnable as `pipe roundtrip`. A codec that loses data is caught before it can
562
+ corrupt a pipe, not after.
563
+ - Validation is two passes — shape, then referential integrity — and
564
+ distinguishes **pre-existing** problems from introduced ones. Pipe <pipeId>
565
+ ships with five `phase_jumps` pointing at phases that no longer exist; without
566
+ that distinction a freshly pulled, unedited workspace fails validation.
567
+ - `pipe apply` snapshots first (labelled `pre-apply/<ts>`), persists every step's
568
+ status to `.ppc/plans/`, so a failure is resumable with `--resume`.
569
+ - Ids that do not exist yet are placeholders resolved at send time, deep-walked
570
+ through the opaque JSON that `action_params`, `event_params`, `search_for` and
571
+ condition values really are.
572
+ - **Verification asks whether the pipe satisfies the intent**, not whether two
573
+ trees are identical. A newly authored entity is a partial specification: the
574
+ author writes what they care about and the server fills `slug`, `card_synced`,
575
+ `unique` and the connector flags. Comparing full rows failed a correct apply
576
+ with seven differences per created field.
577
+ - **The loop is idempotent, and no longer needs a read-back to stay that way.**
578
+ A create mutation's own response already carries the id — the same one
579
+ `%{_new:<uuid>}` substitution for later steps in the same run gets it from —
580
+ so `pipe apply` writes it straight into the file and the baseline, no network
581
+ read required. A created entity otherwise still reads `"id": null, "_new":
582
+ true"` where it was written, and the next apply would make a duplicate.
583
+ - **A second, live-read snapshot is never taken on its own initiative.** That
584
+ read-back is exactly what used to make the pre-apply rollback snapshot stop
585
+ being restorable the moment the apply that took it finished (`pipe rollback`
586
+ only restores the *latest* one). Verification instead defaults to a free
587
+ GraphQL reconstruction; a full snapshot-based verify-and-adopt is offered
588
+ as a yes/no prompt (default no), or `--post-snapshot` to skip asking.
589
+ - **On a failed or partial apply, `pipe apply` offers to roll back instead** —
590
+ to the pre-apply snapshot, which is normally still the latest one now that
591
+ nothing else has snapshotted the repo in between. Declining (the default)
592
+ leaves the record for `pipe apply --resume` or a manual `pipe rollback`.
593
+ The "is it still the latest" check this depends on was broken until it was
594
+ actually exercised against a real failure — see `Pipe.snapshots(last: N)`
595
+ above — and is now verified live: a real API rejection, a real restorable
596
+ check, a real `restoreRepoToSnapshot` that reaches `SUCCEEDED`.
597
+ - **One apply, one revert question — not three mechanisms scoped separately.**
598
+ On any failure partway — pipe, flow, or agent — `pipe apply` asks exactly
599
+ once whether to revert *everything* that run changed, not just whichever
600
+ half happened to error. Each part is reverted the way its platform actually
601
+ allows: pipe structure via `restoreRepoToSnapshot` (Pipefy's own mechanism,
602
+ only offered while that snapshot is still the latest); flows and agents —
603
+ neither ever had a Pipefy snapshot — by diffing the current live state
604
+ against the read taken right before this apply touched it and sending the
605
+ reverse plan through the same compiler every ordinary apply uses. Rendered
606
+ in full before the one question is asked; reverted, on confirmation, in the
607
+ reverse of the order they were applied. Still true regardless: no revert
608
+ un-sends an email or un-fires a webhook an automation already triggered —
609
+ that's the platform's own irreversibility, not something a bigger rollback
610
+ fixes. Verified live: a real API rejection, the correct reverse plan, and
611
+ the flow back to its exact original chain once applied.
612
+ - No draft rehearsal: `createRepoDraft` does not resolve on this endpoint, and
613
+ the draft's relation semantics could mutate production connected repos. It
614
+ slots in ahead of the pre-apply snapshot when it becomes available.
615
+
616
+ ## What the live endpoint actually answers
617
+
618
+ Checked against `api.pipefy.com` and pipe <pipeId>, not assumed. Several of
619
+ these contradict `PLAN.md`, which is why the read paths are introspection-driven
620
+ rather than hard-coded.
621
+
622
+ | | Reality |
623
+ | --- | --- |
624
+ | `createRepoSnapshot`, `renameRepoSnapshot`, `createPresignedUrl` | present |
625
+ | `restoreRepoSnapshot`, `createRepoDraft`, `importRepoSnapshot` | **absent** — no atomic rollback, no rehearsal, no Route B |
626
+ | the 28 mutations the registry needs | all present |
627
+ | `Pipe.snapshots` | a Relay `RepoSnapshotConnection` (`nodes`), not a flat array |
628
+ | `Pipe.snapshots(last: N)` | **not the most recent N.** Measured on a pipe with 86 snapshots: `last: 1` returned `sequenceIndex: 1`, the *oldest* one; `first: 1` correctly returned `sequenceIndex: 86`. The connection is ordered newest-first internally, the opposite of the usual Relay convention — `SnapshotClient.list()` now asks with `first`. This silently broke `pipe rollback`'s and `pipe apply`'s "is this still the latest snapshot" check, which used `last` and compared against the oldest one instead |
629
+ | `Pipe.snapshot(versionId:)` | takes `ID`; declaring `String!` fails the query |
630
+ | `RepoSnapshotStatus` | `PENDING \| UPLOADED \| FAILED` — **UPLOADED** is success |
631
+ | `pipe(id:)` vs `table(id:)` | **both answer for every repo**; `Table.url` (`/pipes/` vs `/apollo_databases/`) is the only discriminator |
632
+ | phase fields | type `PhaseField`; `synced_with_card`, `canCreateNewConnected`, `canConnectExisting`, `canConnectMultiples`, no `unique` |
633
+ | `Field.connectedRepo` | a `PublicRepoUnion` of `PublicPipe \| PublicTable` |
634
+ | `Phase.next_phase_ids` | public, but **forward jumps only** — not the payload's `phase_jumps` |
635
+ | `GET settings/phases/:id` | answers `data.attributes.jump_targets`; the **PUT** answers `data.next_phase_ids` — different shapes |
636
+ | internal `AutomationActionParams` | has `taskParams`, `aiBehaviorParams`, `authenticationType` that the Change Migrator query omits |
637
+ | `CreatePhaseFieldInput` | has **no `uuid`** — the server mints it, so a client uuid is only a local handle |
638
+ | renaming a field | does **not** regenerate the slug. `triage_owner` survived a rename to "Triage owner (FDE)", so `PLAN.md` §9's worry about breaking slug-keyed integrations does not apply |
639
+
640
+ ## Deviations from PLAN.md
641
+
642
+ Three, all deliberate:
643
+
644
+ 1. **CLI only.** The Electron shell of `PLAN.md` phase 3 was dropped, not built:
645
+ `pipe diff --open` opens the HTML view in the browser.
646
+ 2. **A phase's jump set lives in its phase file** as `jump_target_ids`, not in a
647
+ separate `phase-jumps.json`. The internal endpoint is per-phase and
648
+ set-replacing, so a set is the honest shape and there is no second file to
649
+ keep consistent.
650
+ 3. **`webhooks.json` is one array file**, matching `labels.json`, rather than a
651
+ directory.
652
+
653
+ ## Layout
654
+
655
+ ```
656
+ src/
657
+ cli.ts command dispatch
658
+ config.ts endpoints, token resolution, limits
659
+ pipefy/ graphql client · internal API adapter · snapshots ·
660
+ reconstruction · discovery · capability detection
661
+ codec/ unpack · pack · round-trip comparator
662
+ workspace/ layout · write · read · lock · generated docs
663
+ validate/ shape · referential integrity
664
+ diff/ change set · text render · HTML view
665
+ apply/ registry · mutation builders · compiler · id map · executor ·
666
+ flowops (iPaaS operations)
667
+ report/ run records · the integration surface of a pipe
668
+ commands/ one file per command
669
+ test/ codec, workspace, diff/plan, id map
670
+ research/snap.json the pipe <pipeId> fixture every test runs against
671
+ ```
672
+
673
+ ## Commands
674
+
675
+ | | |
676
+ | --- | --- |
677
+ | `pipe --version` | version, Node version, and which clone is on PATH |
678
+ | `pipe login` | store a token in `~/.ppc/credentials.json` |
679
+ | `pipe doctor` | capabilities, missing mutations, internal-API status |
680
+ | `pipe skills` | the agent skills this install ships with; `install` hands off to `npx skills add` |
681
+ | `pipe create <name>` | create an empty pipe and pull it, ready to be built from files |
682
+ | `pipe pull <id>` | pull into a workspace; `--depth`, `--reuse-snapshot`, `--from-file` |
683
+ | `pipe export <id>` | one repo as a single payload, no workspace, no token needed to consume |
684
+ | `pipe validate` | offline shape + integrity |
685
+ | `pipe diff` | baseline, live, or cross-repo; `--html`, `--open`, `--json` |
686
+ | `pipe plan` | classify and order; works offline for classification |
687
+ | `pipe apply` | snapshot, execute, adopt ids, verify for free; `--resume`, `--dry-run`, `--post-snapshot` |
688
+ | `pipe verify` | read back and diff against the workspace |
689
+ | `pipe rollback` | restore a snapshot (needs `restoreRepoSnapshot`) |
690
+ | `pipe status` | what is in the workspace and how it was read |
691
+ | `pipe capabilities` | the write-coverage registry |
692
+ | `pipe runs` | every run recorded in the workspace; `--last`, `--show`, `--failed` |
693
+ | `pipe roundtrip` | codec self-check |
694
+
695
+ `pipe export` and `pipe pull --from-file` pair up — see **The two workflows**
696
+ above for when that is the right shape, and why `--envelope` matters.
697
+
698
+ ## Runs
699
+
700
+ Off by default. Pass `--report` to a pull, apply, dry run or verify and it
701
+ writes a folder into the workspace recording what happened:
702
+
703
+ ```
704
+ runs/index.md one line per run, oldest first
705
+ runs/<when>--<kind>/report.md what happened, readable, no credentials
706
+ runs/<when>--<kind>/run.json the same thing structured
707
+ runs/<when>--<kind>/pipe.json the payload as of the end of that run
708
+ runs/<when>--<kind>/integrations.json webhooks, relations, cross-repo automations
709
+ ```
710
+
711
+ The reason this is a file and not terminal scrollback is that the write path is
712
+ non-atomic. When an apply half-succeeds, the plan file already says what the
713
+ tool meant to send; the run folder says which of those calls landed, in what
714
+ order, with what error, and what the pipe looked like on either side — an apply
715
+ also writes `pipe.before.json`, so both sides of the change sit in one place.
716
+
717
+ `integrations.json` is the outward-facing surface pulled out on its own:
718
+ webhooks, pipe relations, automations that reach another repo, connector fields,
719
+ inbound email. Those are the parts of a change whose blast radius leaves the
720
+ pipe, and the parts that carry credentials — so `report.md` names hosts and ids
721
+ and never a secret, while the two payload copies are covered by the generated
722
+ `.gitignore`.
723
+
724
+ It is opt-in because a run folder is a full copy of the pipe: one per command
725
+ would grow the workspace without bound and put webhook URLs and HTTP-request
726
+ credentials in one more place. The record an apply *needs* is the plan file
727
+ under `.ppc/plans/`, which is written either way and is what `--resume` reads.
728
+
729
+ A finished plan carries its outcome in the filename, so the directory reads as a
730
+ history without opening anything:
731
+
732
+ ```
733
+ .ppc/plans/
734
+ 2026-09-01T20-14-12Z--945e060b--applied.json
735
+ 2026-09-01T20-04-15Z--29374430--partial.json
736
+ 2026-09-01T20-00-46Z--9588c4ac--failed.json
737
+ 2026-09-01T19-58-02Z--cdf84d72.json <- no suffix: never applied
738
+ ```
739
+
740
+ The plan id does not change, so `pipe apply --resume <id>` keeps working whatever
741
+ the file is called.
742
+ `runs/` is for the times you want the human account kept — a migration someone
743
+ will ask about later, a change with an audit trail, a sequence of runs compared
744
+ side by side.
745
+
746
+ ```bash
747
+ pipe apply --report # keep the account of this one
748
+ pipe runs # what has been recorded in this workspace
749
+ pipe runs --last # the newest report in full
750
+ pipe runs --failed # only the ones that did not finish
751
+ ```
752
+
753
+ ## Secrets
754
+
755
+ A pulled workspace contains webhook URLs — possession is authorization — and
756
+ HTTP-request automations can carry live credentials in `action_params`. A
757
+ `.gitignore` is generated on pull and the first pull warns. Do not commit a
758
+ workspace.
759
+
760
+ ## Tests
761
+
762
+ ```bash
763
+ npm test # node --test, no test framework to install
764
+ npm run typecheck # tsc --noEmit, strict
765
+ ```
766
+
767
+ Every test runs against `research/snap.json`, the real pipe <pipeId> snapshot
768
+ captured during the research in `PLAN.md`.
769
+
770
+
771
+
772
+ ## Internal
773
+ ```bash
774
+ pipe pull <pipeId> --out ./my-pipe # ~63s per repo (snapshot generation)
775
+ cd my-pipe-request
776
+ # ...edit the JSON, with Claude or by hand...
777
+ pipe validate # offline, instant
778
+ pipe diff # or: pipe diff --open for the HTML view
779
+ pipe plan --dry-run # refuses what can't be applied, before anything is sent
780
+ pipe apply # snapshot → apply → verify → sync the files back
781
+ ```
782
+
783
+ Three things that differ from a plain git workflow
784
+
785
+ The diff has three modes, and only the first is "what did I change":
786
+ pipe diff # vs .ppc/baseline — your edits
787
+ pipe diff --against live # re-read the pipe now — did someone else touch it?
788
+ pipe diff --against 302208097 # cross-repo: a dev→prod migration diff
789
+ pipe diff --open # HTML view
790
+
791
+ pipe plan will refuse things, by design — a field's type_id, a field's phase_id, reordereadonly/. It names the entity and the reason rather than applying a partial change andreporting success.
792
+
793
+ A successful pipe apply rewrites your files. It adopts each created entity's real id straight from the create response — no read-back needed — because a created entity is written as "id": null, "_new": true and a second apply would otherwise duplicate it. Verification then runs for free against a GraphQL reconstruction unless you ask for a full snapshot-based one (pipe apply --post-snapshot), since a second snapshot is what used to make the pre-apply rollback point stop being restorable. A failed or partial apply offers one combined revert covering everything that run actually changed — pipe structure via the pre-apply snapshot, plus flows and agents via their own reverse plan, since neither ever had a Pipefy snapshot to begin with. Rendered in full before asking once; declining leaves the files untouched so --resume works. It cannot un-send an email or un-fire a webhook an automation already triggered — that's the platform's own irreversibility, not something the revert can reach.
794
+
795
+
796
+ ## TODO
797
+
798
+ [] - Define dynamic content in the construction
799
+ [] - Index the workspace faster so it doesnt consume much tokens
800
+ [x] - After applying, update the baseline
801
+ [] - Add verbose config
802
+ [x] - add Databases
803
+ [] - add possibility to add pipe to a workspace
804
+ [] - Make the apply verbose on plan items
805
+ [] - Check the baseline is not stale before planning: compare the pulled version id against the live one, and refuse to propose deletions when the pipe has moved since the pull. A stale baseline turns someone else's changes into a diff — seen live, where a verification diff listed five changes made elsewhere as deletions the workspace wanted
806
+ [] - pipe pull inside a workspace pulls the pipes automatically
807
+ [] - One design point worth stating
808
+ Each repo still applies independently — own plan file, own pre-apply snapshot, own verify. That's deliberate: they're separate repos with separate baselines, and a failure in one must not roll back or abandon another that already succeeded. So it's a loop over the real thing, not a merged mega-plan. Exit code is the worst of them. --plan and --resume stay single-repo, since a saved plan belongs to exactly one.