@qikdev/mcp 6.7.5 → 6.7.8
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/create.d.ts.map +1 -1
- package/build/src/tools/create.js +34 -642
- package/build/src/tools/create.js.map +1 -1
- 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/list.d.ts.map +1 -1
- package/build/src/tools/list.js +79 -323
- package/build/src/tools/list.js.map +1 -1
- package/build/src/tools/update.d.ts.map +1 -1
- package/build/src/tools/update.js +30 -345
- package/build/src/tools/update.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
|
@@ -1,41 +1,43 @@
|
|
|
1
1
|
import { ConfigManager } from "../config.js";
|
|
2
|
-
import { getUserSessionData, getAvailableScopes, getScopeTitle } from "./user.js";
|
|
3
|
-
import { handleListContent } from "./list.js";
|
|
4
2
|
export const createContentTool = {
|
|
5
3
|
name: "create_content",
|
|
6
4
|
description: `Create new content items in Qik.
|
|
7
5
|
|
|
6
|
+
**Endpoint:** POST /content/:typeKey/create
|
|
7
|
+
|
|
8
|
+
**Required:**
|
|
9
|
+
- typeKey: The content type key (e.g., 'article', 'profile', 'event')
|
|
10
|
+
|
|
11
|
+
All other properties are passed directly to the API as the create payload.
|
|
12
|
+
|
|
8
13
|
**Tags Shorthand:**
|
|
9
|
-
|
|
14
|
+
Pass \`tags: ['green', 'red']\` as string names and the backend auto-creates tags.
|
|
15
|
+
|
|
16
|
+
**Scope:**
|
|
17
|
+
Set \`meta.scopes: ['scopeId']\` to specify which scope(s) the content belongs to.
|
|
10
18
|
|
|
11
19
|
**Example:**
|
|
12
20
|
\`\`\`json
|
|
13
21
|
{
|
|
14
22
|
"typeKey": "article",
|
|
15
23
|
"title": "My Article",
|
|
16
|
-
"tags": ["featured", "news"]
|
|
24
|
+
"tags": ["featured", "news"],
|
|
25
|
+
"meta": {
|
|
26
|
+
"scopes": ["scopeId123"]
|
|
27
|
+
}
|
|
17
28
|
}
|
|
18
|
-
|
|
19
|
-
The backend creates/finds tags named "featured" and "news", then sets meta.tags to their IDs.`,
|
|
29
|
+
\`\`\``,
|
|
20
30
|
inputSchema: {
|
|
21
31
|
type: "object",
|
|
22
32
|
properties: {
|
|
23
33
|
typeKey: {
|
|
24
34
|
type: "string",
|
|
25
|
-
description: "The content type key (e.g., 'article', 'profile')
|
|
26
|
-
},
|
|
27
|
-
title: {
|
|
28
|
-
type: "string",
|
|
29
|
-
description: "The title of the content item"
|
|
35
|
+
description: "The content type key (e.g., 'article', 'profile')"
|
|
30
36
|
},
|
|
31
37
|
tags: {
|
|
32
38
|
type: "array",
|
|
33
39
|
items: { type: "string" },
|
|
34
|
-
description: "Tag names - backend auto-creates tags and attaches IDs
|
|
35
|
-
},
|
|
36
|
-
scope: {
|
|
37
|
-
type: "string",
|
|
38
|
-
description: "Scope ID to create in. If not provided, you'll be prompted if multiple are available."
|
|
40
|
+
description: "Tag names - backend auto-creates tags and attaches IDs"
|
|
39
41
|
}
|
|
40
42
|
},
|
|
41
43
|
required: ["typeKey"],
|
|
@@ -49,408 +51,27 @@ export async function handleCreateContent(args) {
|
|
|
49
51
|
if (!config) {
|
|
50
52
|
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return createErrorResponse('Please provide a description of what you want to create (e.g., "article", "incident report")');
|
|
56
|
-
}
|
|
57
|
-
// STEP 1: Translate natural language to content type
|
|
58
|
-
// Check if this is a UI/interface creation request first
|
|
59
|
-
const uiKeywords = ['website', 'web app', 'webapp', 'interface', 'ui', 'user interface', 'site', 'portal', 'blog', 'app'];
|
|
60
|
-
const isUIRequest = uiKeywords.some(keyword => naturalLanguage.toLowerCase().includes(keyword));
|
|
61
|
-
if (isUIRequest) {
|
|
62
|
-
// Extract user arguments (everything except typeKey)
|
|
63
|
-
const { typeKey, ...userArgs } = args;
|
|
64
|
-
return await createUICreationSuggestion(naturalLanguage, userArgs);
|
|
65
|
-
}
|
|
66
|
-
const contentType = await findContentTypeFromNaturalLanguage(config, naturalLanguage);
|
|
67
|
-
if (!contentType) {
|
|
68
|
-
return createErrorResponse(`I couldn't find a content type matching "${naturalLanguage}". Please check available types using get_glossary.`);
|
|
69
|
-
}
|
|
70
|
-
// STEP 2: Get user session to check permissions and scopes
|
|
71
|
-
const userSession = await getUserSessionData();
|
|
72
|
-
const createPermission = `${contentType.key}.create`;
|
|
73
|
-
const createParentTypePermission = contentType.definesType ? `${contentType.definesType}.create` : '---';
|
|
74
|
-
const availableScopesForDefinition = getAvailableScopes(userSession, createPermission);
|
|
75
|
-
const availableScopesForParentType = getAvailableScopes(userSession, createParentTypePermission);
|
|
76
|
-
const availableScopes = [
|
|
77
|
-
...availableScopesForDefinition,
|
|
78
|
-
...availableScopesForParentType
|
|
79
|
-
];
|
|
80
|
-
if (availableScopes.length === 0) {
|
|
81
|
-
return createErrorResponse(`You don't have permission to create ${contentType.title} content. Required permission: ${createPermission}`);
|
|
82
|
-
}
|
|
83
|
-
// STEP 3: Check if we have the create example payload
|
|
84
|
-
if (!contentType.examples || !contentType.examples.create) {
|
|
85
|
-
return createErrorResponse(`No create example found for ${contentType.title}. Cannot proceed without API example.`);
|
|
86
|
-
}
|
|
87
|
-
// STEP 4: Extract field values from user input (everything except typeKey, scope, scopeId)
|
|
88
|
-
const { typeKey, scope, scopeId, ...userFieldValues } = args;
|
|
89
|
-
// STEP 5: Handle scope selection
|
|
90
|
-
const selectedScopeId = scope || scopeId;
|
|
91
|
-
// If no scope provided and multiple scopes available, ask user to select
|
|
92
|
-
if (!selectedScopeId && availableScopes.length > 1) {
|
|
93
|
-
return createScopeSelectionPrompt(contentType.title, availableScopes, userSession, naturalLanguage);
|
|
94
|
-
}
|
|
95
|
-
// Use selected scope or default to first available
|
|
96
|
-
const targetScopeId = selectedScopeId || availableScopes[0];
|
|
97
|
-
// Validate the selected scope is actually available
|
|
98
|
-
if (!availableScopes.includes(targetScopeId)) {
|
|
99
|
-
return createErrorResponse(`Invalid scope "${targetScopeId}". Available scopes: ${availableScopes.map(id => `${getScopeTitle(userSession, id)} (${id})`).join(', ')}`);
|
|
54
|
+
const { typeKey, ...payload } = args;
|
|
55
|
+
if (!typeKey) {
|
|
56
|
+
throw new Error('typeKey is required');
|
|
100
57
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// STEP 7: If fields are missing, ask user for them
|
|
109
|
-
if (missingFields.length > 0) {
|
|
110
|
-
// Check if any missing fields are reference fields that need special handling
|
|
111
|
-
const referenceFieldsNeedingHelp = missingFields.filter(field => field.type === 'reference' && !userFieldValues[field.path] && !userFieldValues[field.path.split('.').pop()]);
|
|
112
|
-
if (referenceFieldsNeedingHelp.length > 0) {
|
|
113
|
-
return createReferenceFieldPrompt(contentType.title, referenceFieldsNeedingHelp, missingFields, targetScopeId, naturalLanguage);
|
|
114
|
-
}
|
|
115
|
-
return createFieldCollectionPrompt(contentType.title, missingFields, contentType.examples.create, targetScopeId, naturalLanguage);
|
|
116
|
-
}
|
|
117
|
-
// STEP 8: Create the content using the exact payload
|
|
118
|
-
const createdContent = await createContentViaAPI(config, payload, contentType.key);
|
|
119
|
-
return createSuccessResponse(contentType.title, createdContent, getScopeTitle(userSession, targetScopeId));
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
return createErrorResponse(`Failed to create content: ${error.message}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// STEP 1: Find content type from natural language
|
|
126
|
-
async function findContentTypeFromNaturalLanguage(config, naturalLanguage) {
|
|
127
|
-
// Get the glossary index
|
|
128
|
-
const indexResponse = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/glossary/ai`, {
|
|
129
|
-
headers: {
|
|
130
|
-
'Authorization': `Bearer ${config.accessToken}`,
|
|
131
|
-
'Content-Type': 'application/json',
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
if (!indexResponse.ok) {
|
|
135
|
-
throw new Error(`Failed to fetch glossary: HTTP ${indexResponse.status}`);
|
|
136
|
-
}
|
|
137
|
-
const glossary = await indexResponse.json();
|
|
138
|
-
// Find matching content type
|
|
139
|
-
const searchTerm = naturalLanguage.toLowerCase().trim();
|
|
140
|
-
let matchedType = null;
|
|
141
|
-
// Try exact key match first
|
|
142
|
-
matchedType = glossary.find(ct => ct.key === searchTerm);
|
|
143
|
-
// Try fuzzy matching on title, plural, and key
|
|
144
|
-
if (!matchedType) {
|
|
145
|
-
matchedType = glossary.find(ct => {
|
|
146
|
-
const title = (ct.title || '').toLowerCase();
|
|
147
|
-
const plural = (ct.plural || '').toLowerCase();
|
|
148
|
-
const key = (ct.key || '').toLowerCase();
|
|
149
|
-
return title.includes(searchTerm) ||
|
|
150
|
-
plural.includes(searchTerm) ||
|
|
151
|
-
key.includes(searchTerm) ||
|
|
152
|
-
searchTerm.includes(title) ||
|
|
153
|
-
searchTerm.includes(plural) ||
|
|
154
|
-
searchTerm.includes(key);
|
|
58
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/${typeKey}/create`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(payload)
|
|
155
65
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
// Fetch detailed documentation with examples
|
|
161
|
-
const detailResponse = await fetch(matchedType.urls.documentation.url, {
|
|
162
|
-
headers: {
|
|
163
|
-
'Authorization': `Bearer ${config.accessToken}`,
|
|
164
|
-
'Content-Type': 'application/json',
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
if (!detailResponse.ok) {
|
|
168
|
-
throw new Error(`Failed to fetch content type details: HTTP ${detailResponse.status}`);
|
|
169
|
-
}
|
|
170
|
-
const detailData = await detailResponse.json();
|
|
171
|
-
return {
|
|
172
|
-
...detailData,
|
|
173
|
-
title: matchedType.title,
|
|
174
|
-
plural: matchedType.plural,
|
|
175
|
-
urls: matchedType.urls
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
// STEP 5: Build exact payload using the API example as template
|
|
179
|
-
async function buildExactPayloadFromExample(examplePayload, fields, userValues, defaultScope, contentType, naturalLanguage) {
|
|
180
|
-
// Start with the structure from the example payload but clear out example values
|
|
181
|
-
const payload = JSON.parse(JSON.stringify(examplePayload));
|
|
182
|
-
const missingFields = [];
|
|
183
|
-
// Clear out all example values, keeping only the structure
|
|
184
|
-
clearExampleValues(payload, fields);
|
|
185
|
-
// Set system-managed fields first
|
|
186
|
-
setValueAtPath(payload, 'meta.scopes', [defaultScope]);
|
|
187
|
-
// Set default values for fields that have them
|
|
188
|
-
fields.forEach(field => {
|
|
189
|
-
if (!field.path || field.readOnly === true || field.type === 'group') {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
// Apply system defaults for common fields
|
|
193
|
-
if (field.path === 'meta.status' && field.defaultValues && field.defaultValues.length > 0) {
|
|
194
|
-
setValueAtPath(payload, field.path, field.defaultValues[0]);
|
|
195
|
-
}
|
|
196
|
-
else if (field.path === 'meta.security' && field.defaultValues && field.defaultValues.length > 0) {
|
|
197
|
-
setValueAtPath(payload, field.path, field.defaultValues[0]);
|
|
198
|
-
}
|
|
199
|
-
else if (field.path === 'meta.created' && field.type === 'date') {
|
|
200
|
-
setValueAtPath(payload, field.path, new Date().toISOString());
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
// Apply user-provided values, handling reference fields specially
|
|
204
|
-
for (const [key, value] of Object.entries(userValues)) {
|
|
205
|
-
// Try to find the field definition for this key
|
|
206
|
-
const field = fields.find(f => f.path === key ||
|
|
207
|
-
f.title === key ||
|
|
208
|
-
f.path.split('.').pop() === key);
|
|
209
|
-
if (field && !field.readOnly) {
|
|
210
|
-
// Handle reference fields that might need search assistance
|
|
211
|
-
if (field.type === 'reference' && typeof value === 'string' && value.startsWith('search for:')) {
|
|
212
|
-
// This is a search request for a reference field - we'll handle this in the missing fields check
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
setValueAtPath(payload, field.path, value);
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
// If no field definition found, try to set it directly
|
|
219
|
-
setValueAtPath(payload, key, value);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
// Check for required fields that still need values, and handle reference field searches
|
|
223
|
-
for (const field of fields) {
|
|
224
|
-
if (!field.path || field.readOnly === true || field.type === 'group') {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
// Check if this is a reference field with a search request
|
|
228
|
-
if (field.type === 'reference') {
|
|
229
|
-
const userValue = getUserValueForField(userValues, field);
|
|
230
|
-
if (typeof userValue === 'string' && userValue.startsWith('search for:')) {
|
|
231
|
-
const searchTerm = userValue.replace('search for:', '').trim();
|
|
232
|
-
if (searchTerm) {
|
|
233
|
-
// Perform the reference search
|
|
234
|
-
const referenceResult = await handleReferenceFieldSearch(field, searchTerm, contentType.title, defaultScope, naturalLanguage);
|
|
235
|
-
if (referenceResult) {
|
|
236
|
-
return referenceResult;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (field.required === true) {
|
|
242
|
-
const currentValue = getValueAtPath(payload, field.path);
|
|
243
|
-
// If the field is empty/null/undefined, mark as missing
|
|
244
|
-
if (currentValue === null || currentValue === undefined || currentValue === '') {
|
|
245
|
-
missingFields.push(field);
|
|
246
|
-
}
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const errorText = await response.text();
|
|
68
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
247
69
|
}
|
|
248
|
-
|
|
249
|
-
return { payload, missingFields };
|
|
250
|
-
}
|
|
251
|
-
// Helper function to clear example values while preserving structure
|
|
252
|
-
function clearExampleValues(obj, fields, currentPath = '') {
|
|
253
|
-
if (obj === null || typeof obj !== 'object') {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
if (Array.isArray(obj)) {
|
|
257
|
-
// For arrays, clear them but keep the structure
|
|
258
|
-
obj.length = 0; // Clear the array but keep it as an array
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
// For objects, recursively clear values
|
|
262
|
-
for (const key in obj) {
|
|
263
|
-
const fullPath = currentPath ? `${currentPath}.${key}` : key;
|
|
264
|
-
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
265
|
-
// Recursively clear nested objects
|
|
266
|
-
clearExampleValues(obj[key], fields, fullPath);
|
|
267
|
-
}
|
|
268
|
-
else if (Array.isArray(obj[key])) {
|
|
269
|
-
// Clear arrays but keep them as arrays
|
|
270
|
-
obj[key] = [];
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
// Clear primitive values - don't preserve system fields at this stage
|
|
274
|
-
// We'll set system fields later in the process
|
|
275
|
-
if (typeof obj[key] === 'string') {
|
|
276
|
-
obj[key] = undefined;
|
|
277
|
-
}
|
|
278
|
-
else if (typeof obj[key] === 'number') {
|
|
279
|
-
obj[key] = undefined;
|
|
280
|
-
}
|
|
281
|
-
else if (typeof obj[key] === 'boolean') {
|
|
282
|
-
obj[key] = undefined;
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
obj[key] = undefined;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
// Helper function to get value at a specific path
|
|
291
|
-
function getValueAtPath(obj, path) {
|
|
292
|
-
const parts = path.split('.');
|
|
293
|
-
let current = obj;
|
|
294
|
-
for (const part of parts) {
|
|
295
|
-
if (current === null || current === undefined) {
|
|
296
|
-
return undefined;
|
|
297
|
-
}
|
|
298
|
-
current = current[part];
|
|
299
|
-
}
|
|
300
|
-
return current;
|
|
301
|
-
}
|
|
302
|
-
// Get user value for a specific field
|
|
303
|
-
function getUserValueForField(userValues, field) {
|
|
304
|
-
// Try exact field path first
|
|
305
|
-
if (userValues[field.path] !== undefined) {
|
|
306
|
-
return userValues[field.path];
|
|
307
|
-
}
|
|
308
|
-
// Try field title
|
|
309
|
-
if (field.title && userValues[field.title] !== undefined) {
|
|
310
|
-
return userValues[field.title];
|
|
311
|
-
}
|
|
312
|
-
// Try last part of path (e.g., "title" for "meta.title")
|
|
313
|
-
const pathParts = field.path.split('.');
|
|
314
|
-
const lastPart = pathParts[pathParts.length - 1];
|
|
315
|
-
if (userValues[lastPart] !== undefined) {
|
|
316
|
-
return userValues[lastPart];
|
|
317
|
-
}
|
|
318
|
-
return undefined;
|
|
319
|
-
}
|
|
320
|
-
// Set value at specific path in object
|
|
321
|
-
function setValueAtPath(obj, path, value) {
|
|
322
|
-
const parts = path.split('.');
|
|
323
|
-
let current = obj;
|
|
324
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
325
|
-
const part = parts[i];
|
|
326
|
-
if (!current[part]) {
|
|
327
|
-
current[part] = {};
|
|
328
|
-
}
|
|
329
|
-
current = current[part];
|
|
330
|
-
}
|
|
331
|
-
const finalPart = parts[parts.length - 1];
|
|
332
|
-
current[finalPart] = value;
|
|
333
|
-
}
|
|
334
|
-
// Create content via API
|
|
335
|
-
async function createContentViaAPI(config, payload, contentTypeKey) {
|
|
336
|
-
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/${contentTypeKey}/create`, {
|
|
337
|
-
method: 'POST',
|
|
338
|
-
headers: {
|
|
339
|
-
'Authorization': `Bearer ${config.accessToken}`,
|
|
340
|
-
'Content-Type': 'application/json',
|
|
341
|
-
},
|
|
342
|
-
body: JSON.stringify(payload)
|
|
343
|
-
});
|
|
344
|
-
if (!response.ok) {
|
|
345
|
-
const errorText = await response.text();
|
|
346
|
-
throw new Error(`HTTP ${response.status} - ${errorText}`);
|
|
347
|
-
}
|
|
348
|
-
return await response.json();
|
|
349
|
-
}
|
|
350
|
-
// Helper functions for responses
|
|
351
|
-
function createErrorResponse(message) {
|
|
352
|
-
return {
|
|
353
|
-
content: [{
|
|
354
|
-
type: "text",
|
|
355
|
-
text: JSON.stringify({ error: message }, null, 2)
|
|
356
|
-
}]
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
function createScopeSelectionPrompt(contentTitle, availableScopes, userSession, naturalLanguage) {
|
|
360
|
-
const scopeOptions = availableScopes.map(scopeId => ({
|
|
361
|
-
id: scopeId,
|
|
362
|
-
title: getScopeTitle(userSession, scopeId)
|
|
363
|
-
}));
|
|
364
|
-
return {
|
|
365
|
-
content: [{
|
|
366
|
-
type: "text",
|
|
367
|
-
text: JSON.stringify({
|
|
368
|
-
needsScopeSelection: true,
|
|
369
|
-
contentTitle,
|
|
370
|
-
availableScopes: scopeOptions,
|
|
371
|
-
typeKey: naturalLanguage
|
|
372
|
-
}, null, 2)
|
|
373
|
-
}]
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
function createFieldCollectionPrompt(contentTitle, missingFields, examplePayload, scopeId, naturalLanguage) {
|
|
377
|
-
return {
|
|
378
|
-
content: [{
|
|
379
|
-
type: "text",
|
|
380
|
-
text: JSON.stringify({
|
|
381
|
-
needsFields: true,
|
|
382
|
-
contentTitle,
|
|
383
|
-
missingFields: missingFields.map(field => ({
|
|
384
|
-
path: field.path,
|
|
385
|
-
title: field.title,
|
|
386
|
-
type: field.type,
|
|
387
|
-
description: field.description,
|
|
388
|
-
referenceType: field.referenceType,
|
|
389
|
-
options: field.options
|
|
390
|
-
})),
|
|
391
|
-
typeKey: naturalLanguage || contentTitle.toLowerCase(),
|
|
392
|
-
scope: scopeId
|
|
393
|
-
}, null, 2)
|
|
394
|
-
}]
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
function createReferenceFieldPrompt(contentTitle, referenceFields, allMissingFields, scopeId, naturalLanguage) {
|
|
398
|
-
const nonReferenceFields = allMissingFields.filter(field => field.type !== 'reference');
|
|
399
|
-
return {
|
|
400
|
-
content: [{
|
|
401
|
-
type: "text",
|
|
402
|
-
text: JSON.stringify({
|
|
403
|
-
needsReferenceFields: true,
|
|
404
|
-
contentTitle,
|
|
405
|
-
referenceFields: referenceFields.map(field => ({
|
|
406
|
-
path: field.path,
|
|
407
|
-
title: field.title,
|
|
408
|
-
description: field.description,
|
|
409
|
-
referenceType: field.referenceType
|
|
410
|
-
})),
|
|
411
|
-
otherMissingFields: nonReferenceFields.map(field => ({
|
|
412
|
-
path: field.path,
|
|
413
|
-
title: field.title,
|
|
414
|
-
type: field.type,
|
|
415
|
-
description: field.description
|
|
416
|
-
})),
|
|
417
|
-
typeKey: naturalLanguage || contentTitle.toLowerCase(),
|
|
418
|
-
scope: scopeId
|
|
419
|
-
}, null, 2)
|
|
420
|
-
}]
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
// Handle reference field search and selection
|
|
424
|
-
async function handleReferenceFieldSearch(field, searchTerm, contentTitle, scopeId, naturalLanguage) {
|
|
425
|
-
try {
|
|
426
|
-
if (!field.referenceType) {
|
|
427
|
-
return {
|
|
428
|
-
content: [{
|
|
429
|
-
type: "text",
|
|
430
|
-
text: JSON.stringify({ error: `Cannot search for ${field.path} references - no reference type specified in field definition.` }, null, 2)
|
|
431
|
-
}]
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
// Use the list function to search for references
|
|
435
|
-
const listArgs = {
|
|
436
|
-
typeKey: field.referenceType,
|
|
437
|
-
search: searchTerm,
|
|
438
|
-
page: { size: 10, index: 1 }
|
|
439
|
-
};
|
|
440
|
-
const searchResults = await handleListContent(listArgs);
|
|
70
|
+
const data = await response.json();
|
|
441
71
|
return {
|
|
442
72
|
content: [{
|
|
443
73
|
type: "text",
|
|
444
|
-
text: JSON.stringify(
|
|
445
|
-
referenceSearch: true,
|
|
446
|
-
field: field.path,
|
|
447
|
-
fieldTitle: field.title,
|
|
448
|
-
referenceType: field.referenceType,
|
|
449
|
-
searchTerm,
|
|
450
|
-
results: JSON.parse(searchResults.content[0]?.text || '{}'),
|
|
451
|
-
typeKey: naturalLanguage,
|
|
452
|
-
scope: scopeId
|
|
453
|
-
}, null, 2)
|
|
74
|
+
text: JSON.stringify(data, null, 2)
|
|
454
75
|
}]
|
|
455
76
|
};
|
|
456
77
|
}
|
|
@@ -458,238 +79,9 @@ async function handleReferenceFieldSearch(field, searchTerm, contentTitle, scope
|
|
|
458
79
|
return {
|
|
459
80
|
content: [{
|
|
460
81
|
type: "text",
|
|
461
|
-
text: JSON.stringify({ error:
|
|
82
|
+
text: JSON.stringify({ error: error.message }, null, 2)
|
|
462
83
|
}]
|
|
463
84
|
};
|
|
464
85
|
}
|
|
465
86
|
}
|
|
466
|
-
async function createUICreationSuggestion(naturalLanguage, userArgs = {}) {
|
|
467
|
-
// Determine interface type based on keywords
|
|
468
|
-
let interfaceType = 'website';
|
|
469
|
-
let description = 'a website interface';
|
|
470
|
-
if (naturalLanguage.toLowerCase().includes('web app') || naturalLanguage.toLowerCase().includes('webapp') || naturalLanguage.toLowerCase().includes('app')) {
|
|
471
|
-
interfaceType = 'web-app';
|
|
472
|
-
description = 'a web application interface';
|
|
473
|
-
}
|
|
474
|
-
else if (naturalLanguage.toLowerCase().includes('portal')) {
|
|
475
|
-
interfaceType = 'portal';
|
|
476
|
-
description = 'a portal interface';
|
|
477
|
-
}
|
|
478
|
-
else if (naturalLanguage.toLowerCase().includes('blog')) {
|
|
479
|
-
interfaceType = 'blog';
|
|
480
|
-
description = 'a blog interface';
|
|
481
|
-
}
|
|
482
|
-
// Step 1: Initial request - ask guided questions
|
|
483
|
-
if (!userArgs.interfaceTitle && !userArgs.confirmPayload) {
|
|
484
|
-
return createInterfaceQuestionPrompt(description, interfaceType, naturalLanguage);
|
|
485
|
-
}
|
|
486
|
-
// Step 2: Generate payload from collected information
|
|
487
|
-
if (userArgs.interfaceTitle && !userArgs.confirmPayload) {
|
|
488
|
-
const generatedPayload = generateInterfacePayload(userArgs, interfaceType);
|
|
489
|
-
return createPayloadReviewPrompt(generatedPayload, naturalLanguage, userArgs);
|
|
490
|
-
}
|
|
491
|
-
// Step 3: User confirmed payload - create the interface
|
|
492
|
-
if (userArgs.confirmPayload) {
|
|
493
|
-
return await handleConfirmedInterfaceCreation(naturalLanguage, userArgs, interfaceType);
|
|
494
|
-
}
|
|
495
|
-
// Invalid state
|
|
496
|
-
return createErrorResponse('Invalid interface creation state. Please start the process again.');
|
|
497
|
-
}
|
|
498
|
-
function createSuccessResponse(contentTitle, createdContent, scopeTitle) {
|
|
499
|
-
return {
|
|
500
|
-
content: [{
|
|
501
|
-
type: "text",
|
|
502
|
-
text: JSON.stringify(createdContent, null, 2)
|
|
503
|
-
}]
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
// Interface creation helper functions
|
|
507
|
-
function createInterfaceQuestionPrompt(description, interfaceType, naturalLanguage) {
|
|
508
|
-
return {
|
|
509
|
-
content: [{
|
|
510
|
-
type: "text",
|
|
511
|
-
text: JSON.stringify({
|
|
512
|
-
needsInterfaceDetails: true,
|
|
513
|
-
description,
|
|
514
|
-
interfaceType,
|
|
515
|
-
typeKey: naturalLanguage,
|
|
516
|
-
requiredFields: ['interfaceTitle'],
|
|
517
|
-
optionalFields: ['pageCount', 'pages', 'primaryColor']
|
|
518
|
-
}, null, 2)
|
|
519
|
-
}]
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
function generateInterfacePayload(args, interfaceType) {
|
|
523
|
-
const title = args.interfaceTitle || 'New Interface';
|
|
524
|
-
const pageCount = args.pageCount || 3;
|
|
525
|
-
const primaryColor = args.primaryColor || '#3570c9';
|
|
526
|
-
// Generate default pages based on interface type and count
|
|
527
|
-
let pages = args.pages;
|
|
528
|
-
if (!pages || !Array.isArray(pages)) {
|
|
529
|
-
pages = generateDefaultPages(interfaceType, pageCount);
|
|
530
|
-
}
|
|
531
|
-
// Basic interface payload structure
|
|
532
|
-
const payload = {
|
|
533
|
-
title: title,
|
|
534
|
-
meta: {
|
|
535
|
-
scopes: [] // Will be filled by scope selection
|
|
536
|
-
},
|
|
537
|
-
seo: {
|
|
538
|
-
title: `${title} - Default SEO Title`,
|
|
539
|
-
description: `${title} - Default SEO Description`
|
|
540
|
-
},
|
|
541
|
-
menus: [
|
|
542
|
-
{
|
|
543
|
-
title: "Primary Menu",
|
|
544
|
-
name: "primaryMenu",
|
|
545
|
-
items: pages.map((pageName, index) => ({
|
|
546
|
-
title: pageName,
|
|
547
|
-
type: "route",
|
|
548
|
-
route: index === 0 ? "home" : pageName.toLowerCase().replace(/\s+/g, ''),
|
|
549
|
-
url: "",
|
|
550
|
-
target: "",
|
|
551
|
-
items: []
|
|
552
|
-
}))
|
|
553
|
-
},
|
|
554
|
-
{
|
|
555
|
-
title: "Footer Menu",
|
|
556
|
-
name: "footerMenu",
|
|
557
|
-
items: [
|
|
558
|
-
{
|
|
559
|
-
title: "Privacy Policy",
|
|
560
|
-
type: "route",
|
|
561
|
-
route: "privacyPolicy",
|
|
562
|
-
url: "",
|
|
563
|
-
target: "",
|
|
564
|
-
items: []
|
|
565
|
-
},
|
|
566
|
-
{
|
|
567
|
-
title: "Terms and Conditions",
|
|
568
|
-
type: "route",
|
|
569
|
-
route: "termsAndConditions",
|
|
570
|
-
url: "",
|
|
571
|
-
target: "",
|
|
572
|
-
items: []
|
|
573
|
-
}
|
|
574
|
-
]
|
|
575
|
-
}
|
|
576
|
-
],
|
|
577
|
-
services: [],
|
|
578
|
-
components: [],
|
|
579
|
-
styles: {
|
|
580
|
-
pre: `:root {\n --primary: ${primaryColor};\n --text: #777;\n --headings: #222;\n --outer-width: 1800px;\n --inner-width: 1400px;\n --btn-border-radius: 3em;\n}\n\nbody, html {\n font-size: clamp(13px, 1.5vw, 23px);\n font-family: 'Poppins', sans-serif;\n scroll-behavior: smooth;\n background: #eee;\n color: var(--text);\n}\n\n@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,700;1,400&display=swap');`,
|
|
581
|
-
post: `.layout-app {\n background: #fff;\n max-width: var(--outer-width);\n margin: auto;\n box-shadow: 0 0 15px rgba(0, 0, 0, 0.05);\n}\n\n.v-wrap {\n padding: 4em 0;\n}\n\n.h-wrap {\n margin: auto;\n padding: 0 20px;\n max-width: var(--inner-width);\n}\n\nh1, h2, h3, h4, h5, h6 {\n letter-spacing: -0.03em;\n line-height: 1.2;\n margin-top: 0;\n margin-bottom: 0.5em;\n color: var(--headings);\n}`,
|
|
582
|
-
includes: [],
|
|
583
|
-
themes: [
|
|
584
|
-
{
|
|
585
|
-
title: "Primary",
|
|
586
|
-
uuid: generateUUID(),
|
|
587
|
-
body: `background: var(--primary);\ncolor: #fff;\n\nh1, h2, h3, h4, h5 {\n color: #fff;\n}`
|
|
588
|
-
}
|
|
589
|
-
]
|
|
590
|
-
},
|
|
591
|
-
header: { sections: [] },
|
|
592
|
-
slots: [],
|
|
593
|
-
layout: "",
|
|
594
|
-
routes: pages.map((pageName, index) => ({
|
|
595
|
-
sections: [],
|
|
596
|
-
title: pageName,
|
|
597
|
-
path: index === 0 ? "/" : `/${pageName.toLowerCase().replace(/\s+/g, '-')}`,
|
|
598
|
-
seo: {
|
|
599
|
-
title: `${pageName} - ${title}`,
|
|
600
|
-
description: `${pageName} page for ${title}`,
|
|
601
|
-
sitemapDisabled: false,
|
|
602
|
-
sitemapPriority: index === 0 ? "1.0" : "0.5",
|
|
603
|
-
sitemapChangeFrequency: ""
|
|
604
|
-
},
|
|
605
|
-
name: index === 0 ? "home" : pageName.toLowerCase().replace(/\s+/g, ''),
|
|
606
|
-
type: "route",
|
|
607
|
-
headerDisabled: false,
|
|
608
|
-
footerDisabled: false,
|
|
609
|
-
advancedOptions: false,
|
|
610
|
-
routes: []
|
|
611
|
-
})),
|
|
612
|
-
footer: { sections: [] },
|
|
613
|
-
usage: [],
|
|
614
|
-
versioning: { latest: [] }
|
|
615
|
-
};
|
|
616
|
-
return payload;
|
|
617
|
-
}
|
|
618
|
-
function generateDefaultPages(interfaceType, pageCount) {
|
|
619
|
-
const pageTemplates = {
|
|
620
|
-
website: ['Home', 'About', 'Services', 'Contact', 'Portfolio', 'Blog'],
|
|
621
|
-
'web-app': ['Dashboard', 'Profile', 'Settings', 'Help', 'Reports', 'Analytics'],
|
|
622
|
-
portal: ['Dashboard', 'Users', 'Reports', 'Settings', 'Profile', 'Help'],
|
|
623
|
-
blog: ['Home', 'Articles', 'Categories', 'About', 'Contact', 'Archive']
|
|
624
|
-
};
|
|
625
|
-
const template = pageTemplates[interfaceType] || pageTemplates.website;
|
|
626
|
-
return template.slice(0, Math.max(pageCount, 1));
|
|
627
|
-
}
|
|
628
|
-
function createPayloadReviewPrompt(payload, naturalLanguage, userArgs) {
|
|
629
|
-
const preview = {
|
|
630
|
-
title: payload.title,
|
|
631
|
-
pages: payload.routes.map((route) => ({
|
|
632
|
-
title: route.title,
|
|
633
|
-
path: route.path
|
|
634
|
-
})),
|
|
635
|
-
primaryColor: payload.styles?.pre?.match(/--primary:\s*([^;]+)/)?.[1] || '#3570c9',
|
|
636
|
-
menus: payload.menus.map((menu) => ({
|
|
637
|
-
name: menu.name,
|
|
638
|
-
items: menu.items.map((item) => item.title)
|
|
639
|
-
}))
|
|
640
|
-
};
|
|
641
|
-
return {
|
|
642
|
-
content: [{
|
|
643
|
-
type: "text",
|
|
644
|
-
text: JSON.stringify({
|
|
645
|
-
needsConfirmation: true,
|
|
646
|
-
preview,
|
|
647
|
-
typeKey: naturalLanguage,
|
|
648
|
-
interfaceTitle: userArgs.interfaceTitle
|
|
649
|
-
}, null, 2)
|
|
650
|
-
}]
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
function generateUUID() {
|
|
654
|
-
return Math.random().toString(36).substr(2, 10);
|
|
655
|
-
}
|
|
656
|
-
async function handleConfirmedInterfaceCreation(naturalLanguage, userArgs, interfaceType) {
|
|
657
|
-
try {
|
|
658
|
-
const configManager = new ConfigManager();
|
|
659
|
-
const config = await configManager.loadConfig();
|
|
660
|
-
if (!config) {
|
|
661
|
-
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
662
|
-
}
|
|
663
|
-
// Generate the interface payload
|
|
664
|
-
const interfacePayload = generateInterfacePayload(userArgs, interfaceType);
|
|
665
|
-
// Get user session for scope handling
|
|
666
|
-
const userSession = await getUserSessionData();
|
|
667
|
-
// Handle scope selection for interface creation
|
|
668
|
-
const selectedScopeId = userArgs.scope || userArgs.scopeId;
|
|
669
|
-
// Get available scopes for interface creation (assuming interface.create permission)
|
|
670
|
-
const interfaceCreatePermission = 'interface.create';
|
|
671
|
-
const availableScopes = getAvailableScopes(userSession, interfaceCreatePermission);
|
|
672
|
-
if (availableScopes.length === 0) {
|
|
673
|
-
return createErrorResponse(`You don't have permission to create interfaces. Required permission: ${interfaceCreatePermission}`);
|
|
674
|
-
}
|
|
675
|
-
// If no scope provided and multiple scopes available, ask user to select
|
|
676
|
-
if (!selectedScopeId && availableScopes.length > 1) {
|
|
677
|
-
return createScopeSelectionPrompt('interface', availableScopes, userSession, naturalLanguage);
|
|
678
|
-
}
|
|
679
|
-
// Use selected scope or default to first available
|
|
680
|
-
const targetScopeId = selectedScopeId || availableScopes[0];
|
|
681
|
-
// Validate the selected scope is actually available
|
|
682
|
-
if (!availableScopes.includes(targetScopeId)) {
|
|
683
|
-
return createErrorResponse(`Invalid scope "${targetScopeId}". Available scopes: ${availableScopes.map(id => `${getScopeTitle(userSession, id)} (${id})`).join(', ')}`);
|
|
684
|
-
}
|
|
685
|
-
// Set the scope in the payload
|
|
686
|
-
interfacePayload.meta.scopes = [targetScopeId];
|
|
687
|
-
// Create the interface using the API
|
|
688
|
-
const createdInterface = await createContentViaAPI(config, interfacePayload, 'interface');
|
|
689
|
-
return createSuccessResponse('Interface', createdInterface, getScopeTitle(userSession, targetScopeId));
|
|
690
|
-
}
|
|
691
|
-
catch (error) {
|
|
692
|
-
return createErrorResponse(`Failed to create interface: ${error.message}`);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
87
|
//# sourceMappingURL=create.js.map
|