@waron97/prbot 3.2.1 → 3.4.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.
@@ -2,14 +2,17 @@ import { mkdirSync, writeFileSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
3
  import inquirer from 'inquirer';
4
4
  import { getToken } from '../../lib/auth.js';
5
+ import { log } from '../../lib/logger.js';
5
6
  import { updateMfa, updatePhase } from '../lib/api.js';
6
7
  import { computeChecksum } from '../lib/checksum.js';
7
8
  import { loadEffectiveEnv, readConfig, writeConfig } from '../lib/config.js';
9
+ import { fetchUpstream } from '../lib/lrpApi.js';
8
10
  import { getProcess } from '../lib/pbApi.js';
9
11
  import { localChecksum, remoteChecksumPb } from '../lib/pbProject.js';
10
12
  import { projectReader } from '../lib/pbWorkspace.js';
11
13
  import { fileExists, readCodeFile } from '../lib/workspace.js';
12
14
  import { fetchRemoteCode, selectEntries } from './pull.js';
15
+ import { deploy, pushLrpEntry } from './pushLrp.js';
13
16
  import { publish, pushPbEntry } from './pushPb.js';
14
17
 
15
18
  const BACKUP_DIR = '.backup';
@@ -21,24 +24,28 @@ async function push(opts = {}) {
21
24
  // Stale-entry check (a path may be a file for phase/mfa or a dir for pb).
22
25
  const stale = config.workspace.filter((e) => !fileExists(e.path));
23
26
  if (stale.length) {
24
- console.log('\nThe following tracked resources no longer exist on disk:');
25
- stale.forEach((e) => console.log(` - ${e.path} (${e.name})`));
26
- const { cleanup } = await inquirer.prompt([
27
- {
28
- type: 'confirm',
29
- name: 'cleanup',
30
- message: 'Remove these entries from the workspace config?',
31
- default: true,
32
- },
33
- ]);
34
- if (cleanup) {
35
- config.workspace = config.workspace.filter((e) => fileExists(e.path));
36
- writeConfig(config);
27
+ log('\nThe following tracked resources no longer exist on disk:');
28
+ stale.forEach((e) => log(` - ${e.path} (${e.name})`));
29
+ if (opts.nonInteractive) {
30
+ log(' (non-interactive: leaving them tracked; run interactively to clean up)');
31
+ } else {
32
+ const { cleanup } = await inquirer.prompt([
33
+ {
34
+ type: 'confirm',
35
+ name: 'cleanup',
36
+ message: 'Remove these entries from the workspace config?',
37
+ default: true,
38
+ },
39
+ ]);
40
+ if (cleanup) {
41
+ config.workspace = config.workspace.filter((e) => fileExists(e.path));
42
+ writeConfig(config);
43
+ }
37
44
  }
38
45
  }
39
46
 
40
47
  if (!config.workspace.length) {
41
- console.log('No tracked resources. Run `agrippa clone` first.');
48
+ log('No tracked resources. Run `agrippa clone` first.');
42
49
  return;
43
50
  }
44
51
 
@@ -46,13 +53,18 @@ async function push(opts = {}) {
46
53
  (e) => e.object_type === 'phase' || e.object_type === 'mfa'
47
54
  );
48
55
  const hasPb = config.workspace.some((e) => e.object_type === 'process_builder');
56
+ const hasLrp = config.workspace.some((e) => e.object_type === 'long_running_process');
49
57
  const ripUrl = process.env.RIP_URL;
50
58
  if (hasCode && !ripUrl)
51
59
  throw new Error('RIP_URL is not configured. Run `prbot init` or set it in agrippa.yaml.');
52
60
  if (hasPb && !process.env.PB_URL)
53
61
  throw new Error('PB_URL is not configured. Run `prbot init` or set it in agrippa.yaml.');
62
+ if (hasLrp && !process.env.IMPORTEXPORT_URL)
63
+ throw new Error(
64
+ 'IMPORTEXPORT_URL is not configured. Run `prbot init` or set it in agrippa.yaml.'
65
+ );
54
66
 
55
- console.log('Fetching remote state...');
67
+ log('Fetching remote state...');
56
68
  const token = await getToken();
57
69
 
58
70
  const remoteCodeMap = hasCode
@@ -66,6 +78,14 @@ async function push(opts = {}) {
66
78
  upstreamMap.set(e.guid, null);
67
79
  }
68
80
  }
81
+ const lrpUpstreamMap = new Map();
82
+ for (const e of config.workspace.filter((x) => x.object_type === 'long_running_process')) {
83
+ try {
84
+ lrpUpstreamMap.set(e.name, await fetchUpstream(token, e.name));
85
+ } catch {
86
+ lrpUpstreamMap.set(e.name, null);
87
+ }
88
+ }
69
89
 
70
90
  // Classify every entry to a status badge — concern: overwriting remote work.
71
91
  const classified = config.workspace.map((entry) => {
@@ -81,6 +101,18 @@ async function push(opts = {}) {
81
101
  return { ...entry, upstream, status };
82
102
  }
83
103
 
104
+ if (entry.object_type === 'long_running_process') {
105
+ const upstream = lrpUpstreamMap.get(entry.name) ?? null;
106
+ const localSemantic = localChecksum(projectReader(entry.path));
107
+ const remoteSemantic = upstream ? remoteChecksumPb(upstream.payload) : null;
108
+ const pullChecksum = entry.checksum_at_pull;
109
+ let status;
110
+ if (localSemantic === remoteSemantic) status = 'unchanged';
111
+ else if (pullChecksum === remoteSemantic) status = 'fast-forward';
112
+ else status = 'conflict';
113
+ return { ...entry, upstream, status };
114
+ }
115
+
84
116
  const key = `${entry.object_type}:${entry.id}`;
85
117
  const remoteCode = remoteCodeMap.get(key) ?? null;
86
118
  const localCode = readCodeFile(entry.path);
@@ -96,22 +128,26 @@ async function push(opts = {}) {
96
128
 
97
129
  const changed = classified.filter((e) => e.status !== 'unchanged');
98
130
  if (!changed.length) {
99
- console.log('Nothing to push — everything matches the last-pulled state.');
131
+ log('Nothing to push — everything matches the last-pulled state.');
100
132
  return;
101
133
  }
102
134
 
103
- const selected = await selectEntries(changed, 'push (overwrites remote)', 'push');
135
+ const selected = await selectEntries(changed, 'push (overwrites remote)', opts);
104
136
  if (!selected.length) {
105
- console.log('Nothing selected. No changes made.');
137
+ log('Nothing selected. No changes made.');
106
138
  return;
107
139
  }
108
140
 
109
141
  const backupTs = new Date().toISOString().replace(/:/g, '-').replace('T', '_').slice(0, 19);
110
142
 
111
- // Back up remote code for phase/mfa before overwriting (pb backs up its own
112
- // full upstream payload inside pushPbEntry).
143
+ // Back up remote code for phase/mfa before overwriting (pb/lrp back up
144
+ // their own full upstream payload inside pushPbEntry/pushLrpEntry).
113
145
  for (const entry of selected) {
114
- if (entry.object_type !== 'process_builder' && entry.remoteCode != null) {
146
+ if (
147
+ entry.object_type !== 'process_builder' &&
148
+ entry.object_type !== 'long_running_process' &&
149
+ entry.remoteCode != null
150
+ ) {
115
151
  const backupPath = join(BACKUP_DIR, backupTs, entry.path);
116
152
  mkdirSync(dirname(backupPath), { recursive: true });
117
153
  writeFileSync(backupPath, (entry.remoteCode ?? '').trim() + '\n', 'utf-8');
@@ -119,15 +155,15 @@ async function push(opts = {}) {
119
155
  }
120
156
 
121
157
  const pushedPb = [];
158
+ const pushedLrp = [];
122
159
  let pushed = 0;
123
160
  for (const entry of selected) {
124
- const idx = config.workspace.findIndex(
125
- (e) =>
126
- e.object_type === entry.object_type &&
127
- (entry.object_type === 'process_builder'
128
- ? e.guid === entry.guid
129
- : e.id === entry.id)
130
- );
161
+ const idx = config.workspace.findIndex((e) => {
162
+ if (e.object_type !== entry.object_type) return false;
163
+ if (entry.object_type === 'process_builder') return e.guid === entry.guid;
164
+ if (entry.object_type === 'long_running_process') return e.name === entry.name;
165
+ return e.id === entry.id;
166
+ });
131
167
 
132
168
  if (entry.object_type === 'process_builder') {
133
169
  const res = await pushPbEntry(token, entry, BACKUP_DIR, backupTs);
@@ -137,13 +173,26 @@ async function push(opts = {}) {
137
173
  ]
138
174
  .filter(Boolean)
139
175
  .join(', ');
140
- console.log(` ${entry.name} → saved (draft)${note ? ` [${note}]` : ''}`);
176
+ log(` ${entry.name} → saved (draft)${note ? ` [${note}]` : ''}`);
141
177
  if (idx !== -1) {
142
178
  config.workspace[idx].checksum_at_pull = res.newChecksum;
143
179
  if (res.newUpdatedDate) config.workspace[idx].updated_date = res.newUpdatedDate;
144
180
  config.workspace[idx].status = res.newStatus || 'draft';
145
181
  }
146
182
  pushedPb.push({ entry, idx });
183
+ } else if (entry.object_type === 'long_running_process') {
184
+ const res = await pushLrpEntry(token, entry, BACKUP_DIR, backupTs);
185
+ log(` ${entry.name} → saved`);
186
+ if (idx !== -1) {
187
+ config.workspace[idx].checksum_at_pull = res.newChecksum;
188
+ config.workspace[idx].tenant_id = res.newRow.tenantId;
189
+ config.workspace[idx].description = res.newRow.description;
190
+ config.workspace[idx].version = res.newRow.version;
191
+ config.workspace[idx].status = res.newRow.status;
192
+ }
193
+ // deployId is the fresh post-save id (re-resolved by name in
194
+ // pushLrpEntry); the pre-save id is dead once the save returns.
195
+ pushedLrp.push({ entry, idx, deployId: res.deployId });
147
196
  } else {
148
197
  const code = entry.localCode ?? '';
149
198
  if (entry.object_type === 'phase') await updatePhase(token, ripUrl, entry.id, code);
@@ -153,15 +202,18 @@ async function push(opts = {}) {
153
202
  pushed++;
154
203
  }
155
204
 
156
- console.log(`\nRemote backups written to ${BACKUP_DIR}/${backupTs}/`);
205
+ log(`\nRemote backups written to ${BACKUP_DIR}/${backupTs}/`);
157
206
 
158
- // Publish step for pushed wizards (now in "draft").
207
+ // Publish/deploy step for pushed wizards and LRPs.
159
208
  if (pushedPb.length) {
160
209
  await handlePublish(token, pushedPb, config, opts);
161
210
  }
211
+ if (pushedLrp.length) {
212
+ await handleDeploy(token, pushedLrp, config, opts);
213
+ }
162
214
 
163
215
  writeConfig(config);
164
- console.log(`\nPushed ${pushed} record(s).`);
216
+ log(`\nPushed ${pushed} record(s).`);
165
217
  }
166
218
 
167
219
  async function handlePublish(token, pushedPb, config, opts) {
@@ -169,6 +221,8 @@ async function handlePublish(token, pushedPb, config, opts) {
169
221
  let doPublish;
170
222
  if (opts.skipPublish) doPublish = false;
171
223
  else if (opts.publish) doPublish = true;
224
+ else if (opts.nonInteractive)
225
+ doPublish = false; // no stdin to prompt on; default to safe
172
226
  else {
173
227
  ({ doPublish } = await inquirer.prompt([
174
228
  {
@@ -182,7 +236,32 @@ async function handlePublish(token, pushedPb, config, opts) {
182
236
  if (doPublish) {
183
237
  await publish(token, entry.guid);
184
238
  if (idx !== -1) config.workspace[idx].status = 'published';
185
- console.log(` published ${entry.name}`);
239
+ log(` published ${entry.name}`);
240
+ }
241
+ }
242
+ }
243
+
244
+ async function handleDeploy(token, pushedLrp, config, opts) {
245
+ for (const { entry, idx, deployId } of pushedLrp) {
246
+ let doDeploy;
247
+ if (opts.skipPublish) doDeploy = false;
248
+ else if (opts.publish) doDeploy = true;
249
+ else if (opts.nonInteractive)
250
+ doDeploy = false; // no stdin to prompt on; default to safe
251
+ else {
252
+ ({ doDeploy } = await inquirer.prompt([
253
+ {
254
+ type: 'confirm',
255
+ name: 'doDeploy',
256
+ message: `Deploy "${entry.name}" now?`,
257
+ default: false,
258
+ },
259
+ ]));
260
+ }
261
+ if (doDeploy) {
262
+ await deploy(token, deployId);
263
+ if (idx !== -1) config.workspace[idx].status = 'deployed';
264
+ log(` deployed ${entry.name}`);
186
265
  }
187
266
  }
188
267
  }
@@ -0,0 +1,56 @@
1
+ import { mkdirSync, writeFileSync } from 'fs';
2
+ import { dirname, join } from 'path';
3
+ import { deployLrp, resolveLrpByName, saveLrp } from '../lib/lrpApi.js';
4
+ import { localChecksum, recompose } from '../lib/pbProject.js';
5
+ import { projectReader } from '../lib/pbWorkspace.js';
6
+
7
+ // Push one LRP entry: back up upstream, re-resolve the CURRENT id/tenantId by
8
+ // name (never trust a stored/stale id — it changes on every save/version),
9
+ // and PATCH the recomposed XML back. Returns a summary; deploy is a separate
10
+ // step (see deploy()), mirroring PB's push-then-publish split.
11
+ //
12
+ // The save bumps the version and mints a NEW id, so the pre-save id is stale
13
+ // the moment saveLrp returns. Both the deploy target and the post-save
14
+ // version/status must come from a fresh resolve-by-name AFTER the save — never
15
+ // from the pre-save row or the PATCH response (which does not echo the new id).
16
+ async function pushLrpEntry(token, entry, backupDir, backupTs) {
17
+ const read = projectReader(entry.path);
18
+ const localPayload = recompose(read);
19
+ const upstream = entry.upstream; // { row, payload } from push.js's classification
20
+
21
+ if (upstream) {
22
+ const backupPath = join(backupDir, backupTs, entry.path, 'upstream.xml');
23
+ mkdirSync(dirname(backupPath), { recursive: true });
24
+ writeFileSync(backupPath, upstream.payload.built_page, 'utf-8');
25
+ }
26
+
27
+ // Re-resolve immediately before saving — the row captured during
28
+ // classification may be stale if something else saved in between.
29
+ const row = await resolveLrpByName(token, entry.name);
30
+ const description = upstream?.row?.description ?? entry.description ?? '';
31
+ const saved = await saveLrp(
32
+ token,
33
+ { ...row, bpmnFileSvg: entry.svg ?? row.bpmnFileSvg },
34
+ localPayload.built_page,
35
+ description
36
+ );
37
+
38
+ // The save minted a new id and bumped the version — re-resolve by name to
39
+ // get the fresh row. deployBpmn must target this new id (the pre-save id is
40
+ // now dead: deploying it 200s but activates nothing), and the workspace
41
+ // entry's version/status must come from here, not the stale pre-save row.
42
+ const deployRow = await resolveLrpByName(token, entry.name);
43
+
44
+ return {
45
+ newChecksum: localChecksum(read),
46
+ newRow: { ...deployRow, description },
47
+ saved,
48
+ deployId: deployRow.id,
49
+ };
50
+ }
51
+
52
+ function deploy(token, id) {
53
+ return deployLrp(token, id);
54
+ }
55
+
56
+ export { pushLrpEntry, deploy };
@@ -1,3 +1,4 @@
1
+ import { log } from '../../lib/logger.js';
1
2
  import { readConfig, writeConfig } from '../lib/config.js';
2
3
  import { fileExists } from '../lib/workspace.js';
3
4
 
@@ -9,16 +10,16 @@ async function repair() {
9
10
  config.workspace = config.workspace.filter((entry) => fileExists(entry.path));
10
11
 
11
12
  if (!stale.length) {
12
- console.log('No stale entries found.');
13
+ log('No stale entries found.');
13
14
  return;
14
15
  }
15
16
 
16
17
  for (const entry of stale) {
17
- console.log(` removed: ${entry.path} (${entry.name})`);
18
+ log(` removed: ${entry.path} (${entry.name})`);
18
19
  }
19
20
 
20
21
  writeConfig(config);
21
- console.log(
22
+ log(
22
23
  `Removed ${stale.length} stale entr${stale.length === 1 ? 'y' : 'ies'} (${before} → ${config.workspace.length}).`
23
24
  );
24
25
  }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  import { program } from 'commander';
4
+ import { error } from '../lib/logger.js';
4
5
  import { clone } from './commands/clone.js';
5
6
  import { diff } from './commands/diff.js';
6
7
  import { init } from './commands/init.js';
@@ -24,7 +25,7 @@ const require = createRequire(import.meta.url);
24
25
  const { version } = require('../../package.json');
25
26
 
26
27
  process.on('unhandledRejection', (err) => {
27
- console.error(`Error: ${err.message}`);
28
+ error(`Error: ${err.message}`);
28
29
  process.exit(1);
29
30
  });
30
31
 
@@ -35,23 +36,26 @@ program
35
36
  .description('Create agrippa.yaml workspace config in the current directory')
36
37
  .action(() =>
37
38
  init().catch((err) => {
38
- console.error(`Error: ${err.message}`);
39
+ error(`Error: ${err.message}`);
39
40
  process.exit(1);
40
41
  })
41
42
  );
42
43
 
43
44
  program
44
45
  .command('clone')
45
- .description('Clone a phase, MFA, or process-builder wizard into this workspace')
46
+ .description(
47
+ 'Clone a phase, MFA, process-builder wizard, or long-running process into this workspace'
48
+ )
46
49
  .option('--phase', 'Clone a phase (select a workflow)')
47
50
  .option('--mfa', 'Clone a Model Function Access record')
48
51
  .option('--pb', 'Clone a process-builder wizard')
52
+ .option('--lrp', 'Clone a long-running process')
49
53
  .option('--id <id>', 'Record ID to clone (phase/mfa)')
50
- .option('--name <document_id>', 'Process-builder document_id to clone (with --pb)')
51
- .option('--path <path>', 'Destination path (file for MFA, base dir for workflow/pb)')
54
+ .option('--name <name>', 'document_id (--pb) or process name (--lrp) to clone')
55
+ .option('--path <path>', 'Destination path (file for MFA, base dir for workflow/pb/lrp)')
52
56
  .action((opts) =>
53
57
  clone(opts).catch((err) => {
54
- console.error(`Error: ${err.message}`);
58
+ error(`Error: ${err.message}`);
55
59
  process.exit(1);
56
60
  })
57
61
  );
@@ -59,21 +63,29 @@ program
59
63
  program
60
64
  .command('pull')
61
65
  .description('Pull remote changes into local files')
62
- .action(() =>
63
- pull().catch((err) => {
64
- console.error(`Error: ${err.message}`);
66
+ .option(
67
+ '--non-interactive',
68
+ 'No prompts; auto-select safe (fast-forward) entries and fail if any is in conflict'
69
+ )
70
+ .action((opts) =>
71
+ pull(opts).catch((err) => {
72
+ error(`Error: ${err.message}`);
65
73
  process.exit(1);
66
74
  })
67
75
  );
68
76
 
69
77
  program
70
78
  .command('push')
71
- .description('Push local changes to RIP / Process Builder (backs up remote first)')
72
- .option('--publish', 'Auto-publish pushed process-builder wizards')
73
- .option('--skip-publish', 'Skip publishing pushed wizards (no prompt)')
79
+ .description('Push local changes to RIP / Process Builder / LRP (backs up remote first)')
80
+ .option('--publish', 'Auto-publish pushed wizards and auto-deploy pushed LRPs')
81
+ .option('--skip-publish', 'Skip publishing/deploying pushed wizards and LRPs (no prompt)')
82
+ .option(
83
+ '--non-interactive',
84
+ 'No prompts; auto-select safe (fast-forward) entries and fail if any is in conflict'
85
+ )
74
86
  .action((opts) =>
75
87
  push(opts).catch((err) => {
76
- console.error(`Error: ${err.message}`);
88
+ error(`Error: ${err.message}`);
77
89
  process.exit(1);
78
90
  })
79
91
  );
@@ -83,7 +95,7 @@ program
83
95
  .description('Show differences between local files and remote code')
84
96
  .action((path) =>
85
97
  diff(path).catch((err) => {
86
- console.error(`Error: ${err.message}`);
98
+ error(`Error: ${err.message}`);
87
99
  process.exit(1);
88
100
  })
89
101
  );
@@ -93,7 +105,7 @@ program
93
105
  .description('Initialize a phase with default code template and result vars')
94
106
  .action(() =>
95
107
  initPhase().catch((err) => {
96
- console.error(`Error: ${err.message}`);
108
+ error(`Error: ${err.message}`);
97
109
  process.exit(1);
98
110
  })
99
111
  );
@@ -103,30 +115,34 @@ program
103
115
  .description('Remove stale workspace entries where local file no longer exists')
104
116
  .action(() =>
105
117
  repair().catch((err) => {
106
- console.error(`Error: ${err.message}`);
118
+ error(`Error: ${err.message}`);
107
119
  process.exit(1);
108
120
  })
109
121
  );
110
122
 
111
- // ---- pb: local editing helpers for a cloned process-builder wizard ----
123
+ // ---- pb: local editing helpers for a cloned process-builder wizard or LRP ----
112
124
  const die = (err) => {
113
- console.error(`Error: ${err.message}`);
125
+ error(`Error: ${err.message}`);
114
126
  process.exit(1);
115
127
  };
116
128
  const pb = program
117
129
  .command('pb')
118
- .description('Edit a cloned process-builder wizard (local; run `pb format` after edits)');
130
+ .description(
131
+ 'Edit a cloned process-builder wizard or long-running process (local; run `pb format` after edits)'
132
+ );
119
133
 
120
134
  pb.command('format')
121
135
  .description('Auto-lay-out the diagram (left→right) and rewrite geometry')
122
- .option('--pb <document_id>', 'Target wizard (else single-entry / fuzzy prompt)')
136
+ .option('--pb <document_id_or_name>', 'Target wizard/LRP (else single-entry / fuzzy prompt)')
123
137
  .action((opts) => pbFormat(opts).catch(die));
124
138
 
125
139
  pb.command('add')
126
140
  .description('Add a node (scaffolds script/page); stub geometry, run format after')
127
141
  .requiredOption(
128
142
  '--type <type>',
129
- 'Node type: scriptTask|serviceTask|userTask|exclusiveGateway|subProcess|transaction|startEvent|endEvent|boundaryEvent'
143
+ 'Node type: scriptTask|serviceTask|userTask|exclusiveGateway|subProcess|transaction|' +
144
+ 'startEvent|endEvent|boundaryEvent|intermediateCatchEvent|intermediateThrowEvent|' +
145
+ 'callActivity|parallelGateway|eventBasedGateway (userTask is process-builder only)'
130
146
  )
131
147
  .option('--name <name>', 'Node name')
132
148
  .option('--parent <id>', 'Place inside this subProcess/transaction')
@@ -136,13 +152,13 @@ pb.command('add')
136
152
  'exactly one edge must already run --from → --to)'
137
153
  )
138
154
  .option('--to <id>', 'Insert between two already-connected nodes: target id (requires --from)')
139
- .option('--pb <document_id>', 'Target wizard')
155
+ .option('--pb <document_id_or_name>', 'Target wizard/LRP')
140
156
  .action((opts) => pbAdd(opts).catch(die));
141
157
 
142
158
  pb.command('rm')
143
159
  .description('Remove a node, its edges, and its script/page files')
144
160
  .requiredOption('--id <id>', 'Node id to remove')
145
- .option('--pb <document_id>', 'Target wizard')
161
+ .option('--pb <document_id_or_name>', 'Target wizard/LRP')
146
162
  .action((opts) => pbRemove(opts).catch(die));
147
163
 
148
164
  pb.command('connect')
@@ -153,7 +169,7 @@ pb.command('connect')
153
169
  .option('--condition <expr>', 'Condition expression, e.g. ${isAlive}')
154
170
  .option('--condition-type <type>', 'xsi:type for the condition (default tFormalExpression)')
155
171
  .option('--default', 'Mark this as the source gateway default flow')
156
- .option('--pb <document_id>', 'Target wizard')
172
+ .option('--pb <document_id_or_name>', 'Target wizard/LRP')
157
173
  .action((opts) => pbConnect(opts).catch(die));
158
174
 
159
175
  pb.command('disconnect')
@@ -161,7 +177,7 @@ pb.command('disconnect')
161
177
  .option('--id <id>', 'Edge id to remove')
162
178
  .option('--from <id>', 'Source node id')
163
179
  .option('--to <id>', 'Target node id')
164
- .option('--pb <document_id>', 'Target wizard')
180
+ .option('--pb <document_id_or_name>', 'Target wizard/LRP')
165
181
  .action((opts) => pbDisconnect(opts).catch(die));
166
182
 
167
183
  pb.command('set-default')
@@ -169,23 +185,25 @@ pb.command('set-default')
169
185
  .option('--id <id>', 'Edge id to mark default')
170
186
  .option('--from <id>', 'Source gateway id')
171
187
  .option('--to <id>', 'Target node id')
172
- .option('--pb <document_id>', 'Target wizard')
188
+ .option('--pb <document_id_or_name>', 'Target wizard/LRP')
173
189
  .action((opts) => pbSetDefault(opts).catch(die));
174
190
 
175
191
  pb.command('lint')
176
- .description('Check diagram for structural issues (edge names, incoming-edge rules, gateway rules)')
177
- .option('--pb <document_id>', 'Target wizard')
192
+ .description(
193
+ 'Check diagram for structural issues (edge names, incoming-edge rules, gateway rules)'
194
+ )
195
+ .option('--pb <document_id_or_name>', 'Target wizard/LRP')
178
196
  .action((opts) => pbLint(opts).catch(die));
179
197
 
180
198
  pb.command('ls')
181
199
  .description('List nodes and edges (discover ids without reading the YAML)')
182
- .option('--pb <document_id>', 'Target wizard')
200
+ .option('--pb <document_id_or_name>', 'Target wizard/LRP')
183
201
  .action((opts) => pbList(opts).catch(die));
184
202
 
185
203
  pb.command('preview')
186
204
  .description('Render the diagram to an SVG (dev check of format output)')
187
205
  .option('--out <file>', 'Output path (default <project>/preview.svg)')
188
- .option('--pb <document_id>', 'Target wizard')
206
+ .option('--pb <document_id_or_name>', 'Target wizard/LRP')
189
207
  .action((opts) => pbPreview(opts).catch(die));
190
208
 
191
209
  program.parse();