@qikdev/mcp 6.8.0 → 6.8.3
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 +1 -19
- package/build/src/tools/create.js.map +1 -1
- package/build/src/tools/index.d.ts.map +1 -1
- package/build/src/tools/index.js +36 -0
- package/build/src/tools/index.js.map +1 -1
- package/build/src/tools/interface-builder.d.ts +189 -12
- package/build/src/tools/interface-builder.d.ts.map +1 -1
- package/build/src/tools/interface-builder.js +1328 -369
- package/build/src/tools/interface-builder.js.map +1 -1
- package/build/src/tools/interface-discovery.d.ts +20 -0
- package/build/src/tools/interface-discovery.d.ts.map +1 -0
- package/build/src/tools/interface-discovery.js +215 -0
- package/build/src/tools/interface-discovery.js.map +1 -0
- package/build/src/tools/schema-validator.d.ts +2 -1
- package/build/src/tools/schema-validator.d.ts.map +1 -1
- package/build/src/tools/schema-validator.js +8 -26
- package/build/src/tools/schema-validator.js.map +1 -1
- package/build/src/tools/update.d.ts.map +1 -1
- package/build/src/tools/update.js +2 -22
- package/build/src/tools/update.js.map +1 -1
- package/build/src/tools/workflow.d.ts.map +1 -1
- package/build/src/tools/workflow.js +155 -23
- package/build/src/tools/workflow.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,425 +1,1384 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interface Builder - Main Orchestration Tool
|
|
3
|
-
* Builds complete interfaces from natural language prompts
|
|
4
|
-
*/
|
|
5
1
|
import { ConfigManager } from "../config.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
import { parseNestedJsonStrings, createErrorResponse } from "./utils.js";
|
|
3
|
+
import { getUserSessionData, getAvailableScopes, getScopeTitle, } from "./user.js";
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// CONSTANTS
|
|
6
|
+
// ============================================================================
|
|
7
|
+
const WELL_KNOWN_COMPONENTS = {
|
|
8
|
+
CUSTOM_CODE: '62b5a2bc6343c2335f85946b',
|
|
9
|
+
WEBSITE_SECTION: '647d02f56348660726cc552d',
|
|
10
|
+
BASIC_DIV: '64d86d3563ed2a77be372c9a',
|
|
11
|
+
};
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// HELPER FUNCTIONS
|
|
14
|
+
// ============================================================================
|
|
15
|
+
function generateUUID() {
|
|
16
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
17
|
+
let result = '';
|
|
18
|
+
for (let i = 0; i < 10; i++) {
|
|
19
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
async function fetchInterface(config, id) {
|
|
24
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/${id}`, {
|
|
25
|
+
headers: {
|
|
26
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
if (response.status === 403)
|
|
32
|
+
throw new Error('Permission denied. Ensure token has interface view permission.');
|
|
33
|
+
if (response.status === 404)
|
|
34
|
+
throw new Error(`Interface ${id} not found.`);
|
|
35
|
+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
36
|
+
}
|
|
37
|
+
return response.json();
|
|
38
|
+
}
|
|
39
|
+
async function saveInterface(config, id, data) {
|
|
40
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/${id}`, {
|
|
41
|
+
method: 'PUT',
|
|
42
|
+
headers: {
|
|
43
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify(data)
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
if (response.status === 403)
|
|
50
|
+
throw new Error('Permission denied. Ensure token has interface edit permission.');
|
|
51
|
+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
52
|
+
}
|
|
53
|
+
return response.json();
|
|
54
|
+
}
|
|
55
|
+
function findRouteByName(routes, name) {
|
|
56
|
+
for (const route of routes) {
|
|
57
|
+
if (route.name === name)
|
|
58
|
+
return route;
|
|
59
|
+
if (route.routes && route.routes.length > 0) {
|
|
60
|
+
const found = findRouteByName(route.routes, name);
|
|
61
|
+
if (found)
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
function getAllRouteNames(routes) {
|
|
68
|
+
const names = [];
|
|
69
|
+
for (const route of routes) {
|
|
70
|
+
if (route.name)
|
|
71
|
+
names.push(route.name);
|
|
72
|
+
if (route.routes && route.routes.length > 0) {
|
|
73
|
+
names.push(...getAllRouteNames(route.routes));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return names;
|
|
77
|
+
}
|
|
78
|
+
function findSectionByUUID(data, uuid) {
|
|
79
|
+
// Search header sections
|
|
80
|
+
if (data.header?.sections) {
|
|
81
|
+
const found = searchSectionsForUUID(data.header.sections, uuid);
|
|
82
|
+
if (found)
|
|
83
|
+
return found;
|
|
84
|
+
}
|
|
85
|
+
// Search footer sections
|
|
86
|
+
if (data.footer?.sections) {
|
|
87
|
+
const found = searchSectionsForUUID(data.footer.sections, uuid);
|
|
88
|
+
if (found)
|
|
89
|
+
return found;
|
|
90
|
+
}
|
|
91
|
+
// Search all routes recursively
|
|
92
|
+
if (data.routes) {
|
|
93
|
+
const found = searchRouteSectionsForUUID(data.routes, uuid);
|
|
94
|
+
if (found)
|
|
95
|
+
return found;
|
|
96
|
+
}
|
|
97
|
+
// Search interface-level slots
|
|
98
|
+
if (data.slots) {
|
|
99
|
+
for (const slot of data.slots) {
|
|
100
|
+
if (slot.sections) {
|
|
101
|
+
const found = searchSectionsForUUID(slot.sections, uuid);
|
|
102
|
+
if (found)
|
|
103
|
+
return found;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
function searchSectionsForUUID(sections, uuid) {
|
|
110
|
+
for (const section of sections) {
|
|
111
|
+
if (section.uuid === uuid)
|
|
112
|
+
return section;
|
|
113
|
+
if (section.slots) {
|
|
114
|
+
for (const slot of section.slots) {
|
|
115
|
+
if (slot.sections) {
|
|
116
|
+
const found = searchSectionsForUUID(slot.sections, uuid);
|
|
117
|
+
if (found)
|
|
118
|
+
return found;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function searchRouteSectionsForUUID(routes, uuid) {
|
|
126
|
+
for (const route of routes) {
|
|
127
|
+
if (route.sections) {
|
|
128
|
+
const found = searchSectionsForUUID(route.sections, uuid);
|
|
129
|
+
if (found)
|
|
130
|
+
return found;
|
|
131
|
+
}
|
|
132
|
+
if (route.routes) {
|
|
133
|
+
const found = searchRouteSectionsForUUID(route.routes, uuid);
|
|
134
|
+
if (found)
|
|
135
|
+
return found;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
function removeSectionByUUID(data, uuid) {
|
|
141
|
+
// Search header
|
|
142
|
+
if (data.header?.sections) {
|
|
143
|
+
if (removeSectionFromArray(data.header.sections, uuid))
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
// Search footer
|
|
147
|
+
if (data.footer?.sections) {
|
|
148
|
+
if (removeSectionFromArray(data.footer.sections, uuid))
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
// Search routes
|
|
152
|
+
if (data.routes) {
|
|
153
|
+
if (removeFromRoutes(data.routes, uuid))
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
// Search interface-level slots
|
|
157
|
+
if (data.slots) {
|
|
158
|
+
for (const slot of data.slots) {
|
|
159
|
+
if (slot.sections && removeSectionFromArray(slot.sections, uuid))
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
function removeSectionFromArray(sections, uuid) {
|
|
166
|
+
const index = sections.findIndex((s) => s.uuid === uuid);
|
|
167
|
+
if (index !== -1) {
|
|
168
|
+
sections.splice(index, 1);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
// Search nested slots
|
|
172
|
+
for (const section of sections) {
|
|
173
|
+
if (section.slots) {
|
|
174
|
+
for (const slot of section.slots) {
|
|
175
|
+
if (slot.sections && removeSectionFromArray(slot.sections, uuid))
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
function removeFromRoutes(routes, uuid) {
|
|
183
|
+
for (const route of routes) {
|
|
184
|
+
if (route.sections && removeSectionFromArray(route.sections, uuid))
|
|
185
|
+
return true;
|
|
186
|
+
if (route.routes && removeFromRoutes(route.routes, uuid))
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
function removeRouteByName(routes, name) {
|
|
192
|
+
const index = routes.findIndex((r) => r.name === name);
|
|
193
|
+
if (index !== -1) {
|
|
194
|
+
routes.splice(index, 1);
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
for (const route of routes) {
|
|
198
|
+
if (route.routes && removeRouteByName(route.routes, name))
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
function resolveTargetArray(data, target) {
|
|
204
|
+
switch (target.area) {
|
|
205
|
+
case 'header':
|
|
206
|
+
if (!data.header)
|
|
207
|
+
data.header = { sections: [] };
|
|
208
|
+
if (!data.header.sections)
|
|
209
|
+
data.header.sections = [];
|
|
210
|
+
return data.header.sections;
|
|
211
|
+
case 'footer':
|
|
212
|
+
if (!data.footer)
|
|
213
|
+
data.footer = { sections: [] };
|
|
214
|
+
if (!data.footer.sections)
|
|
215
|
+
data.footer.sections = [];
|
|
216
|
+
return data.footer.sections;
|
|
217
|
+
case 'route':
|
|
218
|
+
if (!target.routeName)
|
|
219
|
+
return null;
|
|
220
|
+
const route = findRouteByName(data.routes || [], target.routeName);
|
|
221
|
+
if (!route)
|
|
222
|
+
return null;
|
|
223
|
+
if (!route.sections)
|
|
224
|
+
route.sections = [];
|
|
225
|
+
return route.sections;
|
|
226
|
+
case 'slot':
|
|
227
|
+
if (!target.sectionUuid || !target.slotKey)
|
|
228
|
+
return null;
|
|
229
|
+
const section = findSectionByUUID(data, target.sectionUuid);
|
|
230
|
+
if (!section)
|
|
231
|
+
return null;
|
|
232
|
+
if (!section.slots)
|
|
233
|
+
section.slots = [];
|
|
234
|
+
let slot = section.slots.find((s) => s.key === target.slotKey);
|
|
235
|
+
if (!slot) {
|
|
236
|
+
slot = { title: target.slotKey, key: target.slotKey, sections: [] };
|
|
237
|
+
section.slots.push(slot);
|
|
238
|
+
}
|
|
239
|
+
if (!slot.sections)
|
|
240
|
+
slot.sections = [];
|
|
241
|
+
return slot.sections;
|
|
242
|
+
default:
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function normalizeComponentId(id) {
|
|
247
|
+
if (!id)
|
|
248
|
+
return '';
|
|
249
|
+
if (typeof id === 'object')
|
|
250
|
+
return id._id || id;
|
|
251
|
+
return id;
|
|
252
|
+
}
|
|
253
|
+
function summarizeSections(sections, indent = '') {
|
|
254
|
+
const lines = [];
|
|
255
|
+
for (const section of sections || []) {
|
|
256
|
+
const compId = normalizeComponentId(section.componentID);
|
|
257
|
+
const shortId = compId.substring(0, 8);
|
|
258
|
+
const disabled = section.disabled ? ' [DISABLED]' : '';
|
|
259
|
+
lines.push(`${indent}- **${section.title || 'Section'}** (uuid: \`${section.uuid}\`, component: \`${shortId}...\`)${disabled}`);
|
|
260
|
+
if (section.slots) {
|
|
261
|
+
for (const slot of section.slots) {
|
|
262
|
+
if (slot.sections && slot.sections.length > 0) {
|
|
263
|
+
lines.push(`${indent} Slot "${slot.key}" (${slot.sections.length} sections):`);
|
|
264
|
+
lines.push(...summarizeSections(slot.sections, indent + ' '));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return lines;
|
|
270
|
+
}
|
|
271
|
+
function summarizeRoutes(routes, indent = '') {
|
|
272
|
+
const lines = [];
|
|
273
|
+
for (const route of routes || []) {
|
|
274
|
+
if (route.type === 'folder') {
|
|
275
|
+
lines.push(`${indent}- **${route.title}** (folder)`);
|
|
276
|
+
if (route.routes) {
|
|
277
|
+
lines.push(...summarizeRoutes(route.routes, indent + ' '));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const sectionCount = (route.sections || []).length;
|
|
282
|
+
lines.push(`${indent}- **${route.title}** (name: \`${route.name}\`, path: \`${route.path}\`, ${sectionCount} sections)`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return lines;
|
|
286
|
+
}
|
|
287
|
+
function summarizeInterface(data) {
|
|
288
|
+
const lines = [];
|
|
289
|
+
lines.push(`# Interface: ${data.title || 'Untitled'}`);
|
|
290
|
+
lines.push(`**ID:** \`${data._id}\` | **Layout:** ${data.layout || 'default'}\n`);
|
|
291
|
+
// Routes
|
|
292
|
+
const routes = data.routes || [];
|
|
293
|
+
lines.push(`## Routes`);
|
|
294
|
+
if (routes.length > 0) {
|
|
295
|
+
lines.push(...summarizeRoutes(routes));
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
lines.push('No routes defined.');
|
|
299
|
+
}
|
|
300
|
+
lines.push('');
|
|
301
|
+
// Header
|
|
302
|
+
lines.push(`## Header (${(data.header?.sections || []).length} sections)`);
|
|
303
|
+
if (data.header?.sections?.length > 0) {
|
|
304
|
+
lines.push(...summarizeSections(data.header.sections));
|
|
305
|
+
}
|
|
306
|
+
lines.push('');
|
|
307
|
+
// Footer
|
|
308
|
+
lines.push(`## Footer (${(data.footer?.sections || []).length} sections)`);
|
|
309
|
+
if (data.footer?.sections?.length > 0) {
|
|
310
|
+
lines.push(...summarizeSections(data.footer.sections));
|
|
311
|
+
}
|
|
312
|
+
lines.push('');
|
|
313
|
+
// Menus
|
|
314
|
+
const menus = data.menus || [];
|
|
315
|
+
lines.push(`## Menus (${menus.length})`);
|
|
316
|
+
for (const menu of menus) {
|
|
317
|
+
lines.push(`- **${menu.title}** (name: \`${menu.name}\`, ${(menu.items || []).length} items)`);
|
|
318
|
+
}
|
|
319
|
+
lines.push('');
|
|
320
|
+
// Custom Components
|
|
321
|
+
const components = data.components || [];
|
|
322
|
+
lines.push(`## Custom Components (${components.length})`);
|
|
323
|
+
for (const comp of components) {
|
|
324
|
+
lines.push(`- \`<${comp.name}/>\` - ${comp.title || comp.name}`);
|
|
325
|
+
}
|
|
326
|
+
lines.push('');
|
|
327
|
+
// Services
|
|
328
|
+
const services = data.services || [];
|
|
329
|
+
lines.push(`## Services (${services.length})`);
|
|
330
|
+
for (const svc of services) {
|
|
331
|
+
lines.push(`- **${svc.title || svc.name}** (name: \`${svc.name}\`)`);
|
|
332
|
+
}
|
|
333
|
+
lines.push('');
|
|
334
|
+
// Styles
|
|
335
|
+
const styles = data.styles || {};
|
|
336
|
+
const hasPre = styles.pre && styles.pre.trim().length > 0;
|
|
337
|
+
const hasPost = styles.post && styles.post.trim().length > 0;
|
|
338
|
+
const themeCount = (styles.themes || []).length;
|
|
339
|
+
lines.push(`## Styles`);
|
|
340
|
+
lines.push(`- Pre-SCSS: ${hasPre ? 'yes' : 'none'}`);
|
|
341
|
+
lines.push(`- Post-SCSS: ${hasPost ? 'yes' : 'none'}`);
|
|
342
|
+
lines.push(`- Themes: ${themeCount}`);
|
|
343
|
+
return lines.join('\n');
|
|
344
|
+
}
|
|
345
|
+
function formatSectionDetail(section) {
|
|
346
|
+
const lines = [];
|
|
347
|
+
const compId = normalizeComponentId(section.componentID);
|
|
348
|
+
lines.push(`## Section: ${section.title || 'Untitled'}`);
|
|
349
|
+
lines.push(`- **UUID:** \`${section.uuid}\``);
|
|
350
|
+
lines.push(`- **Component ID:** \`${compId}\``);
|
|
351
|
+
lines.push(`- **Version:** ${section.componentVersion || 'latest'}`);
|
|
352
|
+
lines.push(`- **Disabled:** ${section.disabled || false}`);
|
|
353
|
+
lines.push(`- **Themes:** ${JSON.stringify(section.themes || [])}`);
|
|
354
|
+
lines.push('');
|
|
355
|
+
lines.push(`### Model`);
|
|
356
|
+
lines.push('```json');
|
|
357
|
+
lines.push(JSON.stringify(section.model || {}, null, 2));
|
|
358
|
+
lines.push('```');
|
|
359
|
+
lines.push('');
|
|
360
|
+
if (section.slots && section.slots.length > 0) {
|
|
361
|
+
lines.push(`### Slots`);
|
|
362
|
+
for (const slot of section.slots) {
|
|
363
|
+
lines.push(`**${slot.title || slot.key}** (key: \`${slot.key}\`, ${(slot.sections || []).length} sections)`);
|
|
364
|
+
lines.push(...summarizeSections(slot.sections || [], ' '));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return lines.join('\n');
|
|
368
|
+
}
|
|
369
|
+
async function getConfigAndFetch(interfaceId) {
|
|
370
|
+
const configManager = new ConfigManager();
|
|
371
|
+
const config = await configManager.loadConfig();
|
|
372
|
+
if (!config)
|
|
373
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
374
|
+
if (!interfaceId)
|
|
375
|
+
throw new Error('interfaceId is required');
|
|
376
|
+
const data = await fetchInterface(config, interfaceId);
|
|
377
|
+
return { config, data };
|
|
378
|
+
}
|
|
379
|
+
function createScopeSelectionPrompt(contentType, availableScopes, userSession) {
|
|
380
|
+
const scopeOptions = availableScopes.map(scopeId => {
|
|
381
|
+
const title = getScopeTitle(userSession, scopeId);
|
|
382
|
+
return `- **${title}** (ID: \`${scopeId}\`)`;
|
|
383
|
+
}).join('\n');
|
|
384
|
+
return {
|
|
385
|
+
content: [{
|
|
386
|
+
type: "text",
|
|
387
|
+
text: `Multiple scopes available for creating ${contentType}. Please specify one:\n\n${scopeOptions}\n\nCall again with the \`scope\` parameter set to the desired scope ID.`
|
|
388
|
+
}]
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
// ============================================================================
|
|
392
|
+
// TOOL DEFINITIONS
|
|
393
|
+
// ============================================================================
|
|
394
|
+
export const createInterfaceTool = {
|
|
395
|
+
name: "create_interface",
|
|
396
|
+
description: `Create a new interface (website/app).
|
|
397
|
+
|
|
398
|
+
An interface is a visual page builder document containing routes, sections, menus, components, and styles.
|
|
399
|
+
|
|
400
|
+
After creating, use \`add_interface_route\`, \`add_interface_section\`, etc. to build it out.
|
|
401
|
+
|
|
402
|
+
**Example:**
|
|
403
|
+
\`\`\`json
|
|
404
|
+
{ "title": "School Fair Website" }
|
|
405
|
+
\`\`\``,
|
|
16
406
|
inputSchema: {
|
|
17
407
|
type: "object",
|
|
18
408
|
properties: {
|
|
19
|
-
|
|
409
|
+
title: {
|
|
20
410
|
type: "string",
|
|
21
|
-
description: "
|
|
411
|
+
description: "Interface title (e.g., 'School Fair Website')"
|
|
22
412
|
},
|
|
23
413
|
scope: {
|
|
24
414
|
type: "string",
|
|
25
|
-
description: "
|
|
415
|
+
description: "Scope ID to create in. Omit to auto-select if only one scope available."
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
required: ["title"]
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
export const getInterfaceTool = {
|
|
422
|
+
name: "get_interface",
|
|
423
|
+
description: `Get an interface's structure.
|
|
424
|
+
|
|
425
|
+
By default returns a **summary** (routes, menus, components, services, styles overview) to save context.
|
|
426
|
+
Use \`routeName\` to see full section details for a specific route.
|
|
427
|
+
Use \`sectionUuid\` to see full details for a specific section including its model.
|
|
428
|
+
|
|
429
|
+
**Examples:**
|
|
430
|
+
\`\`\`json
|
|
431
|
+
{ "interfaceId": "abc123" }
|
|
432
|
+
{ "interfaceId": "abc123", "routeName": "home" }
|
|
433
|
+
{ "interfaceId": "abc123", "sectionUuid": "PBqx5sBIGc" }
|
|
434
|
+
\`\`\``,
|
|
435
|
+
inputSchema: {
|
|
436
|
+
type: "object",
|
|
437
|
+
properties: {
|
|
438
|
+
interfaceId: {
|
|
439
|
+
type: "string",
|
|
440
|
+
description: "The interface content ID"
|
|
26
441
|
},
|
|
27
|
-
|
|
442
|
+
routeName: {
|
|
28
443
|
type: "string",
|
|
29
|
-
description: "
|
|
444
|
+
description: "Get full section details for this route"
|
|
30
445
|
},
|
|
31
|
-
|
|
32
|
-
type: "
|
|
33
|
-
description: "
|
|
446
|
+
sectionUuid: {
|
|
447
|
+
type: "string",
|
|
448
|
+
description: "Get full details for this specific section"
|
|
34
449
|
}
|
|
35
450
|
},
|
|
36
|
-
required: ["
|
|
37
|
-
|
|
38
|
-
},
|
|
451
|
+
required: ["interfaceId"]
|
|
452
|
+
}
|
|
39
453
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
pages: parsed.pages.map(p => p.name),
|
|
58
|
-
features: parsed.features
|
|
59
|
-
});
|
|
60
|
-
// Step 2: Fetch available components and glossary
|
|
61
|
-
const [components, glossaryResponse] = await Promise.all([
|
|
62
|
-
getMarketplaceComponents(config),
|
|
63
|
-
fetchGlossary(config)
|
|
64
|
-
]);
|
|
65
|
-
console.log(`📦 Found ${components.length} available components`);
|
|
66
|
-
console.log(`📚 Found ${glossaryResponse.length} content types in glossary`);
|
|
67
|
-
// Step 3: Generate routes from parsed pages
|
|
68
|
-
const routes = await generateRoutes(parsed.pages, components, glossaryResponse, config);
|
|
69
|
-
console.log(`🛣️ Generated ${routes.length} routes`);
|
|
70
|
-
// Step 4: Add authentication pages if needed
|
|
71
|
-
if (parsed.requiresAuth || args.includeAuth) {
|
|
72
|
-
addAuthPages(routes, components);
|
|
73
|
-
console.log('🔐 Added authentication pages');
|
|
74
|
-
}
|
|
75
|
-
// Step 5: Add legal pages (Privacy Policy, Terms)
|
|
76
|
-
addLegalPages(routes, components);
|
|
77
|
-
console.log('📄 Added legal pages');
|
|
78
|
-
// Step 6: Generate menus
|
|
79
|
-
const menus = generateMenus(routes);
|
|
80
|
-
console.log(`📋 Generated ${menus.length} menus`);
|
|
81
|
-
// Step 7: Generate styles
|
|
82
|
-
const styles = generateInterfaceStyles(args.primaryColor);
|
|
83
|
-
const header = generateHeader(menus, components);
|
|
84
|
-
const footer = generateFooter(menus, components);
|
|
85
|
-
console.log('✨ Interface structure complete, delegating to create_content...');
|
|
86
|
-
// Step 8: Delegate to create_content with the built interface structure
|
|
87
|
-
// This will handle scope selection, permissions, and creation
|
|
88
|
-
return await handleCreateContent({
|
|
89
|
-
typeKey: "interface",
|
|
90
|
-
title: parsed.appName,
|
|
91
|
-
seo: {
|
|
92
|
-
title: parsed.appName,
|
|
93
|
-
description: `${parsed.appName} - Built with Qik`
|
|
94
|
-
},
|
|
95
|
-
menus,
|
|
96
|
-
services: [],
|
|
97
|
-
components: [],
|
|
98
|
-
styles,
|
|
99
|
-
header,
|
|
100
|
-
footer,
|
|
101
|
-
slots: [],
|
|
102
|
-
layout: '<div><header-slot /><route-slot /><footer-slot /></div>',
|
|
103
|
-
routes,
|
|
104
|
-
fields: [],
|
|
105
|
-
scope: args.scope // Pass through scope if provided
|
|
106
|
-
});
|
|
454
|
+
export const publishInterfaceTool = {
|
|
455
|
+
name: "publish_interface",
|
|
456
|
+
description: `Publish an interface snapshot. Creates a frozen version that can be deployed.
|
|
457
|
+
|
|
458
|
+
**Example:**
|
|
459
|
+
\`\`\`json
|
|
460
|
+
{ "interfaceId": "abc123" }
|
|
461
|
+
\`\`\``,
|
|
462
|
+
inputSchema: {
|
|
463
|
+
type: "object",
|
|
464
|
+
properties: {
|
|
465
|
+
interfaceId: {
|
|
466
|
+
type: "string",
|
|
467
|
+
description: "The interface content ID"
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
required: ["interfaceId"]
|
|
107
471
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
472
|
+
};
|
|
473
|
+
export const addInterfaceRouteTool = {
|
|
474
|
+
name: "add_interface_route",
|
|
475
|
+
description: `Add a route (page) to an interface.
|
|
476
|
+
|
|
477
|
+
Routes can be nested inside folder-type routes using \`parentRouteName\`.
|
|
478
|
+
Dynamic segments use \`:param\` syntax (e.g., \`/article/:slug\`).
|
|
479
|
+
|
|
480
|
+
**Examples:**
|
|
481
|
+
\`\`\`json
|
|
482
|
+
{ "interfaceId": "abc123", "title": "Home", "path": "/", "name": "home" }
|
|
483
|
+
{ "interfaceId": "abc123", "title": "Article Detail", "path": "/articles/:slug", "name": "articleDetail" }
|
|
484
|
+
{ "interfaceId": "abc123", "title": "Legal", "path": "", "name": "legal", "type": "folder" }
|
|
485
|
+
{ "interfaceId": "abc123", "title": "Privacy", "path": "/privacy", "name": "privacy", "parentRouteName": "legal" }
|
|
486
|
+
\`\`\``,
|
|
487
|
+
inputSchema: {
|
|
488
|
+
type: "object",
|
|
489
|
+
properties: {
|
|
490
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
491
|
+
title: { type: "string", description: "Route title (e.g., 'About Us')" },
|
|
492
|
+
path: { type: "string", description: "URL path (e.g., '/about'). Use :param for dynamic segments." },
|
|
493
|
+
name: { type: "string", description: "Unique route name in camelCase (e.g., 'aboutUs')" },
|
|
494
|
+
type: { type: "string", enum: ["route", "folder"], description: "Route type. Default: 'route'" },
|
|
495
|
+
parentRouteName: { type: "string", description: "Name of parent folder route for nesting" },
|
|
496
|
+
headerDisabled: { type: "boolean", description: "Disable the global header on this route" },
|
|
497
|
+
footerDisabled: { type: "boolean", description: "Disable the global footer on this route" }
|
|
498
|
+
},
|
|
499
|
+
required: ["interfaceId", "title", "path", "name"]
|
|
116
500
|
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
501
|
+
};
|
|
502
|
+
export const updateInterfaceRouteTool = {
|
|
503
|
+
name: "update_interface_route",
|
|
504
|
+
description: `Update a route's properties.
|
|
505
|
+
|
|
506
|
+
**Example:**
|
|
507
|
+
\`\`\`json
|
|
508
|
+
{ "interfaceId": "abc123", "routeName": "home", "title": "Home Page", "headerDisabled": true }
|
|
509
|
+
\`\`\``,
|
|
510
|
+
inputSchema: {
|
|
511
|
+
type: "object",
|
|
512
|
+
properties: {
|
|
513
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
514
|
+
routeName: { type: "string", description: "Name of the route to update" },
|
|
515
|
+
title: { type: "string" },
|
|
516
|
+
path: { type: "string" },
|
|
517
|
+
headerDisabled: { type: "boolean" },
|
|
518
|
+
footerDisabled: { type: "boolean" },
|
|
519
|
+
seo: {
|
|
520
|
+
type: "object",
|
|
521
|
+
properties: {
|
|
522
|
+
title: { type: "string" },
|
|
523
|
+
description: { type: "string" }
|
|
524
|
+
}
|
|
525
|
+
}
|
|
126
526
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!authComponent) {
|
|
144
|
-
console.warn('⚠️ Auth component not found in marketplace');
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (!hasLogin) {
|
|
148
|
-
routes.push(createAuthPage('Login', '/login', 'userLogin', authComponent, 'login'));
|
|
149
|
-
}
|
|
150
|
-
if (!hasSignup) {
|
|
151
|
-
routes.push(createAuthPage('Signup', '/signup', 'userSignup', authComponent, 'signup'));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Create an authentication page
|
|
156
|
-
*/
|
|
157
|
-
function createAuthPage(title, path, name, component, defaultState) {
|
|
158
|
-
return {
|
|
159
|
-
title,
|
|
160
|
-
path,
|
|
161
|
-
name,
|
|
162
|
-
type: 'route',
|
|
163
|
-
sections: [{
|
|
164
|
-
title: component.title,
|
|
165
|
-
uuid: generateUUID(),
|
|
166
|
-
componentID: component._id,
|
|
167
|
-
componentVersion: 'latest',
|
|
168
|
-
model: {
|
|
169
|
-
hideTitle: false,
|
|
170
|
-
redirectTo: 'home',
|
|
171
|
-
redirectFromIntent: true,
|
|
172
|
-
defaultState
|
|
173
|
-
},
|
|
174
|
-
slots: []
|
|
175
|
-
}],
|
|
176
|
-
seo: {
|
|
177
|
-
title,
|
|
178
|
-
description: `${title} page`,
|
|
179
|
-
sitemapDisabled: false
|
|
527
|
+
required: ["interfaceId", "routeName"]
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
export const removeInterfaceRouteTool = {
|
|
531
|
+
name: "remove_interface_route",
|
|
532
|
+
description: `Remove a route from an interface. All sections within the route will be lost.
|
|
533
|
+
|
|
534
|
+
**Example:**
|
|
535
|
+
\`\`\`json
|
|
536
|
+
{ "interfaceId": "abc123", "routeName": "aboutUs" }
|
|
537
|
+
\`\`\``,
|
|
538
|
+
inputSchema: {
|
|
539
|
+
type: "object",
|
|
540
|
+
properties: {
|
|
541
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
542
|
+
routeName: { type: "string", description: "Name of the route to remove" }
|
|
180
543
|
},
|
|
181
|
-
|
|
182
|
-
footerDisabled: false,
|
|
183
|
-
contextVisibility: {
|
|
184
|
-
hideAuthenticated: true,
|
|
185
|
-
hideUnauthenticated: false
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Generate comprehensive interface styles
|
|
191
|
-
*/
|
|
192
|
-
function generateInterfaceStyles(primaryColor) {
|
|
193
|
-
const primary = primaryColor || '#4a90e2';
|
|
194
|
-
return {
|
|
195
|
-
pre: `:root {
|
|
196
|
-
--primary: ${primary};
|
|
197
|
-
--text: #333;
|
|
198
|
-
--headings: #111;
|
|
199
|
-
--bg: #fff;
|
|
200
|
-
--bg-secondary: #f8f9fa;
|
|
201
|
-
--border: #e0e0e0;
|
|
202
|
-
--wrap-width: 1200px;
|
|
203
|
-
--radius: 0.5rem;
|
|
204
|
-
--shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
@media(prefers-color-scheme: dark) {
|
|
208
|
-
:root {
|
|
209
|
-
--bg: #1a1a1a;
|
|
210
|
-
--bg-secondary: #2d2d2d;
|
|
211
|
-
--text: #e0e0e0;
|
|
212
|
-
--headings: #fff;
|
|
213
|
-
--border: #404040;
|
|
214
|
-
--shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
544
|
+
required: ["interfaceId", "routeName"]
|
|
215
545
|
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
line-height: 1.6;
|
|
221
|
-
color: var(--text);
|
|
222
|
-
background: var(--bg);
|
|
223
|
-
margin: 0;
|
|
224
|
-
padding: 0;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
* {
|
|
228
|
-
box-sizing: border-box;
|
|
229
|
-
}
|
|
546
|
+
};
|
|
547
|
+
export const addInterfaceSectionTool = {
|
|
548
|
+
name: "add_interface_section",
|
|
549
|
+
description: `Add a block component section to an interface.
|
|
230
550
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
551
|
+
This is the core building tool. Place a marketplace component as a section in the header, footer, a route, or nested inside another section's slot.
|
|
552
|
+
|
|
553
|
+
**Target areas:**
|
|
554
|
+
- \`"header"\` - Interface header (appears on all routes)
|
|
555
|
+
- \`"footer"\` - Interface footer (appears on all routes)
|
|
556
|
+
- \`"route"\` - A specific route's content (requires \`routeName\`)
|
|
557
|
+
- \`"slot"\` - Nested inside another section's slot (requires \`sectionUuid\` and \`slotKey\`)
|
|
235
558
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
559
|
+
**Common components:**
|
|
560
|
+
- Custom Code (\`62b5a2bc6343c2335f85946b\`) - model: { html, javascript, scss }
|
|
561
|
+
- Website Section (\`647d02f56348660726cc552d\`) - model: { outerClass, innerClass } - has slot "cells"
|
|
562
|
+
- Basic Div (\`64d86d3563ed2a77be372c9a\`) - has slot "content"
|
|
563
|
+
|
|
564
|
+
**Examples:**
|
|
565
|
+
\`\`\`json
|
|
566
|
+
{
|
|
567
|
+
"interfaceId": "abc123",
|
|
568
|
+
"target": { "area": "route", "routeName": "home" },
|
|
569
|
+
"componentId": "62b5a2bc6343c2335f85946b",
|
|
570
|
+
"title": "Hero Section",
|
|
571
|
+
"model": {
|
|
572
|
+
"html": "<div><h1>Welcome</h1></div>",
|
|
573
|
+
"javascript": "{}",
|
|
574
|
+
"scss": "h1 { font-size: 3em; }"
|
|
575
|
+
}
|
|
240
576
|
}
|
|
577
|
+
\`\`\`
|
|
241
578
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
font-weight: 700;
|
|
579
|
+
\`\`\`json
|
|
580
|
+
{
|
|
581
|
+
"interfaceId": "abc123",
|
|
582
|
+
"target": { "area": "slot", "sectionUuid": "PBqx5sBIGc", "slotKey": "cells" },
|
|
583
|
+
"componentId": "62b5a2bc6343c2335f85946b",
|
|
584
|
+
"title": "Content Block"
|
|
249
585
|
}
|
|
586
|
+
\`\`\`
|
|
250
587
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
588
|
+
Returns the new section's UUID - save this for updating or nesting into its slots.`,
|
|
589
|
+
inputSchema: {
|
|
590
|
+
type: "object",
|
|
591
|
+
properties: {
|
|
592
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
593
|
+
target: {
|
|
594
|
+
type: "object",
|
|
595
|
+
description: "Where to place the section",
|
|
596
|
+
properties: {
|
|
597
|
+
area: {
|
|
598
|
+
type: "string",
|
|
599
|
+
enum: ["header", "footer", "route", "slot"],
|
|
600
|
+
description: "Target area"
|
|
601
|
+
},
|
|
602
|
+
routeName: { type: "string", description: "Required when area='route'" },
|
|
603
|
+
sectionUuid: { type: "string", description: "Required when area='slot' - parent section UUID" },
|
|
604
|
+
slotKey: { type: "string", description: "Required when area='slot' - slot key (e.g., 'cells', 'content')" }
|
|
605
|
+
},
|
|
606
|
+
required: ["area"]
|
|
607
|
+
},
|
|
608
|
+
componentId: {
|
|
609
|
+
type: "string",
|
|
610
|
+
description: "Component ObjectId. Common: Custom Code='62b5a2bc6343c2335f85946b', Website Section='647d02f56348660726cc552d', Basic Div='64d86d3563ed2a77be372c9a'"
|
|
611
|
+
},
|
|
612
|
+
title: { type: "string", description: "Display title for this section" },
|
|
613
|
+
model: {
|
|
614
|
+
type: "object",
|
|
615
|
+
description: "Field values for the component. For Custom Code: { html, javascript, scss }. Use get_interface_component_details to see fields."
|
|
616
|
+
},
|
|
617
|
+
componentVersion: { type: "string", description: "Component version: 'latest' (default) or 'draft'" }
|
|
618
|
+
},
|
|
619
|
+
required: ["interfaceId", "target", "componentId"]
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
export const updateInterfaceSectionTool = {
|
|
623
|
+
name: "update_interface_section",
|
|
624
|
+
description: `Update a section's model, title, or other properties.
|
|
257
625
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
626
|
+
Model fields are **shallow-merged**: only the fields you provide are updated, others are preserved.
|
|
627
|
+
For example, to update just the HTML of a Custom Code section, send \`model: { html: "<new>" }\` and javascript/scss remain unchanged.
|
|
261
628
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
629
|
+
**Example:**
|
|
630
|
+
\`\`\`json
|
|
631
|
+
{
|
|
632
|
+
"interfaceId": "abc123",
|
|
633
|
+
"sectionUuid": "PBqx5sBIGc",
|
|
634
|
+
"model": { "html": "<div>Updated content</div>" }
|
|
266
635
|
}
|
|
636
|
+
\`\`\``,
|
|
637
|
+
inputSchema: {
|
|
638
|
+
type: "object",
|
|
639
|
+
properties: {
|
|
640
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
641
|
+
sectionUuid: { type: "string", description: "UUID of the section to update" },
|
|
642
|
+
model: { type: "object", description: "Updated field values (merged with existing)" },
|
|
643
|
+
title: { type: "string", description: "New title" },
|
|
644
|
+
disabled: { type: "boolean", description: "Disable/enable the section" },
|
|
645
|
+
themes: { type: "array", items: { type: "string" }, description: "Theme UUIDs to apply" }
|
|
646
|
+
},
|
|
647
|
+
required: ["interfaceId", "sectionUuid"]
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
export const removeInterfaceSectionTool = {
|
|
651
|
+
name: "remove_interface_section",
|
|
652
|
+
description: `Remove a section by UUID. Searches header, footer, all routes, and nested slots.
|
|
267
653
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
654
|
+
**Example:**
|
|
655
|
+
\`\`\`json
|
|
656
|
+
{ "interfaceId": "abc123", "sectionUuid": "PBqx5sBIGc" }
|
|
657
|
+
\`\`\``,
|
|
658
|
+
inputSchema: {
|
|
659
|
+
type: "object",
|
|
660
|
+
properties: {
|
|
661
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
662
|
+
sectionUuid: { type: "string", description: "UUID of the section to remove" }
|
|
663
|
+
},
|
|
664
|
+
required: ["interfaceId", "sectionUuid"]
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
export const setInterfaceCustomComponentTool = {
|
|
668
|
+
name: "set_interface_custom_component",
|
|
669
|
+
description: `Add or update an inline custom component in the interface.
|
|
271
670
|
|
|
272
|
-
|
|
273
|
-
.font-xs { font-size: 0.75rem; }
|
|
274
|
-
.font-sm { font-size: 0.875rem; }
|
|
275
|
-
.font-lg { font-size: 1.25rem; }
|
|
276
|
-
.font-xl { font-size: 1.5rem; }
|
|
277
|
-
.font-muted { opacity: 0.6; }
|
|
671
|
+
Custom components are defined with HTML, JS, and CSS and can be used as tags in other component templates (e.g., \`<event-card />\`).
|
|
278
672
|
|
|
279
|
-
|
|
280
|
-
.text-right { text-align: right; }
|
|
673
|
+
**JS format:** Vue Options API as a plain object \`{}\` without \`export default\`.
|
|
281
674
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
675
|
+
**Example:**
|
|
676
|
+
\`\`\`json
|
|
677
|
+
{
|
|
678
|
+
"interfaceId": "abc123",
|
|
679
|
+
"name": "event-card",
|
|
680
|
+
"title": "Event Card",
|
|
681
|
+
"html": "<div class=\\"card\\"><h3>{{item.title}}</h3><p>{{item.data?.description}}</p></div>",
|
|
682
|
+
"js": "{ props: { item: { type: Object } } }",
|
|
683
|
+
"css": ".card { padding: 1em; border: 1px solid; border-radius: 0.3em; }"
|
|
287
684
|
}
|
|
685
|
+
\`\`\`
|
|
288
686
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
687
|
+
The component is then available as \`<event-card :item="myItem" />\` in Custom Code sections or other inline components.`,
|
|
688
|
+
inputSchema: {
|
|
689
|
+
type: "object",
|
|
690
|
+
properties: {
|
|
691
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
692
|
+
name: { type: "string", description: "Component tag name in kebab-case (e.g., 'event-card')" },
|
|
693
|
+
title: { type: "string", description: "Display title" },
|
|
694
|
+
html: { type: "string", description: "Vue template HTML" },
|
|
695
|
+
js: { type: "string", description: "Vue Options API object as plain {} (no export default)" },
|
|
696
|
+
css: { type: "string", description: "CSS/SCSS styles" }
|
|
697
|
+
},
|
|
698
|
+
required: ["interfaceId", "name", "html"]
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
export const setInterfaceServiceTool = {
|
|
702
|
+
name: "set_interface_service",
|
|
703
|
+
description: `Add or update a JavaScript service in the interface.
|
|
296
704
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
border: none;
|
|
302
|
-
padding: 0.75rem 1.5rem;
|
|
303
|
-
border-radius: var(--radius);
|
|
304
|
-
cursor: pointer;
|
|
305
|
-
font-size: 1rem;
|
|
306
|
-
transition: all 0.2s;
|
|
307
|
-
}
|
|
705
|
+
Services are reactive Vue instances available as \`this.$sdk.app.services.serviceName\` in all components.
|
|
706
|
+
Use for shared state, API calls, or computed data.
|
|
707
|
+
|
|
708
|
+
**JS format:** Vue Options API as a plain object \`{}\` without \`export default\`.
|
|
308
709
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
710
|
+
**Example:**
|
|
711
|
+
\`\`\`json
|
|
712
|
+
{
|
|
713
|
+
"interfaceId": "abc123",
|
|
714
|
+
"name": "eventService",
|
|
715
|
+
"title": "Event Service",
|
|
716
|
+
"js": "{ data() { return { events: [], loading: false } }, created() { this.reload() }, methods: { async reload() { this.loading = true; const { items } = await $sdk.content.list('event'); this.events = items; this.loading = false; } } }"
|
|
312
717
|
}
|
|
718
|
+
\`\`\``,
|
|
719
|
+
inputSchema: {
|
|
720
|
+
type: "object",
|
|
721
|
+
properties: {
|
|
722
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
723
|
+
name: { type: "string", description: "Service name in camelCase (e.g., 'eventService')" },
|
|
724
|
+
title: { type: "string", description: "Display title" },
|
|
725
|
+
js: { type: "string", description: "Vue Options API object as plain {}" }
|
|
726
|
+
},
|
|
727
|
+
required: ["interfaceId", "name", "js"]
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
export const setInterfaceMenuTool = {
|
|
731
|
+
name: "set_interface_menu",
|
|
732
|
+
description: `Create or update a navigation menu in the interface.
|
|
733
|
+
|
|
734
|
+
Menu items link to routes (by name) or external URLs. Items can be nested for dropdown menus.
|
|
313
735
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
736
|
+
**Example:**
|
|
737
|
+
\`\`\`json
|
|
738
|
+
{
|
|
739
|
+
"interfaceId": "abc123",
|
|
740
|
+
"name": "primaryMenu",
|
|
741
|
+
"title": "Primary Menu",
|
|
742
|
+
"items": [
|
|
743
|
+
{ "title": "Home", "type": "route", "route": "home" },
|
|
744
|
+
{ "title": "About", "type": "route", "route": "about" },
|
|
745
|
+
{ "title": "Docs", "type": "url", "url": "https://docs.example.com", "target": "_blank" }
|
|
746
|
+
]
|
|
323
747
|
}
|
|
748
|
+
\`\`\``,
|
|
749
|
+
inputSchema: {
|
|
750
|
+
type: "object",
|
|
751
|
+
properties: {
|
|
752
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
753
|
+
name: { type: "string", description: "Menu key/name (e.g., 'primaryMenu', 'footerMenu')" },
|
|
754
|
+
title: { type: "string", description: "Menu title" },
|
|
755
|
+
items: {
|
|
756
|
+
type: "array",
|
|
757
|
+
description: "Menu items",
|
|
758
|
+
items: {
|
|
759
|
+
type: "object",
|
|
760
|
+
properties: {
|
|
761
|
+
title: { type: "string", description: "Display text" },
|
|
762
|
+
type: { type: "string", enum: ["route", "url"], description: "Link type. Default: 'route'" },
|
|
763
|
+
route: { type: "string", description: "Route name (when type='route')" },
|
|
764
|
+
url: { type: "string", description: "URL (when type='url')" },
|
|
765
|
+
target: { type: "string", description: "Window target (e.g., '_blank')" },
|
|
766
|
+
items: { type: "array", description: "Submenu items (same structure)" }
|
|
767
|
+
},
|
|
768
|
+
required: ["title"]
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
},
|
|
772
|
+
required: ["interfaceId", "name", "title", "items"]
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
export const setInterfaceStylesTool = {
|
|
776
|
+
name: "set_interface_styles",
|
|
777
|
+
description: `Update the interface's global SCSS styles and themes.
|
|
778
|
+
|
|
779
|
+
Only the provided fields are updated; others remain unchanged.
|
|
324
780
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
781
|
+
- **pre**: SCSS injected before component styles. Use for CSS custom properties and variables.
|
|
782
|
+
- **post**: SCSS injected after component styles. Use for global rules, fonts, and overrides.
|
|
783
|
+
- **themes**: Named CSS blocks that can be applied to sections via their themes array.
|
|
784
|
+
|
|
785
|
+
**Example:**
|
|
786
|
+
\`\`\`json
|
|
787
|
+
{
|
|
788
|
+
"interfaceId": "abc123",
|
|
789
|
+
"pre": ":root { --primary: #4F46E5; --text: #333; --headings: #111; --wrap-width: 1200px; }",
|
|
790
|
+
"post": "body { font-family: 'Inter', sans-serif; color: var(--text); } .h-wrap { max-width: var(--wrap-width); margin: auto; padding: 0 20px; } .v-wrap { padding: 2em 0; }"
|
|
328
791
|
}
|
|
792
|
+
\`\`\``,
|
|
793
|
+
inputSchema: {
|
|
794
|
+
type: "object",
|
|
795
|
+
properties: {
|
|
796
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
797
|
+
pre: { type: "string", description: "SCSS before component styles (variables, mixins)" },
|
|
798
|
+
post: { type: "string", description: "SCSS after component styles (global rules, fonts)" },
|
|
799
|
+
themes: {
|
|
800
|
+
type: "array",
|
|
801
|
+
description: "Theme definitions",
|
|
802
|
+
items: {
|
|
803
|
+
type: "object",
|
|
804
|
+
properties: {
|
|
805
|
+
title: { type: "string" },
|
|
806
|
+
uuid: { type: "string", description: "Unique theme ID (10-char alphanumeric)" },
|
|
807
|
+
body: { type: "string", description: "CSS rules for this theme" }
|
|
808
|
+
},
|
|
809
|
+
required: ["title", "uuid", "body"]
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
required: ["interfaceId"]
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
export const setInterfaceLayoutTool = {
|
|
817
|
+
name: "set_interface_layout",
|
|
818
|
+
description: `Set the interface's layout template.
|
|
819
|
+
|
|
820
|
+
The default layout is \`<header-slot/><route-slot/><footer-slot/>\` (empty string = default).
|
|
821
|
+
Set to a custom component tag to use a custom layout (e.g., \`<custom-layout />\`).
|
|
822
|
+
The custom component must be defined via \`set_interface_custom_component\` first.
|
|
329
823
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
824
|
+
**Example:**
|
|
825
|
+
\`\`\`json
|
|
826
|
+
{ "interfaceId": "abc123", "layout": "<custom-layout />" }
|
|
827
|
+
\`\`\``,
|
|
828
|
+
inputSchema: {
|
|
829
|
+
type: "object",
|
|
830
|
+
properties: {
|
|
831
|
+
interfaceId: { type: "string", description: "The interface content ID" },
|
|
832
|
+
layout: { type: "string", description: "Layout template. Empty string for default." }
|
|
833
|
+
},
|
|
834
|
+
required: ["interfaceId", "layout"]
|
|
334
835
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
836
|
+
};
|
|
837
|
+
// ============================================================================
|
|
838
|
+
// HANDLERS
|
|
839
|
+
// ============================================================================
|
|
840
|
+
export async function handleCreateInterface(args) {
|
|
841
|
+
try {
|
|
842
|
+
const configManager = new ConfigManager();
|
|
843
|
+
const config = await configManager.loadConfig();
|
|
844
|
+
if (!config) {
|
|
845
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
846
|
+
}
|
|
847
|
+
if (!args.title) {
|
|
848
|
+
return createErrorResponse('title is required');
|
|
849
|
+
}
|
|
850
|
+
// Get user session and check permissions
|
|
851
|
+
const userSession = await getUserSessionData();
|
|
852
|
+
const createPermission = 'interface.create';
|
|
853
|
+
const availableScopes = getAvailableScopes(userSession, createPermission);
|
|
854
|
+
if (availableScopes.length === 0) {
|
|
855
|
+
return createErrorResponse(`You don't have permission to create interfaces in any scope.`);
|
|
856
|
+
}
|
|
857
|
+
const selectedScopeId = args.scope;
|
|
858
|
+
if (!selectedScopeId && availableScopes.length > 1) {
|
|
859
|
+
return createScopeSelectionPrompt('interface', availableScopes, userSession);
|
|
860
|
+
}
|
|
861
|
+
const targetScopeId = selectedScopeId || availableScopes[0];
|
|
862
|
+
if (!availableScopes.includes(targetScopeId)) {
|
|
863
|
+
return createErrorResponse(`Invalid scope "${targetScopeId}".`);
|
|
864
|
+
}
|
|
865
|
+
const payload = {
|
|
866
|
+
title: args.title,
|
|
867
|
+
meta: {
|
|
868
|
+
scopes: [targetScopeId],
|
|
869
|
+
status: 'active'
|
|
870
|
+
},
|
|
871
|
+
routes: [
|
|
872
|
+
{
|
|
873
|
+
title: 'Home',
|
|
874
|
+
path: '/',
|
|
875
|
+
name: 'home',
|
|
876
|
+
type: 'route',
|
|
877
|
+
sections: [],
|
|
878
|
+
routes: []
|
|
879
|
+
}
|
|
880
|
+
],
|
|
881
|
+
header: { sections: [] },
|
|
882
|
+
footer: { sections: [] },
|
|
883
|
+
menus: [],
|
|
884
|
+
components: [],
|
|
885
|
+
services: [],
|
|
886
|
+
styles: { pre: '', post: '', themes: [], includes: [] },
|
|
887
|
+
};
|
|
888
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/interface/create`, {
|
|
889
|
+
method: 'POST',
|
|
890
|
+
headers: {
|
|
891
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
892
|
+
'Content-Type': 'application/json',
|
|
893
|
+
},
|
|
894
|
+
body: JSON.stringify(payload)
|
|
895
|
+
});
|
|
896
|
+
if (!response.ok) {
|
|
897
|
+
const errorText = await response.text();
|
|
898
|
+
throw new Error(`HTTP ${response.status} - ${errorText}`);
|
|
899
|
+
}
|
|
900
|
+
const result = await response.json();
|
|
901
|
+
const id = result._id || result.id;
|
|
902
|
+
return {
|
|
903
|
+
content: [{
|
|
904
|
+
type: "text",
|
|
905
|
+
text: `# Interface Created\n\n**Title:** ${args.title}\n**ID:** \`${id}\`\n\nA default "Home" route (path: /) has been created. Use \`get_interface\` to see the structure, then start adding routes and sections.`
|
|
906
|
+
}]
|
|
907
|
+
};
|
|
338
908
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
grid-template-columns: 1fr;
|
|
342
|
-
gap: 1rem;
|
|
909
|
+
catch (error) {
|
|
910
|
+
return createErrorResponse(`Failed to create interface: ${error.message}`);
|
|
343
911
|
}
|
|
344
912
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
913
|
+
export async function handleGetInterface(args) {
|
|
914
|
+
try {
|
|
915
|
+
const { data } = await getConfigAndFetch(args.interfaceId);
|
|
916
|
+
// If requesting a specific section
|
|
917
|
+
if (args.sectionUuid) {
|
|
918
|
+
const section = findSectionByUUID(data, args.sectionUuid);
|
|
919
|
+
if (!section) {
|
|
920
|
+
return createErrorResponse(`Section with UUID '${args.sectionUuid}' not found.`);
|
|
921
|
+
}
|
|
922
|
+
return {
|
|
923
|
+
content: [{
|
|
924
|
+
type: "text",
|
|
925
|
+
text: formatSectionDetail(section)
|
|
926
|
+
}]
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
// If requesting a specific route's details
|
|
930
|
+
if (args.routeName) {
|
|
931
|
+
const route = findRouteByName(data.routes || [], args.routeName);
|
|
932
|
+
if (!route) {
|
|
933
|
+
const available = getAllRouteNames(data.routes || []);
|
|
934
|
+
return createErrorResponse(`Route '${args.routeName}' not found. Available: ${available.join(', ')}`);
|
|
935
|
+
}
|
|
936
|
+
const lines = [];
|
|
937
|
+
lines.push(`# Route: ${route.title}`);
|
|
938
|
+
lines.push(`**Name:** \`${route.name}\` | **Path:** \`${route.path}\` | **Type:** ${route.type || 'route'}`);
|
|
939
|
+
lines.push(`**Header disabled:** ${route.headerDisabled || false} | **Footer disabled:** ${route.footerDisabled || false}`);
|
|
940
|
+
lines.push('');
|
|
941
|
+
lines.push(`## Sections (${(route.sections || []).length})`);
|
|
942
|
+
for (const section of route.sections || []) {
|
|
943
|
+
lines.push(formatSectionDetail(section));
|
|
944
|
+
lines.push('---');
|
|
945
|
+
}
|
|
946
|
+
return {
|
|
947
|
+
content: [{
|
|
948
|
+
type: "text",
|
|
949
|
+
text: lines.join('\n')
|
|
950
|
+
}]
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
// Default: summary view
|
|
954
|
+
return {
|
|
955
|
+
content: [{
|
|
956
|
+
type: "text",
|
|
957
|
+
text: summarizeInterface(data)
|
|
958
|
+
}]
|
|
959
|
+
};
|
|
350
960
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
min-height: 44px;
|
|
354
|
-
min-width: 44px;
|
|
961
|
+
catch (error) {
|
|
962
|
+
return createErrorResponse(`Failed to get interface: ${error.message}`);
|
|
355
963
|
}
|
|
356
|
-
}`,
|
|
357
|
-
includes: [],
|
|
358
|
-
themes: []
|
|
359
|
-
};
|
|
360
964
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
965
|
+
export async function handlePublishInterface(args) {
|
|
966
|
+
try {
|
|
967
|
+
const configManager = new ConfigManager();
|
|
968
|
+
const config = await configManager.loadConfig();
|
|
969
|
+
if (!config)
|
|
970
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
971
|
+
if (!args.interfaceId)
|
|
972
|
+
return createErrorResponse('interfaceId is required');
|
|
973
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/interface/${args.interfaceId}/publish`, {
|
|
974
|
+
method: 'POST',
|
|
975
|
+
headers: {
|
|
976
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
977
|
+
'Content-Type': 'application/json',
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
if (!response.ok) {
|
|
981
|
+
const errorText = await response.text();
|
|
982
|
+
throw new Error(`HTTP ${response.status} - ${errorText}`);
|
|
983
|
+
}
|
|
984
|
+
const result = await response.json();
|
|
985
|
+
return {
|
|
986
|
+
content: [{
|
|
987
|
+
type: "text",
|
|
988
|
+
text: `# Interface Published\n\n**Interface ID:** \`${args.interfaceId}\`\n**Snapshot ID:** \`${result._id || 'created'}\`\n\nThe interface snapshot has been published.`
|
|
989
|
+
}]
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
catch (error) {
|
|
993
|
+
return createErrorResponse(`Failed to publish interface: ${error.message}`);
|
|
370
994
|
}
|
|
371
|
-
return {
|
|
372
|
-
sections: [{
|
|
373
|
-
title: 'Header Navigation',
|
|
374
|
-
uuid: generateUUID(),
|
|
375
|
-
componentID: menuComponent._id,
|
|
376
|
-
componentVersion: 'latest',
|
|
377
|
-
model: {
|
|
378
|
-
menu: 'primaryMenu',
|
|
379
|
-
style: 'horizontal'
|
|
380
|
-
},
|
|
381
|
-
slots: []
|
|
382
|
-
}]
|
|
383
|
-
};
|
|
384
995
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
996
|
+
export async function handleAddInterfaceRoute(args) {
|
|
997
|
+
try {
|
|
998
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
999
|
+
if (!args.title)
|
|
1000
|
+
return createErrorResponse('title is required');
|
|
1001
|
+
if (!args.name)
|
|
1002
|
+
return createErrorResponse('name is required');
|
|
1003
|
+
if (args.path === undefined || args.path === null)
|
|
1004
|
+
return createErrorResponse('path is required');
|
|
1005
|
+
if (!data.routes)
|
|
1006
|
+
data.routes = [];
|
|
1007
|
+
// Validate unique name
|
|
1008
|
+
const existingNames = getAllRouteNames(data.routes);
|
|
1009
|
+
if (existingNames.includes(args.name)) {
|
|
1010
|
+
return createErrorResponse(`Route name '${args.name}' already exists. Available names: ${existingNames.join(', ')}`);
|
|
1011
|
+
}
|
|
1012
|
+
const newRoute = {
|
|
1013
|
+
title: args.title,
|
|
1014
|
+
path: args.path,
|
|
1015
|
+
name: args.name,
|
|
1016
|
+
type: args.type || 'route',
|
|
1017
|
+
sections: [],
|
|
1018
|
+
routes: [],
|
|
1019
|
+
};
|
|
1020
|
+
if (args.headerDisabled !== undefined)
|
|
1021
|
+
newRoute.headerDisabled = args.headerDisabled;
|
|
1022
|
+
if (args.footerDisabled !== undefined)
|
|
1023
|
+
newRoute.footerDisabled = args.footerDisabled;
|
|
1024
|
+
// Add to parent or top level
|
|
1025
|
+
if (args.parentRouteName) {
|
|
1026
|
+
const parent = findRouteByName(data.routes, args.parentRouteName);
|
|
1027
|
+
if (!parent) {
|
|
1028
|
+
return createErrorResponse(`Parent route '${args.parentRouteName}' not found. Available: ${existingNames.join(', ')}`);
|
|
1029
|
+
}
|
|
1030
|
+
if (!parent.routes)
|
|
1031
|
+
parent.routes = [];
|
|
1032
|
+
parent.routes.push(newRoute);
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
data.routes.push(newRoute);
|
|
1036
|
+
}
|
|
1037
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1038
|
+
return {
|
|
1039
|
+
content: [{
|
|
1040
|
+
type: "text",
|
|
1041
|
+
text: `# Route Added\n\n**Title:** ${args.title}\n**Name:** \`${args.name}\`\n**Path:** \`${args.path}\`\n**Type:** ${newRoute.type}${args.parentRouteName ? `\n**Parent:** ${args.parentRouteName}` : ''}`
|
|
1042
|
+
}]
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
catch (error) {
|
|
1046
|
+
return createErrorResponse(`Failed to add route: ${error.message}`);
|
|
399
1047
|
}
|
|
400
|
-
return {
|
|
401
|
-
sections: [{
|
|
402
|
-
title: 'Footer Navigation',
|
|
403
|
-
uuid: generateUUID(),
|
|
404
|
-
componentID: menuComponent._id,
|
|
405
|
-
componentVersion: 'latest',
|
|
406
|
-
model: {
|
|
407
|
-
menu: 'footerMenu',
|
|
408
|
-
style: 'horizontal'
|
|
409
|
-
},
|
|
410
|
-
slots: []
|
|
411
|
-
}]
|
|
412
|
-
};
|
|
413
1048
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
1049
|
+
export async function handleUpdateInterfaceRoute(args) {
|
|
1050
|
+
try {
|
|
1051
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1052
|
+
if (!args.routeName)
|
|
1053
|
+
return createErrorResponse('routeName is required');
|
|
1054
|
+
const route = findRouteByName(data.routes || [], args.routeName);
|
|
1055
|
+
if (!route) {
|
|
1056
|
+
const available = getAllRouteNames(data.routes || []);
|
|
1057
|
+
return createErrorResponse(`Route '${args.routeName}' not found. Available: ${available.join(', ')}`);
|
|
1058
|
+
}
|
|
1059
|
+
if (args.title !== undefined)
|
|
1060
|
+
route.title = args.title;
|
|
1061
|
+
if (args.path !== undefined)
|
|
1062
|
+
route.path = args.path;
|
|
1063
|
+
if (args.headerDisabled !== undefined)
|
|
1064
|
+
route.headerDisabled = args.headerDisabled;
|
|
1065
|
+
if (args.footerDisabled !== undefined)
|
|
1066
|
+
route.footerDisabled = args.footerDisabled;
|
|
1067
|
+
if (args.seo) {
|
|
1068
|
+
if (!route.seo)
|
|
1069
|
+
route.seo = {};
|
|
1070
|
+
Object.assign(route.seo, args.seo);
|
|
1071
|
+
}
|
|
1072
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1073
|
+
return {
|
|
1074
|
+
content: [{
|
|
1075
|
+
type: "text",
|
|
1076
|
+
text: `# Route Updated\n\n**Name:** \`${args.routeName}\`\n**Title:** ${route.title}\n**Path:** \`${route.path}\``
|
|
1077
|
+
}]
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
catch (error) {
|
|
1081
|
+
return createErrorResponse(`Failed to update route: ${error.message}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
export async function handleRemoveInterfaceRoute(args) {
|
|
1085
|
+
try {
|
|
1086
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1087
|
+
if (!args.routeName)
|
|
1088
|
+
return createErrorResponse('routeName is required');
|
|
1089
|
+
if (!removeRouteByName(data.routes || [], args.routeName)) {
|
|
1090
|
+
const available = getAllRouteNames(data.routes || []);
|
|
1091
|
+
return createErrorResponse(`Route '${args.routeName}' not found. Available: ${available.join(', ')}`);
|
|
1092
|
+
}
|
|
1093
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1094
|
+
return {
|
|
1095
|
+
content: [{
|
|
1096
|
+
type: "text",
|
|
1097
|
+
text: `# Route Removed\n\nRoute \`${args.routeName}\` has been removed.`
|
|
1098
|
+
}]
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
catch (error) {
|
|
1102
|
+
return createErrorResponse(`Failed to remove route: ${error.message}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
export async function handleAddInterfaceSection(args) {
|
|
1106
|
+
try {
|
|
1107
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1108
|
+
if (!args.target?.area)
|
|
1109
|
+
return createErrorResponse('target.area is required (header, footer, route, or slot)');
|
|
1110
|
+
if (!args.componentId)
|
|
1111
|
+
return createErrorResponse('componentId is required');
|
|
1112
|
+
if (args.target.area === 'route' && !args.target.routeName) {
|
|
1113
|
+
return createErrorResponse('target.routeName is required when area is "route"');
|
|
1114
|
+
}
|
|
1115
|
+
if (args.target.area === 'slot') {
|
|
1116
|
+
if (!args.target.sectionUuid)
|
|
1117
|
+
return createErrorResponse('target.sectionUuid is required when area is "slot"');
|
|
1118
|
+
if (!args.target.slotKey)
|
|
1119
|
+
return createErrorResponse('target.slotKey is required when area is "slot"');
|
|
1120
|
+
}
|
|
1121
|
+
const uuid = generateUUID();
|
|
1122
|
+
const model = args.model ? parseNestedJsonStrings(args.model) : {};
|
|
1123
|
+
const section = {
|
|
1124
|
+
title: args.title || 'Section',
|
|
1125
|
+
uuid,
|
|
1126
|
+
componentID: args.componentId,
|
|
1127
|
+
componentVersion: args.componentVersion || 'latest',
|
|
1128
|
+
model,
|
|
1129
|
+
slots: [],
|
|
1130
|
+
themes: [],
|
|
1131
|
+
collapsed: false,
|
|
1132
|
+
};
|
|
1133
|
+
const targetArray = resolveTargetArray(data, args.target);
|
|
1134
|
+
if (!targetArray) {
|
|
1135
|
+
if (args.target.area === 'route') {
|
|
1136
|
+
const available = getAllRouteNames(data.routes || []);
|
|
1137
|
+
return createErrorResponse(`Route '${args.target.routeName}' not found. Available: ${available.join(', ')}`);
|
|
1138
|
+
}
|
|
1139
|
+
if (args.target.area === 'slot') {
|
|
1140
|
+
return createErrorResponse(`Section '${args.target.sectionUuid}' not found. Use get_interface to see current UUIDs.`);
|
|
1141
|
+
}
|
|
1142
|
+
return createErrorResponse(`Invalid target: ${JSON.stringify(args.target)}`);
|
|
1143
|
+
}
|
|
1144
|
+
targetArray.push(section);
|
|
1145
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1146
|
+
const locationStr = args.target.area === 'route' ? `route "${args.target.routeName}"` :
|
|
1147
|
+
args.target.area === 'slot' ? `slot "${args.target.slotKey}" of section "${args.target.sectionUuid}"` :
|
|
1148
|
+
args.target.area;
|
|
1149
|
+
return {
|
|
1150
|
+
content: [{
|
|
1151
|
+
type: "text",
|
|
1152
|
+
text: `# Section Added\n\n**Title:** ${section.title}\n**UUID:** \`${uuid}\`\n**Component:** \`${args.componentId}\`\n**Location:** ${locationStr}\n\nUse this UUID to update the section or add nested sections to its slots.`
|
|
1153
|
+
}]
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
return createErrorResponse(`Failed to add section: ${error.message}`);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
export async function handleUpdateInterfaceSection(args) {
|
|
1161
|
+
try {
|
|
1162
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1163
|
+
if (!args.sectionUuid)
|
|
1164
|
+
return createErrorResponse('sectionUuid is required');
|
|
1165
|
+
const section = findSectionByUUID(data, args.sectionUuid);
|
|
1166
|
+
if (!section) {
|
|
1167
|
+
return createErrorResponse(`Section '${args.sectionUuid}' not found. Use get_interface to see current UUIDs.`);
|
|
1168
|
+
}
|
|
1169
|
+
if (args.title !== undefined)
|
|
1170
|
+
section.title = args.title;
|
|
1171
|
+
if (args.disabled !== undefined)
|
|
1172
|
+
section.disabled = args.disabled;
|
|
1173
|
+
if (args.themes !== undefined)
|
|
1174
|
+
section.themes = args.themes;
|
|
1175
|
+
if (args.model) {
|
|
1176
|
+
const parsedModel = parseNestedJsonStrings(args.model);
|
|
1177
|
+
section.model = Object.assign(section.model || {}, parsedModel);
|
|
1178
|
+
}
|
|
1179
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1180
|
+
return {
|
|
1181
|
+
content: [{
|
|
1182
|
+
type: "text",
|
|
1183
|
+
text: `# Section Updated\n\n**UUID:** \`${args.sectionUuid}\`\n**Title:** ${section.title}`
|
|
1184
|
+
}]
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
catch (error) {
|
|
1188
|
+
return createErrorResponse(`Failed to update section: ${error.message}`);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
export async function handleRemoveInterfaceSection(args) {
|
|
1192
|
+
try {
|
|
1193
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1194
|
+
if (!args.sectionUuid)
|
|
1195
|
+
return createErrorResponse('sectionUuid is required');
|
|
1196
|
+
if (!removeSectionByUUID(data, args.sectionUuid)) {
|
|
1197
|
+
return createErrorResponse(`Section '${args.sectionUuid}' not found. Use get_interface to see current UUIDs.`);
|
|
1198
|
+
}
|
|
1199
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1200
|
+
return {
|
|
1201
|
+
content: [{
|
|
1202
|
+
type: "text",
|
|
1203
|
+
text: `# Section Removed\n\nSection \`${args.sectionUuid}\` has been removed.`
|
|
1204
|
+
}]
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
catch (error) {
|
|
1208
|
+
return createErrorResponse(`Failed to remove section: ${error.message}`);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
export async function handleSetInterfaceCustomComponent(args) {
|
|
1212
|
+
try {
|
|
1213
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1214
|
+
if (!args.name)
|
|
1215
|
+
return createErrorResponse('name is required (kebab-case tag name)');
|
|
1216
|
+
if (!args.html)
|
|
1217
|
+
return createErrorResponse('html is required');
|
|
1218
|
+
if (!data.components)
|
|
1219
|
+
data.components = [];
|
|
1220
|
+
const existing = data.components.find((c) => c.name === args.name);
|
|
1221
|
+
const isUpdate = !!existing;
|
|
1222
|
+
if (existing) {
|
|
1223
|
+
existing.html = args.html;
|
|
1224
|
+
if (args.js !== undefined)
|
|
1225
|
+
existing.js = args.js;
|
|
1226
|
+
if (args.css !== undefined)
|
|
1227
|
+
existing.css = args.css;
|
|
1228
|
+
if (args.title !== undefined)
|
|
1229
|
+
existing.title = args.title;
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
data.components.push({
|
|
1233
|
+
title: args.title || args.name,
|
|
1234
|
+
name: args.name,
|
|
1235
|
+
html: args.html,
|
|
1236
|
+
js: args.js || '{}',
|
|
1237
|
+
css: args.css || '',
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1241
|
+
return {
|
|
1242
|
+
content: [{
|
|
1243
|
+
type: "text",
|
|
1244
|
+
text: `# Custom Component ${isUpdate ? 'Updated' : 'Created'}\n\n**Tag:** \`<${args.name} />\`\n**Title:** ${args.title || args.name}\n\nUse \`<${args.name} />\` in Custom Code sections or other inline components.`
|
|
1245
|
+
}]
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
catch (error) {
|
|
1249
|
+
return createErrorResponse(`Failed to set custom component: ${error.message}`);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
export async function handleSetInterfaceService(args) {
|
|
1253
|
+
try {
|
|
1254
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1255
|
+
if (!args.name)
|
|
1256
|
+
return createErrorResponse('name is required');
|
|
1257
|
+
if (!args.js)
|
|
1258
|
+
return createErrorResponse('js is required');
|
|
1259
|
+
if (!data.services)
|
|
1260
|
+
data.services = [];
|
|
1261
|
+
const existing = data.services.find((s) => s.name === args.name);
|
|
1262
|
+
const isUpdate = !!existing;
|
|
1263
|
+
if (existing) {
|
|
1264
|
+
existing.js = args.js;
|
|
1265
|
+
if (args.title !== undefined)
|
|
1266
|
+
existing.title = args.title;
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
data.services.push({
|
|
1270
|
+
title: args.title || args.name,
|
|
1271
|
+
name: args.name,
|
|
1272
|
+
js: args.js,
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1276
|
+
return {
|
|
1277
|
+
content: [{
|
|
1278
|
+
type: "text",
|
|
1279
|
+
text: `# Service ${isUpdate ? 'Updated' : 'Created'}\n\n**Name:** \`${args.name}\`\n**Title:** ${args.title || args.name}\n\nAccess via \`this.$sdk.app.services.${args.name}\` in components.`
|
|
1280
|
+
}]
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
catch (error) {
|
|
1284
|
+
return createErrorResponse(`Failed to set service: ${error.message}`);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
export async function handleSetInterfaceMenu(args) {
|
|
1288
|
+
try {
|
|
1289
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1290
|
+
if (!args.name)
|
|
1291
|
+
return createErrorResponse('name is required');
|
|
1292
|
+
if (!args.title)
|
|
1293
|
+
return createErrorResponse('title is required');
|
|
1294
|
+
if (!args.items)
|
|
1295
|
+
return createErrorResponse('items is required');
|
|
1296
|
+
if (!data.menus)
|
|
1297
|
+
data.menus = [];
|
|
1298
|
+
const items = parseNestedJsonStrings(args.items);
|
|
1299
|
+
// Normalize menu items
|
|
1300
|
+
const normalizeItems = (menuItems) => {
|
|
1301
|
+
return (menuItems || []).map((item) => ({
|
|
1302
|
+
title: item.title || '',
|
|
1303
|
+
type: item.type || 'route',
|
|
1304
|
+
route: item.route || '',
|
|
1305
|
+
url: item.url || '',
|
|
1306
|
+
target: item.target || '',
|
|
1307
|
+
items: item.items ? normalizeItems(item.items) : [],
|
|
1308
|
+
}));
|
|
1309
|
+
};
|
|
1310
|
+
const normalizedItems = normalizeItems(items);
|
|
1311
|
+
const existing = data.menus.find((m) => m.name === args.name);
|
|
1312
|
+
const isUpdate = !!existing;
|
|
1313
|
+
if (existing) {
|
|
1314
|
+
existing.title = args.title;
|
|
1315
|
+
existing.items = normalizedItems;
|
|
1316
|
+
}
|
|
1317
|
+
else {
|
|
1318
|
+
data.menus.push({
|
|
1319
|
+
title: args.title,
|
|
1320
|
+
name: args.name,
|
|
1321
|
+
items: normalizedItems,
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1325
|
+
return {
|
|
1326
|
+
content: [{
|
|
1327
|
+
type: "text",
|
|
1328
|
+
text: `# Menu ${isUpdate ? 'Updated' : 'Created'}\n\n**Name:** \`${args.name}\`\n**Title:** ${args.title}\n**Items:** ${normalizedItems.length}`
|
|
1329
|
+
}]
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
catch (error) {
|
|
1333
|
+
return createErrorResponse(`Failed to set menu: ${error.message}`);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
export async function handleSetInterfaceStyles(args) {
|
|
1337
|
+
try {
|
|
1338
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1339
|
+
if (!data.styles)
|
|
1340
|
+
data.styles = { pre: '', post: '', themes: [], includes: [] };
|
|
1341
|
+
if (args.pre !== undefined)
|
|
1342
|
+
data.styles.pre = args.pre;
|
|
1343
|
+
if (args.post !== undefined)
|
|
1344
|
+
data.styles.post = args.post;
|
|
1345
|
+
if (args.themes !== undefined)
|
|
1346
|
+
data.styles.themes = args.themes;
|
|
1347
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1348
|
+
const updated = [];
|
|
1349
|
+
if (args.pre !== undefined)
|
|
1350
|
+
updated.push('pre-SCSS');
|
|
1351
|
+
if (args.post !== undefined)
|
|
1352
|
+
updated.push('post-SCSS');
|
|
1353
|
+
if (args.themes !== undefined)
|
|
1354
|
+
updated.push(`themes (${args.themes.length})`);
|
|
1355
|
+
return {
|
|
1356
|
+
content: [{
|
|
1357
|
+
type: "text",
|
|
1358
|
+
text: `# Styles Updated\n\nUpdated: ${updated.join(', ')}`
|
|
1359
|
+
}]
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
catch (error) {
|
|
1363
|
+
return createErrorResponse(`Failed to set styles: ${error.message}`);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
export async function handleSetInterfaceLayout(args) {
|
|
1367
|
+
try {
|
|
1368
|
+
const { config, data } = await getConfigAndFetch(args.interfaceId);
|
|
1369
|
+
if (args.layout === undefined)
|
|
1370
|
+
return createErrorResponse('layout is required');
|
|
1371
|
+
data.layout = args.layout;
|
|
1372
|
+
await saveInterface(config, args.interfaceId, data);
|
|
1373
|
+
return {
|
|
1374
|
+
content: [{
|
|
1375
|
+
type: "text",
|
|
1376
|
+
text: `# Layout Updated\n\n**Layout:** ${args.layout || '(default: header-slot/route-slot/footer-slot)'}`
|
|
1377
|
+
}]
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
catch (error) {
|
|
1381
|
+
return createErrorResponse(`Failed to set layout: ${error.message}`);
|
|
422
1382
|
}
|
|
423
|
-
return result;
|
|
424
1383
|
}
|
|
425
1384
|
//# sourceMappingURL=interface-builder.js.map
|