@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/bin/pipe.js ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ // Thin launcher so `pipe` works when the package is linked or installed
3
+ // globally. Node >=22.18 strips the types in src/*.ts natively, so there is no
4
+ // build step.
5
+ //
6
+ // The path must be converted to a file:// URL before import(): on Windows a
7
+ // bare path like C:\...\src\cli.ts is rejected with
8
+ // ERR_UNSUPPORTED_ESM_URL_SCHEME, since `C:` reads as a protocol.
9
+ import { fileURLToPath, pathToFileURL } from 'node:url';
10
+ import { dirname, join } from 'node:path';
11
+
12
+ const here = dirname(fileURLToPath(import.meta.url));
13
+ await import(pathToFileURL(join(here, '..', 'src', 'cli.ts')).href);
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@pipefy/pipefy-process-coder",
3
+ "version": "0.1.0",
4
+ "description": "Pull a Pipefy pipe to disk as readable JSON, edit it with an agent, diff it, push it back.",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Guilherme-CA-Dias/pipefy-process-coder.git"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "files": [
15
+ "bin",
16
+ "src",
17
+ ".agents/skills",
18
+ "AGENTS.md"
19
+ ],
20
+ "engines": {
21
+ "node": ">=22.18"
22
+ },
23
+ "bin": {
24
+ "pipe": "bin/pipe.js"
25
+ },
26
+ "scripts": {
27
+ "pipe": "node src/cli.ts",
28
+ "typecheck": "tsc -p tsconfig.json",
29
+ "test": "node --test \"test/*.test.ts\""
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.7.0",
33
+ "typescript": "^5.6.0"
34
+ }
35
+ }
@@ -0,0 +1,150 @@
1
+ import type { Row } from '../model/payload.ts';
2
+ import type { Tree } from '../model/tree.ts';
3
+ import type { ChangeSet } from '../diff/diff.ts';
4
+ import type { Plan } from './types.ts';
5
+ import { changeByKey } from './types.ts';
6
+ import type { IdMap } from './idmap.ts';
7
+
8
+ /**
9
+ * Adopting a created entity's real id without reading the pipe back.
10
+ *
11
+ * A create mutation's response already carries the id — that is where `IdMap`
12
+ * gets it from, to substitute `%{_new:<uuid>}` into *later* steps in the same
13
+ * run (idmap.ts). This is the other half: writing that same id into the
14
+ * workspace file the entity was authored in, so it stops reading `"id": null,
15
+ * "_new": true` and a re-plan does not create it a second time.
16
+ *
17
+ * No network read is needed because nothing about *which* row this is has to
18
+ * be guessed: `change.after` (from the same ChangeSet apply.ts already
19
+ * computed) is not a copy — `diff/diff.ts` builds every `Change` by reference
20
+ * into the authored tree — so mutating it here mutates the tree that gets
21
+ * written back.
22
+ *
23
+ * Scoped to creates on purpose. An update or delete that succeeded already
24
+ * matches what the file states (that is what "succeeded" means for those
25
+ * ops); a create is the only case where the file is missing information the
26
+ * mutation response just supplied.
27
+ */
28
+
29
+ export type Adopted = { entity: string; label: string; id: number };
30
+
31
+ export const adoptCreatedIds = (changeSet: ChangeSet, plan: Plan, idMap: IdMap): Adopted[] => {
32
+ const adopted: Adopted[] = [];
33
+
34
+ for (const step of plan.steps) {
35
+ if (step.status !== 'done' || !step.produces) continue;
36
+ const id = idMap.placeholderValue(step.produces.placeholder);
37
+ if (id === undefined) continue;
38
+
39
+ const change = changeByKey(changeSet, step.changeKey);
40
+ if (!change || change.op !== 'create' || change.entity === 'repo' || !change.after) continue;
41
+
42
+ const row = change.after as Row;
43
+ if (Number(row['id']) === id && row['_new'] !== true) continue; // a resumed step, already adopted
44
+ row['id'] = id;
45
+ delete row['_new'];
46
+ adopted.push({ entity: change.entity, label: change.label, id });
47
+ }
48
+
49
+ return adopted;
50
+ };
51
+
52
+ /**
53
+ * Where an adopted row belongs in a baseline tree — additive only.
54
+ *
55
+ * A created entity is pure addition: the previous baseline has no row for it
56
+ * at all, by definition, so there is nothing to overwrite and no unapplied
57
+ * edit to lose. `entity` and `id` are enough to find it in the already
58
+ * id-and-reference-resolved tree (`patched`, the output of `idMap.rewrite`),
59
+ * because the id this run just adopted is unique within its array.
60
+ */
61
+ const findAdoptedRow = (patched: Tree, a: Adopted): Row | undefined => {
62
+ switch (a.entity) {
63
+ case 'phases':
64
+ return patched.phases.find((p) => Number(p['id']) === a.id);
65
+ case 'fields':
66
+ return patched.phases.flatMap((p) => p.fields).find((f) => Number(f['id']) === a.id);
67
+ case 'automations':
68
+ return patched.automations.find((au) => Number(au['id']) === a.id);
69
+ case 'labels':
70
+ return patched.labels.find((l) => Number(l['id']) === a.id);
71
+ case 'field_conditions':
72
+ return patched.field_conditions.find((fc) => Number(fc['id']) === a.id);
73
+ case 'webhooks':
74
+ return patched.webhooks.find((w) => Number(w['id']) === a.id);
75
+ case 'pipe_relations':
76
+ return patched.pipe_relations.find((r) => Number(r['id']) === a.id);
77
+ default:
78
+ return undefined;
79
+ }
80
+ };
81
+
82
+ /**
83
+ * Merge every adopted row into a baseline tree, in the order they were
84
+ * adopted — a new field's phase must land in `baseline.phases` before the
85
+ * field itself is pushed into it, and `plan.steps` already runs creates in
86
+ * that order (registry/compile.ts), so `adopted` does too.
87
+ *
88
+ * Mutates and returns `baseline`. Never touches a row baseline already has
89
+ * an opinion about — this is append-only, which is what makes it safe without
90
+ * a live read: the alternative (replaying an update or delete too) would
91
+ * require knowing the *current* baseline row to update, which is exactly the
92
+ * information a network read would supply and this path does not have.
93
+ */
94
+ export const mergeAdoptedIntoBaseline = (baseline: Tree, patched: Tree, adopted: Adopted[]): boolean => {
95
+ let changed = false;
96
+ for (const a of adopted) {
97
+ const row = findAdoptedRow(patched, a);
98
+ if (!row) continue;
99
+ const clone = { ...row } as Row;
100
+
101
+ switch (a.entity) {
102
+ case 'phases':
103
+ if (baseline.phases.some((p) => Number(p['id']) === a.id)) continue;
104
+ baseline.phases.push(clone as Tree['phases'][number]);
105
+ break;
106
+ case 'fields': {
107
+ const phaseId = Number(clone['phase_id']);
108
+ const phase = baseline.phases.find((p) => Number(p['id']) === phaseId);
109
+ if (!phase || phase.fields.some((f) => Number(f['id']) === a.id)) continue;
110
+ phase.fields.push(clone);
111
+ break;
112
+ }
113
+ case 'automations':
114
+ if (baseline.automations.some((au) => Number(au['id']) === a.id)) continue;
115
+ baseline.automations.push(clone as Tree['automations'][number]);
116
+ break;
117
+ case 'labels':
118
+ if (baseline.labels.some((l) => Number(l['id']) === a.id)) continue;
119
+ baseline.labels.push(clone);
120
+ break;
121
+ case 'field_conditions':
122
+ if (baseline.field_conditions.some((fc) => Number(fc['id']) === a.id)) continue;
123
+ baseline.field_conditions.push(clone as Tree['field_conditions'][number]);
124
+ break;
125
+ case 'webhooks':
126
+ if (baseline.webhooks.some((w) => Number(w['id']) === a.id)) continue;
127
+ baseline.webhooks.push(clone);
128
+ break;
129
+ case 'pipe_relations':
130
+ if (baseline.pipe_relations.some((r) => Number(r['id']) === a.id)) continue;
131
+ baseline.pipe_relations.push(clone);
132
+ break;
133
+ default:
134
+ continue;
135
+ }
136
+ changed = true;
137
+ }
138
+ return changed;
139
+ };
140
+
141
+ export const renderAdopted = (adopted: Adopted[]): string[] => {
142
+ if (!adopted.length) return [];
143
+ const lines = [
144
+ `adopted ${adopted.length} server-assigned id${adopted.length === 1 ? '' : 's'} directly from the create ` +
145
+ `response(s) — no read-back needed, so a re-plan will not create them again`,
146
+ ];
147
+ for (const a of adopted.slice(0, 8)) lines.push(` ${a.entity} ${a.label} → ${a.id}`);
148
+ if (adopted.length > 8) lines.push(` …and ${adopted.length - 8} more`);
149
+ return lines;
150
+ };
@@ -0,0 +1,71 @@
1
+ import { createAiAgent, updateAiAgent, deleteAiAgent } from './mutations.ts';
2
+ import type { StepRequest } from './types.ts';
3
+ import type { AgentChangeSet } from '../diff/agents.ts';
4
+ import type { PipefyClient } from '../pipefy/client.ts';
5
+
6
+ /**
7
+ * Compiling agent changes into GraphQL steps.
8
+ *
9
+ * Kept separate from `apply/compile.ts`, same reasoning as the flow half:
10
+ * agents are not part of the `Tree` this tool folds a pipe into (they are not
11
+ * in the snapshot payload at all — `workspace/agents.ts`), so there is no
12
+ * `EntityName` for the generic compiler to key a `Change` on. Unlike flows,
13
+ * the requests themselves are ordinary public GraphQL — `buildGraphql`,
14
+ * `apply/mutations.ts` — so this stays a thin compiler over that, not a
15
+ * second protocol.
16
+ */
17
+
18
+ export type AgentStep = {
19
+ id: string;
20
+ index: number;
21
+ changeKey: string;
22
+ op: 'create' | 'update' | 'delete';
23
+ description: string;
24
+ request: StepRequest;
25
+ destructive?: boolean;
26
+ status: 'pending' | 'running' | 'done' | 'failed' | 'skipped';
27
+ error?: string;
28
+ result?: unknown;
29
+ };
30
+
31
+ export type AgentPlan = { steps: AgentStep[] };
32
+
33
+ export const compileAgents = async (client: PipefyClient | null, cs: AgentChangeSet): Promise<AgentPlan> => {
34
+ const steps: AgentStep[] = [];
35
+ for (const [i, c] of cs.changes.entries()) {
36
+ const id = `a${String(i + 1).padStart(3, '0')}`;
37
+ if (c.op === 'create') {
38
+ steps.push({
39
+ id,
40
+ index: i,
41
+ changeKey: c.key,
42
+ op: 'create',
43
+ description: `createAiAgent — ${c.label}`,
44
+ request: await createAiAgent(client, c.after ?? {}, c.behaviors),
45
+ status: 'pending',
46
+ });
47
+ } else if (c.op === 'update') {
48
+ steps.push({
49
+ id,
50
+ index: i,
51
+ changeKey: c.key,
52
+ op: 'update',
53
+ description: `updateAiAgent — ${c.label}`,
54
+ request: await updateAiAgent(client, String(c.before?.['uuid'] ?? ''), c.after ?? {}, c.behaviors),
55
+ status: 'pending',
56
+ });
57
+ } else {
58
+ steps.push({
59
+ id,
60
+ index: i,
61
+ changeKey: c.key,
62
+ op: 'delete',
63
+ description: `deleteAiAgent — ${c.label}`,
64
+ destructive: true,
65
+ request: await deleteAiAgent(client, String(c.before?.['uuid'] ?? '')),
66
+ status: 'pending',
67
+ });
68
+ }
69
+ }
70
+ return { steps };
71
+ };