@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.
- package/.agents/skills/ppc-pipefy-flow-authoring/SKILL.md +262 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-danfe-consulta.json +247 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-webhook-retorno-consulta.json +589 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-4-recebimento-barramento.json +1391 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-2-danfe-retorno.json +623 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-4-criacao-operacao.json +636 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/03-subflow-4-criacao-titulo.json +3642 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-cedente.json +863 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-sacado.json +799 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/README.md +42 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-acompanhamento-cobranca.json +581 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-nfe-monitoramento.json +503 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-retorno-bancario.json +562 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-cedente.json +557 -0
- package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-sacado.json +609 -0
- package/.agents/skills/ppc-pipefy-pipe-authoring/SKILL.md +146 -0
- package/.agents/skills/ppc-pipefy-process-design/SKILL.md +127 -0
- package/.agents/skills/ppc-pipefy-workspace/SKILL.md +91 -0
- package/AGENTS.md +456 -0
- package/README.md +808 -0
- package/bin/pipe.js +13 -0
- package/package.json +35 -0
- package/src/apply/adopt.ts +150 -0
- package/src/apply/agentops.ts +71 -0
- package/src/apply/compile.ts +875 -0
- package/src/apply/execute.ts +336 -0
- package/src/apply/flowops.ts +399 -0
- package/src/apply/idmap.ts +241 -0
- package/src/apply/mutations.ts +955 -0
- package/src/apply/registry.ts +430 -0
- package/src/apply/types.ts +134 -0
- package/src/cli/args.ts +88 -0
- package/src/cli.ts +211 -0
- package/src/codec/flow.ts +199 -0
- package/src/codec/pack.ts +103 -0
- package/src/codec/roundtrip.ts +94 -0
- package/src/codec/unpack.ts +198 -0
- package/src/commands/agents.ts +184 -0
- package/src/commands/apply.ts +1144 -0
- package/src/commands/context.ts +119 -0
- package/src/commands/create.ts +79 -0
- package/src/commands/diff.ts +314 -0
- package/src/commands/flows.ts +414 -0
- package/src/commands/misc.ts +644 -0
- package/src/commands/plan.ts +331 -0
- package/src/commands/pull.ts +567 -0
- package/src/commands/runs.ts +83 -0
- package/src/commands/skills.ts +137 -0
- package/src/commands/verify.ts +253 -0
- package/src/config.ts +168 -0
- package/src/diff/agents.ts +122 -0
- package/src/diff/diff.ts +1130 -0
- package/src/diff/flow.ts +318 -0
- package/src/diff/html.ts +322 -0
- package/src/diff/render.ts +101 -0
- package/src/model/payload.ts +154 -0
- package/src/model/tree.ts +99 -0
- package/src/model/volatile.ts +55 -0
- package/src/pipefy/agents.ts +165 -0
- package/src/pipefy/automations.ts +219 -0
- package/src/pipefy/capability.ts +119 -0
- package/src/pipefy/client.ts +267 -0
- package/src/pipefy/discovery.ts +209 -0
- package/src/pipefy/internal.ts +380 -0
- package/src/pipefy/ipaas.ts +365 -0
- package/src/pipefy/reconstruct.ts +775 -0
- package/src/pipefy/reference.ts +251 -0
- package/src/pipefy/snapshot.ts +245 -0
- package/src/pipefy/toolkit.ts +200 -0
- package/src/pipefy/toolkit_bearer.py +137 -0
- package/src/report/integrations.ts +231 -0
- package/src/report/run.ts +475 -0
- package/src/util/fsx.ts +45 -0
- package/src/util/git.ts +32 -0
- package/src/util/json.ts +55 -0
- package/src/util/log.ts +76 -0
- package/src/util/pool.ts +48 -0
- package/src/util/slug.ts +26 -0
- package/src/util/tui.ts +335 -0
- package/src/validate/index.ts +123 -0
- package/src/validate/integrity.ts +387 -0
- package/src/validate/reference.ts +136 -0
- package/src/validate/schema.ts +328 -0
- package/src/workspace/agents.ts +290 -0
- package/src/workspace/docs.ts +407 -0
- package/src/workspace/flows.ts +191 -0
- package/src/workspace/layout.ts +165 -0
- package/src/workspace/lock.ts +148 -0
- package/src/workspace/read.ts +165 -0
- package/src/workspace/reference.ts +24 -0
- package/src/workspace/stamp.ts +301 -0
- 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
|
+
};
|