@waron97/prbot 3.1.4 → 3.2.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.
- package/README.md +50 -11
- package/agrippa-pb.md +375 -0
- package/package.json +7 -3
- package/src/agrippa/commands/clone.js +29 -13
- package/src/agrippa/commands/clonePb.js +107 -0
- package/src/agrippa/commands/diff.js +109 -20
- package/src/agrippa/commands/init.js +66 -12
- package/src/agrippa/commands/initPhase.js +16 -17
- package/src/agrippa/commands/pb.js +291 -0
- package/src/agrippa/commands/pull.js +122 -16
- package/src/agrippa/commands/pullPb.js +54 -0
- package/src/agrippa/commands/push.js +112 -48
- package/src/agrippa/commands/pushPb.js +87 -0
- package/src/agrippa/commands/repair.js +3 -1
- package/src/agrippa/index.js +144 -14
- package/src/agrippa/lib/api.js +17 -3
- package/src/agrippa/lib/checksum.js +3 -1
- package/src/agrippa/lib/config.js +2 -2
- package/src/agrippa/lib/pbApi.js +71 -0
- package/src/agrippa/lib/pbEdit.js +543 -0
- package/src/agrippa/lib/pbLayout.js +304 -0
- package/src/agrippa/lib/pbModel.js +701 -0
- package/src/agrippa/lib/pbPreview.js +153 -0
- package/src/agrippa/lib/pbProject.js +401 -0
- package/src/agrippa/lib/pbWorkspace.js +30 -0
- package/src/agrippa/lib/workspace.js +23 -3
- package/src/commands/autopr.js +5 -2
- package/src/commands/changelog.js +4 -1
- package/src/commands/export.js +3 -3
- package/src/commands/exportEmailTemplates.js +25 -15
- package/src/commands/exportImperex.js +4 -4
- package/src/commands/exportLrp.js +10 -7
- package/src/commands/exportPb.js +4 -5
- package/src/commands/exportWorkflow.js +27 -14
- package/src/commands/init.js +7 -0
- package/src/commands/routine.js +7 -5
- package/src/index.js +24 -7
- package/src/lib/premigrate.js +3 -3
- package/src/lib/updateCheck.js +5 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ CLI tool for managing PRs, changelogs, and Odoo workflow XML files in the addons
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install -g
|
|
8
|
+
npm install -g @waron97/prbot
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Setup
|
|
@@ -36,7 +36,7 @@ Prompts for all required config values and writes them to `~/.config/prbot/confi
|
|
|
36
36
|
| `DEVOPS_PROJECT` | Azure DevOps project |
|
|
37
37
|
| `DEVOPS_REPO` | Azure DevOps repository name |
|
|
38
38
|
| `AUTOPR_TARGET_BRANCH` | Target branch for auto-created PRs (default: `15.0-dev`) |
|
|
39
|
-
| `IMPORTEXPORT_URL` | ImportExport API base URL
|
|
39
|
+
| `IMPORTEXPORT_URL` | ImportExport API base URL |
|
|
40
40
|
|
|
41
41
|
## Commands
|
|
42
42
|
|
|
@@ -208,23 +208,29 @@ agrippa init
|
|
|
208
208
|
|
|
209
209
|
### `agrippa clone`
|
|
210
210
|
|
|
211
|
-
Clones all `from_code` phases for a selected workflow,
|
|
211
|
+
Clones all `from_code` phases for a selected workflow, a single MFA, or a **process-builder wizard**, into the workspace. Writes files to disk and registers them in `agrippa.yaml`. With no flag, prompts for the object type (MFA / Phase / Process Builder).
|
|
212
|
+
|
|
213
|
+
A wizard is downloaded and **decomposed** into editable files (`structure.yaml`, `process.yaml`, `scripts/`, `pages/`, manifest); see [`agrippa pb`](#agrippa-pb) and [`agrippa-pb.md`](agrippa-pb.md).
|
|
212
214
|
|
|
213
215
|
```bash
|
|
214
216
|
agrippa clone
|
|
215
217
|
agrippa clone --phase
|
|
216
218
|
agrippa clone --mfa
|
|
217
219
|
agrippa clone --phase --id 123 --path my-workflow/
|
|
220
|
+
agrippa clone --pb # select a wizard
|
|
221
|
+
agrippa clone --pb --name ml_review_billing --path my-wizard/
|
|
218
222
|
```
|
|
219
223
|
|
|
220
224
|
Options:
|
|
221
225
|
|
|
222
|
-
| Flag
|
|
223
|
-
|
|
|
224
|
-
| `--phase`
|
|
225
|
-
| `--mfa`
|
|
226
|
-
| `--
|
|
227
|
-
| `--
|
|
226
|
+
| Flag | Description |
|
|
227
|
+
| ---------------------- | ------------------------------------------------------------- |
|
|
228
|
+
| `--phase` | Clone a phase (select a workflow) |
|
|
229
|
+
| `--mfa` | Clone an MFA record |
|
|
230
|
+
| `--pb` | Clone a process-builder wizard |
|
|
231
|
+
| `--id <id>` | Skip selection, clone by ID (phase/mfa) |
|
|
232
|
+
| `--name <document_id>` | Skip selection, clone a wizard by `document_id` (with `--pb`) |
|
|
233
|
+
| `--path <path>` | Destination path (base dir for phases/wizard, file for MFA) |
|
|
228
234
|
|
|
229
235
|
### `agrippa pull`
|
|
230
236
|
|
|
@@ -232,25 +238,34 @@ Fetches remote code for all tracked entries and shows what changed. Classifies e
|
|
|
232
238
|
|
|
233
239
|
After pulling, also checks tracked workflows for newly added `from_code` phases and auto-clones any not yet present locally.
|
|
234
240
|
|
|
241
|
+
Tracked **process-builder wizards** are also refreshed from upstream: the local project is re-decomposed from the latest payload (orphan script/page files pruned), with the same `fast-forward`/`conflict` classification (based on the wizard's upstream `updated_date` vs. the last pulled state). The current local state is backed up to `.backup/<timestamp>/<path>/local.json` first.
|
|
242
|
+
|
|
235
243
|
```bash
|
|
236
244
|
agrippa pull
|
|
237
245
|
```
|
|
238
246
|
|
|
239
247
|
### `agrippa push`
|
|
240
248
|
|
|
241
|
-
Pushes local file changes back to RIP. Backs up current remote
|
|
249
|
+
Pushes local file changes back to RIP (phases/MFAs) and to the Process Builder API (wizards). Backs up current remote state to `.backup/<timestamp>/` before overwriting. Same conflict detection as pull, with the concern inverted.
|
|
250
|
+
|
|
251
|
+
Pushing a wizard saves it as a **draft**; publish it so live consumers see the change with `--publish` (auto) or answer the prompt. Page edits (`pages/`) are saved independently of the whole-wizard save, mirroring the UI.
|
|
242
252
|
|
|
243
253
|
```bash
|
|
244
|
-
agrippa push
|
|
254
|
+
agrippa push # prompts whether to publish each pushed wizard
|
|
255
|
+
agrippa push --publish # auto-publish pushed wizards
|
|
256
|
+
agrippa push --skip-publish # never publish (no prompt)
|
|
245
257
|
```
|
|
246
258
|
|
|
247
259
|
### `agrippa diff [path]`
|
|
248
260
|
|
|
249
261
|
Shows a diff between local files and remote code. Optionally filter to a specific file path.
|
|
250
262
|
|
|
263
|
+
For process-builder wizards, diffs the whole project tree against what the workspace would look like if the wizard were decomposed fresh from upstream right now (`.backup/` and `preview.svg` are excluded as local-only artifacts).
|
|
264
|
+
|
|
251
265
|
```bash
|
|
252
266
|
agrippa diff
|
|
253
267
|
agrippa diff my-workflow/some-phase.py
|
|
268
|
+
agrippa diff ml_review_billing # a cloned wizard's directory
|
|
254
269
|
```
|
|
255
270
|
|
|
256
271
|
### `agrippa init-phase`
|
|
@@ -269,3 +284,27 @@ Removes entries from `agrippa.yaml` whose local files no longer exist on disk.
|
|
|
269
284
|
agrippa repair
|
|
270
285
|
```
|
|
271
286
|
|
|
287
|
+
### `agrippa pb`
|
|
288
|
+
|
|
289
|
+
Local editing helpers for a **cloned process-builder wizard** (no network — they edit the decomposed files in place). Each operates on **one** wizard, resolved by `--pb <document_id>`, single-entry auto-select, or a fuzzy prompt.
|
|
290
|
+
|
|
291
|
+
These commands exist mainly so an **AI agent** can add/remove/connect blocks without hand-editing the multi-thousand-line `structure.yaml`. Human users typically edit blocks in the UI instead. Full agent-facing guide (also usable as a workspace `CLAUDE.md`): [`agrippa-pb.md`](agrippa-pb.md).
|
|
292
|
+
|
|
293
|
+
Newly added blocks get placeholder geometry. `pb format` re-lays-out the **whole** diagram (discarding any hand-tuned layout), so running it is a deliberate **human decision** — the alternative is positioning the new blocks by hand in the UI. Agents are expected to make structural edits but **not** to run `format` or to `pull`/`push`; a human reviews and syncs.
|
|
294
|
+
|
|
295
|
+
| Command | Purpose |
|
|
296
|
+
| --------------- | ------------------------------------------------------------------------------------ |
|
|
297
|
+
| `pb format` | Re-lay-out the **entire** diagram (elkjs, left→right); overwrites existing layout |
|
|
298
|
+
| `pb add` | Add a node (`--type`, `--name`, `--parent`); scaffolds script/page files |
|
|
299
|
+
| `pb rm` | Remove a node (`--id`), its edges, and its script/page files |
|
|
300
|
+
| `pb connect` | Add a flow (`--from`, `--to`, `--condition`, `--default`); enforces the gateway rule |
|
|
301
|
+
| `pb disconnect` | Remove a flow (`--id`, or `--from`/`--to`) |
|
|
302
|
+
| `pb ls` | List nodes and edges with their ids (discover targets without reading the YAML) |
|
|
303
|
+
| `pb preview` | Render the diagram to an SVG (`--out`) for a quick visual check |
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
agrippa pb ls --pb ml_review_billing
|
|
307
|
+
agrippa pb add --type scriptTask --name "Check pod" --pb ml_review_billing
|
|
308
|
+
agrippa pb connect --from ScriptTask_x --to ExclusiveGateway_y --pb ml_review_billing
|
|
309
|
+
agrippa pb format --pb ml_review_billing
|
|
310
|
+
```
|
package/agrippa-pb.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# Instructions for working with Phases, MFAs, and Process-Builder wizards
|
|
2
|
+
|
|
3
|
+
You are my coding assistant developing MFAs, phases, and process-builder wizards for
|
|
4
|
+
our Odoo CRM. Follow instructions precisely, without doing fixes or enhancements
|
|
5
|
+
outside the scope of your instructions.
|
|
6
|
+
|
|
7
|
+
This workspace is an **agrippa** workspace: a local checkout of Odoo code synced via
|
|
8
|
+
the `agrippa` CLI. **You (the agent) never run `agrippa clone`, `agrippa pull`, or
|
|
9
|
+
`agrippa push`** — those are human-only operations. You edit local files, and for
|
|
10
|
+
wizards you also drive the `agrippa pb` editing commands (below). A human reviews and
|
|
11
|
+
syncs.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## CRM structure
|
|
16
|
+
|
|
17
|
+
### MFAs and phases
|
|
18
|
+
|
|
19
|
+
The Odoo CRM uses a special module for exposing HTTP methods to the external world.
|
|
20
|
+
The module name is RIP, which lets developers define "Model Function Access" records.
|
|
21
|
+
Model Function Access (MFA) records contain Python code executed in `safe_eval`.
|
|
22
|
+
|
|
23
|
+
Phases are `symple.triplet.phase` records tied to `helpdesk.ticket` objects.
|
|
24
|
+
A helpdesk ticket has an associated `helpdesk.ticket.type`, which in turn has a
|
|
25
|
+
`symple.workflow`. A workflow has a sequence of `symple.triplet.phase` that the ticket
|
|
26
|
+
crosses in its lifecycle. There are many kinds of phases, but you only care about
|
|
27
|
+
automatic phases that have Python code inside. In the directory structure, unless the
|
|
28
|
+
current folder is named "mfa", each subdirectory is a workflow, and every Python file
|
|
29
|
+
inside corresponds to a Python phase with code.
|
|
30
|
+
|
|
31
|
+
For phases and MFAs you simply **edit the `.py` files** — there are no agrippa
|
|
32
|
+
commands for you to run; a human clones/pulls/pushes them.
|
|
33
|
+
|
|
34
|
+
### Workflow structure (`workflow.yml`)
|
|
35
|
+
|
|
36
|
+
Each cloned workflow directory contains a `workflow.yml` next to its phase `.py`
|
|
37
|
+
files. It is **auto-generated, read-only context** describing how the workflow is
|
|
38
|
+
wired — the graph the editable phase files live inside. Read it to understand a
|
|
39
|
+
workflow before editing a phase; **never edit it** (it is regenerated on every
|
|
40
|
+
clone/pull and is not pushed anywhere). It does not contain phase Python code —
|
|
41
|
+
that lives in the `.py` files.
|
|
42
|
+
|
|
43
|
+
It holds:
|
|
44
|
+
|
|
45
|
+
- `workflow` — identity + flags (`process_type`, `is_tiqv`, `start_phase`,
|
|
46
|
+
`code_excluded_phases`, and the `triplets` / ticket-type details that enter it).
|
|
47
|
+
- `phases` — every phase as a graph node: `id`, `name`, `phase_code`,
|
|
48
|
+
`set_result_automatically`, `has_code_file` (true ⇒ an editable `.py` was cloned
|
|
49
|
+
for it), process/integration flags, `timeout`, `allowed_processes`,
|
|
50
|
+
`allowed_manual_phases`, and `results`.
|
|
51
|
+
- `results` (per phase) — the **outgoing edges**: each `result` has a `next_phase`
|
|
52
|
+
plus, for `from_code` phases, the `code_values` the phase's Python must assign to
|
|
53
|
+
`result` to take that edge (and `triplet_details` for `from_triplet` phases).
|
|
54
|
+
|
|
55
|
+
So to find "what value does this phase set to go to phase X", read `workflow.yml`:
|
|
56
|
+
match the result whose `next_phase` is X and use its `code_values`.
|
|
57
|
+
|
|
58
|
+
### Code inside phases and MFAs
|
|
59
|
+
|
|
60
|
+
The following applies to both phases and MFAs.
|
|
61
|
+
|
|
62
|
+
Some global utilities are made available inside the script, such as:
|
|
63
|
+
|
|
64
|
+
- `json_dumps` (from `json.dumps`)
|
|
65
|
+
- `json_loads` (from `json.loads`)
|
|
66
|
+
- `datetime` (root-level import from datetime)
|
|
67
|
+
- `dateutil` (root-level import from dateutil)
|
|
68
|
+
- `request` (from `requests.request`)
|
|
69
|
+
- `log` (logging method)
|
|
70
|
+
- `format_exc` (from `traceback.format_exc`)
|
|
71
|
+
- `first` (from `fields.first`)
|
|
72
|
+
- `case_id` (phases only — the current `helpdesk.ticket`)
|
|
73
|
+
|
|
74
|
+
The value of the `result` variable is used to construct the HTTP response.
|
|
75
|
+
The `make_response` helper allows returning error states, e.g.
|
|
76
|
+
`result = make_response((500, 500), "Custom error message")`.
|
|
77
|
+
|
|
78
|
+
Some patterns not allowed in Odoo's `safe_eval`:
|
|
79
|
+
|
|
80
|
+
- f-strings
|
|
81
|
+
- imports
|
|
82
|
+
- lambdas inside functions (`def` inside another function is fine)
|
|
83
|
+
- doc strings (you cannot write to dunder fields)
|
|
84
|
+
|
|
85
|
+
Notably unavailable:
|
|
86
|
+
|
|
87
|
+
- `getattr`
|
|
88
|
+
- `setattr`
|
|
89
|
+
|
|
90
|
+
### Communication between phases
|
|
91
|
+
|
|
92
|
+
A key entity is `symple.pb.process.data`. This model stores arbitrary data (most
|
|
93
|
+
commonly JSON) in the `payload` field. It also has a `get_payload` method, which gives
|
|
94
|
+
you the parsed JSON it contains. Phases, MFAs and other components commonly communicate
|
|
95
|
+
via this model.
|
|
96
|
+
|
|
97
|
+
The key-value store (kv_store) is a special process-data record accessible via
|
|
98
|
+
`case_id.kv_store()`. It gives shorthand access to a process-data record unique to each
|
|
99
|
+
case. It exposes 4 methods:
|
|
100
|
+
|
|
101
|
+
- `get("key")`
|
|
102
|
+
- `set("key", "value")`
|
|
103
|
+
- `get_many("key", "key2")` → `a, b = case_id.sudo().kv_store().get_many("a, b")`
|
|
104
|
+
- `set_many({"a": 1, "b": 2})`
|
|
105
|
+
|
|
106
|
+
When possible, prefer the kv_store over manually creating and searching process-data
|
|
107
|
+
records.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Process-builder wizards
|
|
112
|
+
|
|
113
|
+
A wizard is a BPMN process the CRM runs as a guided flow. Locally it has been
|
|
114
|
+
**decomposed** into editable files. For wizards you are expected to **drive the
|
|
115
|
+
`agrippa pb` commands** for structural changes — they hide BPMN id generation, the
|
|
116
|
+
nested-YAML graph, dangling-edge cleanup, and the script/page/manifest bookkeeping you
|
|
117
|
+
would otherwise get wrong by hand.
|
|
118
|
+
|
|
119
|
+
> **You never run `agrippa push` or `agrippa pull`.** You only edit local files and run
|
|
120
|
+
> the local, read/write `agrippa pb` subcommands. A human syncs and publishes.
|
|
121
|
+
|
|
122
|
+
### Decomposed project layout
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
<wizard-dir>/
|
|
126
|
+
process.yaml identity + top-level flags (envelope)
|
|
127
|
+
structure.yaml THE graph: nodes hold their outgoing `edges` and nested
|
|
128
|
+
`nodes` (for subProcess/transaction); inline geometry
|
|
129
|
+
scripts/NNNN_*.js one scriptTask body each (byte-exact)
|
|
130
|
+
pages/<formKey>.yml one userTask page object each
|
|
131
|
+
.agrippa-pb.json manifest: scalars, namespaces, id↔file maps (do not hand-edit)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`structure.yaml` is large and nested. **Do not read it to find ids** — use `pb ls`.
|
|
135
|
+
`agrippa-pb.json` is a massive json file. **Do not read it unless strictly required** — use `pb ls` and explore deconstructed files.
|
|
136
|
+
|
|
137
|
+
A node looks like:
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
- id: ScriptTask_0mmmti4
|
|
141
|
+
type: scriptTask
|
|
142
|
+
name: Init
|
|
143
|
+
script: scripts/0010_init.js # ref to the body file
|
|
144
|
+
layout: { x: -462, y: 78, width: 84, height: 84 }
|
|
145
|
+
edges: # OUTGOING flows belong to the source node
|
|
146
|
+
- id: SequenceFlow_1lso8x0
|
|
147
|
+
target: ExclusiveGateway_1lghfov
|
|
148
|
+
waypoints: [[-378, 120], [-345, 120]]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Node types: `startEvent`, `endEvent`, `boundaryEvent`, `exclusiveGateway`,
|
|
152
|
+
`scriptTask`, `serviceTask`, `userTask`, `subProcess`, `transaction`.
|
|
153
|
+
|
|
154
|
+
### Your loop
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
agrippa pb ls # discover node/edge ids
|
|
158
|
+
agrippa pb add / rm / connect / disconnect / set-default # change the graph
|
|
159
|
+
agrippa pb lint # check for structural issues before handing off
|
|
160
|
+
# STOP. Do NOT format. Do NOT push. Report back to the human (see Formatting below).
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Every `pb` command targets **one** wizard. **Always pass `--pb <document_id>`** so you
|
|
164
|
+
never hit an interactive prompt.
|
|
165
|
+
|
|
166
|
+
### Commands
|
|
167
|
+
|
|
168
|
+
#### `pb ls` — discover ids
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
agrippa pb ls --pb <document_id>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Flat list of every node with its id, type, name, parent (if nested), and outgoing
|
|
175
|
+
edges. **Start here** to find the ids you need. Example:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
ScriptTask_0mmmti4 (scriptTask) "Init"
|
|
179
|
+
→ ExclusiveGateway_1lghfov (SequenceFlow_1lso8x0)
|
|
180
|
+
ExclusiveGateway_1lghfov (exclusiveGateway) "eg err"
|
|
181
|
+
→ EndEvent_err (SequenceFlow_a) [default]
|
|
182
|
+
→ ScriptTask_next (SequenceFlow_b) if ${isAlive}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`[default]` marks a gateway's default flow; `if …` shows a flow condition.
|
|
186
|
+
|
|
187
|
+
#### `pb add` — add a node
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
agrippa pb add --type scriptTask --name "Check pod" --pb <document_id>
|
|
191
|
+
agrippa pb add --type userTask --name "Review" --pb <document_id>
|
|
192
|
+
agrippa pb add --type subProcess --name "Retry loop" --pb <document_id>
|
|
193
|
+
agrippa pb add --type scriptTask --name "Inner" --parent SubProcess_x --pb <document_id>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Prints the new node id. Side effects, handled for you:
|
|
197
|
+
|
|
198
|
+
- `scriptTask` → creates an **empty** `scripts/NNNN_<slug>.js`; edit that file for the body.
|
|
199
|
+
- `userTask` → creates a **stub** `pages/<slug>.yml` + a manifest entry (no page content).
|
|
200
|
+
- `subProcess`/`transaction` → empty container; add children with `--parent <its-id>`.
|
|
201
|
+
|
|
202
|
+
The node is added **disconnected** with placeholder geometry. Connect it next.
|
|
203
|
+
|
|
204
|
+
**Insert mode** — `--from <id> --to <id>` splices the new node into an existing flow
|
|
205
|
+
instead of adding it disconnected:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
agrippa pb add --type scriptTask --name "New" --from A --to End --pb <document_id>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Requires **exactly one** existing edge `A → End` already (errors if there's none, or
|
|
212
|
+
more than one — ambiguous). That edge is kept (same id, name, condition, gateway
|
|
213
|
+
`default` reference) and retargeted onto the new node; a second plain edge runs
|
|
214
|
+
new-node → `End`. `--parent` is implied by `A`/`End`'s container, so don't pass both.
|
|
215
|
+
`A`/`End` must be in the same container — it errors on boundary-crossing flows.
|
|
216
|
+
|
|
217
|
+
#### `pb rm` — remove a node
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
agrippa pb rm --id ScriptTask_0mmmti4 --pb <document_id>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Removes the node (and, for a container, its children), **every edge pointing at it**
|
|
224
|
+
(from anywhere in the graph), its own outgoing edges, and its script/page files +
|
|
225
|
+
manifest entries. No dangling references left behind.
|
|
226
|
+
|
|
227
|
+
#### `pb connect` — add a flow
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# plain sequence
|
|
231
|
+
agrippa pb connect --from ScriptTask_a --to ScriptTask_b --pb <document_id>
|
|
232
|
+
|
|
233
|
+
# named branch (required when the source has 2+ outgoing flows and this is non-default)
|
|
234
|
+
agrippa pb connect --from ExclusiveGateway_g --to ScriptTask_b \
|
|
235
|
+
--name "alive" --condition '${isAlive}' --pb <document_id>
|
|
236
|
+
|
|
237
|
+
# the gateway's fallback branch (default needs no name)
|
|
238
|
+
agrippa pb connect --from ExclusiveGateway_g --to EndEvent_err \
|
|
239
|
+
--default --pb <document_id>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
A flow's id is printed. Conditions default to `xsi:type="tFormalExpression"`
|
|
243
|
+
(override with `--condition-type`). **Rules** (warned by this command and by `pb lint`):
|
|
244
|
+
|
|
245
|
+
- An `exclusiveGateway` with more than one outgoing flow must have **exactly one**
|
|
246
|
+
`--default` flow, and **every other** outgoing flow must carry a `--condition`.
|
|
247
|
+
- Any node with more than one outgoing flow: every **non-default** flow must have a
|
|
248
|
+
`--name` (operators need labels to distinguish branches at runtime).
|
|
249
|
+
- Only `exclusiveGateway` may have more than one **incoming** flow.
|
|
250
|
+
- An `exclusiveGateway` may not have both multiple incoming **and** multiple outgoing
|
|
251
|
+
flows — pick one direction (merging or splitting), not both.
|
|
252
|
+
|
|
253
|
+
Heed the `!` warnings `connect` prints.
|
|
254
|
+
|
|
255
|
+
#### `pb disconnect` — remove a flow
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
agrippa pb disconnect --id SequenceFlow_1lso8x0 --pb <document_id>
|
|
259
|
+
agrippa pb disconnect --from ScriptTask_a --to ScriptTask_b --pb <document_id>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### `pb lint` — check for structural issues
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
agrippa pb lint --pb <document_id>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Runs all diagram rules and prints any violations. Exits 1 if issues found. Run after
|
|
269
|
+
every batch of structural edits before handing off to a human. Rules checked:
|
|
270
|
+
|
|
271
|
+
- `exclusiveGateway` default/condition rule (2+ outgoing → one default, rest conditioned)
|
|
272
|
+
- Non-default outgoing flows must have a name when the source has 2+ outgoing flows
|
|
273
|
+
- Only `exclusiveGateway` may have multiple incoming flows
|
|
274
|
+
- `exclusiveGateway` may not have both multiple incoming and multiple outgoing flows
|
|
275
|
+
|
|
276
|
+
`pb format` and `pb connect` also surface the same warnings inline.
|
|
277
|
+
|
|
278
|
+
#### `pb set-default` — change a gateway's default flow
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
agrippa pb set-default --id SequenceFlow_b --pb <document_id>
|
|
282
|
+
agrippa pb set-default --from ExclusiveGateway_g --to EndEvent_err --pb <document_id>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Flips an **already-existing** flow to be its source gateway's `default`, by edge id or
|
|
286
|
+
by `--from`/`--to` pair. Use this when a gateway already has a default and you want a
|
|
287
|
+
different outgoing flow to become it — no need to `disconnect` and re-`connect` just to
|
|
288
|
+
move the flag. The source must be an `exclusiveGateway`. Re-runs the gateway lint and
|
|
289
|
+
prints any `!` warnings (e.g. a now-default flow that still carries a condition, or a
|
|
290
|
+
non-default flow left without one).
|
|
291
|
+
|
|
292
|
+
#### `pb preview` — visual check
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
agrippa pb preview --pb <document_id> --out /tmp/wizard.svg
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Renders the current geometry to an SVG so a human can eyeball the result. Safe to run.
|
|
299
|
+
Not byte-faithful to the real renderer — a sanity check only.
|
|
300
|
+
|
|
301
|
+
### Formatting — a human decision, do NOT run it yourself
|
|
302
|
+
|
|
303
|
+
`agrippa pb format` re-lays-out the **entire** wizard with an automatic algorithm.
|
|
304
|
+
On an existing wizard this **discards the human's hand-tuned layout** and produces a
|
|
305
|
+
drastically different diagram. That may or may not be acceptable — **only the human
|
|
306
|
+
decides.**
|
|
307
|
+
|
|
308
|
+
So when you have added/connected blocks, the new nodes are left with placeholder
|
|
309
|
+
positions, and **you stop there**. Report to the human what you changed and that the
|
|
310
|
+
new blocks need positioning, then let them choose one of:
|
|
311
|
+
|
|
312
|
+
1. **Run `agrippa pb format`** themselves — accepts a full automatic re-layout of the
|
|
313
|
+
whole wizard (existing arrangement is lost), or
|
|
314
|
+
2. **Position the new blocks by hand in the UI** after a human pushes the change —
|
|
315
|
+
preserving the existing layout.
|
|
316
|
+
|
|
317
|
+
Never run `pb format` unless the human explicitly tells you to, with that trade-off
|
|
318
|
+
understood.
|
|
319
|
+
|
|
320
|
+
### What to edit by hand (vs. commands)
|
|
321
|
+
|
|
322
|
+
Use the **commands** for graph structure (adding/removing nodes, wiring flows).
|
|
323
|
+
Edit the **files directly** for content within an existing node:
|
|
324
|
+
|
|
325
|
+
| Change | How |
|
|
326
|
+
| --- | --- |
|
|
327
|
+
| A scriptTask body | edit its `scripts/NNNN_*.js` file |
|
|
328
|
+
| A userTask page | edit its `pages/<formKey>.yml` file |
|
|
329
|
+
| A node's `name`, a flow's `condition`/`name`, serviceTask `fields`/`class` | edit `structure.yaml` for that node/edge |
|
|
330
|
+
| Identity/flags | edit `process.yaml` |
|
|
331
|
+
|
|
332
|
+
Never hand-edit `.agrippa-pb.json`, and never hand-assign `layout`/`waypoints`.
|
|
333
|
+
|
|
334
|
+
### Recipes
|
|
335
|
+
|
|
336
|
+
**Insert a block into an existing path** (`A → B` becomes `A → New → B`, anywhere in
|
|
337
|
+
the graph, not just the end):
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
agrippa pb add --type scriptTask --name "New" --from A --to B --pb W # → ScriptTask_new
|
|
341
|
+
# then edit scripts/NNNN_new.js with the body, and report back (see Formatting)
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Errors out instead of guessing if `A → B` doesn't have exactly one edge — fix the
|
|
345
|
+
graph (`pb ls`) first if so.
|
|
346
|
+
|
|
347
|
+
**Insert a decision branch** (gateway with two conditioned exits + a default):
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
agrippa pb add --type exclusiveGateway --name "alive?" --pb W # → ExclusiveGateway_g
|
|
351
|
+
agrippa pb connect --from ExclusiveGateway_g --to ScriptTask_ok \
|
|
352
|
+
--name "alive" --condition '${isAlive}' --pb W
|
|
353
|
+
agrippa pb connect --from ExclusiveGateway_g --to EndEvent_err --default --pb W
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Add a subprocess with an inner step:**
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
agrippa pb add --type subProcess --name "Retry" --pb W # → SubProcess_s
|
|
360
|
+
agrippa pb add --type scriptTask --name "Attempt" --parent SubProcess_s --pb W
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
In every recipe: after the structural edits, **stop** and report — do not format, do
|
|
364
|
+
not push.
|
|
365
|
+
|
|
366
|
+
### Safety
|
|
367
|
+
|
|
368
|
+
- `pb` commands are **local only** — nothing reaches upstream. Syncing
|
|
369
|
+
(`clone`/`pull`/`push`) and publishing are the human's job, never yours.
|
|
370
|
+
- After every command the project is re-checked for recomposability; a broken edit is
|
|
371
|
+
reported with a `WARNING`.
|
|
372
|
+
- When in doubt: `pb ls` to see the graph, make the structural change, `pb preview` to
|
|
373
|
+
show the human, then hand off.
|
|
374
|
+
|
|
375
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waron97/prbot",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,26 +14,30 @@
|
|
|
14
14
|
"@inquirer/search": "^2.0.1",
|
|
15
15
|
"commander": "^14.0.2",
|
|
16
16
|
"dotenv": "^17.2.3",
|
|
17
|
+
"elkjs": "^0.11.1",
|
|
17
18
|
"eslint-plugin-es5": "^1.5.0",
|
|
19
|
+
"fast-xml-parser": "^5.9.2",
|
|
18
20
|
"inquirer": "^13.4.2",
|
|
19
21
|
"inquirer-search-list": "^1.2.6",
|
|
20
22
|
"node-fetch": "^3.3.2",
|
|
21
23
|
"omelette": "^0.4.17",
|
|
22
|
-
"prettier": "^3.5.3",
|
|
23
24
|
"slugify": "^1.6.9",
|
|
24
25
|
"yaml": "^2.9.0"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
28
|
"@eslint/js": "^9.21.0",
|
|
29
|
+
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
|
|
28
30
|
"@types/node": "^25.0.10",
|
|
29
31
|
"@typescript-eslint/parser": "^8.26.0",
|
|
30
32
|
"eslint": "^9.21.0",
|
|
33
|
+
"prettier": "^3.8.4",
|
|
31
34
|
"typescript": "^5.8.2",
|
|
32
35
|
"typescript-eslint": "^8.26.0"
|
|
33
36
|
},
|
|
34
37
|
"files": [
|
|
35
38
|
"src/",
|
|
36
|
-
"agrippa_typings/"
|
|
39
|
+
"agrippa_typings/",
|
|
40
|
+
"agrippa-pb.md"
|
|
37
41
|
],
|
|
38
42
|
"keywords": [],
|
|
39
43
|
"author": "",
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import inquirer from 'inquirer';
|
|
2
|
-
import select from '@inquirer/select';
|
|
3
1
|
import search from '@inquirer/search';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import select from '@inquirer/select';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
6
4
|
import { getToken } from '../../lib/auth.js';
|
|
7
|
-
import { computeChecksum } from '../lib/checksum.js';
|
|
8
|
-
import { toSlug, defaultMfaPath, writeCodeFile } from '../lib/workspace.js';
|
|
9
5
|
import { fuzzyMatch } from '../../lib/fuzzy.js';
|
|
6
|
+
import { describeWorkflow, getPhasesByWorkflow, listMfas, listWorkflows } from '../lib/api.js';
|
|
7
|
+
import { computeChecksum } from '../lib/checksum.js';
|
|
8
|
+
import { loadEffectiveEnv, readConfig, writeConfig } from '../lib/config.js';
|
|
9
|
+
import { defaultMfaPath, toSlug, writeCodeFile, writeWorkflowDoc } from '../lib/workspace.js';
|
|
10
|
+
import { clonePb } from './clonePb.js';
|
|
10
11
|
|
|
11
12
|
async function clone(opts) {
|
|
13
|
+
if (opts.pb) {
|
|
14
|
+
return clonePb(opts);
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
const config = readConfig();
|
|
13
18
|
loadEffectiveEnv(config);
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
if (!ripUrl) throw new Error('RIP_URL is not configured. Run `prbot init` or set it in agrippa.yaml.');
|
|
17
|
-
|
|
18
|
-
// Determine object type
|
|
20
|
+
// Determine object type (interactive prompt when no flag was passed)
|
|
19
21
|
let objectType = opts.mfa ? 'mfa' : opts.phase ? 'phase' : null;
|
|
20
22
|
if (!objectType) {
|
|
21
23
|
objectType = await select({
|
|
@@ -23,9 +25,16 @@ async function clone(opts) {
|
|
|
23
25
|
choices: [
|
|
24
26
|
{ name: 'MFA', value: 'mfa' },
|
|
25
27
|
{ name: 'Phase', value: 'phase' },
|
|
28
|
+
{ name: 'Process Builder', value: 'pb' },
|
|
26
29
|
],
|
|
27
30
|
});
|
|
28
31
|
}
|
|
32
|
+
// Process-builder wizards have their own clone flow (PB_URL, recompose verify).
|
|
33
|
+
if (objectType === 'pb') return clonePb(opts);
|
|
34
|
+
|
|
35
|
+
const ripUrl = process.env.RIP_URL;
|
|
36
|
+
if (!ripUrl)
|
|
37
|
+
throw new Error('RIP_URL is not configured. Run `prbot init` or set it in agrippa.yaml.');
|
|
29
38
|
|
|
30
39
|
console.log('Fetching records...');
|
|
31
40
|
const token = await getToken();
|
|
@@ -52,9 +61,7 @@ async function clone(opts) {
|
|
|
52
61
|
record = await search({
|
|
53
62
|
message: `Select a ${objectType}:`,
|
|
54
63
|
source: (input) => {
|
|
55
|
-
const filtered = input
|
|
56
|
-
? records.filter((r) => fuzzyMatch(r.name, input))
|
|
57
|
-
: records;
|
|
64
|
+
const filtered = input ? records.filter((r) => fuzzyMatch(r.name, input)) : records;
|
|
58
65
|
return filtered.map((r) => ({
|
|
59
66
|
name: objectType === 'mfa' ? `${r.model_name} / ${r.name}` : r.name,
|
|
60
67
|
value: r,
|
|
@@ -102,6 +109,15 @@ async function clone(opts) {
|
|
|
102
109
|
console.log(` wrote ${filePath}`);
|
|
103
110
|
}
|
|
104
111
|
|
|
112
|
+
// Drop the workflow graph alongside the phase files as read-only context.
|
|
113
|
+
try {
|
|
114
|
+
const structure = await describeWorkflow(token, ripUrl, record.id);
|
|
115
|
+
const docPath = writeWorkflowDoc(basePath, structure);
|
|
116
|
+
console.log(` wrote ${docPath}`);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.warn(` could not fetch workflow structure: ${err.message}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
105
121
|
console.log(`Cloned ${phases.length} phase(s) to ${basePath}/`);
|
|
106
122
|
} else {
|
|
107
123
|
const defaultPath = defaultMfaPath(record.model_name, record.name);
|