@qikdev/mcp 6.7.6 → 6.7.7
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/build/src/tools/form.d.ts +17 -0
- package/build/src/tools/form.d.ts.map +1 -0
- package/build/src/tools/form.js +136 -0
- package/build/src/tools/form.js.map +1 -0
- package/build/src/tools/index.d.ts.map +1 -1
- package/build/src/tools/index.js +16 -0
- package/build/src/tools/index.js.map +1 -1
- package/build/src/tools/workflow.d.ts +110 -0
- package/build/src/tools/workflow.d.ts.map +1 -0
- package/build/src/tools/workflow.js +1047 -0
- package/build/src/tools/workflow.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,1047 @@
|
|
|
1
|
+
import { ConfigManager } from "../config.js";
|
|
2
|
+
import { getUserSessionData, getAvailableScopes, getScopeTitle } from "./user.js";
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// CONSTANTS
|
|
5
|
+
// ============================================================================
|
|
6
|
+
const STEP_COLORS = [
|
|
7
|
+
'#3B82F6', // Blue
|
|
8
|
+
'#10B981', // Green
|
|
9
|
+
'#F59E0B', // Amber
|
|
10
|
+
'#EF4444', // Red
|
|
11
|
+
'#8B5CF6', // Purple
|
|
12
|
+
'#EC4899', // Pink
|
|
13
|
+
'#06B6D4', // Cyan
|
|
14
|
+
'#F97316', // Orange
|
|
15
|
+
];
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// TOOL DEFINITIONS
|
|
18
|
+
// ============================================================================
|
|
19
|
+
export const listWorkflowsTool = {
|
|
20
|
+
name: "list_workflows",
|
|
21
|
+
description: `List workflow definitions in the platform.
|
|
22
|
+
|
|
23
|
+
Workflows are kanban-style boards where cards move through steps with automation.
|
|
24
|
+
|
|
25
|
+
**Example:**
|
|
26
|
+
\`\`\`json
|
|
27
|
+
{ "search": "order" }
|
|
28
|
+
\`\`\`
|
|
29
|
+
|
|
30
|
+
Returns workflows matching "order" with their IDs and structure summary.`,
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
search: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Search term to filter workflows by title"
|
|
37
|
+
},
|
|
38
|
+
category: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Filter by category"
|
|
41
|
+
},
|
|
42
|
+
page: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
size: { type: "number", description: "Items per page (default: 20)" },
|
|
46
|
+
index: { type: "number", description: "Page number, 1-based (default: 1)" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
export const getWorkflowTool = {
|
|
53
|
+
name: "get_workflow",
|
|
54
|
+
description: `Get full workflow details including all columns, steps, and automation.
|
|
55
|
+
|
|
56
|
+
**IMPORTANT:** Use this before updating a workflow to understand its current structure.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
- Columns with their steps
|
|
60
|
+
- Step configuration (assignees, due dates, requirements)
|
|
61
|
+
- Automation functions (entry, exit, success, fail)
|
|
62
|
+
- Navigation rules (success/failure actions)`,
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
workflowId: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "The workflow definition ID"
|
|
69
|
+
},
|
|
70
|
+
workflowKey: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "The workflow key (e.g., 'orderWorkflow')"
|
|
73
|
+
},
|
|
74
|
+
workflowTitle: {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "Search by workflow title"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export const createWorkflowTool = {
|
|
82
|
+
name: "create_workflow",
|
|
83
|
+
description: `Create a new workflow definition.
|
|
84
|
+
|
|
85
|
+
A workflow is a kanban board with columns and steps. Cards move through steps based on completion criteria and automated actions.
|
|
86
|
+
|
|
87
|
+
## Workflow Structure
|
|
88
|
+
|
|
89
|
+
\`\`\`json
|
|
90
|
+
{
|
|
91
|
+
"title": "Order Processing",
|
|
92
|
+
"category": "Operations",
|
|
93
|
+
"workflow": [
|
|
94
|
+
{
|
|
95
|
+
"title": "Column Name",
|
|
96
|
+
"description": "Optional description",
|
|
97
|
+
"steps": [
|
|
98
|
+
{
|
|
99
|
+
"title": "Step Name",
|
|
100
|
+
"key": "uniqueStepKey",
|
|
101
|
+
"color": "#3B82F6",
|
|
102
|
+
"description": "Help text for users",
|
|
103
|
+
|
|
104
|
+
"assignees": [],
|
|
105
|
+
"assigneeBehaviour": "",
|
|
106
|
+
|
|
107
|
+
"dueDateBehaviour": "period",
|
|
108
|
+
"dueDateQuantity": 3,
|
|
109
|
+
"dueDateUnit": "days",
|
|
110
|
+
|
|
111
|
+
"hasRequiredTasks": true,
|
|
112
|
+
"requireTasks": [{ "title": "Review application" }],
|
|
113
|
+
|
|
114
|
+
"completionWhen": "immediate",
|
|
115
|
+
|
|
116
|
+
"successFunctions": [
|
|
117
|
+
{ "action": "addTag", "title": "Mark processed", "tag": "processed" }
|
|
118
|
+
],
|
|
119
|
+
|
|
120
|
+
"successAction": "next",
|
|
121
|
+
"failureAction": "step",
|
|
122
|
+
"failureTargetStep": "rejected"
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
\`\`\`
|
|
129
|
+
|
|
130
|
+
## Step Properties
|
|
131
|
+
|
|
132
|
+
**Assignees:**
|
|
133
|
+
- \`assignees\`: Array of profile IDs to auto-assign
|
|
134
|
+
- \`assigneeBehaviour\`: "" (replace), "leave" (keep existing), "remove" (clear all)
|
|
135
|
+
|
|
136
|
+
**Due Dates:**
|
|
137
|
+
- \`dueDateBehaviour\`: "" (none), "period" (relative), "point" (fixed time)
|
|
138
|
+
- \`dueDateQuantity\`: Number (e.g., 3)
|
|
139
|
+
- \`dueDateUnit\`: "minutes", "hours", "days", "weeks"
|
|
140
|
+
|
|
141
|
+
**Requirements:**
|
|
142
|
+
- \`hasRequiredTasks\`: true/false
|
|
143
|
+
- \`requireTasks\`: [{ title, description }]
|
|
144
|
+
- \`hasRequiredForms\`: true/false (requires form submission)
|
|
145
|
+
- \`hasRequiredCriteria\`: true/false (requires filter match)
|
|
146
|
+
|
|
147
|
+
**Automation Timing:**
|
|
148
|
+
- \`completionWhen\`: "" (manual), "immediate" (auto when done), "wait" (at due date)
|
|
149
|
+
|
|
150
|
+
**Automation Functions:**
|
|
151
|
+
- \`entryFunctions\`: Run when card enters step
|
|
152
|
+
- \`exitFunctions\`: Run when card leaves step
|
|
153
|
+
- \`successFunctions\`: Run on successful completion
|
|
154
|
+
- \`failFunctions\`: Run on failure
|
|
155
|
+
|
|
156
|
+
**Navigation:**
|
|
157
|
+
- \`successAction\`: "next", "previous", "step", "archive"
|
|
158
|
+
- \`successTargetStep\`: Step key if successAction="step"
|
|
159
|
+
- \`failureAction\`: Same options
|
|
160
|
+
- \`failureTargetStep\`: Step key if failureAction="step"
|
|
161
|
+
|
|
162
|
+
## Function Types
|
|
163
|
+
|
|
164
|
+
**addTag** - Add a tag to the card
|
|
165
|
+
\`\`\`json
|
|
166
|
+
{ "action": "addTag", "title": "Description", "tag": "tagName" }
|
|
167
|
+
\`\`\`
|
|
168
|
+
|
|
169
|
+
**removeTag** - Remove a tag
|
|
170
|
+
\`\`\`json
|
|
171
|
+
{ "action": "removeTag", "title": "Description", "tag": "tagName" }
|
|
172
|
+
\`\`\`
|
|
173
|
+
|
|
174
|
+
**email** - Send email notification
|
|
175
|
+
\`\`\`json
|
|
176
|
+
{ "action": "email", "title": "Description", "to": "assignees" }
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
179
|
+
**sms** - Send SMS
|
|
180
|
+
\`\`\`json
|
|
181
|
+
{ "action": "sms", "title": "Description", "to": "assignees", "message": "Text" }
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
**update** - Update card/profile fields
|
|
185
|
+
\`\`\`json
|
|
186
|
+
{ "action": "update", "title": "Description", "target": "card", "data": { "fieldKey": "value" } }
|
|
187
|
+
\`\`\`
|
|
188
|
+
|
|
189
|
+
## Common Patterns
|
|
190
|
+
|
|
191
|
+
**Simple Order Flow:**
|
|
192
|
+
\`\`\`json
|
|
193
|
+
{
|
|
194
|
+
"workflow": [
|
|
195
|
+
{ "title": "New", "steps": [{ "title": "Order Received" }] },
|
|
196
|
+
{ "title": "Processing", "steps": [{ "title": "Preparing" }] },
|
|
197
|
+
{ "title": "Complete", "steps": [{ "title": "Ready", "successAction": "archive" }] }
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
\`\`\`
|
|
201
|
+
|
|
202
|
+
**Approval Flow:**
|
|
203
|
+
\`\`\`json
|
|
204
|
+
{
|
|
205
|
+
"workflow": [
|
|
206
|
+
{ "title": "Submitted", "steps": [{ "title": "New Application" }] },
|
|
207
|
+
{ "title": "Review", "steps": [{ "title": "Under Review", "hasRequiredTasks": true, "requireTasks": [{ "title": "Review documents" }] }] },
|
|
208
|
+
{ "title": "Decision", "steps": [{ "title": "Approved", "successFunctions": [{ "action": "addTag", "tag": "approved" }] }, { "title": "Rejected" }] }
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
\`\`\``,
|
|
212
|
+
inputSchema: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
title: {
|
|
216
|
+
type: "string",
|
|
217
|
+
description: "Workflow name (e.g., 'Food Order Workflow')"
|
|
218
|
+
},
|
|
219
|
+
plural: {
|
|
220
|
+
type: "string",
|
|
221
|
+
description: "Plural name (auto-generated if not provided)"
|
|
222
|
+
},
|
|
223
|
+
category: {
|
|
224
|
+
type: "string",
|
|
225
|
+
description: "Category for organization (e.g., 'Operations')"
|
|
226
|
+
},
|
|
227
|
+
workflow: {
|
|
228
|
+
type: "array",
|
|
229
|
+
description: "Array of columns, each containing steps",
|
|
230
|
+
items: {
|
|
231
|
+
type: "object",
|
|
232
|
+
properties: {
|
|
233
|
+
title: { type: "string" },
|
|
234
|
+
description: { type: "string" },
|
|
235
|
+
steps: { type: "array" }
|
|
236
|
+
},
|
|
237
|
+
required: ["title", "steps"]
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
scope: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Scope ID to create the workflow in"
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
required: ["title", "workflow"]
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
export const updateWorkflowTool = {
|
|
249
|
+
name: "update_workflow",
|
|
250
|
+
description: `Update an existing workflow definition.
|
|
251
|
+
|
|
252
|
+
**IMPORTANT:** Use get_workflow first to see the current structure before making changes.
|
|
253
|
+
|
|
254
|
+
## Update Operations
|
|
255
|
+
|
|
256
|
+
**Add a column:**
|
|
257
|
+
\`\`\`json
|
|
258
|
+
{
|
|
259
|
+
"workflowId": "...",
|
|
260
|
+
"addColumns": [{ "title": "Archive", "steps": [{ "title": "Archived" }] }]
|
|
261
|
+
}
|
|
262
|
+
\`\`\`
|
|
263
|
+
|
|
264
|
+
**Add a step to existing column:**
|
|
265
|
+
\`\`\`json
|
|
266
|
+
{
|
|
267
|
+
"workflowId": "...",
|
|
268
|
+
"addSteps": [{ "columnTitle": "Processing", "step": { "title": "Quality Check" } }]
|
|
269
|
+
}
|
|
270
|
+
\`\`\`
|
|
271
|
+
|
|
272
|
+
**Modify a step:**
|
|
273
|
+
\`\`\`json
|
|
274
|
+
{
|
|
275
|
+
"workflowId": "...",
|
|
276
|
+
"updateSteps": [{
|
|
277
|
+
"stepKey": "newOrder",
|
|
278
|
+
"changes": {
|
|
279
|
+
"title": "New Order Received",
|
|
280
|
+
"successFunctions": [{ "action": "addTag", "tag": "received" }]
|
|
281
|
+
}
|
|
282
|
+
}]
|
|
283
|
+
}
|
|
284
|
+
\`\`\`
|
|
285
|
+
|
|
286
|
+
**Remove a step:**
|
|
287
|
+
\`\`\`json
|
|
288
|
+
{
|
|
289
|
+
"workflowId": "...",
|
|
290
|
+
"removeSteps": ["oldStepKey"]
|
|
291
|
+
}
|
|
292
|
+
\`\`\`
|
|
293
|
+
|
|
294
|
+
**Add automation to a step:**
|
|
295
|
+
\`\`\`json
|
|
296
|
+
{
|
|
297
|
+
"workflowId": "...",
|
|
298
|
+
"updateSteps": [{
|
|
299
|
+
"stepKey": "approved",
|
|
300
|
+
"changes": {
|
|
301
|
+
"successFunctions": [
|
|
302
|
+
{ "action": "addTag", "title": "Add confirmed tag", "tag": "confirmed" }
|
|
303
|
+
]
|
|
304
|
+
}
|
|
305
|
+
}]
|
|
306
|
+
}
|
|
307
|
+
\`\`\`
|
|
308
|
+
|
|
309
|
+
**Change navigation:**
|
|
310
|
+
\`\`\`json
|
|
311
|
+
{
|
|
312
|
+
"workflowId": "...",
|
|
313
|
+
"updateSteps": [{
|
|
314
|
+
"stepKey": "review",
|
|
315
|
+
"changes": {
|
|
316
|
+
"successAction": "step",
|
|
317
|
+
"successTargetStep": "approved",
|
|
318
|
+
"failureAction": "step",
|
|
319
|
+
"failureTargetStep": "rejected"
|
|
320
|
+
}
|
|
321
|
+
}]
|
|
322
|
+
}
|
|
323
|
+
\`\`\``,
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
workflowId: {
|
|
328
|
+
type: "string",
|
|
329
|
+
description: "The workflow definition ID"
|
|
330
|
+
},
|
|
331
|
+
workflowKey: {
|
|
332
|
+
type: "string",
|
|
333
|
+
description: "The workflow key"
|
|
334
|
+
},
|
|
335
|
+
workflowTitle: {
|
|
336
|
+
type: "string",
|
|
337
|
+
description: "Search by workflow title"
|
|
338
|
+
},
|
|
339
|
+
title: {
|
|
340
|
+
type: "string",
|
|
341
|
+
description: "New title for the workflow"
|
|
342
|
+
},
|
|
343
|
+
category: {
|
|
344
|
+
type: "string",
|
|
345
|
+
description: "New category"
|
|
346
|
+
},
|
|
347
|
+
addColumns: {
|
|
348
|
+
type: "array",
|
|
349
|
+
description: "New columns to add",
|
|
350
|
+
items: {
|
|
351
|
+
type: "object",
|
|
352
|
+
properties: {
|
|
353
|
+
title: { type: "string" },
|
|
354
|
+
description: { type: "string" },
|
|
355
|
+
steps: { type: "array" },
|
|
356
|
+
insertAfter: { type: "string", description: "Column title to insert after" }
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
removeColumns: {
|
|
361
|
+
type: "array",
|
|
362
|
+
items: { type: "string" },
|
|
363
|
+
description: "Column titles to remove"
|
|
364
|
+
},
|
|
365
|
+
addSteps: {
|
|
366
|
+
type: "array",
|
|
367
|
+
description: "Steps to add to existing columns",
|
|
368
|
+
items: {
|
|
369
|
+
type: "object",
|
|
370
|
+
properties: {
|
|
371
|
+
columnTitle: { type: "string", description: "Column to add step to" },
|
|
372
|
+
step: { type: "object", description: "Step configuration" },
|
|
373
|
+
insertAfter: { type: "string", description: "Step key to insert after" }
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
removeSteps: {
|
|
378
|
+
type: "array",
|
|
379
|
+
items: { type: "string" },
|
|
380
|
+
description: "Step keys to remove"
|
|
381
|
+
},
|
|
382
|
+
updateSteps: {
|
|
383
|
+
type: "array",
|
|
384
|
+
description: "Steps to modify",
|
|
385
|
+
items: {
|
|
386
|
+
type: "object",
|
|
387
|
+
properties: {
|
|
388
|
+
stepKey: { type: "string", description: "Key of step to update" },
|
|
389
|
+
changes: { type: "object", description: "Fields to update" }
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
// ============================================================================
|
|
397
|
+
// HANDLERS
|
|
398
|
+
// ============================================================================
|
|
399
|
+
export async function handleListWorkflows(args) {
|
|
400
|
+
try {
|
|
401
|
+
const configManager = new ConfigManager();
|
|
402
|
+
const config = await configManager.loadConfig();
|
|
403
|
+
if (!config) {
|
|
404
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
405
|
+
}
|
|
406
|
+
const filters = [
|
|
407
|
+
{ key: 'definesType', comparator: 'equal', value: 'workflowcard' }
|
|
408
|
+
];
|
|
409
|
+
if (args.search) {
|
|
410
|
+
filters.push({
|
|
411
|
+
operator: 'or',
|
|
412
|
+
filters: [
|
|
413
|
+
{ key: 'title', comparator: 'contains', value: args.search },
|
|
414
|
+
{ key: 'key', comparator: 'contains', value: args.search }
|
|
415
|
+
]
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (args.category) {
|
|
419
|
+
filters.push({ key: 'category', comparator: 'contains', value: args.category });
|
|
420
|
+
}
|
|
421
|
+
const listPayload = {
|
|
422
|
+
filter: { operator: 'and', filters },
|
|
423
|
+
sort: { key: 'title', direction: 'asc', type: 'string' },
|
|
424
|
+
page: {
|
|
425
|
+
size: args.page?.size || 20,
|
|
426
|
+
index: args.page?.index || 1
|
|
427
|
+
},
|
|
428
|
+
select: ['_id', 'title', 'plural', 'key', 'category', 'workflow', 'meta']
|
|
429
|
+
};
|
|
430
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/definition/list`, {
|
|
431
|
+
method: 'POST',
|
|
432
|
+
headers: {
|
|
433
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
434
|
+
'Content-Type': 'application/json',
|
|
435
|
+
},
|
|
436
|
+
body: JSON.stringify(listPayload)
|
|
437
|
+
});
|
|
438
|
+
if (!response.ok) {
|
|
439
|
+
const errorText = await response.text();
|
|
440
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
441
|
+
}
|
|
442
|
+
const results = await response.json();
|
|
443
|
+
const items = results.items || [];
|
|
444
|
+
const total = results.total || items.length;
|
|
445
|
+
const currentPage = args.page?.index || 1;
|
|
446
|
+
const pageSize = args.page?.size || 20;
|
|
447
|
+
if (items.length === 0) {
|
|
448
|
+
const searchNote = args.search ? ` matching "${args.search}"` : '';
|
|
449
|
+
return {
|
|
450
|
+
content: [{
|
|
451
|
+
type: "text",
|
|
452
|
+
text: `No workflows found${searchNote}.`
|
|
453
|
+
}]
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
let resultText = `# Workflows (${total} total)`;
|
|
457
|
+
if (total > pageSize) {
|
|
458
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
459
|
+
resultText += ` - Page ${currentPage} of ${totalPages}`;
|
|
460
|
+
}
|
|
461
|
+
resultText += '\n\n';
|
|
462
|
+
items.forEach((item, index) => {
|
|
463
|
+
const itemNumber = ((currentPage - 1) * pageSize) + index + 1;
|
|
464
|
+
const columnCount = item.workflow?.length || 0;
|
|
465
|
+
const stepCount = item.workflow?.reduce((acc, col) => acc + (col.steps?.length || 0), 0) || 0;
|
|
466
|
+
resultText += `**${itemNumber}. ${item.title}**\n`;
|
|
467
|
+
resultText += ` ID: \`${item._id}\`\n`;
|
|
468
|
+
resultText += ` Key: \`${item.key}\`\n`;
|
|
469
|
+
if (item.category) {
|
|
470
|
+
resultText += ` Category: ${item.category}\n`;
|
|
471
|
+
}
|
|
472
|
+
resultText += ` Structure: ${columnCount} columns, ${stepCount} steps\n\n`;
|
|
473
|
+
});
|
|
474
|
+
if (total > pageSize && currentPage < Math.ceil(total / pageSize)) {
|
|
475
|
+
resultText += `\nTo see more: use page: { index: ${currentPage + 1} }`;
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
content: [{
|
|
479
|
+
type: "text",
|
|
480
|
+
text: resultText
|
|
481
|
+
}]
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
return {
|
|
486
|
+
content: [{
|
|
487
|
+
type: "text",
|
|
488
|
+
text: `Failed to list workflows: ${error.message}`
|
|
489
|
+
}]
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
export async function handleGetWorkflow(args) {
|
|
494
|
+
try {
|
|
495
|
+
const configManager = new ConfigManager();
|
|
496
|
+
const config = await configManager.loadConfig();
|
|
497
|
+
if (!config) {
|
|
498
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
499
|
+
}
|
|
500
|
+
let workflowId = args.workflowId;
|
|
501
|
+
// Resolve ID from key or title
|
|
502
|
+
if (!workflowId && (args.workflowKey || args.workflowTitle)) {
|
|
503
|
+
const searchFilters = [
|
|
504
|
+
{ key: 'definesType', comparator: 'equal', value: 'workflowcard' }
|
|
505
|
+
];
|
|
506
|
+
if (args.workflowKey) {
|
|
507
|
+
searchFilters.push({ key: 'key', comparator: 'equal', value: args.workflowKey });
|
|
508
|
+
}
|
|
509
|
+
else if (args.workflowTitle) {
|
|
510
|
+
searchFilters.push({ key: 'title', comparator: 'contains', value: args.workflowTitle });
|
|
511
|
+
}
|
|
512
|
+
const searchResponse = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/definition/list`, {
|
|
513
|
+
method: 'POST',
|
|
514
|
+
headers: {
|
|
515
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
516
|
+
'Content-Type': 'application/json',
|
|
517
|
+
},
|
|
518
|
+
body: JSON.stringify({
|
|
519
|
+
filter: { operator: 'and', filters: searchFilters },
|
|
520
|
+
page: { size: 1, index: 1 },
|
|
521
|
+
select: ['_id', 'title']
|
|
522
|
+
})
|
|
523
|
+
});
|
|
524
|
+
if (!searchResponse.ok) {
|
|
525
|
+
throw new Error(`HTTP ${searchResponse.status}: ${await searchResponse.text()}`);
|
|
526
|
+
}
|
|
527
|
+
const searchResults = await searchResponse.json();
|
|
528
|
+
if (!searchResults.items?.length) {
|
|
529
|
+
const searchTerm = args.workflowKey || args.workflowTitle;
|
|
530
|
+
return {
|
|
531
|
+
content: [{
|
|
532
|
+
type: "text",
|
|
533
|
+
text: `No workflow found matching "${searchTerm}"`
|
|
534
|
+
}]
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
workflowId = searchResults.items[0]._id;
|
|
538
|
+
}
|
|
539
|
+
if (!workflowId) {
|
|
540
|
+
return {
|
|
541
|
+
content: [{
|
|
542
|
+
type: "text",
|
|
543
|
+
text: `Please provide workflowId, workflowKey, or workflowTitle`
|
|
544
|
+
}]
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/${workflowId}`, {
|
|
548
|
+
headers: {
|
|
549
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
550
|
+
'Content-Type': 'application/json',
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
if (!response.ok) {
|
|
554
|
+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
555
|
+
}
|
|
556
|
+
const workflow = await response.json();
|
|
557
|
+
let resultText = `# Workflow: ${workflow.title}\n\n`;
|
|
558
|
+
resultText += `**ID:** \`${workflow._id}\`\n`;
|
|
559
|
+
resultText += `**Key:** \`${workflow.key}\`\n`;
|
|
560
|
+
if (workflow.category) {
|
|
561
|
+
resultText += `**Category:** ${workflow.category}\n`;
|
|
562
|
+
}
|
|
563
|
+
resultText += '\n---\n';
|
|
564
|
+
resultText += formatWorkflowForDisplay(workflow.workflow || []);
|
|
565
|
+
resultText += '\n---\n\n## Raw Structure\n\n';
|
|
566
|
+
resultText += '```json\n' + JSON.stringify(workflow.workflow, null, 2) + '\n```';
|
|
567
|
+
return {
|
|
568
|
+
content: [{
|
|
569
|
+
type: "text",
|
|
570
|
+
text: resultText
|
|
571
|
+
}]
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
return {
|
|
576
|
+
content: [{
|
|
577
|
+
type: "text",
|
|
578
|
+
text: `Failed to get workflow: ${error.message}`
|
|
579
|
+
}]
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
export async function handleCreateWorkflow(args) {
|
|
584
|
+
try {
|
|
585
|
+
const configManager = new ConfigManager();
|
|
586
|
+
const config = await configManager.loadConfig();
|
|
587
|
+
if (!config) {
|
|
588
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
589
|
+
}
|
|
590
|
+
if (!args.title) {
|
|
591
|
+
return createErrorResponse('Title is required');
|
|
592
|
+
}
|
|
593
|
+
if (!args.workflow || args.workflow.length === 0) {
|
|
594
|
+
return createErrorResponse('Workflow must have at least one column');
|
|
595
|
+
}
|
|
596
|
+
// Get user session for permissions
|
|
597
|
+
const userSession = await getUserSessionData();
|
|
598
|
+
const createPermission = 'definition.create';
|
|
599
|
+
const availableScopes = getAvailableScopes(userSession, createPermission);
|
|
600
|
+
if (availableScopes.length === 0) {
|
|
601
|
+
return createErrorResponse(`You don't have permission to create definitions.`);
|
|
602
|
+
}
|
|
603
|
+
const selectedScopeId = args.scope;
|
|
604
|
+
if (!selectedScopeId && availableScopes.length > 1) {
|
|
605
|
+
return createScopeSelectionPrompt('workflow', availableScopes, userSession);
|
|
606
|
+
}
|
|
607
|
+
const targetScopeId = selectedScopeId || availableScopes[0];
|
|
608
|
+
if (!availableScopes.includes(targetScopeId)) {
|
|
609
|
+
return createErrorResponse(`Invalid scope "${targetScopeId}".`);
|
|
610
|
+
}
|
|
611
|
+
// Normalize and validate workflow
|
|
612
|
+
const normalizedWorkflow = normalizeWorkflow(args.workflow);
|
|
613
|
+
const validation = validateWorkflow(normalizedWorkflow);
|
|
614
|
+
if (!validation.valid) {
|
|
615
|
+
return createErrorResponse(`Invalid workflow structure:\n- ${validation.errors.join('\n- ')}`);
|
|
616
|
+
}
|
|
617
|
+
// Generate key from title
|
|
618
|
+
const key = generateKey(args.title);
|
|
619
|
+
const definitionPayload = {
|
|
620
|
+
title: args.title,
|
|
621
|
+
plural: args.plural || `${args.title}s`,
|
|
622
|
+
key: key,
|
|
623
|
+
definesType: 'workflowcard',
|
|
624
|
+
category: args.category || '',
|
|
625
|
+
workflow: normalizedWorkflow,
|
|
626
|
+
fields: [],
|
|
627
|
+
defaultScopes: [],
|
|
628
|
+
restrictScopes: [],
|
|
629
|
+
weight: 0,
|
|
630
|
+
meta: {
|
|
631
|
+
scopes: [targetScopeId],
|
|
632
|
+
status: 'active'
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/definition/create`, {
|
|
636
|
+
method: 'POST',
|
|
637
|
+
headers: {
|
|
638
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
639
|
+
'Content-Type': 'application/json',
|
|
640
|
+
},
|
|
641
|
+
body: JSON.stringify(definitionPayload)
|
|
642
|
+
});
|
|
643
|
+
if (!response.ok) {
|
|
644
|
+
const errorText = await response.text();
|
|
645
|
+
throw new Error(`HTTP ${response.status} - ${errorText}`);
|
|
646
|
+
}
|
|
647
|
+
const created = await response.json();
|
|
648
|
+
const columnCount = normalizedWorkflow.length;
|
|
649
|
+
const stepCount = normalizedWorkflow.reduce((acc, col) => acc + col.steps.length, 0);
|
|
650
|
+
return {
|
|
651
|
+
content: [{
|
|
652
|
+
type: "text",
|
|
653
|
+
text: `# Workflow Created Successfully!
|
|
654
|
+
|
|
655
|
+
**Title:** ${args.title}
|
|
656
|
+
**ID:** \`${created._id}\`
|
|
657
|
+
**Key:** \`${key}\`
|
|
658
|
+
**Category:** ${args.category || 'None'}
|
|
659
|
+
**Structure:** ${columnCount} columns, ${stepCount} steps
|
|
660
|
+
**Scope:** ${getScopeTitle(userSession, targetScopeId)}
|
|
661
|
+
|
|
662
|
+
## Structure Overview
|
|
663
|
+
${formatWorkflowForDisplay(normalizedWorkflow)}`
|
|
664
|
+
}]
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
return createErrorResponse(`Failed to create workflow: ${error.message}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
export async function handleUpdateWorkflow(args) {
|
|
672
|
+
try {
|
|
673
|
+
const configManager = new ConfigManager();
|
|
674
|
+
const config = await configManager.loadConfig();
|
|
675
|
+
if (!config) {
|
|
676
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
677
|
+
}
|
|
678
|
+
// Resolve workflow ID
|
|
679
|
+
let workflowId = args.workflowId;
|
|
680
|
+
if (!workflowId && (args.workflowKey || args.workflowTitle)) {
|
|
681
|
+
const searchFilters = [
|
|
682
|
+
{ key: 'definesType', comparator: 'equal', value: 'workflowcard' }
|
|
683
|
+
];
|
|
684
|
+
if (args.workflowKey) {
|
|
685
|
+
searchFilters.push({ key: 'key', comparator: 'equal', value: args.workflowKey });
|
|
686
|
+
}
|
|
687
|
+
else if (args.workflowTitle) {
|
|
688
|
+
searchFilters.push({ key: 'title', comparator: 'contains', value: args.workflowTitle });
|
|
689
|
+
}
|
|
690
|
+
const searchResponse = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/definition/list`, {
|
|
691
|
+
method: 'POST',
|
|
692
|
+
headers: {
|
|
693
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
694
|
+
'Content-Type': 'application/json',
|
|
695
|
+
},
|
|
696
|
+
body: JSON.stringify({
|
|
697
|
+
filter: { operator: 'and', filters: searchFilters },
|
|
698
|
+
page: { size: 1, index: 1 }
|
|
699
|
+
})
|
|
700
|
+
});
|
|
701
|
+
if (!searchResponse.ok) {
|
|
702
|
+
throw new Error(`HTTP ${searchResponse.status}`);
|
|
703
|
+
}
|
|
704
|
+
const searchResults = await searchResponse.json();
|
|
705
|
+
if (!searchResults.items?.length) {
|
|
706
|
+
return createErrorResponse(`No workflow found matching "${args.workflowKey || args.workflowTitle}"`);
|
|
707
|
+
}
|
|
708
|
+
workflowId = searchResults.items[0]._id;
|
|
709
|
+
}
|
|
710
|
+
if (!workflowId) {
|
|
711
|
+
return createErrorResponse('Please provide workflowId, workflowKey, or workflowTitle');
|
|
712
|
+
}
|
|
713
|
+
// Fetch current workflow
|
|
714
|
+
const getResponse = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/${workflowId}`, {
|
|
715
|
+
headers: {
|
|
716
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
717
|
+
'Content-Type': 'application/json',
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
if (!getResponse.ok) {
|
|
721
|
+
throw new Error(`HTTP ${getResponse.status}`);
|
|
722
|
+
}
|
|
723
|
+
const current = await getResponse.json();
|
|
724
|
+
let workflow = [...(current.workflow || [])];
|
|
725
|
+
const changes = [];
|
|
726
|
+
// Update title/category
|
|
727
|
+
if (args.title) {
|
|
728
|
+
current.title = args.title;
|
|
729
|
+
changes.push(`Title changed to "${args.title}"`);
|
|
730
|
+
}
|
|
731
|
+
if (args.category) {
|
|
732
|
+
current.category = args.category;
|
|
733
|
+
changes.push(`Category changed to "${args.category}"`);
|
|
734
|
+
}
|
|
735
|
+
// Remove columns
|
|
736
|
+
if (args.removeColumns?.length) {
|
|
737
|
+
const originalCount = workflow.length;
|
|
738
|
+
workflow = workflow.filter(col => !args.removeColumns.includes(col.title));
|
|
739
|
+
changes.push(`Removed ${originalCount - workflow.length} column(s)`);
|
|
740
|
+
}
|
|
741
|
+
// Remove steps
|
|
742
|
+
if (args.removeSteps?.length) {
|
|
743
|
+
let removedCount = 0;
|
|
744
|
+
workflow = workflow.map(col => ({
|
|
745
|
+
...col,
|
|
746
|
+
steps: col.steps.filter(step => {
|
|
747
|
+
if (args.removeSteps.includes(step.key)) {
|
|
748
|
+
removedCount++;
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
return true;
|
|
752
|
+
})
|
|
753
|
+
}));
|
|
754
|
+
changes.push(`Removed ${removedCount} step(s)`);
|
|
755
|
+
}
|
|
756
|
+
// Update steps
|
|
757
|
+
if (args.updateSteps?.length) {
|
|
758
|
+
for (const update of args.updateSteps) {
|
|
759
|
+
for (const col of workflow) {
|
|
760
|
+
const stepIndex = col.steps.findIndex(s => s.key === update.stepKey);
|
|
761
|
+
if (stepIndex !== -1) {
|
|
762
|
+
col.steps[stepIndex] = { ...col.steps[stepIndex], ...update.changes };
|
|
763
|
+
changes.push(`Updated step "${update.stepKey}"`);
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// Add columns
|
|
770
|
+
if (args.addColumns?.length) {
|
|
771
|
+
const existingKeys = getAllStepKeys(workflow);
|
|
772
|
+
for (const newCol of args.addColumns) {
|
|
773
|
+
const normalizedSteps = newCol.steps.map((step, i) => normalizeStep(step, existingKeys, workflow.length * 10 + i));
|
|
774
|
+
const column = {
|
|
775
|
+
title: newCol.title,
|
|
776
|
+
description: newCol.description || '',
|
|
777
|
+
steps: normalizedSteps
|
|
778
|
+
};
|
|
779
|
+
if (newCol.insertAfter) {
|
|
780
|
+
const insertIndex = workflow.findIndex(c => c.title === newCol.insertAfter);
|
|
781
|
+
if (insertIndex !== -1) {
|
|
782
|
+
workflow.splice(insertIndex + 1, 0, column);
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
workflow.push(column);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
workflow.push(column);
|
|
790
|
+
}
|
|
791
|
+
changes.push(`Added column "${newCol.title}"`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
// Add steps
|
|
795
|
+
if (args.addSteps?.length) {
|
|
796
|
+
const existingKeys = getAllStepKeys(workflow);
|
|
797
|
+
for (const addStep of args.addSteps) {
|
|
798
|
+
const column = workflow.find(c => c.title === addStep.columnTitle);
|
|
799
|
+
if (column) {
|
|
800
|
+
const normalizedStep = normalizeStep(addStep.step, existingKeys, column.steps.length);
|
|
801
|
+
if (addStep.insertAfter) {
|
|
802
|
+
const insertIndex = column.steps.findIndex(s => s.key === addStep.insertAfter);
|
|
803
|
+
if (insertIndex !== -1) {
|
|
804
|
+
column.steps.splice(insertIndex + 1, 0, normalizedStep);
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
column.steps.push(normalizedStep);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
column.steps.push(normalizedStep);
|
|
812
|
+
}
|
|
813
|
+
changes.push(`Added step "${normalizedStep.title}" to "${column.title}"`);
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
changes.push(`Warning: Column "${addStep.columnTitle}" not found`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
// Validate final structure
|
|
821
|
+
const validation = validateWorkflow(workflow);
|
|
822
|
+
if (!validation.valid) {
|
|
823
|
+
return createErrorResponse(`Invalid workflow structure after changes:\n- ${validation.errors.join('\n- ')}`);
|
|
824
|
+
}
|
|
825
|
+
// Update
|
|
826
|
+
current.workflow = workflow;
|
|
827
|
+
const updateResponse = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/${workflowId}`, {
|
|
828
|
+
method: 'PUT',
|
|
829
|
+
headers: {
|
|
830
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
831
|
+
'Content-Type': 'application/json',
|
|
832
|
+
},
|
|
833
|
+
body: JSON.stringify(current)
|
|
834
|
+
});
|
|
835
|
+
if (!updateResponse.ok) {
|
|
836
|
+
const errorText = await updateResponse.text();
|
|
837
|
+
throw new Error(`HTTP ${updateResponse.status} - ${errorText}`);
|
|
838
|
+
}
|
|
839
|
+
const columnCount = workflow.length;
|
|
840
|
+
const stepCount = workflow.reduce((acc, col) => acc + col.steps.length, 0);
|
|
841
|
+
return {
|
|
842
|
+
content: [{
|
|
843
|
+
type: "text",
|
|
844
|
+
text: `# Workflow Updated Successfully!
|
|
845
|
+
|
|
846
|
+
**Title:** ${current.title}
|
|
847
|
+
**ID:** \`${workflowId}\`
|
|
848
|
+
**Structure:** ${columnCount} columns, ${stepCount} steps
|
|
849
|
+
|
|
850
|
+
## Changes Made
|
|
851
|
+
${changes.map(c => `- ${c}`).join('\n')}
|
|
852
|
+
|
|
853
|
+
## Current Structure
|
|
854
|
+
${formatWorkflowForDisplay(workflow)}`
|
|
855
|
+
}]
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
return createErrorResponse(`Failed to update workflow: ${error.message}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
// ============================================================================
|
|
863
|
+
// HELPER FUNCTIONS
|
|
864
|
+
// ============================================================================
|
|
865
|
+
function createErrorResponse(message) {
|
|
866
|
+
return {
|
|
867
|
+
content: [{
|
|
868
|
+
type: "text",
|
|
869
|
+
text: message
|
|
870
|
+
}]
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
function createScopeSelectionPrompt(contentType, availableScopes, userSession) {
|
|
874
|
+
const scopeOptions = availableScopes.map(scopeId => {
|
|
875
|
+
const title = getScopeTitle(userSession, scopeId);
|
|
876
|
+
return `- **${title}** (ID: \`${scopeId}\`)`;
|
|
877
|
+
}).join('\n');
|
|
878
|
+
return {
|
|
879
|
+
content: [{
|
|
880
|
+
type: "text",
|
|
881
|
+
text: `You can create this ${contentType} in multiple scopes. Please select one:
|
|
882
|
+
|
|
883
|
+
${scopeOptions}
|
|
884
|
+
|
|
885
|
+
Call create_workflow again with the scope parameter.`
|
|
886
|
+
}]
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function generateKey(title) {
|
|
890
|
+
return title
|
|
891
|
+
.toLowerCase()
|
|
892
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
893
|
+
.split(/\s+/)
|
|
894
|
+
.map((word, i) => i === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
|
|
895
|
+
.join('') + 'Workflow';
|
|
896
|
+
}
|
|
897
|
+
function generateStepKey(title, existingKeys) {
|
|
898
|
+
let key = title
|
|
899
|
+
.toLowerCase()
|
|
900
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
901
|
+
.split(/\s+/)
|
|
902
|
+
.map((word, i) => i === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
|
|
903
|
+
.join('');
|
|
904
|
+
let finalKey = key;
|
|
905
|
+
let counter = 1;
|
|
906
|
+
while (existingKeys.includes(finalKey)) {
|
|
907
|
+
finalKey = `${key}${counter}`;
|
|
908
|
+
counter++;
|
|
909
|
+
}
|
|
910
|
+
existingKeys.push(finalKey);
|
|
911
|
+
return finalKey;
|
|
912
|
+
}
|
|
913
|
+
function normalizeStep(step, existingKeys, colorIndex) {
|
|
914
|
+
const key = step.key || generateStepKey(step.title, existingKeys);
|
|
915
|
+
return {
|
|
916
|
+
title: step.title,
|
|
917
|
+
key,
|
|
918
|
+
color: step.color || STEP_COLORS[colorIndex % STEP_COLORS.length],
|
|
919
|
+
description: step.description || '',
|
|
920
|
+
assignees: step.assignees || [],
|
|
921
|
+
assigneeBehaviour: step.assigneeBehaviour || '',
|
|
922
|
+
dueDateBehaviour: step.dueDateBehaviour || '',
|
|
923
|
+
dueDateQuantity: step.dueDateQuantity || 0,
|
|
924
|
+
dueDateUnit: step.dueDateUnit || 'days',
|
|
925
|
+
dueDateReplacement: step.dueDateReplacement || '',
|
|
926
|
+
hasRequiredTasks: step.hasRequiredTasks || false,
|
|
927
|
+
requireTasks: step.requireTasks || [],
|
|
928
|
+
hasRequiredForms: step.hasRequiredForms || false,
|
|
929
|
+
requireForms: step.requireForms || [],
|
|
930
|
+
hasRequiredCriteria: step.hasRequiredCriteria || false,
|
|
931
|
+
completionCriteria: step.completionCriteria || [],
|
|
932
|
+
completionWhen: step.completionWhen || '',
|
|
933
|
+
entryFunctions: step.entryFunctions || [],
|
|
934
|
+
exitFunctions: step.exitFunctions || [],
|
|
935
|
+
successFunctions: step.successFunctions || [],
|
|
936
|
+
failFunctions: step.failFunctions || [],
|
|
937
|
+
successAction: step.successAction || 'next',
|
|
938
|
+
successTargetStep: step.successTargetStep || '',
|
|
939
|
+
failureAction: step.failureAction || '',
|
|
940
|
+
failureTargetStep: step.failureTargetStep || ''
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function normalizeWorkflow(workflow) {
|
|
944
|
+
const existingKeys = [];
|
|
945
|
+
let colorIndex = 0;
|
|
946
|
+
return workflow.map(column => ({
|
|
947
|
+
title: column.title,
|
|
948
|
+
description: column.description || '',
|
|
949
|
+
steps: (column.steps || []).map((step) => {
|
|
950
|
+
const normalized = normalizeStep(step, existingKeys, colorIndex);
|
|
951
|
+
colorIndex++;
|
|
952
|
+
return normalized;
|
|
953
|
+
})
|
|
954
|
+
}));
|
|
955
|
+
}
|
|
956
|
+
function getAllStepKeys(workflow) {
|
|
957
|
+
const keys = [];
|
|
958
|
+
for (const col of workflow) {
|
|
959
|
+
for (const step of col.steps) {
|
|
960
|
+
if (step.key)
|
|
961
|
+
keys.push(step.key);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return keys;
|
|
965
|
+
}
|
|
966
|
+
function formatWorkflowForDisplay(workflow) {
|
|
967
|
+
let output = '';
|
|
968
|
+
workflow.forEach((column, colIndex) => {
|
|
969
|
+
output += `\n## Column ${colIndex + 1}: ${column.title}\n`;
|
|
970
|
+
if (column.description) {
|
|
971
|
+
output += `_${column.description}_\n`;
|
|
972
|
+
}
|
|
973
|
+
column.steps.forEach((step, stepIndex) => {
|
|
974
|
+
output += `\n### ${colIndex + 1}.${stepIndex + 1} ${step.title}\n`;
|
|
975
|
+
output += `- Key: \`${step.key}\`\n`;
|
|
976
|
+
if (step.dueDateBehaviour === 'period' && step.dueDateQuantity) {
|
|
977
|
+
output += `- Due: ${step.dueDateQuantity} ${step.dueDateUnit} after entry\n`;
|
|
978
|
+
}
|
|
979
|
+
if (step.hasRequiredTasks && step.requireTasks?.length) {
|
|
980
|
+
output += `- Required tasks: ${step.requireTasks.map(t => t.title).join(', ')}\n`;
|
|
981
|
+
}
|
|
982
|
+
const functions = [
|
|
983
|
+
step.entryFunctions?.length ? `entry: ${step.entryFunctions.length}` : null,
|
|
984
|
+
step.exitFunctions?.length ? `exit: ${step.exitFunctions.length}` : null,
|
|
985
|
+
step.successFunctions?.length ? `success: ${step.successFunctions.length}` : null,
|
|
986
|
+
step.failFunctions?.length ? `fail: ${step.failFunctions.length}` : null
|
|
987
|
+
].filter(Boolean);
|
|
988
|
+
if (functions.length) {
|
|
989
|
+
output += `- Automation: ${functions.join(', ')}\n`;
|
|
990
|
+
}
|
|
991
|
+
output += `- On success: ${step.successAction || 'next'}`;
|
|
992
|
+
if (step.successAction === 'step' && step.successTargetStep) {
|
|
993
|
+
output += ` → \`${step.successTargetStep}\``;
|
|
994
|
+
}
|
|
995
|
+
output += '\n';
|
|
996
|
+
if (step.failureAction) {
|
|
997
|
+
output += `- On failure: ${step.failureAction}`;
|
|
998
|
+
if (step.failureAction === 'step' && step.failureTargetStep) {
|
|
999
|
+
output += ` → \`${step.failureTargetStep}\``;
|
|
1000
|
+
}
|
|
1001
|
+
output += '\n';
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
return output;
|
|
1006
|
+
}
|
|
1007
|
+
function validateWorkflow(workflow) {
|
|
1008
|
+
const errors = [];
|
|
1009
|
+
const allStepKeys = new Set();
|
|
1010
|
+
workflow.forEach((column, colIndex) => {
|
|
1011
|
+
if (!column.title) {
|
|
1012
|
+
errors.push(`Column ${colIndex + 1} is missing a title`);
|
|
1013
|
+
}
|
|
1014
|
+
if (!column.steps || column.steps.length === 0) {
|
|
1015
|
+
errors.push(`Column "${column.title}" has no steps`);
|
|
1016
|
+
}
|
|
1017
|
+
column.steps?.forEach((step, stepIndex) => {
|
|
1018
|
+
if (!step.title) {
|
|
1019
|
+
errors.push(`Step ${colIndex + 1}.${stepIndex + 1} is missing a title`);
|
|
1020
|
+
}
|
|
1021
|
+
if (!step.key) {
|
|
1022
|
+
errors.push(`Step "${step.title}" is missing a key`);
|
|
1023
|
+
}
|
|
1024
|
+
if (allStepKeys.has(step.key)) {
|
|
1025
|
+
errors.push(`Duplicate step key: "${step.key}"`);
|
|
1026
|
+
}
|
|
1027
|
+
allStepKeys.add(step.key);
|
|
1028
|
+
});
|
|
1029
|
+
});
|
|
1030
|
+
// Validate navigation targets
|
|
1031
|
+
workflow.forEach(column => {
|
|
1032
|
+
column.steps?.forEach((step) => {
|
|
1033
|
+
if (step.successAction === 'step' && step.successTargetStep) {
|
|
1034
|
+
if (!allStepKeys.has(step.successTargetStep)) {
|
|
1035
|
+
errors.push(`Step "${step.key}" has invalid successTargetStep: "${step.successTargetStep}"`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (step.failureAction === 'step' && step.failureTargetStep) {
|
|
1039
|
+
if (!allStepKeys.has(step.failureTargetStep)) {
|
|
1040
|
+
errors.push(`Step "${step.key}" has invalid failureTargetStep: "${step.failureTargetStep}"`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
});
|
|
1045
|
+
return { valid: errors.length === 0, errors };
|
|
1046
|
+
}
|
|
1047
|
+
//# sourceMappingURL=workflow.js.map
|