@pipefy/pipefy-process-coder 0.1.0 → 0.1.1

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 (159) hide show
  1. package/.agents/skills/ppc-pipefy-flow-authoring/SKILL.md +262 -262
  2. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-danfe-consulta.json +247 -247
  3. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-webhook-retorno-consulta.json +589 -589
  4. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-4-recebimento-barramento.json +1391 -1391
  5. package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-2-danfe-retorno.json +623 -623
  6. package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-4-criacao-operacao.json +636 -636
  7. package/.agents/skills/ppc-pipefy-flow-authoring/examples/03-subflow-4-criacao-titulo.json +3642 -3642
  8. package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-cedente.json +863 -863
  9. package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-sacado.json +799 -799
  10. package/.agents/skills/ppc-pipefy-flow-authoring/examples/README.md +42 -42
  11. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-acompanhamento-cobranca.json +581 -581
  12. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-nfe-monitoramento.json +503 -503
  13. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-retorno-bancario.json +562 -562
  14. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-cedente.json +557 -557
  15. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-sacado.json +609 -609
  16. package/.agents/skills/ppc-pipefy-pipe-authoring/SKILL.md +146 -146
  17. package/.agents/skills/ppc-pipefy-process-design/SKILL.md +127 -127
  18. package/.agents/skills/ppc-pipefy-workspace/SKILL.md +91 -91
  19. package/AGENTS.md +465 -456
  20. package/README.md +825 -807
  21. package/bin/pipe.js +25 -4
  22. package/dist/apply/adopt.js +128 -0
  23. package/dist/apply/agentops.js +42 -0
  24. package/dist/apply/compile.js +722 -0
  25. package/dist/apply/execute.js +277 -0
  26. package/dist/apply/flowops.js +293 -0
  27. package/dist/apply/idmap.js +233 -0
  28. package/dist/apply/mutations.js +704 -0
  29. package/dist/apply/registry.js +348 -0
  30. package/dist/apply/types.js +18 -0
  31. package/dist/cli/args.js +79 -0
  32. package/dist/cli.js +187 -0
  33. package/dist/codec/flow.js +164 -0
  34. package/dist/codec/pack.js +88 -0
  35. package/dist/codec/roundtrip.js +86 -0
  36. package/dist/codec/unpack.js +181 -0
  37. package/dist/commands/agents.js +145 -0
  38. package/dist/commands/apply.js +982 -0
  39. package/dist/commands/context.js +79 -0
  40. package/dist/commands/create.js +63 -0
  41. package/dist/commands/diff.js +294 -0
  42. package/dist/commands/flows.js +348 -0
  43. package/dist/commands/misc.js +528 -0
  44. package/dist/commands/plan.js +301 -0
  45. package/dist/commands/pull.js +511 -0
  46. package/dist/commands/runs.js +74 -0
  47. package/dist/commands/skills.js +131 -0
  48. package/dist/commands/verify.js +205 -0
  49. package/dist/config.js +125 -0
  50. package/dist/diff/agents.js +80 -0
  51. package/dist/diff/diff.js +983 -0
  52. package/dist/diff/flow.js +258 -0
  53. package/{src/diff/html.ts → dist/diff/html.js} +274 -322
  54. package/dist/diff/render.js +90 -0
  55. package/dist/model/payload.js +84 -0
  56. package/dist/model/tree.js +7 -0
  57. package/dist/model/volatile.js +54 -0
  58. package/dist/pipefy/agents.js +122 -0
  59. package/dist/pipefy/automations.js +155 -0
  60. package/dist/pipefy/capability.js +84 -0
  61. package/dist/pipefy/client.js +204 -0
  62. package/dist/pipefy/discovery.js +148 -0
  63. package/dist/pipefy/internal.js +315 -0
  64. package/dist/pipefy/ipaas.js +269 -0
  65. package/dist/pipefy/reconstruct.js +674 -0
  66. package/dist/pipefy/reference.js +172 -0
  67. package/dist/pipefy/snapshot.js +185 -0
  68. package/dist/pipefy/toolkit.js +158 -0
  69. package/dist/report/integrations.js +165 -0
  70. package/dist/report/run.js +355 -0
  71. package/dist/util/fsx.js +50 -0
  72. package/dist/util/git.js +25 -0
  73. package/dist/util/json.js +56 -0
  74. package/dist/util/log.js +69 -0
  75. package/dist/util/pool.js +42 -0
  76. package/dist/util/slug.js +23 -0
  77. package/dist/util/tui.js +287 -0
  78. package/dist/validate/index.js +86 -0
  79. package/dist/validate/integrity.js +370 -0
  80. package/dist/validate/reference.js +120 -0
  81. package/dist/validate/schema.js +278 -0
  82. package/dist/workspace/agents.js +245 -0
  83. package/{src/workspace/docs.ts → dist/workspace/docs.js} +367 -407
  84. package/dist/workspace/flows.js +149 -0
  85. package/dist/workspace/layout.js +146 -0
  86. package/dist/workspace/lock.js +83 -0
  87. package/dist/workspace/read.js +168 -0
  88. package/dist/workspace/reference.js +24 -0
  89. package/dist/workspace/stamp.js +234 -0
  90. package/dist/workspace/write.js +194 -0
  91. package/package.json +37 -35
  92. package/src/apply/adopt.ts +0 -150
  93. package/src/apply/agentops.ts +0 -71
  94. package/src/apply/compile.ts +0 -875
  95. package/src/apply/execute.ts +0 -336
  96. package/src/apply/flowops.ts +0 -399
  97. package/src/apply/idmap.ts +0 -241
  98. package/src/apply/mutations.ts +0 -955
  99. package/src/apply/registry.ts +0 -430
  100. package/src/apply/types.ts +0 -134
  101. package/src/cli/args.ts +0 -88
  102. package/src/cli.ts +0 -211
  103. package/src/codec/flow.ts +0 -199
  104. package/src/codec/pack.ts +0 -103
  105. package/src/codec/roundtrip.ts +0 -94
  106. package/src/codec/unpack.ts +0 -198
  107. package/src/commands/agents.ts +0 -184
  108. package/src/commands/apply.ts +0 -1144
  109. package/src/commands/context.ts +0 -119
  110. package/src/commands/create.ts +0 -79
  111. package/src/commands/diff.ts +0 -314
  112. package/src/commands/flows.ts +0 -414
  113. package/src/commands/misc.ts +0 -644
  114. package/src/commands/plan.ts +0 -331
  115. package/src/commands/pull.ts +0 -567
  116. package/src/commands/runs.ts +0 -83
  117. package/src/commands/skills.ts +0 -137
  118. package/src/commands/verify.ts +0 -253
  119. package/src/config.ts +0 -168
  120. package/src/diff/agents.ts +0 -122
  121. package/src/diff/diff.ts +0 -1130
  122. package/src/diff/flow.ts +0 -318
  123. package/src/diff/render.ts +0 -101
  124. package/src/model/payload.ts +0 -154
  125. package/src/model/tree.ts +0 -99
  126. package/src/model/volatile.ts +0 -55
  127. package/src/pipefy/agents.ts +0 -165
  128. package/src/pipefy/automations.ts +0 -219
  129. package/src/pipefy/capability.ts +0 -119
  130. package/src/pipefy/client.ts +0 -267
  131. package/src/pipefy/discovery.ts +0 -209
  132. package/src/pipefy/internal.ts +0 -380
  133. package/src/pipefy/ipaas.ts +0 -365
  134. package/src/pipefy/reconstruct.ts +0 -775
  135. package/src/pipefy/reference.ts +0 -251
  136. package/src/pipefy/snapshot.ts +0 -245
  137. package/src/pipefy/toolkit.ts +0 -200
  138. package/src/report/integrations.ts +0 -231
  139. package/src/report/run.ts +0 -475
  140. package/src/util/fsx.ts +0 -45
  141. package/src/util/git.ts +0 -32
  142. package/src/util/json.ts +0 -55
  143. package/src/util/log.ts +0 -76
  144. package/src/util/pool.ts +0 -48
  145. package/src/util/slug.ts +0 -26
  146. package/src/util/tui.ts +0 -335
  147. package/src/validate/index.ts +0 -123
  148. package/src/validate/integrity.ts +0 -387
  149. package/src/validate/reference.ts +0 -136
  150. package/src/validate/schema.ts +0 -328
  151. package/src/workspace/agents.ts +0 -290
  152. package/src/workspace/flows.ts +0 -191
  153. package/src/workspace/layout.ts +0 -165
  154. package/src/workspace/lock.ts +0 -148
  155. package/src/workspace/read.ts +0 -165
  156. package/src/workspace/reference.ts +0 -24
  157. package/src/workspace/stamp.ts +0 -301
  158. package/src/workspace/write.ts +0 -225
  159. /package/{src → dist}/pipefy/toolkit_bearer.py +0 -0
@@ -0,0 +1,269 @@
1
+ import { debug } from "../util/log.js";
2
+ /**
3
+ * A pipe's iPaaS workspace — "Advanced Automations", Activepieces underneath.
4
+ *
5
+ * Every pipe has its own workspace, and the session proves it: the identity that
6
+ * comes back has the pipe's name as `firstName` and its id as `lastName`. Flows
7
+ * and connections are scoped to it, so a flow belongs to exactly one pipe even
8
+ * though it may orchestrate across many.
9
+ *
10
+ * Authentication needs no new credential. Two hops from the token `pipe` already
11
+ * holds:
12
+ *
13
+ * 1. the INTERNAL API mints a short-lived, pipe-scoped JWT
14
+ * (`advancedAutomationsToken(repoId:)`)
15
+ * 2. `POST /managed-authn/external-token` exchanges it for a session — which
16
+ * also hands back the projectId, so nothing has to be configured
17
+ *
18
+ * The OAuth + PKCE dance in Pipefy's own gateway exists only to reach the iPaaS
19
+ * MCP tool surface. The REST API used here answers the step-2 session directly.
20
+ */
21
+ export const IPAAS_BASE = 'https://ipaas.pipefy.com/api/v1';
22
+ /**
23
+ * Operations that act on an empty request instead of rejecting it.
24
+ *
25
+ * Learned the hard way: probing the enum by sending every candidate with
26
+ * `request: {}` published and enabled a draft flow, because these four do not
27
+ * validate a request they can default. Nothing in this module may send one
28
+ * without an explicit, complete request.
29
+ */
30
+ export const ACTS_ON_EMPTY_REQUEST = new Set([
31
+ 'CHANGE_STATUS',
32
+ 'LOCK_AND_PUBLISH',
33
+ 'LOCK_FLOW',
34
+ 'CHANGE_FOLDER',
35
+ 'UPDATE_METADATA',
36
+ ]);
37
+ export class IpaasError extends Error {
38
+ status;
39
+ step;
40
+ constructor(step, status, body) {
41
+ super(`iPaaS ${step} failed (HTTP ${status}): ${body.slice(0, 300)}`);
42
+ this.name = 'IpaasError';
43
+ this.status = status;
44
+ this.step = step;
45
+ }
46
+ }
47
+ /** iPaaS is off for the org, or the caller cannot administer this pipe's automations. */
48
+ export class IpaasUnavailable extends Error {
49
+ pipeId;
50
+ constructor(pipeId, reason) {
51
+ super(reason);
52
+ this.name = 'IpaasUnavailable';
53
+ this.pipeId = pipeId;
54
+ }
55
+ }
56
+ export class IpaasClient {
57
+ internal;
58
+ base;
59
+ /** pipeId → session, because minting is two round trips and a pull does many reads. */
60
+ sessions = new Map();
61
+ /** A session lives a while, but not forever; re-mint rather than discover expiry mid-apply. */
62
+ static TTL_MS = 10 * 60 * 1000;
63
+ constructor(internal, base = IPAAS_BASE) {
64
+ this.internal = internal;
65
+ this.base = base.replace(/\/$/, '');
66
+ }
67
+ /**
68
+ * Whether this pipe has a reachable iPaaS workspace.
69
+ *
70
+ * A refusal here is `unknown` coverage, never "no flows" — the distinction the
71
+ * whole coverage doctrine rests on. Minting requires iPaaS enabled on the
72
+ * organization and permission to create automations on the pipe.
73
+ */
74
+ async available(pipeId) {
75
+ try {
76
+ const session = await this.session(pipeId);
77
+ return { ok: true, session };
78
+ }
79
+ catch (e) {
80
+ return { ok: false, reason: e instanceof Error ? e.message : String(e) };
81
+ }
82
+ }
83
+ async session(pipeId) {
84
+ const hit = this.sessions.get(pipeId);
85
+ if (hit && Date.now() - hit.at < IpaasClient.TTL_MS)
86
+ return hit.session;
87
+ const minted = await this.internal.gql(`query GetAdvancedAutomationsToken($repoId: ID!) {
88
+ advancedAutomationsToken(repoId: $repoId) { token }
89
+ }`, { repoId: String(pipeId) }, 'GetAdvancedAutomationsToken');
90
+ const token = minted?.advancedAutomationsToken?.token;
91
+ if (!token) {
92
+ throw new IpaasUnavailable(pipeId, `no advanced-automations token for pipe ${pipeId} — iPaaS may be off for this organization, ` +
93
+ 'or this credential may not be allowed to create automations on the pipe');
94
+ }
95
+ const res = await fetch(`${this.base}/managed-authn/external-token`, {
96
+ method: 'POST',
97
+ headers: { 'Content-Type': 'application/json' },
98
+ body: JSON.stringify({ externalAccessToken: token }),
99
+ });
100
+ const body = await res.text();
101
+ if (!res.ok)
102
+ throw new IpaasError('session exchange', res.status, body);
103
+ const parsed = JSON.parse(body);
104
+ if (!parsed.token || !parsed.projectId) {
105
+ throw new IpaasError('session exchange', res.status, 'the response carried no token or projectId');
106
+ }
107
+ const session = { pipeId, projectId: parsed.projectId, userId: parsed.id };
108
+ this.sessions.set(pipeId, { session, token: parsed.token, at: Date.now() });
109
+ debug(`ipaas session for pipe ${pipeId}: project ${parsed.projectId}`);
110
+ return session;
111
+ }
112
+ async token(pipeId) {
113
+ await this.session(pipeId);
114
+ return this.sessions.get(pipeId).token;
115
+ }
116
+ async request(pipeId, method, path, body) {
117
+ const token = await this.token(pipeId);
118
+ const res = await fetch(`${this.base}/${path.replace(/^\//, '')}`, {
119
+ method,
120
+ // Content-Type only with a body: a bodyless DELETE carrying it is rejected
121
+ // outright with FST_ERR_CTP_EMPTY_JSON_BODY, so deleting a flow used to fail
122
+ // on a header rather than on anything to do with the flow.
123
+ headers: { Authorization: `Bearer ${token}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) },
124
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
125
+ });
126
+ const text = await res.text();
127
+ if (res.status === 401) {
128
+ // The session aged out mid-run; mint once more before giving up.
129
+ this.sessions.delete(pipeId);
130
+ const retryToken = await this.token(pipeId);
131
+ const again = await fetch(`${this.base}/${path.replace(/^\//, '')}`, {
132
+ method,
133
+ headers: { Authorization: `Bearer ${retryToken}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) },
134
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
135
+ });
136
+ const againText = await again.text();
137
+ if (!again.ok)
138
+ throw new IpaasError(`${method} ${path}`, again.status, againText);
139
+ return (againText ? JSON.parse(againText) : null);
140
+ }
141
+ if (!res.ok)
142
+ throw new IpaasError(`${method} ${path}`, res.status, text);
143
+ return (text ? JSON.parse(text) : null);
144
+ }
145
+ // ── reads ─────────────────────────────────────────────────────────────────
146
+ async listFlows(pipeId) {
147
+ const { projectId } = await this.session(pipeId);
148
+ const out = await this.request(pipeId, 'GET', `flows?projectId=${projectId}`);
149
+ return out?.data ?? [];
150
+ }
151
+ async getFlow(pipeId, flowId) {
152
+ return this.request(pipeId, 'GET', `flows/${flowId}`);
153
+ }
154
+ async listConnections(pipeId) {
155
+ const { projectId } = await this.session(pipeId);
156
+ const out = await this.request(pipeId, 'GET', `app-connections?projectId=${projectId}`);
157
+ // Only the four columns a reference needs. A credential is never one of them.
158
+ return (out?.data ?? []).map((c) => ({
159
+ externalId: String(c['externalId'] ?? ''),
160
+ pieceName: String(c['pieceName'] ?? ''),
161
+ displayName: String(c['displayName'] ?? ''),
162
+ status: String(c['status'] ?? ''),
163
+ }));
164
+ }
165
+ async listRuns(pipeId) {
166
+ const { projectId } = await this.session(pipeId);
167
+ const out = await this.request(pipeId, 'GET', `flow-runs?projectId=${projectId}`);
168
+ return (out?.data ?? []).map((r) => ({
169
+ id: String(r['id'] ?? ''),
170
+ flowId: String(r['flowId'] ?? ''),
171
+ status: String(r['status'] ?? ''),
172
+ startTime: String(r['startTime'] ?? ''),
173
+ }));
174
+ }
175
+ /** pieceName → the version this instance offers. Instance-wide, not per project. */
176
+ async listPieces(pipeId) {
177
+ const raw = await this.request(pipeId, 'GET', 'pieces');
178
+ return Object.fromEntries((raw ?? []).filter((p) => p.name && p.version).map((p) => [p.name, p.version]));
179
+ }
180
+ /** One piece's action and trigger names, for checking a node against the catalogue. */
181
+ async pieceMembers(pipeId, pieceName) {
182
+ const meta = await this.request(pipeId, 'GET', `pieces/${encodeURIComponent(pieceName)}`);
183
+ return { actions: Object.keys(meta?.actions ?? {}), triggers: Object.keys(meta?.triggers ?? {}) };
184
+ }
185
+ // ── writes ────────────────────────────────────────────────────────────────
186
+ /**
187
+ * Create a connection with a placeholder credential.
188
+ *
189
+ * A pipe whose iPaaS workspace has no connections cannot host a flow, and the
190
+ * credential is not obtainable here: `piece-pipefy` authenticates by
191
+ * `client_credentials` against Pipefy's own OAuth token endpoint, and this
192
+ * host has no OAuth client registered for it — only for Slack. Connections are
193
+ * also hard-isolated per project: the update endpoint accepts `scope` and
194
+ * `projectIds` and returns 200 while ignoring both, and a different project's
195
+ * session gets 403 on the connection itself.
196
+ *
197
+ * So a placeholder is created instead, which is how Pipefy's own import path
198
+ * behaves — import, then remap the connection in the UI. The flow structure is
199
+ * what is being built and it does not depend on the credential working.
200
+ *
201
+ * **The host does not validate the value.** It answers 201 and marks the
202
+ * connection ACTIVE whatever is sent, so `status: ACTIVE` is not evidence that
203
+ * a connection works. Everything this tool creates is tagged `_placeholder` so
204
+ * a flow that depends on one can be reported as unreconnected, and a flow with
205
+ * a placeholder connection is never enabled.
206
+ */
207
+ async createPlaceholderConnection(pipeId, spec) {
208
+ const { projectId } = await this.session(pipeId);
209
+ const externalId = spec.externalId ?? `pipe-placeholder-${spec.pieceName.split('/').pop()}`;
210
+ const type = 'CUSTOM_AUTH';
211
+ const out = await this.request(pipeId, 'POST', 'app-connections', {
212
+ externalId,
213
+ displayName: spec.displayName,
214
+ pieceName: spec.pieceName,
215
+ projectId,
216
+ // The discriminator goes in both positions; the host wants it twice.
217
+ type,
218
+ value: {
219
+ type,
220
+ props: {
221
+ // Named so anyone reading the connection in the product knows what it is.
222
+ placeholder: 'created by pipe — reconnect this before enabling any flow',
223
+ },
224
+ },
225
+ });
226
+ return {
227
+ externalId: String(out?.['externalId'] ?? externalId),
228
+ pieceName: spec.pieceName,
229
+ displayName: spec.displayName,
230
+ status: String(out?.['status'] ?? 'UNKNOWN'),
231
+ _placeholder: true,
232
+ };
233
+ }
234
+ async deleteConnection(pipeId, connectionId) {
235
+ await this.request(pipeId, 'DELETE', `app-connections/${connectionId}`);
236
+ }
237
+ async createFlow(pipeId, displayName) {
238
+ const { projectId } = await this.session(pipeId);
239
+ const out = await this.request(pipeId, 'POST', 'flows', { projectId, displayName });
240
+ if (!out?.id)
241
+ throw new IpaasError('POST flows', 200, 'the response carried no flow id');
242
+ return out.id;
243
+ }
244
+ async deleteFlow(pipeId, flowId) {
245
+ await this.request(pipeId, 'DELETE', `flows/${flowId}`);
246
+ }
247
+ /**
248
+ * One flow operation.
249
+ *
250
+ * `IMPORT_FLOW` is refused here on purpose. It replaces the whole tree *and*
251
+ * runs the schema migration an old export needs, so a flow already in live form
252
+ * has every datapill rewritten — `{{step_1['output'].x}}` becomes
253
+ * `{{step_1['output']['output'].x}}` — and then resolves nothing at runtime,
254
+ * while every validator still calls it valid. The granular operations do not
255
+ * migrate. If an actual export ever needs importing, that is a separate path
256
+ * with its own conversion.
257
+ */
258
+ async operate(pipeId, flowId, type, request) {
259
+ if (type === 'IMPORT_FLOW') {
260
+ throw new Error('IMPORT_FLOW is not used by this tool: it migrates the tree it is given, which silently ' +
261
+ 'doubles every datapill of a flow already in live form. Use the granular operations.');
262
+ }
263
+ if (ACTS_ON_EMPTY_REQUEST.has(type) && Object.keys(request).length === 0) {
264
+ throw new Error(`${type} acts on an empty request rather than rejecting it — sending one would take a ` +
265
+ 'default nobody asked for. Supply the request explicitly.');
266
+ }
267
+ return this.request(pipeId, 'POST', `flows/${flowId}`, { type, request });
268
+ }
269
+ }