gufi-cli 0.1.25 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp.js +1033 -133
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -172,10 +172,37 @@ const TOOLS = [
|
|
|
172
172
|
type: "object",
|
|
173
173
|
properties: {
|
|
174
174
|
company_id: { type: "string", description: "Company ID (optional if user has only one company)" },
|
|
175
|
-
|
|
175
|
+
format: { type: "string", description: "Output format: 'flat' (text, default) or 'json' (structured)" },
|
|
176
176
|
},
|
|
177
177
|
},
|
|
178
178
|
},
|
|
179
|
+
{
|
|
180
|
+
name: "gufi_schema_modify",
|
|
181
|
+
description: getDesc("gufi_schema_modify"),
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: {
|
|
185
|
+
company_id: { type: "string", description: "Company ID (optional if user has only one company)" },
|
|
186
|
+
preview: { type: "boolean", description: "If true, show what would be done without executing (dry run)" },
|
|
187
|
+
operations: {
|
|
188
|
+
type: "array",
|
|
189
|
+
description: "Array of operations to execute",
|
|
190
|
+
items: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
op: { type: "string", description: "Operation: add_field, update_field, remove_field, add_entity, update_entity, remove_entity" },
|
|
194
|
+
entity: { type: "string", description: "Entity reference: id, name, or 'module.entity'" },
|
|
195
|
+
module: { type: "string", description: "Module reference (for add_entity)" },
|
|
196
|
+
field: { type: "object", description: "Field definition (for add_field)" },
|
|
197
|
+
field_name: { type: "string", description: "Field name (for update_field, remove_field)" },
|
|
198
|
+
changes: { type: "object", description: "Changes to apply (for update_*)" },
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
required: ["operations"],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
179
206
|
{
|
|
180
207
|
name: "gufi_docs",
|
|
181
208
|
description: getDesc("gufi_docs"),
|
|
@@ -226,33 +253,47 @@ const TOOLS = [
|
|
|
226
253
|
inputSchema: {
|
|
227
254
|
type: "object",
|
|
228
255
|
properties: {
|
|
229
|
-
module_id: { type: "string", description: "Module ID
|
|
256
|
+
module_id: { type: "string", description: "Module ID" },
|
|
257
|
+
company_id: { type: "string", description: "Company ID (required - module IDs are local per company)" },
|
|
230
258
|
},
|
|
231
|
-
required: ["module_id"],
|
|
259
|
+
required: ["module_id", "company_id"],
|
|
232
260
|
},
|
|
233
261
|
},
|
|
234
262
|
{
|
|
235
|
-
name: "
|
|
236
|
-
description: getDesc("
|
|
263
|
+
name: "gufi_entity",
|
|
264
|
+
description: getDesc("gufi_entity"),
|
|
237
265
|
inputSchema: {
|
|
238
266
|
type: "object",
|
|
239
267
|
properties: {
|
|
240
|
-
|
|
241
|
-
|
|
268
|
+
entity_id: { type: "string", description: "Entity ID" },
|
|
269
|
+
company_id: { type: "string", description: "Company ID (required - entity IDs are local per company)" },
|
|
242
270
|
},
|
|
243
|
-
required: ["
|
|
271
|
+
required: ["entity_id", "company_id"],
|
|
244
272
|
},
|
|
245
273
|
},
|
|
246
274
|
{
|
|
247
|
-
name: "
|
|
248
|
-
description: getDesc("
|
|
275
|
+
name: "gufi_field",
|
|
276
|
+
description: getDesc("gufi_field"),
|
|
249
277
|
inputSchema: {
|
|
250
278
|
type: "object",
|
|
251
279
|
properties: {
|
|
252
|
-
|
|
253
|
-
|
|
280
|
+
entity: { type: "string", description: "Entity reference: id, name, or 'module.entity'" },
|
|
281
|
+
field: { type: "string", description: "Field name" },
|
|
282
|
+
company_id: { type: "string", description: "Company ID (required)" },
|
|
254
283
|
},
|
|
255
|
-
required: ["
|
|
284
|
+
required: ["entity", "field", "company_id"],
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: "gufi_relations",
|
|
289
|
+
description: getDesc("gufi_relations"),
|
|
290
|
+
inputSchema: {
|
|
291
|
+
type: "object",
|
|
292
|
+
properties: {
|
|
293
|
+
module_id: { type: "string", description: "Module ID (optional - if omitted, shows all relations in company)" },
|
|
294
|
+
company_id: { type: "string", description: "Company ID (required)" },
|
|
295
|
+
},
|
|
296
|
+
required: ["company_id"],
|
|
256
297
|
},
|
|
257
298
|
},
|
|
258
299
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -276,8 +317,9 @@ const TOOLS = [
|
|
|
276
317
|
type: "object",
|
|
277
318
|
properties: {
|
|
278
319
|
automation_id: { type: "string", description: "Automation script ID" },
|
|
320
|
+
company_id: { type: "string", description: "Company ID (required - automation IDs are local per company)" },
|
|
279
321
|
},
|
|
280
|
-
required: ["automation_id"],
|
|
322
|
+
required: ["automation_id", "company_id"],
|
|
281
323
|
},
|
|
282
324
|
},
|
|
283
325
|
{
|
|
@@ -301,8 +343,9 @@ const TOOLS = [
|
|
|
301
343
|
type: "object",
|
|
302
344
|
properties: {
|
|
303
345
|
entity_id: { type: "string", description: "Entity ID (from module schema)" },
|
|
346
|
+
company_id: { type: "string", description: "Company ID (required - entity IDs are local per company)" },
|
|
304
347
|
},
|
|
305
|
-
required: ["entity_id"],
|
|
348
|
+
required: ["entity_id", "company_id"],
|
|
306
349
|
},
|
|
307
350
|
},
|
|
308
351
|
{
|
|
@@ -312,12 +355,13 @@ const TOOLS = [
|
|
|
312
355
|
type: "object",
|
|
313
356
|
properties: {
|
|
314
357
|
entity_id: { type: "string", description: "Entity ID" },
|
|
358
|
+
company_id: { type: "string", description: "Company ID (required - entity IDs are local per company)" },
|
|
315
359
|
automations: {
|
|
316
360
|
type: "object",
|
|
317
361
|
description: "Automation configuration with on_create, on_update, on_delete, on_click arrays",
|
|
318
362
|
},
|
|
319
363
|
},
|
|
320
|
-
required: ["entity_id", "automations"],
|
|
364
|
+
required: ["entity_id", "company_id", "automations"],
|
|
321
365
|
},
|
|
322
366
|
},
|
|
323
367
|
{
|
|
@@ -336,21 +380,41 @@ const TOOLS = [
|
|
|
336
380
|
// ─────────────────────────────────────────────────────────────────────────
|
|
337
381
|
// Data (CRUD)
|
|
338
382
|
// ─────────────────────────────────────────────────────────────────────────
|
|
383
|
+
{
|
|
384
|
+
name: "gufi_data",
|
|
385
|
+
description: getDesc("gufi_data"),
|
|
386
|
+
inputSchema: {
|
|
387
|
+
type: "object",
|
|
388
|
+
properties: {
|
|
389
|
+
action: { type: "string", description: "Action: list, get, create, update, delete" },
|
|
390
|
+
table: { type: "string", description: "Table name (physical ID like m360_t16192)" },
|
|
391
|
+
company_id: { type: "string", description: "Company ID (required)" },
|
|
392
|
+
id: { type: "number", description: "Row ID (for get, update, delete)" },
|
|
393
|
+
data: { type: "object", description: "Row data (for create, update)" },
|
|
394
|
+
limit: { type: "number", description: "Number of rows for list (default 20)" },
|
|
395
|
+
offset: { type: "number", description: "Offset for pagination (default 0)" },
|
|
396
|
+
sort: { type: "string", description: "Field to sort by (default: id)" },
|
|
397
|
+
order: { type: "string", description: "ASC or DESC (default: DESC)" },
|
|
398
|
+
filter: { type: "string", description: "Filter expression (field=value)" },
|
|
399
|
+
},
|
|
400
|
+
required: ["action", "table", "company_id"],
|
|
401
|
+
},
|
|
402
|
+
},
|
|
339
403
|
{
|
|
340
404
|
name: "gufi_rows",
|
|
341
405
|
description: getDesc("gufi_rows"),
|
|
342
406
|
inputSchema: {
|
|
343
407
|
type: "object",
|
|
344
408
|
properties: {
|
|
345
|
-
table: { type: "string", description: "Table name (physical ID like m360_t16192
|
|
346
|
-
company_id: { type: "string", description: "Company ID (required
|
|
409
|
+
table: { type: "string", description: "Table name (physical ID like m360_t16192)" },
|
|
410
|
+
company_id: { type: "string", description: "Company ID (required - table names may collide between companies)" },
|
|
347
411
|
limit: { type: "number", description: "Number of rows (default 20)" },
|
|
348
412
|
offset: { type: "number", description: "Offset for pagination (default 0)" },
|
|
349
413
|
sort: { type: "string", description: "Field to sort by (default: id)" },
|
|
350
414
|
order: { type: "string", description: "ASC or DESC (default: DESC)" },
|
|
351
415
|
filter: { type: "string", description: "Filter expression (field=value)" },
|
|
352
416
|
},
|
|
353
|
-
required: ["table"],
|
|
417
|
+
required: ["table", "company_id"],
|
|
354
418
|
},
|
|
355
419
|
},
|
|
356
420
|
{
|
|
@@ -360,10 +424,10 @@ const TOOLS = [
|
|
|
360
424
|
type: "object",
|
|
361
425
|
properties: {
|
|
362
426
|
table: { type: "string", description: "Table name (physical ID like m360_t16192)" },
|
|
363
|
-
company_id: { type: "string", description: "Company ID (required
|
|
427
|
+
company_id: { type: "string", description: "Company ID (required - table names may collide between companies)" },
|
|
364
428
|
id: { type: "number", description: "Row ID" },
|
|
365
429
|
},
|
|
366
|
-
required: ["table", "id"],
|
|
430
|
+
required: ["table", "company_id", "id"],
|
|
367
431
|
},
|
|
368
432
|
},
|
|
369
433
|
{
|
|
@@ -373,10 +437,10 @@ const TOOLS = [
|
|
|
373
437
|
type: "object",
|
|
374
438
|
properties: {
|
|
375
439
|
table: { type: "string", description: "Table name (physical ID like m360_t16192)" },
|
|
376
|
-
company_id: { type: "string", description: "Company ID (required
|
|
440
|
+
company_id: { type: "string", description: "Company ID (required - table names may collide between companies)" },
|
|
377
441
|
data: { type: "object", description: "Row data as key-value pairs" },
|
|
378
442
|
},
|
|
379
|
-
required: ["table", "data"],
|
|
443
|
+
required: ["table", "company_id", "data"],
|
|
380
444
|
},
|
|
381
445
|
},
|
|
382
446
|
{
|
|
@@ -386,11 +450,11 @@ const TOOLS = [
|
|
|
386
450
|
type: "object",
|
|
387
451
|
properties: {
|
|
388
452
|
table: { type: "string", description: "Table name (physical ID like m360_t16192)" },
|
|
389
|
-
company_id: { type: "string", description: "Company ID (required
|
|
453
|
+
company_id: { type: "string", description: "Company ID (required - table names may collide between companies)" },
|
|
390
454
|
id: { type: "number", description: "Row ID" },
|
|
391
455
|
data: { type: "object", description: "Fields to update" },
|
|
392
456
|
},
|
|
393
|
-
required: ["table", "id", "data"],
|
|
457
|
+
required: ["table", "company_id", "id", "data"],
|
|
394
458
|
},
|
|
395
459
|
},
|
|
396
460
|
{
|
|
@@ -400,10 +464,10 @@ const TOOLS = [
|
|
|
400
464
|
type: "object",
|
|
401
465
|
properties: {
|
|
402
466
|
table: { type: "string", description: "Table name (physical ID like m360_t16192)" },
|
|
403
|
-
company_id: { type: "string", description: "Company ID (required
|
|
467
|
+
company_id: { type: "string", description: "Company ID (required - table names may collide between companies)" },
|
|
404
468
|
id: { type: "number", description: "Row ID" },
|
|
405
469
|
},
|
|
406
|
-
required: ["table", "id"],
|
|
470
|
+
required: ["table", "company_id", "id"],
|
|
407
471
|
},
|
|
408
472
|
},
|
|
409
473
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -427,8 +491,9 @@ const TOOLS = [
|
|
|
427
491
|
properties: {
|
|
428
492
|
key: { type: "string", description: "Variable name (uppercase recommended)" },
|
|
429
493
|
value: { type: "string", description: "Variable value" },
|
|
494
|
+
company_id: { type: "string", description: "Company ID (required)" },
|
|
430
495
|
},
|
|
431
|
-
required: ["key", "value"],
|
|
496
|
+
required: ["key", "value", "company_id"],
|
|
432
497
|
},
|
|
433
498
|
},
|
|
434
499
|
{
|
|
@@ -438,8 +503,9 @@ const TOOLS = [
|
|
|
438
503
|
type: "object",
|
|
439
504
|
properties: {
|
|
440
505
|
key: { type: "string", description: "Variable name to delete" },
|
|
506
|
+
company_id: { type: "string", description: "Company ID (required)" },
|
|
441
507
|
},
|
|
442
|
-
required: ["key"],
|
|
508
|
+
required: ["key", "company_id"],
|
|
443
509
|
},
|
|
444
510
|
},
|
|
445
511
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -503,6 +569,20 @@ const TOOLS = [
|
|
|
503
569
|
required: ["view_id"],
|
|
504
570
|
},
|
|
505
571
|
},
|
|
572
|
+
{
|
|
573
|
+
name: "gufi_view_create",
|
|
574
|
+
description: getDesc("gufi_view_create"),
|
|
575
|
+
inputSchema: {
|
|
576
|
+
type: "object",
|
|
577
|
+
properties: {
|
|
578
|
+
name: { type: "string", description: "View name (e.g., 'Sales Dashboard')" },
|
|
579
|
+
template: { type: "string", description: "Template: 'dashboard', 'table', 'form', or 'blank'" },
|
|
580
|
+
package_id: { type: "string", description: "Package ID to add the view to (optional)" },
|
|
581
|
+
description: { type: "string", description: "View description (optional)" },
|
|
582
|
+
},
|
|
583
|
+
required: ["name", "template"],
|
|
584
|
+
},
|
|
585
|
+
},
|
|
506
586
|
// ─────────────────────────────────────────────────────────────────────────
|
|
507
587
|
// Packages (Marketplace Distribution)
|
|
508
588
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -691,6 +771,22 @@ const TOOLS = [
|
|
|
691
771
|
required: ["package_id"],
|
|
692
772
|
},
|
|
693
773
|
},
|
|
774
|
+
{
|
|
775
|
+
name: "gufi_link_view",
|
|
776
|
+
description: getDesc("gufi_link_view"),
|
|
777
|
+
inputSchema: {
|
|
778
|
+
type: "object",
|
|
779
|
+
properties: {
|
|
780
|
+
package_id: { type: "string", description: "Package ID" },
|
|
781
|
+
module_id: { type: "string", description: "Package module ID (from gufi_package)" },
|
|
782
|
+
submodule_name: { type: "string", description: "Submodule name to add the view to" },
|
|
783
|
+
view_id: { type: "number", description: "Marketplace view ID to link" },
|
|
784
|
+
label: { type: "string", description: "Display name for the view in the menu" },
|
|
785
|
+
icon: { type: "string", description: "Lucide icon name (e.g., 'BarChart3', 'Package')" },
|
|
786
|
+
},
|
|
787
|
+
required: ["package_id", "module_id", "submodule_name", "view_id", "label"],
|
|
788
|
+
},
|
|
789
|
+
},
|
|
694
790
|
];
|
|
695
791
|
// ════════════════════════════════════════════════════════════════════════════
|
|
696
792
|
// Tool Handlers
|
|
@@ -966,54 +1062,95 @@ const toolHandlers = {
|
|
|
966
1062
|
}
|
|
967
1063
|
},
|
|
968
1064
|
async gufi_schema(params) {
|
|
969
|
-
// Use /api/
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
for (const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1065
|
+
// Use the new /api/schema/readable endpoint for FLAT format (default)
|
|
1066
|
+
// This returns human-readable text optimized for Claude comprehension
|
|
1067
|
+
if (params.format === "json") {
|
|
1068
|
+
// JSON format for programmatic use
|
|
1069
|
+
const data = await apiRequest("/api/company/schema", {
|
|
1070
|
+
headers: params.company_id ? { "X-Company-ID": params.company_id } : {},
|
|
1071
|
+
}, params.company_id);
|
|
1072
|
+
const modulesRaw = data.modules || data.data?.modules || [];
|
|
1073
|
+
const modules = [];
|
|
1074
|
+
for (const mod of modulesRaw) {
|
|
1075
|
+
let moduleId;
|
|
1076
|
+
const entities = [];
|
|
1077
|
+
for (const sub of mod.submodules || []) {
|
|
1078
|
+
for (const ent of sub.entities || []) {
|
|
1079
|
+
if (ent.ui?.moduleId)
|
|
1080
|
+
moduleId = ent.ui.moduleId;
|
|
1081
|
+
const entityId = ent.ui?.entityId || ent.pk_id || ent.id;
|
|
1082
|
+
entities.push({
|
|
1083
|
+
id: entityId,
|
|
1084
|
+
name: ent.name,
|
|
1085
|
+
label: ent.label,
|
|
1086
|
+
table: moduleId && entityId ? `m${moduleId}_t${entityId}` : null,
|
|
1087
|
+
fields: (ent.fields || []).map((f) => ({
|
|
1088
|
+
name: f.name,
|
|
1089
|
+
type: f.type,
|
|
1090
|
+
label: f.label,
|
|
1091
|
+
required: f.required,
|
|
1092
|
+
})),
|
|
1093
|
+
});
|
|
985
1094
|
}
|
|
986
1095
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
for (const ent of sub.entities || []) {
|
|
993
|
-
const entityId = ent.ui?.entityId || ent.pk_id || ent.id;
|
|
994
|
-
entities.push({
|
|
995
|
-
id: entityId,
|
|
996
|
-
name: ent.name,
|
|
997
|
-
label: ent.label,
|
|
998
|
-
table: moduleId && entityId ? `m${moduleId}_t${entityId}` : null,
|
|
999
|
-
fields: (ent.fields || []).map((f) => ({
|
|
1000
|
-
name: f.name,
|
|
1001
|
-
type: f.type,
|
|
1002
|
-
label: f.label,
|
|
1003
|
-
})),
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
if (params.module_id && String(moduleId) !== params.module_id) {
|
|
1008
|
-
continue;
|
|
1096
|
+
modules.push({
|
|
1097
|
+
id: moduleId,
|
|
1098
|
+
name: mod.name || mod.displayName,
|
|
1099
|
+
entities,
|
|
1100
|
+
});
|
|
1009
1101
|
}
|
|
1010
|
-
modules
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1102
|
+
return { format: "json", modules };
|
|
1103
|
+
}
|
|
1104
|
+
// Default: FLAT text format optimized for Claude comprehension
|
|
1105
|
+
const url = `${getApiUrl()}/api/schema/readable`;
|
|
1106
|
+
let token = getToken();
|
|
1107
|
+
if (!token) {
|
|
1108
|
+
token = await autoLogin();
|
|
1109
|
+
if (!token)
|
|
1110
|
+
throw new Error("Not logged in. Run: gufi login");
|
|
1111
|
+
}
|
|
1112
|
+
const headers = {
|
|
1113
|
+
Authorization: `Bearer ${token}`,
|
|
1114
|
+
"X-Client": "mcp",
|
|
1115
|
+
};
|
|
1116
|
+
if (params.company_id) {
|
|
1117
|
+
headers["X-Company-ID"] = params.company_id;
|
|
1015
1118
|
}
|
|
1016
|
-
|
|
1119
|
+
const response = await fetch(url, { headers });
|
|
1120
|
+
if (!response.ok) {
|
|
1121
|
+
const text = await response.text();
|
|
1122
|
+
throw new Error(`API Error ${response.status}: ${text}`);
|
|
1123
|
+
}
|
|
1124
|
+
// Return as plain text for maximum readability
|
|
1125
|
+
const schema = await response.text();
|
|
1126
|
+
return { format: "flat", schema };
|
|
1127
|
+
},
|
|
1128
|
+
async gufi_schema_modify(params) {
|
|
1129
|
+
// Use the new /api/schema/operations endpoint for semantic operations
|
|
1130
|
+
const result = await apiRequest("/api/schema/operations", {
|
|
1131
|
+
method: "POST",
|
|
1132
|
+
body: JSON.stringify({
|
|
1133
|
+
operations: params.operations,
|
|
1134
|
+
preview: params.preview || false,
|
|
1135
|
+
}),
|
|
1136
|
+
}, params.company_id);
|
|
1137
|
+
if (params.preview) {
|
|
1138
|
+
return {
|
|
1139
|
+
preview: true,
|
|
1140
|
+
operations_count: params.operations.length,
|
|
1141
|
+
would_execute: result.preview || result.results?.map((r) => ({
|
|
1142
|
+
op: r.op,
|
|
1143
|
+
description: r.message,
|
|
1144
|
+
sql: r.sql || null,
|
|
1145
|
+
})),
|
|
1146
|
+
hint: "Set preview: false to execute these operations",
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
return {
|
|
1150
|
+
success: result.success,
|
|
1151
|
+
results: result.results,
|
|
1152
|
+
summary: result.results?.map((r) => r.message).join("; "),
|
|
1153
|
+
};
|
|
1017
1154
|
},
|
|
1018
1155
|
async gufi_docs(params) {
|
|
1019
1156
|
// Map topics to file names
|
|
@@ -1144,49 +1281,201 @@ const toolHandlers = {
|
|
|
1144
1281
|
};
|
|
1145
1282
|
},
|
|
1146
1283
|
async gufi_module(params) {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1284
|
+
// 💜 Use relational endpoint /api/schema/modules/:id
|
|
1285
|
+
const data = await apiRequest(`/api/schema/modules/${params.module_id}`, {
|
|
1286
|
+
headers: { "X-Company-ID": params.company_id },
|
|
1287
|
+
}, params.company_id);
|
|
1288
|
+
const { module, submodules, entities } = data;
|
|
1289
|
+
if (!module) {
|
|
1290
|
+
throw new Error(`Module ${params.module_id} not found in company ${params.company_id}`);
|
|
1150
1291
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1292
|
+
// Build submodules with entities (lightweight, no field definitions)
|
|
1293
|
+
const submodulesWithEntities = submodules.map((sub) => {
|
|
1294
|
+
const subEntities = entities.filter((e) => e.submodule_id === sub.id);
|
|
1295
|
+
return {
|
|
1296
|
+
id: sub.id,
|
|
1297
|
+
name: sub.name,
|
|
1298
|
+
label: sub.label,
|
|
1299
|
+
position: sub.position,
|
|
1300
|
+
entities: subEntities.map((e) => ({
|
|
1301
|
+
id: e.id,
|
|
1302
|
+
name: e.name,
|
|
1303
|
+
label: e.label,
|
|
1304
|
+
kind: e.kind,
|
|
1305
|
+
tableName: `m${module.id}_t${e.id}`,
|
|
1306
|
+
fieldCount: parseInt(e.field_count) || 0,
|
|
1307
|
+
// Include view_spec if present
|
|
1308
|
+
...(e.view_spec && { view_spec: e.view_spec }),
|
|
1309
|
+
})),
|
|
1310
|
+
};
|
|
1311
|
+
});
|
|
1312
|
+
// Extract relations from entities for quick reference
|
|
1313
|
+
const relations = [];
|
|
1314
|
+
// Note: Full field details require gufi_entity call
|
|
1160
1315
|
return {
|
|
1161
|
-
module_id:
|
|
1162
|
-
company_id:
|
|
1163
|
-
|
|
1316
|
+
module_id: params.module_id,
|
|
1317
|
+
company_id: params.company_id,
|
|
1318
|
+
module: {
|
|
1319
|
+
id: module.id,
|
|
1164
1320
|
name: module.name,
|
|
1165
|
-
|
|
1321
|
+
display_name: module.display_name,
|
|
1166
1322
|
icon: module.icon,
|
|
1167
|
-
|
|
1323
|
+
position: module.position,
|
|
1168
1324
|
},
|
|
1325
|
+
submodules: submodulesWithEntities,
|
|
1326
|
+
entityCount: entities.length,
|
|
1327
|
+
hint: "Use gufi_entity({ entity_id, company_id }) to see fields and relations for a specific entity",
|
|
1169
1328
|
};
|
|
1170
1329
|
},
|
|
1171
|
-
async
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1330
|
+
async gufi_entity(params) {
|
|
1331
|
+
// 💜 Fetch entity with all fields from relational endpoint
|
|
1332
|
+
const data = await apiRequest(`/api/schema/entities/${params.entity_id}`, {
|
|
1333
|
+
headers: { "X-Company-ID": params.company_id },
|
|
1334
|
+
}, params.company_id);
|
|
1335
|
+
const { entity, fields } = data;
|
|
1336
|
+
if (!entity) {
|
|
1337
|
+
throw new Error(`Entity ${params.entity_id} not found in company ${params.company_id}`);
|
|
1175
1338
|
}
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1339
|
+
const tableName = `m${entity.module_id}_t${entity.id}`;
|
|
1340
|
+
// Extract relations for quick reference
|
|
1341
|
+
const relations = fields
|
|
1342
|
+
.filter((f) => f.type === "relation" && f.settings?.ref)
|
|
1343
|
+
.map((f) => ({
|
|
1344
|
+
field: f.name,
|
|
1345
|
+
ref: f.settings.ref,
|
|
1346
|
+
display: f.settings.display || "id",
|
|
1347
|
+
}));
|
|
1348
|
+
// Format fields for readability
|
|
1349
|
+
const formattedFields = fields.map((f) => {
|
|
1350
|
+
const constraints = [];
|
|
1351
|
+
if (f.required)
|
|
1352
|
+
constraints.push("required");
|
|
1353
|
+
if (f.settings?.unique)
|
|
1354
|
+
constraints.push("unique");
|
|
1355
|
+
if (f.settings?.ref)
|
|
1356
|
+
constraints.push(`→ ${f.settings.ref}`);
|
|
1357
|
+
if (f.settings?.options?.length > 0) {
|
|
1358
|
+
const opts = f.settings.options.slice(0, 5).map((o) => o.value || o);
|
|
1359
|
+
constraints.push(`[${opts.join("|")}${f.settings.options.length > 5 ? "..." : ""}]`);
|
|
1360
|
+
}
|
|
1361
|
+
return {
|
|
1362
|
+
name: f.name,
|
|
1363
|
+
type: f.type,
|
|
1364
|
+
label: f.label,
|
|
1365
|
+
...(constraints.length > 0 && { constraints: constraints.join(", ") }),
|
|
1366
|
+
...(f.settings && Object.keys(f.settings).length > 0 && { settings: f.settings }),
|
|
1367
|
+
};
|
|
1180
1368
|
});
|
|
1181
|
-
return {
|
|
1369
|
+
return {
|
|
1370
|
+
entity_id: params.entity_id,
|
|
1371
|
+
company_id: params.company_id,
|
|
1372
|
+
entity: {
|
|
1373
|
+
id: entity.id,
|
|
1374
|
+
name: entity.name,
|
|
1375
|
+
label: entity.label,
|
|
1376
|
+
kind: entity.kind,
|
|
1377
|
+
tableName,
|
|
1378
|
+
moduleName: entity.module_name,
|
|
1379
|
+
submoduleName: entity.submodule_name,
|
|
1380
|
+
...(entity.view_spec && { view_spec: entity.view_spec }),
|
|
1381
|
+
},
|
|
1382
|
+
fields: formattedFields,
|
|
1383
|
+
relations,
|
|
1384
|
+
fieldCount: fields.length,
|
|
1385
|
+
hint: "Use gufi_schema_modify to add/update/remove fields",
|
|
1386
|
+
};
|
|
1182
1387
|
},
|
|
1183
|
-
async
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1388
|
+
async gufi_field(params) {
|
|
1389
|
+
// 💜 Get a single field's full details
|
|
1390
|
+
// First resolve entity to ID if needed
|
|
1391
|
+
const schemaData = await apiRequest("/api/company/schema", {
|
|
1187
1392
|
headers: { "X-Company-ID": params.company_id },
|
|
1188
|
-
});
|
|
1189
|
-
|
|
1393
|
+
}, params.company_id);
|
|
1394
|
+
const modules = schemaData.modules || schemaData.data?.modules || [];
|
|
1395
|
+
let targetEntity = null;
|
|
1396
|
+
let targetField = null;
|
|
1397
|
+
// Search for entity by id, name, or module.entity path
|
|
1398
|
+
const entityRef = params.entity;
|
|
1399
|
+
const [modulePart, entityPart] = entityRef.includes(".") ? entityRef.split(".") : [null, entityRef];
|
|
1400
|
+
for (const mod of modules) {
|
|
1401
|
+
for (const sub of mod.submodules || []) {
|
|
1402
|
+
for (const ent of sub.entities || []) {
|
|
1403
|
+
const matchesId = String(ent.ui?.entityId || ent.id) === entityRef;
|
|
1404
|
+
const matchesName = ent.name === (entityPart || entityRef);
|
|
1405
|
+
const matchesPath = modulePart && mod.name === modulePart && ent.name === entityPart;
|
|
1406
|
+
if (matchesId || matchesName || matchesPath) {
|
|
1407
|
+
targetEntity = { ...ent, moduleName: mod.name, submoduleName: sub.name };
|
|
1408
|
+
targetField = (ent.fields || []).find((f) => f.name === params.field);
|
|
1409
|
+
break;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (targetEntity)
|
|
1413
|
+
break;
|
|
1414
|
+
}
|
|
1415
|
+
if (targetEntity)
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1418
|
+
if (!targetEntity) {
|
|
1419
|
+
throw new Error(`Entity "${params.entity}" not found in company ${params.company_id}`);
|
|
1420
|
+
}
|
|
1421
|
+
if (!targetField) {
|
|
1422
|
+
const availableFields = (targetEntity.fields || []).map((f) => f.name).slice(0, 10);
|
|
1423
|
+
throw new Error(`Field "${params.field}" not found in entity "${targetEntity.name}". Available: ${availableFields.join(", ")}${availableFields.length >= 10 ? "..." : ""}`);
|
|
1424
|
+
}
|
|
1425
|
+
return {
|
|
1426
|
+
entity: targetEntity.name,
|
|
1427
|
+
field: {
|
|
1428
|
+
name: targetField.name,
|
|
1429
|
+
type: targetField.type,
|
|
1430
|
+
label: targetField.label,
|
|
1431
|
+
required: targetField.required || false,
|
|
1432
|
+
settings: targetField.settings || {},
|
|
1433
|
+
},
|
|
1434
|
+
modify_hint: `gufi_schema_modify({ operations: [{ op: "update_field", entity: "${targetEntity.name}", field_name: "${params.field}", changes: { ... } }] })`,
|
|
1435
|
+
};
|
|
1436
|
+
},
|
|
1437
|
+
async gufi_relations(params) {
|
|
1438
|
+
// 💜 Get all relations in a module (or all modules if not specified)
|
|
1439
|
+
const schemaData = await apiRequest("/api/company/schema", {
|
|
1440
|
+
headers: { "X-Company-ID": params.company_id },
|
|
1441
|
+
}, params.company_id);
|
|
1442
|
+
const modules = schemaData.modules || schemaData.data?.modules || [];
|
|
1443
|
+
const relations = [];
|
|
1444
|
+
for (const mod of modules) {
|
|
1445
|
+
// Skip if module_id specified and doesn't match
|
|
1446
|
+
const moduleId = mod.submodules?.[0]?.entities?.[0]?.ui?.moduleId;
|
|
1447
|
+
if (params.module_id && String(moduleId) !== params.module_id)
|
|
1448
|
+
continue;
|
|
1449
|
+
for (const sub of mod.submodules || []) {
|
|
1450
|
+
for (const ent of sub.entities || []) {
|
|
1451
|
+
const entityPath = `${mod.name}.${ent.name}`;
|
|
1452
|
+
for (const field of ent.fields || []) {
|
|
1453
|
+
if (field.type === "relation" && field.settings?.ref) {
|
|
1454
|
+
relations.push({
|
|
1455
|
+
from: entityPath,
|
|
1456
|
+
field: field.name,
|
|
1457
|
+
to: field.settings.ref,
|
|
1458
|
+
display: field.settings.display || "id",
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
// Group by source entity for readability
|
|
1466
|
+
const grouped = {};
|
|
1467
|
+
for (const rel of relations) {
|
|
1468
|
+
if (!grouped[rel.from])
|
|
1469
|
+
grouped[rel.from] = [];
|
|
1470
|
+
grouped[rel.from].push({ field: rel.field, to: rel.to, display: rel.display });
|
|
1471
|
+
}
|
|
1472
|
+
return {
|
|
1473
|
+
company_id: params.company_id,
|
|
1474
|
+
...(params.module_id && { module_id: params.module_id }),
|
|
1475
|
+
total_relations: relations.length,
|
|
1476
|
+
relations_by_entity: grouped,
|
|
1477
|
+
flat_list: relations,
|
|
1478
|
+
};
|
|
1190
1479
|
},
|
|
1191
1480
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1192
1481
|
// Automations
|
|
@@ -1205,16 +1494,21 @@ const toolHandlers = {
|
|
|
1205
1494
|
};
|
|
1206
1495
|
},
|
|
1207
1496
|
async gufi_automation(params) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1497
|
+
// 💜 Fetch automation directly from specified company (IDs are local per company)
|
|
1498
|
+
const data = await apiRequest("/api/automation-scripts", {
|
|
1499
|
+
headers: { "X-Company-ID": params.company_id },
|
|
1500
|
+
});
|
|
1501
|
+
const automations = Array.isArray(data) ? data : data.data || [];
|
|
1502
|
+
const found = automations.find((a) => String(a.id) === params.automation_id);
|
|
1503
|
+
if (!found) {
|
|
1504
|
+
throw new Error(`Automation ${params.automation_id} not found in company ${params.company_id}`);
|
|
1211
1505
|
}
|
|
1212
1506
|
return {
|
|
1213
|
-
id:
|
|
1214
|
-
name:
|
|
1215
|
-
description:
|
|
1216
|
-
company_id:
|
|
1217
|
-
code:
|
|
1507
|
+
id: found.id,
|
|
1508
|
+
name: found.name,
|
|
1509
|
+
description: found.description,
|
|
1510
|
+
company_id: params.company_id,
|
|
1511
|
+
code: found.code || "",
|
|
1218
1512
|
};
|
|
1219
1513
|
},
|
|
1220
1514
|
async gufi_automation_create(params) {
|
|
@@ -1230,16 +1524,24 @@ const toolHandlers = {
|
|
|
1230
1524
|
return { success: true, automation: response.data || response };
|
|
1231
1525
|
},
|
|
1232
1526
|
async gufi_entity_automations(params) {
|
|
1233
|
-
const response = await apiRequest(`/api/entities/${params.entity_id}/automations
|
|
1527
|
+
const response = await apiRequest(`/api/entities/${params.entity_id}/automations`, {
|
|
1528
|
+
headers: { "X-Company-ID": params.company_id },
|
|
1529
|
+
}, params.company_id);
|
|
1234
1530
|
const data = response.data || response;
|
|
1235
|
-
return {
|
|
1531
|
+
return {
|
|
1532
|
+
entity_id: params.entity_id,
|
|
1533
|
+
company_id: params.company_id,
|
|
1534
|
+
automations: data.automations || data || [],
|
|
1535
|
+
availableScripts: data.availableScripts || [],
|
|
1536
|
+
};
|
|
1236
1537
|
},
|
|
1237
1538
|
async gufi_entity_automations_update(params) {
|
|
1238
1539
|
const response = await apiRequest(`/api/entities/${params.entity_id}/automations`, {
|
|
1239
1540
|
method: "PUT",
|
|
1240
1541
|
body: JSON.stringify(params.automations),
|
|
1241
|
-
|
|
1242
|
-
|
|
1542
|
+
headers: { "X-Company-ID": params.company_id },
|
|
1543
|
+
}, params.company_id);
|
|
1544
|
+
return { success: true, entity_id: params.entity_id, company_id: params.company_id };
|
|
1243
1545
|
},
|
|
1244
1546
|
async gufi_automations_executions(params) {
|
|
1245
1547
|
let endpoint = `/api/automation-executions?limit=${params.limit || 20}`;
|
|
@@ -1254,8 +1556,72 @@ const toolHandlers = {
|
|
|
1254
1556
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1255
1557
|
// Data (CRUD)
|
|
1256
1558
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1559
|
+
async gufi_data(params) {
|
|
1560
|
+
// 💜 Unified data tool - all CRUD operations in one
|
|
1561
|
+
const { action, table, company_id } = params;
|
|
1562
|
+
switch (action) {
|
|
1563
|
+
case "list": {
|
|
1564
|
+
const limit = params.limit || 20;
|
|
1565
|
+
const offset = params.offset || 0;
|
|
1566
|
+
const body = {
|
|
1567
|
+
pagination: { current: Math.floor(offset / limit) + 1, pageSize: limit },
|
|
1568
|
+
sorters: [{ field: params.sort || "id", order: params.order || "desc" }],
|
|
1569
|
+
filters: [],
|
|
1570
|
+
};
|
|
1571
|
+
if (params.filter) {
|
|
1572
|
+
const [field, value] = params.filter.split("=");
|
|
1573
|
+
if (field && value)
|
|
1574
|
+
body.filters.push({ field, operator: "eq", value });
|
|
1575
|
+
}
|
|
1576
|
+
const result = await apiRequest(`/api/tables/${table}/list`, {
|
|
1577
|
+
method: "POST",
|
|
1578
|
+
body: JSON.stringify(body),
|
|
1579
|
+
}, company_id);
|
|
1580
|
+
return { action: "list", table, total: result.total, rows: result.data || [] };
|
|
1581
|
+
}
|
|
1582
|
+
case "get": {
|
|
1583
|
+
if (!params.id)
|
|
1584
|
+
throw new Error("id is required for 'get' action");
|
|
1585
|
+
const result = await apiRequest(`/api/tables/${table}/getOne`, {
|
|
1586
|
+
method: "POST",
|
|
1587
|
+
body: JSON.stringify({ id: params.id }),
|
|
1588
|
+
}, company_id);
|
|
1589
|
+
return { action: "get", table, id: params.id, row: result.data || result };
|
|
1590
|
+
}
|
|
1591
|
+
case "create": {
|
|
1592
|
+
if (!params.data)
|
|
1593
|
+
throw new Error("data is required for 'create' action");
|
|
1594
|
+
const result = await apiRequest(`/api/tables/${table}`, {
|
|
1595
|
+
method: "POST",
|
|
1596
|
+
body: JSON.stringify(params.data),
|
|
1597
|
+
}, company_id);
|
|
1598
|
+
return { action: "create", table, success: true, id: result.id || result.data?.id, row: result.data || result };
|
|
1599
|
+
}
|
|
1600
|
+
case "update": {
|
|
1601
|
+
if (!params.id)
|
|
1602
|
+
throw new Error("id is required for 'update' action");
|
|
1603
|
+
if (!params.data)
|
|
1604
|
+
throw new Error("data is required for 'update' action");
|
|
1605
|
+
const result = await apiRequest(`/api/tables/${table}/${params.id}`, {
|
|
1606
|
+
method: "PUT",
|
|
1607
|
+
body: JSON.stringify(params.data),
|
|
1608
|
+
}, company_id);
|
|
1609
|
+
return { action: "update", table, success: true, id: params.id, row: result.data || result };
|
|
1610
|
+
}
|
|
1611
|
+
case "delete": {
|
|
1612
|
+
if (!params.id)
|
|
1613
|
+
throw new Error("id is required for 'delete' action");
|
|
1614
|
+
await apiRequest(`/api/tables/${table}/${params.id}`, {
|
|
1615
|
+
method: "DELETE",
|
|
1616
|
+
}, company_id);
|
|
1617
|
+
return { action: "delete", table, success: true, id: params.id };
|
|
1618
|
+
}
|
|
1619
|
+
default:
|
|
1620
|
+
throw new Error(`Unknown action: ${action}. Use: list, get, create, update, delete`);
|
|
1621
|
+
}
|
|
1622
|
+
},
|
|
1257
1623
|
async gufi_rows(params) {
|
|
1258
|
-
|
|
1624
|
+
// 💜 company_id is now required - IDs are local per company
|
|
1259
1625
|
const limit = params.limit || 20;
|
|
1260
1626
|
const offset = params.offset || 0;
|
|
1261
1627
|
const body = {
|
|
@@ -1272,51 +1638,50 @@ const toolHandlers = {
|
|
|
1272
1638
|
const result = await apiRequest(`/api/tables/${params.table}/list`, {
|
|
1273
1639
|
method: "POST",
|
|
1274
1640
|
body: JSON.stringify(body),
|
|
1275
|
-
},
|
|
1641
|
+
}, params.company_id);
|
|
1276
1642
|
return {
|
|
1277
1643
|
table: params.table,
|
|
1644
|
+
company_id: params.company_id,
|
|
1278
1645
|
total: result.total,
|
|
1279
1646
|
rows: result.data || [],
|
|
1280
1647
|
};
|
|
1281
1648
|
},
|
|
1282
1649
|
async gufi_row(params) {
|
|
1283
|
-
const companyId = params.company_id || await detectCompanyFromTable(params.table);
|
|
1284
1650
|
const result = await apiRequest(`/api/tables/${params.table}/getOne`, {
|
|
1285
1651
|
method: "POST",
|
|
1286
1652
|
body: JSON.stringify({ id: params.id }),
|
|
1287
|
-
},
|
|
1653
|
+
}, params.company_id);
|
|
1288
1654
|
return {
|
|
1289
1655
|
table: params.table,
|
|
1656
|
+
company_id: params.company_id,
|
|
1290
1657
|
id: params.id,
|
|
1291
1658
|
row: result.data || result,
|
|
1292
1659
|
};
|
|
1293
1660
|
},
|
|
1294
1661
|
async gufi_row_create(params) {
|
|
1295
|
-
const companyId = params.company_id || await detectCompanyFromTable(params.table);
|
|
1296
1662
|
const result = await apiRequest(`/api/tables/${params.table}`, {
|
|
1297
1663
|
method: "POST",
|
|
1298
1664
|
body: JSON.stringify(params.data),
|
|
1299
|
-
},
|
|
1665
|
+
}, params.company_id);
|
|
1300
1666
|
return {
|
|
1301
1667
|
success: true,
|
|
1668
|
+
company_id: params.company_id,
|
|
1302
1669
|
id: result.id || result.data?.id,
|
|
1303
1670
|
row: result.data || result,
|
|
1304
1671
|
};
|
|
1305
1672
|
},
|
|
1306
1673
|
async gufi_row_update(params) {
|
|
1307
|
-
const companyId = params.company_id || await detectCompanyFromTable(params.table);
|
|
1308
1674
|
const result = await apiRequest(`/api/tables/${params.table}/${params.id}`, {
|
|
1309
1675
|
method: "PUT",
|
|
1310
1676
|
body: JSON.stringify(params.data),
|
|
1311
|
-
},
|
|
1312
|
-
return { success: true, row: result.data || result };
|
|
1677
|
+
}, params.company_id);
|
|
1678
|
+
return { success: true, company_id: params.company_id, row: result.data || result };
|
|
1313
1679
|
},
|
|
1314
1680
|
async gufi_row_delete(params) {
|
|
1315
|
-
const companyId = params.company_id || await detectCompanyFromTable(params.table);
|
|
1316
1681
|
await apiRequest(`/api/tables/${params.table}/${params.id}`, {
|
|
1317
1682
|
method: "DELETE",
|
|
1318
|
-
},
|
|
1319
|
-
return { success: true };
|
|
1683
|
+
}, params.company_id);
|
|
1684
|
+
return { success: true, company_id: params.company_id };
|
|
1320
1685
|
},
|
|
1321
1686
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1322
1687
|
// Environment Variables
|
|
@@ -1329,14 +1694,14 @@ const toolHandlers = {
|
|
|
1329
1694
|
await apiRequest("/api/cli/env", {
|
|
1330
1695
|
method: "POST",
|
|
1331
1696
|
body: JSON.stringify({ key: params.key, value: params.value }),
|
|
1332
|
-
});
|
|
1333
|
-
return { success: true };
|
|
1697
|
+
}, params.company_id);
|
|
1698
|
+
return { success: true, company_id: params.company_id };
|
|
1334
1699
|
},
|
|
1335
1700
|
async gufi_env_delete(params) {
|
|
1336
1701
|
await apiRequest(`/api/cli/env/${encodeURIComponent(params.key)}`, {
|
|
1337
1702
|
method: "DELETE",
|
|
1338
|
-
});
|
|
1339
|
-
return { success: true };
|
|
1703
|
+
}, params.company_id);
|
|
1704
|
+
return { success: true, company_id: params.company_id };
|
|
1340
1705
|
},
|
|
1341
1706
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1342
1707
|
// Views
|
|
@@ -1392,6 +1757,516 @@ const toolHandlers = {
|
|
|
1392
1757
|
: `❌ ${response.errors?.length || 0} errors, ${response.warnings?.length || 0} warnings`,
|
|
1393
1758
|
};
|
|
1394
1759
|
},
|
|
1760
|
+
async gufi_view_create(params) {
|
|
1761
|
+
// Generate safe function name
|
|
1762
|
+
const safeName = params.name.replace(/[^a-zA-Z0-9]/g, "");
|
|
1763
|
+
// Template generators
|
|
1764
|
+
const getBlankTemplate = (name, fnName) => ({
|
|
1765
|
+
index: `// ${name}
|
|
1766
|
+
import React from 'react';
|
|
1767
|
+
import { Package } from 'lucide-react';
|
|
1768
|
+
import { type FeatureProps } from '@/sdk';
|
|
1769
|
+
|
|
1770
|
+
export { featureConfig } from './core/dataProvider';
|
|
1771
|
+
|
|
1772
|
+
export default function ${fnName}View({ id, gufi }: FeatureProps) {
|
|
1773
|
+
const viewSpec = gufi?.context?.viewSpec || {};
|
|
1774
|
+
|
|
1775
|
+
return (
|
|
1776
|
+
<div className="flex flex-col min-h-screen bg-gradient-to-br from-violet-50/40 via-white to-purple-50/40 p-6">
|
|
1777
|
+
<header className="mb-6">
|
|
1778
|
+
<h1 className="text-xl font-bold flex items-center gap-2">
|
|
1779
|
+
<Package className="w-5 h-5 text-violet-600" />
|
|
1780
|
+
${name}
|
|
1781
|
+
</h1>
|
|
1782
|
+
</header>
|
|
1783
|
+
|
|
1784
|
+
<div className="bg-white rounded-xl border shadow-sm p-6">
|
|
1785
|
+
<p className="text-gray-600">Start building your view here!</p>
|
|
1786
|
+
<p className="text-sm text-gray-400 mt-2">
|
|
1787
|
+
viewSpec.mainTable: {viewSpec.mainTable || 'Not configured'}
|
|
1788
|
+
</p>
|
|
1789
|
+
</div>
|
|
1790
|
+
</div>
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
`,
|
|
1794
|
+
dataProvider: `import type { FeatureConfig } from '@/sdk';
|
|
1795
|
+
|
|
1796
|
+
export const featureConfig: FeatureConfig = {
|
|
1797
|
+
dataSources: {
|
|
1798
|
+
mainTable: {
|
|
1799
|
+
type: 'table',
|
|
1800
|
+
module: 'default',
|
|
1801
|
+
entity: 'items',
|
|
1802
|
+
label: 'Main Table'
|
|
1803
|
+
}
|
|
1804
|
+
},
|
|
1805
|
+
inputs: {},
|
|
1806
|
+
seedData: {
|
|
1807
|
+
description: { es: 'Vista', en: 'View' },
|
|
1808
|
+
data: {},
|
|
1809
|
+
order: []
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
`,
|
|
1813
|
+
});
|
|
1814
|
+
const getDashboardTemplate = (name, fnName) => ({
|
|
1815
|
+
index: `// ${name}
|
|
1816
|
+
import React, { useState, useEffect } from 'react';
|
|
1817
|
+
import { Package, TrendingUp, Users, DollarSign } from 'lucide-react';
|
|
1818
|
+
import { cn, toastError, type FeatureProps } from '@/sdk';
|
|
1819
|
+
import { Button, Card, CardContent, CardHeader, CardTitle } from '@/sdk';
|
|
1820
|
+
|
|
1821
|
+
export { featureConfig } from './core/dataProvider';
|
|
1822
|
+
|
|
1823
|
+
export default function ${fnName}View({ id, gufi }: FeatureProps) {
|
|
1824
|
+
const dataProvider = gufi?.dataProvider;
|
|
1825
|
+
const viewSpec = gufi?.context?.viewSpec || {};
|
|
1826
|
+
const isPreview = gufi?.context?.isPreview;
|
|
1827
|
+
const seedData = gufi?.seedData;
|
|
1828
|
+
|
|
1829
|
+
const [data, setData] = useState<any[]>([]);
|
|
1830
|
+
const [loading, setLoading] = useState(true);
|
|
1831
|
+
const [stats, setStats] = useState({ total: 0, active: 0, revenue: 0 });
|
|
1832
|
+
|
|
1833
|
+
useEffect(() => {
|
|
1834
|
+
const load = async () => {
|
|
1835
|
+
setLoading(true);
|
|
1836
|
+
|
|
1837
|
+
if (isPreview && seedData?.data) {
|
|
1838
|
+
const items = seedData.data.items || [];
|
|
1839
|
+
setData(items);
|
|
1840
|
+
setStats({
|
|
1841
|
+
total: items.length,
|
|
1842
|
+
active: items.filter((i: any) => i.status === 'active').length,
|
|
1843
|
+
revenue: items.reduce((acc: number, i: any) => acc + (i.amount || 0), 0)
|
|
1844
|
+
});
|
|
1845
|
+
setLoading(false);
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
if (!dataProvider || !viewSpec.mainTable) {
|
|
1850
|
+
setLoading(false);
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
try {
|
|
1855
|
+
const res = await dataProvider.getList({
|
|
1856
|
+
resource: viewSpec.mainTable,
|
|
1857
|
+
pagination: { current: 1, pageSize: 100 },
|
|
1858
|
+
});
|
|
1859
|
+
setData(res.data || []);
|
|
1860
|
+
} catch (err) {
|
|
1861
|
+
toastError('Error loading data');
|
|
1862
|
+
}
|
|
1863
|
+
setLoading(false);
|
|
1864
|
+
};
|
|
1865
|
+
load();
|
|
1866
|
+
}, [dataProvider, viewSpec.mainTable, seedData, isPreview]);
|
|
1867
|
+
|
|
1868
|
+
if (loading) {
|
|
1869
|
+
return (
|
|
1870
|
+
<div className="p-6 space-y-4">
|
|
1871
|
+
{[1,2,3].map(i => (
|
|
1872
|
+
<div key={i} className="h-24 bg-gray-100 rounded-xl animate-pulse" />
|
|
1873
|
+
))}
|
|
1874
|
+
</div>
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
return (
|
|
1879
|
+
<div className="flex flex-col min-h-screen bg-gradient-to-br from-violet-50/40 via-white to-purple-50/40 p-6">
|
|
1880
|
+
<header className="mb-6">
|
|
1881
|
+
<h1 className="text-2xl font-bold flex items-center gap-2">
|
|
1882
|
+
<Package className="w-6 h-6 text-violet-600" />
|
|
1883
|
+
${name}
|
|
1884
|
+
</h1>
|
|
1885
|
+
</header>
|
|
1886
|
+
|
|
1887
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
1888
|
+
<Card>
|
|
1889
|
+
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
1890
|
+
<CardTitle className="text-sm font-medium text-gray-500">Total</CardTitle>
|
|
1891
|
+
<Users className="w-4 h-4 text-violet-500" />
|
|
1892
|
+
</CardHeader>
|
|
1893
|
+
<CardContent>
|
|
1894
|
+
<div className="text-2xl font-bold">{stats.total}</div>
|
|
1895
|
+
</CardContent>
|
|
1896
|
+
</Card>
|
|
1897
|
+
<Card>
|
|
1898
|
+
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
1899
|
+
<CardTitle className="text-sm font-medium text-gray-500">Active</CardTitle>
|
|
1900
|
+
<TrendingUp className="w-4 h-4 text-green-500" />
|
|
1901
|
+
</CardHeader>
|
|
1902
|
+
<CardContent>
|
|
1903
|
+
<div className="text-2xl font-bold text-green-600">{stats.active}</div>
|
|
1904
|
+
</CardContent>
|
|
1905
|
+
</Card>
|
|
1906
|
+
<Card>
|
|
1907
|
+
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
1908
|
+
<CardTitle className="text-sm font-medium text-gray-500">Revenue</CardTitle>
|
|
1909
|
+
<DollarSign className="w-4 h-4 text-violet-500" />
|
|
1910
|
+
</CardHeader>
|
|
1911
|
+
<CardContent>
|
|
1912
|
+
<div className="text-2xl font-bold">\${stats.revenue.toLocaleString()}</div>
|
|
1913
|
+
</CardContent>
|
|
1914
|
+
</Card>
|
|
1915
|
+
</div>
|
|
1916
|
+
|
|
1917
|
+
<Card className="flex-1">
|
|
1918
|
+
<CardHeader>
|
|
1919
|
+
<CardTitle>Records</CardTitle>
|
|
1920
|
+
</CardHeader>
|
|
1921
|
+
<CardContent>
|
|
1922
|
+
<div className="space-y-2">
|
|
1923
|
+
{data.slice(0, 10).map((item, i) => (
|
|
1924
|
+
<div key={item.id || i} className="p-3 bg-gray-50 rounded-lg flex justify-between">
|
|
1925
|
+
<span>{item.name || item.nombre || 'Item ' + (i + 1)}</span>
|
|
1926
|
+
<span className="text-gray-500">{item.status || '-'}</span>
|
|
1927
|
+
</div>
|
|
1928
|
+
))}
|
|
1929
|
+
{data.length === 0 && (
|
|
1930
|
+
<div className="text-center text-gray-500 py-8">No data available</div>
|
|
1931
|
+
)}
|
|
1932
|
+
</div>
|
|
1933
|
+
</CardContent>
|
|
1934
|
+
</Card>
|
|
1935
|
+
</div>
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
`,
|
|
1939
|
+
dataProvider: `import type { FeatureConfig } from '@/sdk';
|
|
1940
|
+
|
|
1941
|
+
export const featureConfig: FeatureConfig = {
|
|
1942
|
+
dataSources: {
|
|
1943
|
+
mainTable: {
|
|
1944
|
+
type: 'table',
|
|
1945
|
+
module: 'default',
|
|
1946
|
+
entity: 'items',
|
|
1947
|
+
label: 'Main Data'
|
|
1948
|
+
}
|
|
1949
|
+
},
|
|
1950
|
+
inputs: {
|
|
1951
|
+
title: {
|
|
1952
|
+
type: 'text',
|
|
1953
|
+
label: 'Dashboard Title',
|
|
1954
|
+
default: '${name}'
|
|
1955
|
+
}
|
|
1956
|
+
},
|
|
1957
|
+
seedData: {
|
|
1958
|
+
description: {
|
|
1959
|
+
es: 'Dashboard de ejemplo',
|
|
1960
|
+
en: 'Example dashboard'
|
|
1961
|
+
},
|
|
1962
|
+
data: {
|
|
1963
|
+
items: [
|
|
1964
|
+
{ id: 1, name: 'Item 1', status: 'active', amount: 1500 },
|
|
1965
|
+
{ id: 2, name: 'Item 2', status: 'active', amount: 2300 },
|
|
1966
|
+
{ id: 3, name: 'Item 3', status: 'pending', amount: 800 },
|
|
1967
|
+
]
|
|
1968
|
+
},
|
|
1969
|
+
order: ['items']
|
|
1970
|
+
}
|
|
1971
|
+
};
|
|
1972
|
+
`,
|
|
1973
|
+
});
|
|
1974
|
+
const getTableTemplate = (name, fnName) => ({
|
|
1975
|
+
index: `// ${name}
|
|
1976
|
+
import React, { useState, useEffect } from 'react';
|
|
1977
|
+
import { TableIcon, Search } from 'lucide-react';
|
|
1978
|
+
import { cn, toastError, type FeatureProps } from '@/sdk';
|
|
1979
|
+
import { Input, Button } from '@/sdk';
|
|
1980
|
+
|
|
1981
|
+
export { featureConfig } from './core/dataProvider';
|
|
1982
|
+
|
|
1983
|
+
export default function ${fnName}View({ id, gufi }: FeatureProps) {
|
|
1984
|
+
const dataProvider = gufi?.dataProvider;
|
|
1985
|
+
const viewSpec = gufi?.context?.viewSpec || {};
|
|
1986
|
+
const isPreview = gufi?.context?.isPreview;
|
|
1987
|
+
const seedData = gufi?.seedData;
|
|
1988
|
+
|
|
1989
|
+
const [data, setData] = useState<any[]>([]);
|
|
1990
|
+
const [loading, setLoading] = useState(true);
|
|
1991
|
+
const [search, setSearch] = useState('');
|
|
1992
|
+
|
|
1993
|
+
useEffect(() => {
|
|
1994
|
+
const load = async () => {
|
|
1995
|
+
setLoading(true);
|
|
1996
|
+
if (isPreview && seedData?.data) {
|
|
1997
|
+
setData(seedData.data.items || []);
|
|
1998
|
+
setLoading(false);
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
if (!dataProvider || !viewSpec.mainTable) {
|
|
2002
|
+
setLoading(false);
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
try {
|
|
2006
|
+
const res = await dataProvider.getList({
|
|
2007
|
+
resource: viewSpec.mainTable,
|
|
2008
|
+
pagination: { current: 1, pageSize: 50 },
|
|
2009
|
+
});
|
|
2010
|
+
setData(res.data || []);
|
|
2011
|
+
} catch (err) {
|
|
2012
|
+
toastError('Error loading data');
|
|
2013
|
+
}
|
|
2014
|
+
setLoading(false);
|
|
2015
|
+
};
|
|
2016
|
+
load();
|
|
2017
|
+
}, [dataProvider, viewSpec.mainTable, seedData, isPreview]);
|
|
2018
|
+
|
|
2019
|
+
const filtered = data.filter(item =>
|
|
2020
|
+
!search || JSON.stringify(item).toLowerCase().includes(search.toLowerCase())
|
|
2021
|
+
);
|
|
2022
|
+
|
|
2023
|
+
if (loading) {
|
|
2024
|
+
return (
|
|
2025
|
+
<div className="p-6">
|
|
2026
|
+
<div className="h-10 bg-gray-100 rounded animate-pulse mb-4" />
|
|
2027
|
+
{[1,2,3,4,5].map(i => (
|
|
2028
|
+
<div key={i} className="h-12 bg-gray-50 rounded animate-pulse mb-2" />
|
|
2029
|
+
))}
|
|
2030
|
+
</div>
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
return (
|
|
2035
|
+
<div className="flex flex-col min-h-screen bg-gradient-to-br from-violet-50/40 via-white to-purple-50/40 p-6">
|
|
2036
|
+
<header className="mb-4 flex items-center justify-between">
|
|
2037
|
+
<h1 className="text-xl font-bold flex items-center gap-2">
|
|
2038
|
+
<TableIcon className="w-5 h-5 text-violet-600" />
|
|
2039
|
+
${name}
|
|
2040
|
+
</h1>
|
|
2041
|
+
<div className="relative">
|
|
2042
|
+
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
|
|
2043
|
+
<Input
|
|
2044
|
+
className="pl-9 w-64"
|
|
2045
|
+
placeholder="Search..."
|
|
2046
|
+
value={search}
|
|
2047
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
2048
|
+
/>
|
|
2049
|
+
</div>
|
|
2050
|
+
</header>
|
|
2051
|
+
|
|
2052
|
+
<div className="bg-white rounded-xl border shadow-sm overflow-hidden">
|
|
2053
|
+
<table className="w-full">
|
|
2054
|
+
<thead className="bg-gray-50 border-b">
|
|
2055
|
+
<tr>
|
|
2056
|
+
<th className="text-left p-3 font-medium text-gray-600">ID</th>
|
|
2057
|
+
<th className="text-left p-3 font-medium text-gray-600">Name</th>
|
|
2058
|
+
<th className="text-left p-3 font-medium text-gray-600">Status</th>
|
|
2059
|
+
</tr>
|
|
2060
|
+
</thead>
|
|
2061
|
+
<tbody>
|
|
2062
|
+
{filtered.map((item, i) => (
|
|
2063
|
+
<tr key={item.id || i} className="border-b hover:bg-gray-50">
|
|
2064
|
+
<td className="p-3">{item.id}</td>
|
|
2065
|
+
<td className="p-3">{item.name || item.nombre || '-'}</td>
|
|
2066
|
+
<td className="p-3">{item.status || '-'}</td>
|
|
2067
|
+
</tr>
|
|
2068
|
+
))}
|
|
2069
|
+
{filtered.length === 0 && (
|
|
2070
|
+
<tr>
|
|
2071
|
+
<td colSpan={3} className="p-8 text-center text-gray-500">No data found</td>
|
|
2072
|
+
</tr>
|
|
2073
|
+
)}
|
|
2074
|
+
</tbody>
|
|
2075
|
+
</table>
|
|
2076
|
+
</div>
|
|
2077
|
+
</div>
|
|
2078
|
+
);
|
|
2079
|
+
}
|
|
2080
|
+
`,
|
|
2081
|
+
dataProvider: `import type { FeatureConfig } from '@/sdk';
|
|
2082
|
+
|
|
2083
|
+
export const featureConfig: FeatureConfig = {
|
|
2084
|
+
dataSources: {
|
|
2085
|
+
mainTable: {
|
|
2086
|
+
type: 'table',
|
|
2087
|
+
module: 'default',
|
|
2088
|
+
entity: 'items',
|
|
2089
|
+
label: 'Data Table'
|
|
2090
|
+
}
|
|
2091
|
+
},
|
|
2092
|
+
inputs: {},
|
|
2093
|
+
seedData: {
|
|
2094
|
+
description: { es: 'Tabla de datos', en: 'Data table' },
|
|
2095
|
+
data: {
|
|
2096
|
+
items: [
|
|
2097
|
+
{ id: 1, name: 'Record 1', status: 'active' },
|
|
2098
|
+
{ id: 2, name: 'Record 2', status: 'pending' },
|
|
2099
|
+
{ id: 3, name: 'Record 3', status: 'completed' },
|
|
2100
|
+
]
|
|
2101
|
+
},
|
|
2102
|
+
order: ['items']
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
`,
|
|
2106
|
+
});
|
|
2107
|
+
const getFormTemplate = (name, fnName) => ({
|
|
2108
|
+
index: `// ${name}
|
|
2109
|
+
import React, { useState } from 'react';
|
|
2110
|
+
import { FileText, Save } from 'lucide-react';
|
|
2111
|
+
import { toastSuccess, toastError, type FeatureProps } from '@/sdk';
|
|
2112
|
+
import { Button, Input, Label } from '@/sdk';
|
|
2113
|
+
|
|
2114
|
+
export { featureConfig } from './core/dataProvider';
|
|
2115
|
+
|
|
2116
|
+
export default function ${fnName}View({ id, gufi }: FeatureProps) {
|
|
2117
|
+
const dataProvider = gufi?.dataProvider;
|
|
2118
|
+
const viewSpec = gufi?.context?.viewSpec || {};
|
|
2119
|
+
const CB = gufi?.CB;
|
|
2120
|
+
|
|
2121
|
+
const [formData, setFormData] = useState({ name: '', email: '', notes: '' });
|
|
2122
|
+
const [saving, setSaving] = useState(false);
|
|
2123
|
+
|
|
2124
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
2125
|
+
e.preventDefault();
|
|
2126
|
+
if (!dataProvider || !viewSpec.mainTable || !CB) {
|
|
2127
|
+
toastError('Configuration error');
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
setSaving(true);
|
|
2131
|
+
try {
|
|
2132
|
+
await dataProvider.create({
|
|
2133
|
+
resource: viewSpec.mainTable,
|
|
2134
|
+
variables: {
|
|
2135
|
+
name: CB.text(formData.name),
|
|
2136
|
+
email: CB.text(formData.email),
|
|
2137
|
+
notes: CB.text(formData.notes),
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
toastSuccess('Saved successfully!');
|
|
2141
|
+
setFormData({ name: '', email: '', notes: '' });
|
|
2142
|
+
} catch (err) {
|
|
2143
|
+
toastError('Error saving');
|
|
2144
|
+
}
|
|
2145
|
+
setSaving(false);
|
|
2146
|
+
};
|
|
2147
|
+
|
|
2148
|
+
return (
|
|
2149
|
+
<div className="flex flex-col min-h-screen bg-gradient-to-br from-violet-50/40 via-white to-purple-50/40 p-6">
|
|
2150
|
+
<header className="mb-6">
|
|
2151
|
+
<h1 className="text-xl font-bold flex items-center gap-2">
|
|
2152
|
+
<FileText className="w-5 h-5 text-violet-600" />
|
|
2153
|
+
${name}
|
|
2154
|
+
</h1>
|
|
2155
|
+
</header>
|
|
2156
|
+
|
|
2157
|
+
<form onSubmit={handleSubmit} className="bg-white rounded-xl border shadow-sm p-6 max-w-lg space-y-4">
|
|
2158
|
+
<div>
|
|
2159
|
+
<Label htmlFor="name">Name</Label>
|
|
2160
|
+
<Input
|
|
2161
|
+
id="name"
|
|
2162
|
+
value={formData.name}
|
|
2163
|
+
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
|
|
2164
|
+
placeholder="Enter name..."
|
|
2165
|
+
required
|
|
2166
|
+
/>
|
|
2167
|
+
</div>
|
|
2168
|
+
<div>
|
|
2169
|
+
<Label htmlFor="email">Email</Label>
|
|
2170
|
+
<Input
|
|
2171
|
+
id="email"
|
|
2172
|
+
type="email"
|
|
2173
|
+
value={formData.email}
|
|
2174
|
+
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
|
2175
|
+
placeholder="Enter email..."
|
|
2176
|
+
/>
|
|
2177
|
+
</div>
|
|
2178
|
+
<div>
|
|
2179
|
+
<Label htmlFor="notes">Notes</Label>
|
|
2180
|
+
<Input
|
|
2181
|
+
id="notes"
|
|
2182
|
+
value={formData.notes}
|
|
2183
|
+
onChange={(e) => setFormData(prev => ({ ...prev, notes: e.target.value }))}
|
|
2184
|
+
placeholder="Additional notes..."
|
|
2185
|
+
/>
|
|
2186
|
+
</div>
|
|
2187
|
+
<Button type="submit" disabled={saving} className="w-full">
|
|
2188
|
+
<Save className="w-4 h-4 mr-2" />
|
|
2189
|
+
{saving ? 'Saving...' : 'Save'}
|
|
2190
|
+
</Button>
|
|
2191
|
+
</form>
|
|
2192
|
+
</div>
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
`,
|
|
2196
|
+
dataProvider: `import type { FeatureConfig } from '@/sdk';
|
|
2197
|
+
|
|
2198
|
+
export const featureConfig: FeatureConfig = {
|
|
2199
|
+
dataSources: {
|
|
2200
|
+
mainTable: {
|
|
2201
|
+
type: 'table',
|
|
2202
|
+
module: 'default',
|
|
2203
|
+
entity: 'submissions',
|
|
2204
|
+
label: 'Form Submissions'
|
|
2205
|
+
}
|
|
2206
|
+
},
|
|
2207
|
+
inputs: {},
|
|
2208
|
+
seedData: {
|
|
2209
|
+
description: { es: 'Formulario', en: 'Form' },
|
|
2210
|
+
data: {},
|
|
2211
|
+
order: []
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2214
|
+
`,
|
|
2215
|
+
});
|
|
2216
|
+
// Get the right template
|
|
2217
|
+
let template;
|
|
2218
|
+
switch (params.template) {
|
|
2219
|
+
case "dashboard":
|
|
2220
|
+
template = getDashboardTemplate(params.name, safeName);
|
|
2221
|
+
break;
|
|
2222
|
+
case "table":
|
|
2223
|
+
template = getTableTemplate(params.name, safeName);
|
|
2224
|
+
break;
|
|
2225
|
+
case "form":
|
|
2226
|
+
template = getFormTemplate(params.name, safeName);
|
|
2227
|
+
break;
|
|
2228
|
+
default:
|
|
2229
|
+
template = getBlankTemplate(params.name, safeName);
|
|
2230
|
+
}
|
|
2231
|
+
// Need package_id to create a view
|
|
2232
|
+
if (!params.package_id) {
|
|
2233
|
+
return {
|
|
2234
|
+
error: "package_id is required to create a view",
|
|
2235
|
+
hint: "First create a package with gufi_package_create, then pass its ID",
|
|
2236
|
+
available_templates: ["dashboard", "table", "form", "blank"],
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
// Create the view via API
|
|
2240
|
+
const response = await apiRequest(`/api/marketplace/packages/${params.package_id}/views`, {
|
|
2241
|
+
method: "POST",
|
|
2242
|
+
body: JSON.stringify({
|
|
2243
|
+
name: params.name,
|
|
2244
|
+
description: params.description || `${params.template} view created via MCP`,
|
|
2245
|
+
view_type: "custom",
|
|
2246
|
+
}),
|
|
2247
|
+
});
|
|
2248
|
+
const viewId = response.pk_id;
|
|
2249
|
+
// Now update with template files
|
|
2250
|
+
const files = [
|
|
2251
|
+
{ file_path: "index.tsx", content: template.index, language: "typescript" },
|
|
2252
|
+
{ file_path: "core/dataProvider.ts", content: template.dataProvider, language: "typescript" },
|
|
2253
|
+
];
|
|
2254
|
+
await saveViewFiles(viewId, files);
|
|
2255
|
+
return {
|
|
2256
|
+
success: true,
|
|
2257
|
+
view_id: viewId,
|
|
2258
|
+
name: params.name,
|
|
2259
|
+
template: params.template,
|
|
2260
|
+
package_id: params.package_id,
|
|
2261
|
+
files_created: files.map(f => f.file_path),
|
|
2262
|
+
next_steps: [
|
|
2263
|
+
`1. Download locally: gufi view:pull ${viewId}`,
|
|
2264
|
+
`2. Edit files in: ~/gufi-dev/view_${viewId}/`,
|
|
2265
|
+
`3. Upload changes: gufi view:push`,
|
|
2266
|
+
`4. Publish: gufi package:sync ${params.package_id}`,
|
|
2267
|
+
],
|
|
2268
|
+
};
|
|
2269
|
+
},
|
|
1395
2270
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1396
2271
|
// Packages
|
|
1397
2272
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -1544,6 +2419,31 @@ const toolHandlers = {
|
|
|
1544
2419
|
const response = await developerRequest(`/packages/${params.package_id}/entities`);
|
|
1545
2420
|
return { entities: response.data || [] };
|
|
1546
2421
|
},
|
|
2422
|
+
async gufi_link_view(params) {
|
|
2423
|
+
const entity = {
|
|
2424
|
+
kind: "marketplace_view",
|
|
2425
|
+
marketplace_view_id: params.view_id,
|
|
2426
|
+
label: params.label,
|
|
2427
|
+
icon: params.icon || "Sparkles",
|
|
2428
|
+
};
|
|
2429
|
+
const response = await developerRequest(`/packages/${params.package_id}/modules/${params.module_id}/add-entity`, {
|
|
2430
|
+
method: "POST",
|
|
2431
|
+
body: JSON.stringify({
|
|
2432
|
+
submodule_name: params.submodule_name,
|
|
2433
|
+
entity,
|
|
2434
|
+
}),
|
|
2435
|
+
});
|
|
2436
|
+
// Invalidate schema cache on main backend
|
|
2437
|
+
try {
|
|
2438
|
+
await apiRequest("/api/company/schema/invalidate", { method: "POST" });
|
|
2439
|
+
}
|
|
2440
|
+
catch { }
|
|
2441
|
+
return {
|
|
2442
|
+
success: true,
|
|
2443
|
+
message: `View "${params.label}" linked to ${params.submodule_name}`,
|
|
2444
|
+
entity: response.data?.entity,
|
|
2445
|
+
};
|
|
2446
|
+
},
|
|
1547
2447
|
};
|
|
1548
2448
|
// ════════════════════════════════════════════════════════════════════════════
|
|
1549
2449
|
// Context Generation Helpers
|