@bpmnkit/cli 0.0.17 → 0.0.18
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/dist/commands/lint.js +157 -3
- package/dist/commands/profile.js +62 -3
- package/dist/commands/worker.js +12 -10
- package/dist/generated/commands.js +4 -0
- package/dist/output.js +25 -0
- package/dist/run.js +4 -1
- package/dist/tui.js +213 -29
- package/package.json +5 -5
package/dist/commands/lint.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { Bpmn, optimize } from "@bpmnkit/core";
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { Bpmn, compactify, optimize } from "@bpmnkit/core";
|
|
3
3
|
const SEVERITY_SYMBOL = {
|
|
4
4
|
error: "✖",
|
|
5
5
|
warning: "⚠",
|
|
6
6
|
info: "ℹ",
|
|
7
7
|
};
|
|
8
|
+
const DEFAULT_SERVER = "http://localhost:3033";
|
|
8
9
|
const lintCmd = {
|
|
9
10
|
name: "lint",
|
|
10
11
|
description: "Lint a BPMN file — run all static analysis and pattern checks",
|
|
@@ -20,6 +21,11 @@ const lintCmd = {
|
|
|
20
21
|
description: "Output format: text (default) or json",
|
|
21
22
|
type: "string",
|
|
22
23
|
},
|
|
24
|
+
{
|
|
25
|
+
name: "fix",
|
|
26
|
+
description: "Auto-apply all fixable findings and write the result back to the file",
|
|
27
|
+
type: "boolean",
|
|
28
|
+
},
|
|
23
29
|
],
|
|
24
30
|
async run(ctx) {
|
|
25
31
|
const filePath = ctx.positional[0];
|
|
@@ -33,6 +39,20 @@ const lintCmd = {
|
|
|
33
39
|
: undefined;
|
|
34
40
|
const report = optimize(defs, categories !== undefined ? { categories } : undefined);
|
|
35
41
|
const { findings } = report;
|
|
42
|
+
// --fix: apply all auto-fixable findings and write back
|
|
43
|
+
if (ctx.flags.fix) {
|
|
44
|
+
const fixable = findings.filter((f) => f.applyFix);
|
|
45
|
+
for (const f of fixable)
|
|
46
|
+
f.applyFix?.(defs);
|
|
47
|
+
if (fixable.length === 0) {
|
|
48
|
+
ctx.output.ok("No auto-fixable issues found.");
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
await writeFile(filePath, Bpmn.export(defs), "utf-8");
|
|
52
|
+
ctx.output.ok(`Fixed ${fixable.length} issue${fixable.length === 1 ? "" : "s"} and wrote ${filePath}`);
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
36
56
|
const formatFlag = ctx.flags.format;
|
|
37
57
|
if (formatFlag === "json") {
|
|
38
58
|
ctx.output.print(findings);
|
|
@@ -57,9 +77,143 @@ const lintCmd = {
|
|
|
57
77
|
}
|
|
58
78
|
},
|
|
59
79
|
};
|
|
80
|
+
function describeOp(op) {
|
|
81
|
+
switch (op.op) {
|
|
82
|
+
case "rename":
|
|
83
|
+
return `Rename to "${op.name}"`;
|
|
84
|
+
case "update":
|
|
85
|
+
return "Update element properties";
|
|
86
|
+
case "delete":
|
|
87
|
+
return "Remove element";
|
|
88
|
+
case "insert":
|
|
89
|
+
return `Add ${op.element.type}${op.element.name ? ` "${op.element.name}"` : ""}`;
|
|
90
|
+
case "add_flow":
|
|
91
|
+
return `Add flow: ${op.from} → ${op.to}`;
|
|
92
|
+
case "delete_flow":
|
|
93
|
+
return "Remove flow";
|
|
94
|
+
case "redirect_flow":
|
|
95
|
+
return "Redirect flow";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const improveCmd = {
|
|
99
|
+
name: "improve",
|
|
100
|
+
description: "AI-assisted BPMN improvement — analyzes and suggests fixes using an AI model",
|
|
101
|
+
args: [{ name: "file", description: "Path to the .bpmn file", required: true }],
|
|
102
|
+
flags: [
|
|
103
|
+
{
|
|
104
|
+
name: "auto",
|
|
105
|
+
description: "Apply the AI-suggested improvements and write the result back to the file",
|
|
106
|
+
type: "boolean",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "server",
|
|
110
|
+
description: `AI proxy server URL (default: ${DEFAULT_SERVER})`,
|
|
111
|
+
type: "string",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
async run(ctx) {
|
|
115
|
+
const filePath = ctx.positional[0];
|
|
116
|
+
if (!filePath)
|
|
117
|
+
throw new Error("Missing required argument: <file>");
|
|
118
|
+
const serverUrl = typeof ctx.flags.server === "string" && ctx.flags.server.length > 0
|
|
119
|
+
? ctx.flags.server
|
|
120
|
+
: DEFAULT_SERVER;
|
|
121
|
+
const xml = await readFile(filePath, "utf-8");
|
|
122
|
+
const defs = Bpmn.parse(xml);
|
|
123
|
+
const compactDiagram = compactify(defs);
|
|
124
|
+
let res;
|
|
125
|
+
try {
|
|
126
|
+
res = await fetch(`${serverUrl}/improve`, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: { "Content-Type": "application/json" },
|
|
129
|
+
body: JSON.stringify({ context: compactDiagram, instruction: null, backend: null }),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
throw new Error(`Cannot reach AI server at ${serverUrl}. Start it with: pnpx @bpmnkit/ai-server\n${String(err)}`);
|
|
134
|
+
}
|
|
135
|
+
if (!res.ok || !res.body) {
|
|
136
|
+
throw new Error(`AI server returned ${res.status}`);
|
|
137
|
+
}
|
|
138
|
+
let capturedOps = [];
|
|
139
|
+
let capturedAutoFixCount = 0;
|
|
140
|
+
let capturedXml;
|
|
141
|
+
let hadOutput = false;
|
|
142
|
+
const reader = res.body.getReader();
|
|
143
|
+
const decoder = new TextDecoder();
|
|
144
|
+
let buf = "";
|
|
145
|
+
try {
|
|
146
|
+
while (true) {
|
|
147
|
+
const { done, value } = await reader.read();
|
|
148
|
+
if (done)
|
|
149
|
+
break;
|
|
150
|
+
buf += decoder.decode(value, { stream: true });
|
|
151
|
+
const parts = buf.split("\n\n");
|
|
152
|
+
buf = parts.pop() ?? "";
|
|
153
|
+
for (const part of parts) {
|
|
154
|
+
const line = part.startsWith("data: ") ? part.slice(6) : part;
|
|
155
|
+
const trimmed = line.trim();
|
|
156
|
+
if (!trimmed)
|
|
157
|
+
continue;
|
|
158
|
+
try {
|
|
159
|
+
const event = JSON.parse(trimmed);
|
|
160
|
+
if (event.type === "token" && event.text) {
|
|
161
|
+
process.stdout.write(event.text);
|
|
162
|
+
hadOutput = true;
|
|
163
|
+
}
|
|
164
|
+
if (event.type === "ops") {
|
|
165
|
+
capturedOps = event.ops ?? [];
|
|
166
|
+
capturedAutoFixCount = event.autoFixCount ?? 0;
|
|
167
|
+
}
|
|
168
|
+
if (event.type === "xml" && event.xml)
|
|
169
|
+
capturedXml = event.xml;
|
|
170
|
+
if (event.type === "error")
|
|
171
|
+
throw new Error(event.message ?? "AI error");
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
if (e instanceof SyntaxError)
|
|
175
|
+
continue;
|
|
176
|
+
throw e;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
reader.releaseLock();
|
|
183
|
+
}
|
|
184
|
+
if (hadOutput)
|
|
185
|
+
process.stdout.write("\n\n");
|
|
186
|
+
// Print diff summary
|
|
187
|
+
if (capturedAutoFixCount > 0) {
|
|
188
|
+
ctx.output.info(`✓ ${capturedAutoFixCount} issue${capturedAutoFixCount === 1 ? "" : "s"} auto-fixed`);
|
|
189
|
+
}
|
|
190
|
+
if (capturedOps.length > 0) {
|
|
191
|
+
ctx.output.info("AI-suggested changes:");
|
|
192
|
+
for (const op of capturedOps) {
|
|
193
|
+
const prefix = op.op === "insert" || op.op === "add_flow"
|
|
194
|
+
? "+"
|
|
195
|
+
: op.op === "delete" || op.op === "delete_flow"
|
|
196
|
+
? "−"
|
|
197
|
+
: "~";
|
|
198
|
+
ctx.output.info(` ${prefix} ${describeOp(op)}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (capturedXml === undefined) {
|
|
202
|
+
ctx.output.info("No changes suggested.");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (ctx.flags.auto) {
|
|
206
|
+
await writeFile(filePath, capturedXml, "utf-8");
|
|
207
|
+
ctx.output.ok(`Improvements applied and written to ${filePath}`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
ctx.output.info("\nRun with --auto to apply these changes.");
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
};
|
|
60
214
|
export const lintGroup = {
|
|
61
215
|
name: "lint",
|
|
62
216
|
description: "Lint BPMN files using the static analyzer",
|
|
63
|
-
commands: [lintCmd],
|
|
217
|
+
commands: [lintCmd, improveCmd],
|
|
64
218
|
};
|
|
65
219
|
//# sourceMappingURL=lint.js.map
|
package/dist/commands/profile.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { deleteProfile, getActiveName, getActiveProfile, getConfigFilePath, getProfile, listProfiles, saveProfile, useProfile, } from "@bpmnkit/profiles";
|
|
2
|
+
import { deleteProfile, getActiveName, getActiveProfile, getConfigFilePath, getProfile, listProfiles, saveProfile, setProfileMeta, useProfile, } from "@bpmnkit/profiles";
|
|
3
3
|
const API_TYPE_FLAG = {
|
|
4
4
|
name: "api-type",
|
|
5
5
|
description: "API type: c8 (default) or admin",
|
|
6
6
|
type: "string",
|
|
7
7
|
default: "c8",
|
|
8
8
|
placeholder: "TYPE",
|
|
9
|
+
enum: ["c8", "admin"],
|
|
9
10
|
};
|
|
11
|
+
const META_FLAGS = [
|
|
12
|
+
{
|
|
13
|
+
name: "description",
|
|
14
|
+
description: "Short description of the profile",
|
|
15
|
+
type: "string",
|
|
16
|
+
placeholder: "TEXT",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "tags",
|
|
20
|
+
description: "Comma-separated tags (predefined: dev, stage, prod)",
|
|
21
|
+
type: "string",
|
|
22
|
+
placeholder: "TAGS",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
10
25
|
const AUTH_FLAGS = [
|
|
11
26
|
{
|
|
12
27
|
name: "base-url",
|
|
@@ -21,6 +36,7 @@ const AUTH_FLAGS = [
|
|
|
21
36
|
type: "string",
|
|
22
37
|
required: true,
|
|
23
38
|
placeholder: "TYPE",
|
|
39
|
+
enum: ["bearer", "oauth2", "basic", "none"],
|
|
24
40
|
},
|
|
25
41
|
{
|
|
26
42
|
name: "token",
|
|
@@ -165,7 +181,7 @@ export const profileGroup = {
|
|
|
165
181
|
name: "create",
|
|
166
182
|
description: "Create or update a profile",
|
|
167
183
|
args: [{ name: "name", description: "Profile name", required: true }],
|
|
168
|
-
flags: [API_TYPE_FLAG, ...AUTH_FLAGS],
|
|
184
|
+
flags: [API_TYPE_FLAG, ...AUTH_FLAGS, ...META_FLAGS],
|
|
169
185
|
examples: [
|
|
170
186
|
{
|
|
171
187
|
description: "Bearer token profile",
|
|
@@ -191,7 +207,15 @@ export const profileGroup = {
|
|
|
191
207
|
const rawApiType = ctx.flags["api-type"] ?? "c8";
|
|
192
208
|
const apiType = rawApiType === "admin" ? "admin" : "c8";
|
|
193
209
|
const config = { baseUrl, auth };
|
|
194
|
-
|
|
210
|
+
const description = ctx.flags.description;
|
|
211
|
+
const tagsRaw = ctx.flags.tags;
|
|
212
|
+
const tags = tagsRaw
|
|
213
|
+
? tagsRaw
|
|
214
|
+
.split(",")
|
|
215
|
+
.map((t) => t.trim())
|
|
216
|
+
.filter(Boolean)
|
|
217
|
+
: undefined;
|
|
218
|
+
saveProfile(name, config, apiType, { description, tags });
|
|
195
219
|
ctx.output.ok(`Profile "${name}" saved [${apiType}] (${getConfigFilePath()})`);
|
|
196
220
|
},
|
|
197
221
|
},
|
|
@@ -214,6 +238,7 @@ export const profileGroup = {
|
|
|
214
238
|
apiType: p.apiType,
|
|
215
239
|
baseUrl: p.config.baseUrl ?? "(from env/file)",
|
|
216
240
|
authType: p.config.auth?.type ?? "—",
|
|
241
|
+
tags: p.tags && p.tags.length > 0 ? p.tags.join(", ") : "—",
|
|
217
242
|
})),
|
|
218
243
|
}, [
|
|
219
244
|
{ key: "active", header: " " },
|
|
@@ -221,6 +246,7 @@ export const profileGroup = {
|
|
|
221
246
|
{ key: "apiType", header: "API" },
|
|
222
247
|
{ key: "baseUrl", header: "BASE URL", maxWidth: 50 },
|
|
223
248
|
{ key: "authType", header: "AUTH TYPE" },
|
|
249
|
+
{ key: "tags", header: "TAGS" },
|
|
224
250
|
]);
|
|
225
251
|
},
|
|
226
252
|
},
|
|
@@ -260,6 +286,10 @@ export const profileGroup = {
|
|
|
260
286
|
const active = getActiveName();
|
|
261
287
|
const isActive = profile.name === active;
|
|
262
288
|
ctx.output.info(`Profile: ${profile.name}${isActive ? " (active)" : ""}`);
|
|
289
|
+
if (profile.description)
|
|
290
|
+
ctx.output.info(`Description: ${profile.description}`);
|
|
291
|
+
if (profile.tags && profile.tags.length > 0)
|
|
292
|
+
ctx.output.info(`Tags: ${profile.tags.join(", ")}`);
|
|
263
293
|
ctx.output.printItem(profile.config);
|
|
264
294
|
},
|
|
265
295
|
},
|
|
@@ -304,6 +334,35 @@ export const profileGroup = {
|
|
|
304
334
|
ctx.output.info(`baseUrl: ${config.baseUrl ?? ""}`);
|
|
305
335
|
},
|
|
306
336
|
},
|
|
337
|
+
{
|
|
338
|
+
name: "meta",
|
|
339
|
+
description: "Update description or tags on an existing profile",
|
|
340
|
+
args: [{ name: "name", description: "Profile name", required: true }],
|
|
341
|
+
flags: META_FLAGS,
|
|
342
|
+
examples: [
|
|
343
|
+
{
|
|
344
|
+
description: "Set description and tags",
|
|
345
|
+
command: 'casen profile meta prod --description "Production cluster" --tags prod',
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
async run(ctx) {
|
|
349
|
+
const name = ctx.positional[0];
|
|
350
|
+
if (!name)
|
|
351
|
+
throw new Error("Missing required argument: <name>");
|
|
352
|
+
const description = ctx.flags.description;
|
|
353
|
+
const tagsRaw = ctx.flags.tags;
|
|
354
|
+
const tags = tagsRaw
|
|
355
|
+
? tagsRaw
|
|
356
|
+
.split(",")
|
|
357
|
+
.map((t) => t.trim())
|
|
358
|
+
.filter(Boolean)
|
|
359
|
+
: undefined;
|
|
360
|
+
if (!setProfileMeta(name, { description, tags })) {
|
|
361
|
+
throw new Error(`Profile "${name}" not found.`);
|
|
362
|
+
}
|
|
363
|
+
ctx.output.ok(`Profile "${name}" updated`);
|
|
364
|
+
},
|
|
365
|
+
},
|
|
307
366
|
{
|
|
308
367
|
name: "delete",
|
|
309
368
|
aliases: ["rm"],
|
package/dist/commands/worker.js
CHANGED
|
@@ -94,7 +94,6 @@ export const workerCmd = {
|
|
|
94
94
|
worker: "casen-worker",
|
|
95
95
|
timeout,
|
|
96
96
|
maxJobsToActivate: maxJobs,
|
|
97
|
-
requestTimeout: 20000, // 20 s long poll
|
|
98
97
|
}));
|
|
99
98
|
}
|
|
100
99
|
catch (err) {
|
|
@@ -109,24 +108,27 @@ export const workerCmd = {
|
|
|
109
108
|
for (const job of jobs) {
|
|
110
109
|
if (!running)
|
|
111
110
|
break;
|
|
112
|
-
|
|
111
|
+
// Some Zeebe-compatible engines return `key` instead of `jobKey`
|
|
112
|
+
const jobKey = job.jobKey ?? job.key;
|
|
113
|
+
if (!jobKey) {
|
|
114
|
+
ctx.output.info("Activated job has no key — skipping");
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
ctx.output.info(`Activated job ${jobKey} process=${job.processDefinitionId} element=${job.elementId} instance=${job.processInstanceKey}`);
|
|
113
118
|
if (Object.keys(job.variables ?? {}).length > 0) {
|
|
114
119
|
ctx.output.info(` Input variables: ${JSON.stringify(job.variables)}`);
|
|
115
120
|
}
|
|
116
121
|
try {
|
|
117
|
-
await client.job.completeJob(
|
|
122
|
+
await client.job.completeJob(jobKey, { variables });
|
|
118
123
|
completed++;
|
|
119
|
-
ctx.output.ok(`Completed job ${
|
|
124
|
+
ctx.output.ok(`Completed job ${jobKey} (total: ${completed})`);
|
|
120
125
|
}
|
|
121
126
|
catch (err) {
|
|
122
|
-
ctx.output.info(`Failed to complete job ${
|
|
127
|
+
ctx.output.info(`Failed to complete job ${jobKey}: ${err instanceof Error ? err.message : String(err)}`);
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
if (jobs.length > 0) {
|
|
128
|
-
await delay(100);
|
|
129
|
-
}
|
|
130
|
+
// Sleep between polls: brief yield after processing jobs, longer pause when idle
|
|
131
|
+
await delay(jobs.length > 0 ? 100 : 2000);
|
|
130
132
|
}
|
|
131
133
|
ctx.output.info(`\nWorker stopped. Completed ${completed} job(s).`);
|
|
132
134
|
},
|
|
@@ -53,6 +53,7 @@ export const authorizationGroup = {
|
|
|
53
53
|
makeCreateCmd({
|
|
54
54
|
description: "Create authorization",
|
|
55
55
|
create: (client, body) => client.authorization.createAuthorization(body),
|
|
56
|
+
bodyFields: [{ name: "ownerId", type: "string", description: "The ID of the owner of the permissions.", required: true }, { name: "ownerType", type: "string", description: "The type of the owner of permissions.", required: true, enum: ["USER", "CLIENT", "ROLE", "GROUP", "MAPPING_RULE", "UNSPECIFIED"] }, { name: "resourceId", type: "string", description: "The ID of the resource to add permissions to.", required: true }, { name: "resourceType", type: "string", description: "The type of resource to add permissions to.", required: true }, { name: "permissionTypes", type: "array", description: "The permission types to add.", required: true }, { name: "resourcePropertyName", type: "string", description: "The name of the resource property on which this authorization is based.", required: true }],
|
|
56
57
|
}),
|
|
57
58
|
makeListCmd({
|
|
58
59
|
description: "Search authorizations",
|
|
@@ -77,6 +78,7 @@ export const authorizationGroup = {
|
|
|
77
78
|
description: "Update authorization",
|
|
78
79
|
argName: "authorizationKey",
|
|
79
80
|
update: (client, key, body) => client.authorization.updateAuthorization(key, body),
|
|
81
|
+
bodyFields: [{ name: "ownerId", type: "string", description: "The ID of the owner of the permissions.", required: true }, { name: "ownerType", type: "string", description: "The type of the owner of permissions.", required: true, enum: ["USER", "CLIENT", "ROLE", "GROUP", "MAPPING_RULE", "UNSPECIFIED"] }, { name: "resourceId", type: "string", description: "The ID of the resource to add permissions to.", required: true }, { name: "resourceType", type: "string", description: "The type of resource to add permissions to.", required: true }, { name: "permissionTypes", type: "array", description: "The permission types to add.", required: true }, { name: "resourcePropertyName", type: "string", description: "The name of the resource property on which this authorization is based.", required: true }],
|
|
80
82
|
}),
|
|
81
83
|
makeDeleteCmd({
|
|
82
84
|
description: "Delete authorization",
|
|
@@ -354,6 +356,7 @@ export const decisionDefinitionGroup = {
|
|
|
354
356
|
name: "evaluate",
|
|
355
357
|
description: "Evaluate decision",
|
|
356
358
|
create: (client, body) => client.decisionDefinition.evaluateDecision(body),
|
|
359
|
+
bodyFields: [{ name: "decisionDefinitionId", type: "string", description: " The ID of the decision to be evaluated.\n When using the decision ID, the latest\n deployed version of the decision is used.", required: true }, { name: "variables", type: "object", description: "The message variables as JSON document." }, { name: "tenantId", type: "string", description: "The tenant ID of the decision." }, { name: "decisionDefinitionKey", type: "string", description: "System-generated key for a decision definition.", required: true }],
|
|
357
360
|
}),
|
|
358
361
|
makeListCmd({
|
|
359
362
|
description: "Search decision definitions",
|
|
@@ -1158,6 +1161,7 @@ export const processInstanceGroup = {
|
|
|
1158
1161
|
makeCreateCmd({
|
|
1159
1162
|
description: "Create process instance",
|
|
1160
1163
|
create: (client, body) => client.processInstance.createProcessInstance(body),
|
|
1164
|
+
bodyFields: [{ name: "processDefinitionKey", type: "string", description: " The unique key identifying the process definition, for example, returned for a process in the\n deploy resources endpoint.", required: true }, { name: "processDefinitionVersion", type: "string", description: " As the version is already identified by the `processDefinitionKey`, the value of this field is ignored.\n It's here for backwards-compatibility only as previous releases accepted it in request bodies." }, { name: "variables", type: "object", description: " Set of variables as JSON object to instantiate in the root variable scope of the process\n instance. Can include nested complex objects." }, { name: "startInstructions", type: "array", description: " List of start instructions. By default, the process instance will start at\n the start event. If provided, the process instance will apply start instructions\n after it has been created." }, { name: "runtimeInstructions", type: "array", description: " Runtime instructions (alpha). List of instructions that affect the runtime behavior of\n the process instance. Refer to specific instruction types for more details.\n\n This parameter is an alpha feature and may be subject to change\n in future releases." }, { name: "tenantId", type: "string", description: " The tenant id of the process definition.\n If multi-tenancy is enabled, provide the tenant id of the process definition to start a\n process instance of. If multi-tenancy is disabled, don't provide this parameter." }, { name: "operationReference", type: "string", description: " A reference key chosen by the user that will be part of all records resulting from this operation.\n Must be > 0 if provided." }, { name: "awaitCompletion", type: "boolean", description: " Wait for the process instance to complete. If the process instance does not complete\n within the request timeout limit, a 504 response status will be returned. The process\n instance will continue to run in the background regardless of the timeout. Disabled by\n default." }, { name: "requestTimeout", type: "string", description: " Timeout (in ms) the request waits for the process to complete. By default or\n when set to 0, the generic request timeout configured in the cluster is applied." }, { name: "fetchVariables", type: "array", description: " List of variables by name to be included in the response when awaitCompletion is set to true.\n If empty, all visible variables in the root scope will be returned." }, { name: "tags", type: "array", description: "List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100." }, { name: "businessId", type: "string", description: " An optional, user-defined string identifier that identifies the process instance\n within the scope of a process definition (scoped by tenant). If provided and uniqueness\n enforcement is enabled, the engine will reject creation if another root process instance\n with the same business id is already active for the same process definition.\n Note that any active child process instances with the same business id are not taken into account." }, { name: "processDefinitionId", type: "string", description: " The BPMN process id of the process definition to start an instance of.", required: true }],
|
|
1161
1165
|
}),
|
|
1162
1166
|
makeCreateCmd({
|
|
1163
1167
|
name: "cancel-batch-operation",
|
package/dist/output.js
CHANGED
|
@@ -259,4 +259,29 @@ export function printRawResponse(raw, noColor) {
|
|
|
259
259
|
process.stdout.write(`${raw.body}\n`);
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
|
+
/** Print HTTP error details (request + response) to stderr. */
|
|
263
|
+
export function printHttpErrorDetails(raw, noColor) {
|
|
264
|
+
const colors = shouldUseColor(noColor);
|
|
265
|
+
process.stderr.write(`\n${dim("→", colors)} ${bold(`${raw.method} ${raw.url}`, colors)}\n`);
|
|
266
|
+
for (const [k, v] of Object.entries(raw.requestHeaders)) {
|
|
267
|
+
process.stderr.write(` ${dim(k, colors)}: ${v}\n`);
|
|
268
|
+
}
|
|
269
|
+
if (raw.requestBody) {
|
|
270
|
+
process.stderr.write(`\n${dim(raw.requestBody, colors)}\n`);
|
|
271
|
+
}
|
|
272
|
+
process.stderr.write(`\n${red(`HTTP ${raw.status}`, colors)}\n`);
|
|
273
|
+
for (const [k, v] of Object.entries(raw.headers)) {
|
|
274
|
+
process.stderr.write(` ${dim(k, colors)}: ${v}\n`);
|
|
275
|
+
}
|
|
276
|
+
if (raw.body) {
|
|
277
|
+
process.stderr.write("\n");
|
|
278
|
+
try {
|
|
279
|
+
const parsed = JSON.parse(raw.body);
|
|
280
|
+
process.stderr.write(`${JSON.stringify(parsed, null, 2)}\n`);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
process.stderr.write(`${raw.body}\n`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
262
287
|
//# sourceMappingURL=output.js.map
|
package/dist/run.js
CHANGED
|
@@ -3,7 +3,7 @@ import { parseArgs } from "./args.js";
|
|
|
3
3
|
import { apiGroups, commandGroups, pinnedGroups, pluginGroup, profileGroup, } from "./commands/index.js";
|
|
4
4
|
import { getRuntimeCompletions } from "./completion.js";
|
|
5
5
|
import { printCommandHelp, printGlobalHelp, printGroupHelp, printVersion } from "./help.js";
|
|
6
|
-
import { createNullWriter, createOutputWriter, printRawResponse } from "./output.js";
|
|
6
|
+
import { createNullWriter, createOutputWriter, printHttpErrorDetails, printRawResponse, } from "./output.js";
|
|
7
7
|
import { loadPlugins } from "./plugin-loader.js";
|
|
8
8
|
import { runProfileManager } from "./profile-tui.js";
|
|
9
9
|
import { runSettingsManager } from "./settings-tui.js";
|
|
@@ -240,6 +240,9 @@ export async function run(argv) {
|
|
|
240
240
|
if (isRaw && capture) {
|
|
241
241
|
printRawResponse(capture, noColor);
|
|
242
242
|
}
|
|
243
|
+
else if (capture) {
|
|
244
|
+
printHttpErrorDetails(capture, noColor);
|
|
245
|
+
}
|
|
243
246
|
const msg = err instanceof Error ? err.message : String(err);
|
|
244
247
|
appendAuditEntry(effectiveProfile, {
|
|
245
248
|
group: group.name,
|
package/dist/tui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { renderBpmnAscii } from "@bpmnkit/ascii";
|
|
3
|
-
import { appendAuditEntry, getAuditLog, getSettings, saveSettings } from "@bpmnkit/profiles";
|
|
3
|
+
import { appendAuditEntry, deleteProfile, getActiveName, getAuditLog, getProfile, getSettings, listProfiles, saveSettings, useProfile, } from "@bpmnkit/profiles";
|
|
4
4
|
import { runAskQuery } from "./commands/ask.js";
|
|
5
5
|
import { pluginGroup, searchNpmRegistry } from "./commands/plugin.js";
|
|
6
6
|
import { profileGroup } from "./commands/profile.js";
|
|
@@ -193,8 +193,9 @@ function entriesToJson(entries) {
|
|
|
193
193
|
}
|
|
194
194
|
/**
|
|
195
195
|
* Build initial entries for the JSON editor.
|
|
196
|
-
* If fieldSpecs are provided, pre-populate
|
|
197
|
-
* merging in any existing values.
|
|
196
|
+
* If fieldSpecs are provided, pre-populate only required fields (in spec order),
|
|
197
|
+
* merging in any existing values. Optional fields are available via the add-row picker.
|
|
198
|
+
* Extra keys from existing JSON that are not in the spec are appended at the end.
|
|
198
199
|
*/
|
|
199
200
|
function buildInitialEntries(existingJson, fieldSpecs) {
|
|
200
201
|
const existing = {};
|
|
@@ -213,7 +214,10 @@ function buildInitialEntries(existingJson, fieldSpecs) {
|
|
|
213
214
|
return parseJsonToEntries(existingJson);
|
|
214
215
|
const seenKeys = new Set();
|
|
215
216
|
const entries = [];
|
|
217
|
+
// Only pre-populate required fields; optional ones are added on demand
|
|
216
218
|
for (const spec of fieldSpecs) {
|
|
219
|
+
if (!spec.required && !(spec.name in existing))
|
|
220
|
+
continue;
|
|
217
221
|
seenKeys.add(spec.name);
|
|
218
222
|
const val = existing[spec.name] ?? "";
|
|
219
223
|
entries.push({ key: spec.name, keyCursor: spec.name.length, val, valCursor: val.length });
|
|
@@ -230,6 +234,57 @@ function buildInitialEntries(existingJson, fieldSpecs) {
|
|
|
230
234
|
function getFieldSpec(key, fieldSpecs) {
|
|
231
235
|
return fieldSpecs?.find((s) => s.name === key);
|
|
232
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Return specs not already used as keys by OTHER entries (not the one at currentIndex).
|
|
239
|
+
* Pass null for currentIndex when adding a brand-new entry.
|
|
240
|
+
*/
|
|
241
|
+
function getAvailableFieldSpecs(currentIndex, entries, fieldSpecs) {
|
|
242
|
+
const usedKeys = new Set(entries
|
|
243
|
+
.filter((_, i) => i !== currentIndex)
|
|
244
|
+
.map((e) => e.key)
|
|
245
|
+
.filter(Boolean));
|
|
246
|
+
return fieldSpecs.filter((s) => !usedKeys.has(s.name));
|
|
247
|
+
}
|
|
248
|
+
/** Build profile info entries (key/value pairs with secrets redacted). */
|
|
249
|
+
function buildProfileInfoEntries(profileName) {
|
|
250
|
+
const p = getProfile(profileName);
|
|
251
|
+
if (!p)
|
|
252
|
+
return [{ key: "status", value: "profile not found" }];
|
|
253
|
+
const info = [
|
|
254
|
+
{ key: "name", value: p.name },
|
|
255
|
+
{ key: "apiType", value: p.apiType },
|
|
256
|
+
{ key: "baseUrl", value: p.config.baseUrl ?? "(default)" },
|
|
257
|
+
];
|
|
258
|
+
const auth = p.config.auth;
|
|
259
|
+
if (auth) {
|
|
260
|
+
info.push({ key: "auth.type", value: auth.type });
|
|
261
|
+
if (auth.type === "bearer") {
|
|
262
|
+
info.push({ key: "auth.token", value: "***" });
|
|
263
|
+
}
|
|
264
|
+
else if (auth.type === "oauth2") {
|
|
265
|
+
info.push({ key: "auth.clientId", value: auth.clientId });
|
|
266
|
+
info.push({ key: "auth.clientSecret", value: "***" });
|
|
267
|
+
info.push({ key: "auth.tokenUrl", value: auth.tokenUrl });
|
|
268
|
+
}
|
|
269
|
+
else if (auth.type === "basic") {
|
|
270
|
+
info.push({ key: "auth.username", value: auth.username });
|
|
271
|
+
info.push({ key: "auth.password", value: "***" });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return info;
|
|
275
|
+
}
|
|
276
|
+
/** Rebuild profile list items after use/delete. */
|
|
277
|
+
function rebuildProfileListItems() {
|
|
278
|
+
const profiles = listProfiles();
|
|
279
|
+
const active = getActiveName();
|
|
280
|
+
return profiles.map((p) => ({
|
|
281
|
+
active: p.name === active ? "●" : " ",
|
|
282
|
+
name: p.name,
|
|
283
|
+
apiType: p.apiType,
|
|
284
|
+
baseUrl: p.config.baseUrl ?? "(from env/file)",
|
|
285
|
+
authType: p.config.auth?.type ?? "—",
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
233
288
|
// ─── Table helpers ────────────────────────────────────────────────────────────
|
|
234
289
|
function getCellStr(item, col) {
|
|
235
290
|
let val = item;
|
|
@@ -515,6 +570,22 @@ function renderInput(state, screen) {
|
|
|
515
570
|
lines.push("");
|
|
516
571
|
if (screen.error) {
|
|
517
572
|
lines.push(` ${red("error:")} ${screen.error}`);
|
|
573
|
+
if (screen.errorRaw) {
|
|
574
|
+
const raw = screen.errorRaw;
|
|
575
|
+
lines.push(` ${dim(`${raw.method} ${raw.url}`)}`);
|
|
576
|
+
lines.push(` ${red(`HTTP ${raw.status}`)}`);
|
|
577
|
+
if (raw.body) {
|
|
578
|
+
try {
|
|
579
|
+
const parsed = JSON.parse(raw.body);
|
|
580
|
+
for (const l of JSON.stringify(parsed, null, 2).split("\n")) {
|
|
581
|
+
lines.push(` ${dim(l)}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
lines.push(` ${dim(raw.body)}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
518
589
|
}
|
|
519
590
|
else if (screen.running) {
|
|
520
591
|
lines.push(` ${dim("running…")}`);
|
|
@@ -707,7 +778,12 @@ function renderResults(state, screen) {
|
|
|
707
778
|
lines.push(` ${dim(`${screen.scroll + 1}–${hi} of ${out.total}`)}`);
|
|
708
779
|
}
|
|
709
780
|
const followupHint = screen.cmd.relations ? ` ${cyan("f")} follow-up` : "";
|
|
710
|
-
|
|
781
|
+
const profileListHints = screen.group.name === "profile" && screen.cmd.name === "list"
|
|
782
|
+
? ` ${cyan("u")} use ${cyan("s")} show ${cyan("d")} delete`
|
|
783
|
+
: "";
|
|
784
|
+
if (screen.message)
|
|
785
|
+
lines.push(` ${green(screen.message)}`);
|
|
786
|
+
lines.push(` ${dim("↑↓")} navigate ${cyan("enter")} detail${followupHint}${profileListHints} ${dim("pgup/pgdn")} page${rawToggle}${curlToggle} ${cyan("m")} main menu ${cyan("esc")} back ${cyan("q")} quit`);
|
|
711
787
|
}
|
|
712
788
|
else if (out.type === "item") {
|
|
713
789
|
lines.push(renderHeader([screen.group.name, screen.cmd.name], cols, state.profile));
|
|
@@ -1032,12 +1108,15 @@ function renderJsonEditor(state, screen) {
|
|
|
1032
1108
|
const editKey = isCursor && screen.col === "key" && screen.editing;
|
|
1033
1109
|
const editVal = isCursor && screen.col === "val" && screen.editing;
|
|
1034
1110
|
const keyStr = renderText(entry.key, entry.keyCursor, keyW - (isKnown ? 0 : 0), editKey);
|
|
1035
|
-
// For known enum fields in nav mode, show cycling hint instead of raw value
|
|
1111
|
+
// For known enum fields in nav mode, show cycling hint instead of raw value.
|
|
1112
|
+
// For non-enum known fields, show a type placeholder when value is empty.
|
|
1036
1113
|
const valDisplay = !editVal && hasEnum && entry.val
|
|
1037
1114
|
? `${entry.val} ${dim("↑↓")}`
|
|
1038
1115
|
: !editVal && hasEnum && !entry.val
|
|
1039
1116
|
? dim("<pick ↑↓>")
|
|
1040
|
-
:
|
|
1117
|
+
: !editVal && !hasEnum && spec && !entry.val
|
|
1118
|
+
? dim(`<${spec.type}>`)
|
|
1119
|
+
: renderText(entry.val, entry.valCursor, valW - 4, editVal);
|
|
1041
1120
|
const valStr = valDisplay;
|
|
1042
1121
|
const keyPart = isCursor && screen.col === "key" && !editKey
|
|
1043
1122
|
? cyan(padEnd(keyStr, keyW))
|
|
@@ -1079,7 +1158,9 @@ function renderJsonEditor(state, screen) {
|
|
|
1079
1158
|
: undefined;
|
|
1080
1159
|
const editHint = editSpec?.enum
|
|
1081
1160
|
? `${dim("↑↓")} pick value ${cyan("enter")} confirm ${cyan("esc")} cancel`
|
|
1082
|
-
:
|
|
1161
|
+
: screen.col === "key" && screen.fieldSpecs && screen.fieldSpecs.length > 0
|
|
1162
|
+
? `${dim("↑↓")} pick field ${dim("←→")} cursor ${cyan("tab")} switch col ${cyan("enter")} confirm ${cyan("esc")} cancel`
|
|
1163
|
+
: `${dim("←→")} cursor ${cyan("tab")} switch col ${cyan("enter")} confirm ${cyan("esc")} cancel`;
|
|
1083
1164
|
lines.push(` ${editHint}`);
|
|
1084
1165
|
}
|
|
1085
1166
|
else {
|
|
@@ -1140,7 +1221,6 @@ async function runWorkerLoop(ws, state, variables, jobTimeout, maxJobs) {
|
|
|
1140
1221
|
worker: "casen-worker",
|
|
1141
1222
|
timeout: jobTimeout,
|
|
1142
1223
|
maxJobsToActivate: maxJobs,
|
|
1143
|
-
requestTimeout: 20000,
|
|
1144
1224
|
}));
|
|
1145
1225
|
}
|
|
1146
1226
|
catch (err) {
|
|
@@ -1154,13 +1234,19 @@ async function runWorkerLoop(ws, state, variables, jobTimeout, maxJobs) {
|
|
|
1154
1234
|
for (const job of jobs) {
|
|
1155
1235
|
if (!running)
|
|
1156
1236
|
break;
|
|
1237
|
+
// Some Zeebe-compatible engines return `key` instead of `jobKey`
|
|
1238
|
+
const jobKey = job.jobKey ?? job.key;
|
|
1239
|
+
if (!jobKey) {
|
|
1240
|
+
addWorkerLog(ws, "err", "Activated job has no key — skipping");
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1157
1243
|
ws.stats.activated++;
|
|
1158
|
-
addWorkerLog(ws, "info", `Activated ${
|
|
1244
|
+
addWorkerLog(ws, "info", `Activated ${jobKey} process=${job.processDefinitionId} element=${job.elementId}`);
|
|
1159
1245
|
let jobResult = { outcome: "complete", variables };
|
|
1160
1246
|
const processJob = ws.cmd._worker?.processJob;
|
|
1161
1247
|
if (processJob) {
|
|
1162
1248
|
try {
|
|
1163
|
-
jobResult = await processJob(job);
|
|
1249
|
+
jobResult = await processJob({ ...job, jobKey });
|
|
1164
1250
|
}
|
|
1165
1251
|
catch (err) {
|
|
1166
1252
|
addWorkerLog(ws, "err", `Handler error: ${err instanceof Error ? err.message : String(err)} — using defaults`);
|
|
@@ -1168,36 +1254,36 @@ async function runWorkerLoop(ws, state, variables, jobTimeout, maxJobs) {
|
|
|
1168
1254
|
}
|
|
1169
1255
|
try {
|
|
1170
1256
|
if (jobResult.outcome === "complete") {
|
|
1171
|
-
await client.job.completeJob(
|
|
1257
|
+
await client.job.completeJob(jobKey, { variables: jobResult.variables });
|
|
1172
1258
|
ws.stats.completed++;
|
|
1173
|
-
addWorkerLog(ws, "ok", `Completed ${
|
|
1259
|
+
addWorkerLog(ws, "ok", `Completed ${jobKey}`);
|
|
1174
1260
|
}
|
|
1175
1261
|
else if (jobResult.outcome === "fail") {
|
|
1176
|
-
await client.job.failJob(
|
|
1262
|
+
await client.job.failJob(jobKey, {
|
|
1177
1263
|
errorMessage: jobResult.errorMessage,
|
|
1178
1264
|
retries: jobResult.retries,
|
|
1179
1265
|
retryBackOff: jobResult.retryBackOff,
|
|
1180
1266
|
});
|
|
1181
1267
|
ws.stats.failed++;
|
|
1182
|
-
addWorkerLog(ws, "err", `Failed ${
|
|
1268
|
+
addWorkerLog(ws, "err", `Failed ${jobKey}: ${jobResult.errorMessage}`);
|
|
1183
1269
|
}
|
|
1184
1270
|
else {
|
|
1185
|
-
await client.job.throwJobError(
|
|
1271
|
+
await client.job.throwJobError(jobKey, {
|
|
1186
1272
|
errorCode: jobResult.errorCode,
|
|
1187
1273
|
errorMessage: jobResult.errorMessage,
|
|
1188
1274
|
variables: jobResult.variables,
|
|
1189
1275
|
});
|
|
1190
1276
|
ws.stats.failed++;
|
|
1191
|
-
addWorkerLog(ws, "err", `Error ${
|
|
1277
|
+
addWorkerLog(ws, "err", `Error ${jobKey} [${jobResult.errorCode}]: ${jobResult.errorMessage ?? ""}`);
|
|
1192
1278
|
}
|
|
1193
1279
|
}
|
|
1194
1280
|
catch (err) {
|
|
1195
1281
|
ws.stats.failed++;
|
|
1196
|
-
addWorkerLog(ws, "err", `Failed to settle ${
|
|
1282
|
+
addWorkerLog(ws, "err", `Failed to settle ${jobKey}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1197
1283
|
}
|
|
1198
1284
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1285
|
+
// Sleep between polls: brief yield after processing jobs, longer pause when idle
|
|
1286
|
+
await new Promise((r) => setTimeout(r, jobs.length > 0 ? 100 : 2000));
|
|
1201
1287
|
}
|
|
1202
1288
|
ws.status = "stopped";
|
|
1203
1289
|
addWorkerLog(ws, "info", `Stopped. Activated: ${ws.stats.activated} Completed: ${ws.stats.completed} Failed: ${ws.stats.failed}`);
|
|
@@ -1321,6 +1407,7 @@ async function runAskInTui(screen, state) {
|
|
|
1321
1407
|
},
|
|
1322
1408
|
raw: null,
|
|
1323
1409
|
rawView: false,
|
|
1410
|
+
message: "",
|
|
1324
1411
|
curlView: false,
|
|
1325
1412
|
altView: false,
|
|
1326
1413
|
cursor: 0,
|
|
@@ -1559,6 +1646,7 @@ function handleMainKey(key, screen, state, done) {
|
|
|
1559
1646
|
scroll: 0,
|
|
1560
1647
|
editing: false,
|
|
1561
1648
|
error: "",
|
|
1649
|
+
errorRaw: null,
|
|
1562
1650
|
running: false,
|
|
1563
1651
|
});
|
|
1564
1652
|
}
|
|
@@ -1626,6 +1714,7 @@ function handleCommandsKey(key, screen, state, done) {
|
|
|
1626
1714
|
scroll: 0,
|
|
1627
1715
|
editing: false,
|
|
1628
1716
|
error: "",
|
|
1717
|
+
errorRaw: null,
|
|
1629
1718
|
running: false,
|
|
1630
1719
|
});
|
|
1631
1720
|
}
|
|
@@ -1655,6 +1744,7 @@ async function executeCommand(screen, state) {
|
|
|
1655
1744
|
}
|
|
1656
1745
|
screen.running = true;
|
|
1657
1746
|
screen.error = "";
|
|
1747
|
+
screen.errorRaw = null;
|
|
1658
1748
|
render(state);
|
|
1659
1749
|
const { writer, get } = makeCapturingWriter();
|
|
1660
1750
|
// Wrap client factories to capture the last raw HTTP response
|
|
@@ -1713,6 +1803,7 @@ async function executeCommand(screen, state) {
|
|
|
1713
1803
|
altView: false,
|
|
1714
1804
|
cursor: 0,
|
|
1715
1805
|
scroll: 0,
|
|
1806
|
+
message: "",
|
|
1716
1807
|
});
|
|
1717
1808
|
}
|
|
1718
1809
|
catch (err) {
|
|
@@ -1726,6 +1817,7 @@ async function executeCommand(screen, state) {
|
|
|
1726
1817
|
error: msg,
|
|
1727
1818
|
});
|
|
1728
1819
|
screen.error = msg;
|
|
1820
|
+
screen.errorRaw = rawCapture;
|
|
1729
1821
|
}
|
|
1730
1822
|
finally {
|
|
1731
1823
|
screen.running = false;
|
|
@@ -1941,6 +2033,60 @@ async function handleInputKey(key, screen, state, done) {
|
|
|
1941
2033
|
function handleResultsKey(key, screen, state, done) {
|
|
1942
2034
|
const { rows } = termSize();
|
|
1943
2035
|
const viewH = Math.max(3, rows - 10);
|
|
2036
|
+
// Profile list inline shortcuts: u=use, s=show, d=delete
|
|
2037
|
+
if (screen.group.name === "profile" &&
|
|
2038
|
+
screen.cmd.name === "list" &&
|
|
2039
|
+
screen.output.type === "list") {
|
|
2040
|
+
// Clear stale message on any key in this context
|
|
2041
|
+
screen.message = "";
|
|
2042
|
+
if (key === "u" || key === "U") {
|
|
2043
|
+
const item = screen.output.items[screen.cursor];
|
|
2044
|
+
const name = item?.name;
|
|
2045
|
+
if (name && useProfile(name)) {
|
|
2046
|
+
state.profile = name;
|
|
2047
|
+
state.profileInfo = buildProfileInfoEntries(name);
|
|
2048
|
+
const items = rebuildProfileListItems();
|
|
2049
|
+
screen.output = { type: "list", items, columns: screen.output.columns, total: items.length };
|
|
2050
|
+
screen.message = `✓ Active profile: ${name}`;
|
|
2051
|
+
}
|
|
2052
|
+
else if (name) {
|
|
2053
|
+
screen.message = `Profile "${name}" not found`;
|
|
2054
|
+
}
|
|
2055
|
+
render(state);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
if (key === "s" || key === "S") {
|
|
2059
|
+
const item = screen.output.items[screen.cursor];
|
|
2060
|
+
if (item) {
|
|
2061
|
+
state.stack.push({
|
|
2062
|
+
kind: "detail",
|
|
2063
|
+
group: screen.group,
|
|
2064
|
+
cmd: screen.cmd,
|
|
2065
|
+
item,
|
|
2066
|
+
label: String(item.name ?? "profile"),
|
|
2067
|
+
cursor: 0,
|
|
2068
|
+
scroll: 0,
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
render(state);
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
if (key === "d" || key === "D") {
|
|
2075
|
+
const item = screen.output.items[screen.cursor];
|
|
2076
|
+
const name = item?.name;
|
|
2077
|
+
if (name && deleteProfile(name)) {
|
|
2078
|
+
const items = rebuildProfileListItems();
|
|
2079
|
+
screen.output = { type: "list", items, columns: screen.output.columns, total: items.length };
|
|
2080
|
+
screen.cursor = Math.min(screen.cursor, Math.max(0, items.length - 1));
|
|
2081
|
+
screen.message = `✓ Deleted profile "${name}"`;
|
|
2082
|
+
}
|
|
2083
|
+
else if (name) {
|
|
2084
|
+
screen.message = `Cannot delete "${name}" (modeler profiles are read-only)`;
|
|
2085
|
+
}
|
|
2086
|
+
render(state);
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
1944
2090
|
// r/R toggles raw view; u/U toggles curl view — mutually exclusive
|
|
1945
2091
|
if (key === "r" || key === "R") {
|
|
1946
2092
|
screen.rawView = !screen.rawView;
|
|
@@ -2381,6 +2527,7 @@ function handleFollowupKey(key, screen, state, done) {
|
|
|
2381
2527
|
scroll: 0,
|
|
2382
2528
|
editing: false,
|
|
2383
2529
|
error: "",
|
|
2530
|
+
errorRaw: null,
|
|
2384
2531
|
running: false,
|
|
2385
2532
|
});
|
|
2386
2533
|
}
|
|
@@ -2431,6 +2578,29 @@ function handleJsonEditorKey(key, screen, state, done) {
|
|
|
2431
2578
|
entry.valCursor = cur;
|
|
2432
2579
|
}
|
|
2433
2580
|
};
|
|
2581
|
+
// Key cycling for key column when fieldSpecs are available
|
|
2582
|
+
if (activeIsKey && screen.fieldSpecs && screen.fieldSpecs.length > 0) {
|
|
2583
|
+
const available = getAvailableFieldSpecs(screen.cursor, screen.entries, screen.fieldSpecs);
|
|
2584
|
+
if (available.length > 0) {
|
|
2585
|
+
if (key === "\x1b[A" || key === "\x1b[B") {
|
|
2586
|
+
const curIdx = available.findIndex((s) => s.name === entry.key);
|
|
2587
|
+
let nextIdx;
|
|
2588
|
+
if (key === "\x1b[A") {
|
|
2589
|
+
nextIdx = curIdx <= 0 ? available.length - 1 : curIdx - 1;
|
|
2590
|
+
}
|
|
2591
|
+
else {
|
|
2592
|
+
nextIdx = curIdx < 0 || curIdx >= available.length - 1 ? 0 : curIdx + 1;
|
|
2593
|
+
}
|
|
2594
|
+
const newSpec = available[nextIdx];
|
|
2595
|
+
if (newSpec) {
|
|
2596
|
+
entry.key = newSpec.name;
|
|
2597
|
+
entry.keyCursor = entry.key.length;
|
|
2598
|
+
}
|
|
2599
|
+
render(state);
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2434
2604
|
// Enum cycling for value column of a known enum field
|
|
2435
2605
|
const valSpec = !activeIsKey ? getFieldSpec(entry.key, screen.fieldSpecs) : undefined;
|
|
2436
2606
|
const enumVals = valSpec?.enum;
|
|
@@ -2553,8 +2723,17 @@ function handleJsonEditorKey(key, screen, state, done) {
|
|
|
2553
2723
|
case "\r":
|
|
2554
2724
|
case "\n":
|
|
2555
2725
|
if (isAddRow) {
|
|
2556
|
-
// Add a new entry
|
|
2557
|
-
|
|
2726
|
+
// Add a new entry; pre-seed key from first available field spec if available
|
|
2727
|
+
const newEntry = { key: "", keyCursor: 0, val: "", valCursor: 0 };
|
|
2728
|
+
if (screen.fieldSpecs && screen.fieldSpecs.length > 0) {
|
|
2729
|
+
const available = getAvailableFieldSpecs(null, screen.entries, screen.fieldSpecs);
|
|
2730
|
+
const firstSpec = available[0];
|
|
2731
|
+
if (firstSpec) {
|
|
2732
|
+
newEntry.key = firstSpec.name;
|
|
2733
|
+
newEntry.keyCursor = firstSpec.name.length;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
screen.entries.push(newEntry);
|
|
2558
2737
|
screen.cursor = screen.entries.length - 1;
|
|
2559
2738
|
screen.col = "key";
|
|
2560
2739
|
screen.editing = true;
|
|
@@ -2572,18 +2751,23 @@ function handleJsonEditorKey(key, screen, state, done) {
|
|
|
2572
2751
|
}
|
|
2573
2752
|
break;
|
|
2574
2753
|
case "a":
|
|
2575
|
-
case "A":
|
|
2576
|
-
// Insert new entry after cursor
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2754
|
+
case "A": {
|
|
2755
|
+
// Insert new entry after cursor; pre-seed key from first available field spec
|
|
2756
|
+
const addEntry = { key: "", keyCursor: 0, val: "", valCursor: 0 };
|
|
2757
|
+
if (screen.fieldSpecs && screen.fieldSpecs.length > 0) {
|
|
2758
|
+
const available = getAvailableFieldSpecs(null, screen.entries, screen.fieldSpecs);
|
|
2759
|
+
const firstSpec = available[0];
|
|
2760
|
+
if (firstSpec) {
|
|
2761
|
+
addEntry.key = firstSpec.name;
|
|
2762
|
+
addEntry.keyCursor = firstSpec.name.length;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
screen.entries.splice(screen.cursor + 1, 0, addEntry);
|
|
2583
2766
|
screen.cursor = Math.min(screen.cursor + 1, screen.entries.length - 1);
|
|
2584
2767
|
screen.col = "key";
|
|
2585
2768
|
screen.editing = true;
|
|
2586
2769
|
break;
|
|
2770
|
+
}
|
|
2587
2771
|
case "d":
|
|
2588
2772
|
case "D":
|
|
2589
2773
|
if (!isAddRow && screen.entries.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bpmnkit/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Command-line interface for Camunda 8 — deploy, manage, and monitor processes from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@bpmnkit/api": "0.0.13",
|
|
19
|
-
"@bpmnkit/ascii": "0.0.
|
|
19
|
+
"@bpmnkit/ascii": "0.0.16",
|
|
20
20
|
"@bpmnkit/connector-gen": "0.0.8",
|
|
21
|
-
"@bpmnkit/core": "0.0.
|
|
22
|
-
"@bpmnkit/engine": "0.1.
|
|
23
|
-
"@bpmnkit/profiles": "0.0.
|
|
21
|
+
"@bpmnkit/core": "0.0.16",
|
|
22
|
+
"@bpmnkit/engine": "0.1.15",
|
|
23
|
+
"@bpmnkit/profiles": "0.0.11"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|