gufi-cli 0.1.51 → 0.1.52
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/index.js +1 -0
- package/dist/mcp.d.ts +3 -1
- package/dist/mcp.js +227 -3
- package/docs/dev-guide/1-01-architecture.md +3 -3
- package/docs/mcp/00-overview.md +1 -1
- package/docs/mcp/01-architecture.md +6 -12
- package/docs/mcp/02-modules.md +4 -4
- package/docs/mcp/04-views.md +3 -3
- package/docs/mcp/06-api.md +0 -51
- package/docs/mcp/tool-descriptions.json +2 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -428,6 +428,7 @@ program
|
|
|
428
428
|
program
|
|
429
429
|
.command("mcp")
|
|
430
430
|
.description("Start MCP server for Claude integration (stdio transport)")
|
|
431
|
+
.option("--company <id>", "Company ID for role-based tool filtering (Workforce mode)")
|
|
431
432
|
.action(startMcpServer);
|
|
432
433
|
// ════════════════════════════════════════════════════════════════════
|
|
433
434
|
// 💜 Claude Code Integration
|
package/dist/mcp.d.ts
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -585,6 +585,47 @@ function getDesc(toolName, fallback = "") {
|
|
|
585
585
|
}
|
|
586
586
|
// Environment parameter description (reused across tools)
|
|
587
587
|
const ENV_PARAM = { type: "string", description: "Environment: 'prod' (default) or 'dev'. Use 'dev' for development against localhost:3000" };
|
|
588
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
589
|
+
// Workforce Mode - Role-based tool filtering
|
|
590
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
591
|
+
const ADMIN_ROLES = ['admin', 'superadmin', 'consultant'];
|
|
592
|
+
function hasAdminAccess(roles) {
|
|
593
|
+
return roles.some(r => ADMIN_ROLES.includes(r.toLowerCase()));
|
|
594
|
+
}
|
|
595
|
+
// Tools that require Admin/Consultant role
|
|
596
|
+
const ADMIN_ONLY_TOOLS = [
|
|
597
|
+
'gufi_schema_modify',
|
|
598
|
+
'gufi_env',
|
|
599
|
+
'gufi_automation_scripts',
|
|
600
|
+
'gufi_automation_meta',
|
|
601
|
+
'gufi_view_pull',
|
|
602
|
+
'gufi_view_push',
|
|
603
|
+
'gufi_view_test',
|
|
604
|
+
'gufi_browse',
|
|
605
|
+
'gufi_endpoints',
|
|
606
|
+
];
|
|
607
|
+
// Workforce state (null = developer mode, everything allowed)
|
|
608
|
+
let workforceState = null;
|
|
609
|
+
function checkEntityWritePermission(table, action, state) {
|
|
610
|
+
const entityPerms = state.entityPermissions[table];
|
|
611
|
+
if (!entityPerms)
|
|
612
|
+
return false;
|
|
613
|
+
const permMap = {
|
|
614
|
+
create: 'entity:edit',
|
|
615
|
+
update: 'entity:edit',
|
|
616
|
+
delete: 'entity:delete',
|
|
617
|
+
};
|
|
618
|
+
const required = permMap[action];
|
|
619
|
+
for (const role of state.roles) {
|
|
620
|
+
const perms = entityPerms[role];
|
|
621
|
+
if (perms === '*')
|
|
622
|
+
return true;
|
|
623
|
+
if (Array.isArray(perms) && (perms.includes(required) || perms.includes(`${required}_own`))) {
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
588
629
|
const TOOLS = [
|
|
589
630
|
// ─────────────────────────────────────────────────────────────────────────
|
|
590
631
|
// Context & Info
|
|
@@ -942,6 +983,61 @@ Examples:
|
|
|
942
983
|
},
|
|
943
984
|
},
|
|
944
985
|
// ─────────────────────────────────────────────────────────────────────────
|
|
986
|
+
// Browse - Headless browser for any Gufi page
|
|
987
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
988
|
+
{
|
|
989
|
+
name: "gufi_browse",
|
|
990
|
+
description: `Browse any Gufi page in headless browser. Returns console logs, errors, API calls, and screenshot.
|
|
991
|
+
|
|
992
|
+
Use to visually verify UI changes, test dialogs, check table layouts, etc.
|
|
993
|
+
|
|
994
|
+
3 ways to specify the page:
|
|
995
|
+
entity: 'ventas.productos' → opens the table view (resolves module/entity automatically)
|
|
996
|
+
page: 'home' | 'automations' | 'audit' | 'views' | 'env-variables' | 'endpoints' | 'integrations' | 'agents' | 'organization' | 'database'
|
|
997
|
+
path: '/150/m308_t4136' → any raw frontend path
|
|
998
|
+
|
|
999
|
+
Examples:
|
|
1000
|
+
gufi_browse({ entity: 'ventas.productos', company_id: '150' })
|
|
1001
|
+
gufi_browse({ page: 'home', company_id: '150' })
|
|
1002
|
+
gufi_browse({ page: 'automations', company_id: '150' })
|
|
1003
|
+
gufi_browse({ entity: 'ventas.productos', company_id: '150', actions: [{ type: 'click', selector: 'text:Exportar' }, { type: 'delay', ms: 500 }] })`,
|
|
1004
|
+
inputSchema: {
|
|
1005
|
+
type: "object",
|
|
1006
|
+
properties: {
|
|
1007
|
+
path: { type: "string", description: "Raw URL path (e.g., '/150/m308_t4136'). Use entity or page shortcuts instead when possible." },
|
|
1008
|
+
entity: { type: "string", description: "Entity shortcut in 'module.entity' format (e.g., 'ventas.productos'). Resolves to table view automatically." },
|
|
1009
|
+
page: { type: "string", description: "Page shortcut: home, automations, audit, views, env-variables, endpoints, integrations, agents, organization, database" },
|
|
1010
|
+
company_id: { type: "string", description: "Company ID for authentication context" },
|
|
1011
|
+
timeout: { type: "number", description: "Navigation timeout in ms (default: 15000)" },
|
|
1012
|
+
actions: {
|
|
1013
|
+
type: "array",
|
|
1014
|
+
description: `Actions to perform after page loads. Same as gufi_view_test.
|
|
1015
|
+
- Explore: {type:'explore'}
|
|
1016
|
+
- Click: {type:'click', selector:'text:Exportar'}
|
|
1017
|
+
- Screenshot: {type:'screenshot'}
|
|
1018
|
+
- Delay: {type:'delay', ms:1000}
|
|
1019
|
+
- Fill: {type:'fill', selector:'input[name=q]', value:'test'}`,
|
|
1020
|
+
items: {
|
|
1021
|
+
type: "object",
|
|
1022
|
+
properties: {
|
|
1023
|
+
type: { type: "string", description: "explore, click, closeModals, fill, clear, select, wait, waitForText, delay, scroll, hover, screenshot, getText, eval" },
|
|
1024
|
+
selector: { type: "string", description: "CSS selector OR 'text:Button Text'" },
|
|
1025
|
+
value: { type: "string", description: "Value for fill/select" },
|
|
1026
|
+
text: { type: "string", description: "Text to wait for (waitForText)" },
|
|
1027
|
+
ms: { type: "number", description: "Milliseconds for delay" },
|
|
1028
|
+
y: { type: "number", description: "Pixels to scroll" },
|
|
1029
|
+
timeout: { type: "number", description: "Timeout for wait (default 5000)" },
|
|
1030
|
+
code: { type: "string", description: "JS code for eval action" },
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
1033
|
+
},
|
|
1034
|
+
capture_screenshot: { type: "boolean", description: "Include base64 screenshot (default: true)" },
|
|
1035
|
+
env: ENV_PARAM,
|
|
1036
|
+
},
|
|
1037
|
+
required: ["company_id"],
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1040
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
945
1041
|
// 💜 DISABLED: Packages (not actively used, keeping MCP simple)
|
|
946
1042
|
// ─────────────────────────────────────────────────────────────────────────
|
|
947
1043
|
// gufi_package → Re-enable when marketplace packages become priority
|
|
@@ -2163,6 +2259,58 @@ const toolHandlers = {
|
|
|
2163
2259
|
},
|
|
2164
2260
|
// gufi_view_create removed - views are created from UI, edited via pull/push
|
|
2165
2261
|
// ─────────────────────────────────────────────────────────────────────────
|
|
2262
|
+
// Browse - Headless browser for any page
|
|
2263
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2264
|
+
async gufi_browse(params) {
|
|
2265
|
+
const { path, entity, page, company_id, timeout, actions, capture_screenshot = true, env } = params;
|
|
2266
|
+
if (!path && !entity && !page) {
|
|
2267
|
+
return { error: "One of path, entity, or page is required" };
|
|
2268
|
+
}
|
|
2269
|
+
const result = await apiRequest("/api/cli/browse", {
|
|
2270
|
+
method: "POST",
|
|
2271
|
+
body: JSON.stringify({
|
|
2272
|
+
path,
|
|
2273
|
+
entity,
|
|
2274
|
+
page,
|
|
2275
|
+
company_id,
|
|
2276
|
+
timeout: timeout || 15000,
|
|
2277
|
+
actions: actions || [],
|
|
2278
|
+
capture_screenshot,
|
|
2279
|
+
}),
|
|
2280
|
+
}, company_id, true, env);
|
|
2281
|
+
const response = {
|
|
2282
|
+
success: result.success,
|
|
2283
|
+
url: result.url,
|
|
2284
|
+
duration_ms: result.duration_ms,
|
|
2285
|
+
};
|
|
2286
|
+
if (result.console?.errors?.length > 0)
|
|
2287
|
+
response.console_errors = result.console.errors;
|
|
2288
|
+
if (result.console?.warnings?.length > 0)
|
|
2289
|
+
response.console_warnings = result.console.warnings;
|
|
2290
|
+
if (result.console?.logs?.length > 0)
|
|
2291
|
+
response.console_logs = result.console.logs;
|
|
2292
|
+
if (result.errors?.length > 0)
|
|
2293
|
+
response.errors = result.errors;
|
|
2294
|
+
const failedCalls = (result.api_calls || []).filter((c) => c.status >= 400);
|
|
2295
|
+
if (failedCalls.length > 0)
|
|
2296
|
+
response.failed_api_calls = failedCalls;
|
|
2297
|
+
response.dom = result.dom;
|
|
2298
|
+
if (result.actions?.length > 0)
|
|
2299
|
+
response.actions = result.actions;
|
|
2300
|
+
if (result.screenshot)
|
|
2301
|
+
response.screenshot = result.screenshot;
|
|
2302
|
+
if (!result.success) {
|
|
2303
|
+
response._hint = `Browse failed: ${result.error}. Check console_errors and errors for details.`;
|
|
2304
|
+
}
|
|
2305
|
+
else if (result.errors?.length > 0 || result.console?.errors?.length > 0) {
|
|
2306
|
+
response._hint = "Page loaded but has errors. Check console_errors and errors arrays.";
|
|
2307
|
+
}
|
|
2308
|
+
else {
|
|
2309
|
+
response._hint = "Page loaded successfully. Check screenshot to verify visual appearance.";
|
|
2310
|
+
}
|
|
2311
|
+
return response;
|
|
2312
|
+
},
|
|
2313
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2166
2314
|
// Packages
|
|
2167
2315
|
// ─────────────────────────────────────────────────────────────────────────
|
|
2168
2316
|
async gufi_package(params) {
|
|
@@ -2664,16 +2812,71 @@ async function handleRequest(request) {
|
|
|
2664
2812
|
case "notifications/initialized":
|
|
2665
2813
|
// No response needed for notifications
|
|
2666
2814
|
break;
|
|
2667
|
-
case "tools/list":
|
|
2815
|
+
case "tools/list": {
|
|
2816
|
+
let visibleTools = TOOLS;
|
|
2817
|
+
if (workforceState && !workforceState.isAdmin) {
|
|
2818
|
+
visibleTools = TOOLS.filter(t => !ADMIN_ONLY_TOOLS.includes(t.name));
|
|
2819
|
+
// Check if user has write on ANY entity - if not, adjust gufi_data description
|
|
2820
|
+
const hasAnyWrite = Object.values(workforceState.entityPermissions).some((perms) => {
|
|
2821
|
+
for (const role of workforceState.roles) {
|
|
2822
|
+
const p = perms[role];
|
|
2823
|
+
if (p === '*' || (Array.isArray(p) && p.some((x) => x.includes('edit'))))
|
|
2824
|
+
return true;
|
|
2825
|
+
}
|
|
2826
|
+
return false;
|
|
2827
|
+
});
|
|
2828
|
+
if (!hasAnyWrite) {
|
|
2829
|
+
visibleTools = visibleTools.map(t => {
|
|
2830
|
+
if (t.name !== 'gufi_data')
|
|
2831
|
+
return t;
|
|
2832
|
+
return {
|
|
2833
|
+
...t,
|
|
2834
|
+
description: t.description.replace(/create:.*?delete:.*?\n/gs, '') + '\n\nNote: Your role only has read access (list, get, aggregate).',
|
|
2835
|
+
};
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2668
2839
|
sendResponse({
|
|
2669
2840
|
jsonrpc: "2.0",
|
|
2670
2841
|
id,
|
|
2671
|
-
result: { tools:
|
|
2842
|
+
result: { tools: visibleTools },
|
|
2672
2843
|
});
|
|
2673
2844
|
break;
|
|
2845
|
+
}
|
|
2674
2846
|
case "tools/call": {
|
|
2675
2847
|
const toolName = params?.name;
|
|
2676
2848
|
const toolParams = params?.arguments || {};
|
|
2849
|
+
// Workforce permission check
|
|
2850
|
+
if (workforceState && !workforceState.isAdmin) {
|
|
2851
|
+
if (ADMIN_ONLY_TOOLS.includes(toolName)) {
|
|
2852
|
+
sendResponse({
|
|
2853
|
+
jsonrpc: "2.0",
|
|
2854
|
+
id,
|
|
2855
|
+
result: {
|
|
2856
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2857
|
+
error: `Permission denied: tool '${toolName}' requires Admin/Consultant role. Your roles: [${workforceState.roles.join(', ')}]`
|
|
2858
|
+
}, null, 2) }],
|
|
2859
|
+
isError: true,
|
|
2860
|
+
},
|
|
2861
|
+
});
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
if (toolName === 'gufi_data' && ['create', 'update', 'delete'].includes(toolParams.action)) {
|
|
2865
|
+
if (!checkEntityWritePermission(toolParams.table, toolParams.action, workforceState)) {
|
|
2866
|
+
sendResponse({
|
|
2867
|
+
jsonrpc: "2.0",
|
|
2868
|
+
id,
|
|
2869
|
+
result: {
|
|
2870
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2871
|
+
error: `Permission denied: your role cannot '${toolParams.action}' on table '${toolParams.table}'`
|
|
2872
|
+
}, null, 2) }],
|
|
2873
|
+
isError: true,
|
|
2874
|
+
},
|
|
2875
|
+
});
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2677
2880
|
const handler = toolHandlers[toolName];
|
|
2678
2881
|
if (!handler) {
|
|
2679
2882
|
sendError(id, -32601, `Unknown tool: ${toolName}`);
|
|
@@ -2745,7 +2948,7 @@ async function handleRequest(request) {
|
|
|
2745
2948
|
sendError(id, -32603, err.message);
|
|
2746
2949
|
}
|
|
2747
2950
|
}
|
|
2748
|
-
export async function startMcpServer() {
|
|
2951
|
+
export async function startMcpServer(options) {
|
|
2749
2952
|
// Check if logged in
|
|
2750
2953
|
if (!isLoggedIn()) {
|
|
2751
2954
|
const token = await autoLogin();
|
|
@@ -2754,6 +2957,27 @@ export async function startMcpServer() {
|
|
|
2754
2957
|
process.exit(1);
|
|
2755
2958
|
}
|
|
2756
2959
|
}
|
|
2960
|
+
// Workforce mode: fetch user permissions for the specified company
|
|
2961
|
+
if (options?.company) {
|
|
2962
|
+
try {
|
|
2963
|
+
const env = getDefaultEnv();
|
|
2964
|
+
const perms = await apiRequestWithEnv("/api/cli/my-permissions", {}, options.company, env);
|
|
2965
|
+
workforceState = {
|
|
2966
|
+
companyId: options.company,
|
|
2967
|
+
roles: perms.roles || [],
|
|
2968
|
+
platformRole: perms.platform_role || 'client',
|
|
2969
|
+
isAdmin: hasAdminAccess(perms.roles || []),
|
|
2970
|
+
entityPermissions: Object.fromEntries((perms.entity_permissions || []).map((e) => [
|
|
2971
|
+
`${e.module_name}.${e.name}`, e.permissions
|
|
2972
|
+
])),
|
|
2973
|
+
};
|
|
2974
|
+
console.error(`[gufi-mcp] Workforce mode: company=${options.company}, roles=[${workforceState.roles}], admin=${workforceState.isAdmin}`);
|
|
2975
|
+
}
|
|
2976
|
+
catch (err) {
|
|
2977
|
+
console.error(`[gufi-mcp] Failed to fetch permissions: ${err.message}`);
|
|
2978
|
+
process.exit(1);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2757
2981
|
const rl = readline.createInterface({
|
|
2758
2982
|
input: process.stdin,
|
|
2759
2983
|
output: process.stdout,
|
|
@@ -34,7 +34,7 @@ Gufi follows a modern microservices architecture optimized for multi-tenancy, re
|
|
|
34
34
|
│ │ │
|
|
35
35
|
▼ ▼ ▼
|
|
36
36
|
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
|
37
|
-
│ Backend ERP │ │
|
|
37
|
+
│ Backend ERP │ │ Views │ │ WebSocket │
|
|
38
38
|
│ (Express) │ │ Backend │ │ Server │
|
|
39
39
|
│ :3000 │ │ :3003 │ │ :4000 │
|
|
40
40
|
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
|
|
@@ -74,9 +74,9 @@ The main API server handling:
|
|
|
74
74
|
- Zod for validation
|
|
75
75
|
- pg (node-postgres) for database
|
|
76
76
|
|
|
77
|
-
###
|
|
77
|
+
### Views Backend (Port 3003)
|
|
78
78
|
|
|
79
|
-
Handles
|
|
79
|
+
Handles view-specific operations:
|
|
80
80
|
|
|
81
81
|
- Package publishing and versioning
|
|
82
82
|
- View code storage and retrieval
|
package/docs/mcp/00-overview.md
CHANGED
|
@@ -293,7 +293,7 @@ const { rows } = await gufi.query(...); // ERROR!
|
|
|
293
293
|
| `architecture` | Componentes del sistema |
|
|
294
294
|
| `modules` | Sistema de modulos |
|
|
295
295
|
| `fields` | Tipos de campos y CB builders |
|
|
296
|
-
| `views` |
|
|
296
|
+
| `views` | Custom Views |
|
|
297
297
|
| `automations` | Scripts y triggers |
|
|
298
298
|
| `api` | API REST |
|
|
299
299
|
| `packages` | Sistema de packages |
|
|
@@ -14,8 +14,7 @@ backend/src/features/
|
|
|
14
14
|
├── modules/ # Estructura de datos (JSON -> DDL)
|
|
15
15
|
├── tables/ # CRUD generico de registros
|
|
16
16
|
├── automations/ # Scripts y triggers
|
|
17
|
-
├── views/ #
|
|
18
|
-
├── marketplace/ # Developer API (packages)
|
|
17
|
+
├── views/ # Custom Views API
|
|
19
18
|
├── files/ # Upload/download GCS
|
|
20
19
|
├── imports/ # Import wizard (Excel)
|
|
21
20
|
├── exports/ # Export (Excel, PDF)
|
|
@@ -31,7 +30,6 @@ backend/src/features/
|
|
|
31
30
|
- `/api/company/modules` - CRUD modulos
|
|
32
31
|
- `/api/tables/:table/*` - CRUD registros
|
|
33
32
|
- `/api/automation-scripts/*` - Scripts
|
|
34
|
-
- `/api/marketplace/*` - Developer API
|
|
35
33
|
- `/api/cli/*` - CLI endpoints
|
|
36
34
|
|
|
37
35
|
### Frontend (React + Vite) - Puerto 5173
|
|
@@ -42,7 +40,7 @@ frontend/src/
|
|
|
42
40
|
│ ├── hooks/ # useViewData, useFeatureData
|
|
43
41
|
│ ├── components/ # FeatureContainer, KPICard
|
|
44
42
|
│ ├── utils/ # formatNumber, groupBy
|
|
45
|
-
│ └──
|
|
43
|
+
│ └── sdk/ # processSeedData, etc.
|
|
46
44
|
├── features/
|
|
47
45
|
│ ├── core_views/ # table, dashboard
|
|
48
46
|
│ ├── custom_views/ # Apps custom (delivery, machines)
|
|
@@ -79,7 +77,7 @@ Archivo: `backend/src/core/services/queue/pgBossWorkerV9.js`
|
|
|
79
77
|
**Colas:**
|
|
80
78
|
- `automation` - Ejecuta automations
|
|
81
79
|
- `duplicate-company` - Duplica companies
|
|
82
|
-
- `
|
|
80
|
+
- `install-package` - Instala packages
|
|
83
81
|
- `geocode-batch` - Geocoding de imports
|
|
84
82
|
|
|
85
83
|
**Flujo automation:**
|
|
@@ -90,9 +88,9 @@ Archivo: `backend/src/core/services/queue/pgBossWorkerV9.js`
|
|
|
90
88
|
4. Resultado en automation_executions
|
|
91
89
|
```
|
|
92
90
|
|
|
93
|
-
###
|
|
91
|
+
### Views Server
|
|
94
92
|
|
|
95
|
-
Archivo: `
|
|
93
|
+
Archivo: `views/src/views.controller.js`
|
|
96
94
|
|
|
97
95
|
**Funcionalidades:**
|
|
98
96
|
- Compilar vistas (esbuild)
|
|
@@ -119,10 +117,6 @@ core.automation_scripts
|
|
|
119
117
|
core.automation_meta
|
|
120
118
|
core.automation_executions
|
|
121
119
|
|
|
122
|
-
-- Marketplace
|
|
123
|
-
core.marketplace_packages
|
|
124
|
-
core.marketplace_views
|
|
125
|
-
core.marketplace_view_files
|
|
126
120
|
|
|
127
121
|
-- Config
|
|
128
122
|
core.company_env_variables
|
|
@@ -223,4 +217,4 @@ Frontend <--HTTP--> Backend <--SQL--> PostgreSQL
|
|
|
223
217
|
| WebSocket | 4000 | Realtime |
|
|
224
218
|
| PostgreSQL | 5432 | Base de datos |
|
|
225
219
|
| Redis | 6379 | Cache (opcional) |
|
|
226
|
-
|
|
|
220
|
+
| Views | 3001 | Compilacion vistas |
|
package/docs/mcp/02-modules.md
CHANGED
|
@@ -90,7 +90,7 @@ gufi_data({
|
|
|
90
90
|
| `table` | Tabla normal con CRUD | GenericTable |
|
|
91
91
|
| `dashboard` | Dashboard de KPIs | DashboardView |
|
|
92
92
|
| `config` | Configuracion (1 registro) | FormView |
|
|
93
|
-
| `
|
|
93
|
+
| `custom_view` | Custom View | CustomView |
|
|
94
94
|
| `custom` | Vista custom hardcoded | Plugin especifico |
|
|
95
95
|
|
|
96
96
|
## Operaciones con MCP
|
|
@@ -158,9 +158,9 @@ gufi_schema_modify({
|
|
|
158
158
|
})
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
### Crear Custom View
|
|
161
|
+
### Crear Custom View
|
|
162
162
|
|
|
163
|
-
Las Custom Views son entidades de tipo `
|
|
163
|
+
Las Custom Views son entidades de tipo `custom_view`. Se crean igual que cualquier entidad:
|
|
164
164
|
|
|
165
165
|
```javascript
|
|
166
166
|
gufi_schema_modify({
|
|
@@ -171,7 +171,7 @@ gufi_schema_modify({
|
|
|
171
171
|
entity: {
|
|
172
172
|
name: "dashboard_ventas",
|
|
173
173
|
label: "Dashboard de Ventas",
|
|
174
|
-
kind: "
|
|
174
|
+
kind: "custom_view" // <-- Esto crea una Custom View
|
|
175
175
|
}
|
|
176
176
|
}]
|
|
177
177
|
})
|
package/docs/mcp/04-views.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Custom Views
|
|
2
2
|
|
|
3
3
|
## TL;DR
|
|
4
4
|
|
|
@@ -15,13 +15,13 @@ mi_vista/
|
|
|
15
15
|
|
|
16
16
|
**Workflow MCP completo:**
|
|
17
17
|
```
|
|
18
|
-
# 1. Crear la vista (es una entity de tipo
|
|
18
|
+
# 1. Crear la vista (es una entity de tipo custom_view)
|
|
19
19
|
gufi_schema_modify({
|
|
20
20
|
company_id: "116",
|
|
21
21
|
operations: [{
|
|
22
22
|
op: "add_entity",
|
|
23
23
|
module: "ventas",
|
|
24
|
-
entity: { name: "mi_dashboard", label: "Mi Dashboard", kind: "
|
|
24
|
+
entity: { name: "mi_dashboard", label: "Mi Dashboard", kind: "custom_view" }
|
|
25
25
|
}]
|
|
26
26
|
})
|
|
27
27
|
|
package/docs/mcp/06-api.md
CHANGED
|
@@ -387,57 +387,6 @@ Response:
|
|
|
387
387
|
]
|
|
388
388
|
```
|
|
389
389
|
|
|
390
|
-
## Marketplace
|
|
391
|
-
|
|
392
|
-
### Mis packages
|
|
393
|
-
|
|
394
|
-
```
|
|
395
|
-
GET /api/marketplace/developer/packages
|
|
396
|
-
|
|
397
|
-
Response:
|
|
398
|
-
[
|
|
399
|
-
{ "id": 14, "name": "Mi Package", "version": "1.0.0", "published": true }
|
|
400
|
-
]
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
### Detalle package
|
|
404
|
-
|
|
405
|
-
```
|
|
406
|
-
GET /api/marketplace/packages/{packageId}
|
|
407
|
-
|
|
408
|
-
Response:
|
|
409
|
-
{
|
|
410
|
-
"id": 14,
|
|
411
|
-
"name": "Mi Package",
|
|
412
|
-
"modules": [...],
|
|
413
|
-
"views": [...]
|
|
414
|
-
}
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
### Archivos de vista
|
|
418
|
-
|
|
419
|
-
```
|
|
420
|
-
GET /api/marketplace/views/{viewId}/files
|
|
421
|
-
|
|
422
|
-
Response:
|
|
423
|
-
{
|
|
424
|
-
"files": [
|
|
425
|
-
{ "path": "index.tsx", "content": "...", "language": "typescript" }
|
|
426
|
-
]
|
|
427
|
-
}
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
### Actualizar archivos
|
|
431
|
-
|
|
432
|
-
```
|
|
433
|
-
PUT /api/marketplace/views/{viewId}/files
|
|
434
|
-
{
|
|
435
|
-
"files": [
|
|
436
|
-
{ "path": "index.tsx", "content": "..." }
|
|
437
|
-
]
|
|
438
|
-
}
|
|
439
|
-
```
|
|
440
|
-
|
|
441
390
|
## Files
|
|
442
391
|
|
|
443
392
|
### Upload
|
|
@@ -1,63 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Gufi MCP - 15 tools. Para usuarios que construyen ERPs con Gufi.",
|
|
3
3
|
"_env_note": "El entorno se configura con 'gufi config:local' o 'gufi config:prod' + 'gufi login'. El parametro env:'dev' es un OVERRIDE que requiere tener credenciales guardadas para ese entorno.",
|
|
4
|
-
|
|
5
4
|
"gufi_context": {
|
|
6
5
|
"description": "THE ONE TOOL FOR CONTEXT. Llama esto PRIMERO.\n\nUsos:\n gufi_context({ company_id: '146' }) - Schema COMPLETO de empresa\n gufi_context({ view_id: '13' }) - Contexto de vista\n gufi_context({ package_id: '14' }) - Contexto de package\n\nSiempre devuelve contexto completo: campos, tipos, relaciones, automations, permisos."
|
|
7
6
|
},
|
|
8
|
-
|
|
9
7
|
"gufi_whoami": {
|
|
10
8
|
"description": "Ver usuario actual y entorno (dev/prod). Usa para verificar autenticación."
|
|
11
9
|
},
|
|
12
|
-
|
|
13
10
|
"gufi_schema_modify": {
|
|
14
|
-
"description": "Modificar schema. Operaciones atómicas para módulos, entities, fields y sections.\n\nMÓDULOS:\n add_module: { op: 'add_module', module: { name: 'inventario', label: 'Inventario' } }\n remove_module: { op: 'remove_module', module: 'inventario' } ⚠️ Borra TODO el módulo y sus datos\n\nSECCIONES (submodules):\n add_submodule: { op: 'add_submodule', module: 'ventas', submodule: { name: 'reportes', label: 'Reportes' } }\n remove_submodule: { op: 'remove_submodule', module: 'ventas', submodule: 'reportes' } → Mueve entities a 'general'\n\nENTIDADES:\n add_entity: { op: 'add_entity', module: 'ventas', entity: { name: 'facturas', label: 'Facturas' } }\n update_entity: { op: 'update_entity', entity: 'clientes', changes: { label: 'Clientes VIP' } }\n remove_entity: { op: 'remove_entity', entity: 'clientes' } ⚠️ Borra tabla y datos\n\n💜 CREAR VISTAS (Custom Views):\n add_entity con kind='
|
|
11
|
+
"description": "Modificar schema. Operaciones atómicas para módulos, entities, fields y sections.\n\nMÓDULOS:\n add_module: { op: 'add_module', module: { name: 'inventario', label: 'Inventario' } }\n remove_module: { op: 'remove_module', module: 'inventario' } ⚠️ Borra TODO el módulo y sus datos\n\nSECCIONES (submodules):\n add_submodule: { op: 'add_submodule', module: 'ventas', submodule: { name: 'reportes', label: 'Reportes' } }\n remove_submodule: { op: 'remove_submodule', module: 'ventas', submodule: 'reportes' } → Mueve entities a 'general'\n\nENTIDADES:\n add_entity: { op: 'add_entity', module: 'ventas', entity: { name: 'facturas', label: 'Facturas' } }\n update_entity: { op: 'update_entity', entity: 'clientes', changes: { label: 'Clientes VIP' } }\n remove_entity: { op: 'remove_entity', entity: 'clientes' } ⚠️ Borra tabla y datos\n\n💜 CREAR VISTAS (Custom Views):\n add_entity con kind='custom_view': { op: 'add_entity', module: 'ventas', entity: { name: 'mi_dashboard', label: 'Mi Dashboard', kind: 'custom_view' } }\n Luego usa gufi_view_pull({ view_id: <id> }) para descargar el template y empezar a codear.\n\nCAMPOS:\n add_field: { op: 'add_field', entity: 'clientes', field: { name: 'phone', type: 'phone', label: 'Teléfono' } }\n update_field: { op: 'update_field', entity: 'clientes', field_name: 'phone', changes: { required: true } }\n remove_field: { op: 'remove_field', entity: 'clientes', field_name: 'old_field' } ⚠️ Borra columna y datos\n\nCAMPOS RELATION (FK):\n { op: 'add_field', entity: 'pedidos', field: { name: 'cliente_id', type: 'relation', ref: 'ventas.clientes', display: 'nombre', label: 'Cliente' } }\n ⚠️ IMPORTANTE: ref usa formato 'modulo.entidad', NO { entity, module }\n\nTipos de campo: text, email, number_int, number_float, currency, date, select, multiselect, relation, phone, location, file, boolean, json\nAliases automáticos: textarea→text, varchar→text, int→number_int, float→number_float, bool→boolean, timestamp→datetime\n\nReferencias: Usa nombre lógico ('ventas.clientes') o ID numérico.\nPreview: Usa preview: true para ver qué haría sin ejecutar.\nBatch: Usa skip_existing: true para saltar campos/entidades que ya existen (no falla)."
|
|
15
12
|
},
|
|
16
|
-
|
|
17
13
|
"gufi_docs": {
|
|
18
14
|
"description": "Leer documentación de Gufi. USA ESTO cuando no sepas cómo funciona algo.\n\nTopics: overview, fields, views, automations, errors, examples, api, modules, packages"
|
|
19
15
|
},
|
|
20
|
-
|
|
21
16
|
"gufi_automation_scripts": {
|
|
22
17
|
"description": "CRUD de automation scripts.\n\nAcciones:\n list: gufi_automation_scripts({ action: 'list', company_id: '146' })\n get: gufi_automation_scripts({ action: 'get', company_id: '146', id: '5' })\n create: gufi_automation_scripts({ action: 'create', company_id: '146', name: 'mi_script', code: '...' })\n update: gufi_automation_scripts({ action: 'update', company_id: '146', name: 'mi_script', code: '...' })\n delete: gufi_automation_scripts({ action: 'delete', company_id: '146', name: 'mi_script' })\n\nIMPORTANTE: api.query() retorna rows directamente (no { rows })."
|
|
23
18
|
},
|
|
24
|
-
|
|
25
19
|
"gufi_automation_script_test": {
|
|
26
20
|
"description": "Ejecutar/testear un automation script manualmente.\n\nParámetros:\n- entity: Nombre lógico (module.entity). Opcional para scripts standalone.\n- row: Datos de la fila (incluir id). Opcional si usas input.\n- input: Datos adicionales (van a context.input). Opcional si usas row.\n\nEjemplo con entidad:\ngufi_automation_script_test({\n company_id: '116',\n script_name: 'send_email',\n entity: 'ventas.facturas',\n trigger_event: 'on_click',\n row: { id: 1, total: 150 },\n input: { email_to: 'test@example.com' }\n})\n\nEjemplo standalone (sin entidad):\ngufi_automation_script_test({\n company_id: '116',\n script_name: 'daily_report',\n trigger_event: 'scheduled',\n input: { date: '2024-01-15' }\n})"
|
|
27
21
|
},
|
|
28
|
-
|
|
29
22
|
"gufi_automation_meta": {
|
|
30
23
|
"description": "Ver/configurar triggers de entidades.\n\nVer: gufi_automation_meta({ entity_id: '123', company_id: '146' })\nSet: gufi_automation_meta({ entity_id: '123', company_id: '146', automations: [{ trigger: 'insert', function_name: 'my_script' }] })\n\nTriggers válidos: insert, update, delete, click, scheduled, custom\n\n💜 CUSTOM: Para vistas, no configures manualmente. Usa core/automations.ts y gufi_view_push lo registra automáticamente."
|
|
31
24
|
},
|
|
32
|
-
|
|
33
25
|
"gufi_automation_integrations": {
|
|
34
26
|
"description": "List Gufi's built-in integrations (Stripe, Nayax, etc.) and their api.* methods. These are pre-built by Gufi - users can also create custom integrations directly in their automations using api.http() for any external service. Credentials are passed using env.* variables."
|
|
35
27
|
},
|
|
36
|
-
|
|
37
28
|
"gufi_automation_executions": {
|
|
38
29
|
"description": "Ver historial de ejecuciones de automations. Útil para debugging."
|
|
39
30
|
},
|
|
40
|
-
|
|
41
31
|
"gufi_data": {
|
|
42
32
|
"description": "CRUD unificado para datos.\n\nAcciones:\n list: gufi_data({ action: 'list', table: 'ventas.productos', company_id: '146' })\n get: gufi_data({ action: 'get', table: '...', id: 123 })\n create: gufi_data({ action: 'create', table: '...', data: {...} })\n update: gufi_data({ action: 'update', table: '...', id: 123, data: {...} })\n delete: gufi_data({ action: 'delete', table: '...', id: 123 })\n\nAnalytics:\n aggregate: gufi_data({ action: 'aggregate', table: 'ventas.pedidos', agg: 'sum', field: 'total' })\n - agg: 'count', 'sum', 'avg', 'min', 'max'\n - groupBy: gufi_data({ action: 'aggregate', table: '...', agg: 'sum', field: 'total', groupBy: 'cliente_id' })\n - filter: gufi_data({ action: 'aggregate', table: '...', agg: 'count', filter: 'estado=activo' })\n - dateField: gufi_data({ action: 'aggregate', table: '...', agg: 'sum', field: 'total', groupBy: 'cliente_id', dateField: 'fecha', dateFrom: '2025-12-01', dateTo: '2025-12-31' })\n\nOpciones list:\n - fields: ['id', 'nombre'] - Solo columnas específicas\n - compact: true - Respuesta mínima (solo data)"
|
|
43
33
|
},
|
|
44
|
-
|
|
45
34
|
"gufi_env": {
|
|
46
35
|
"description": "Variables de entorno. Se usan en automations como process.env.KEY.\n\nAcciones:\n list: gufi_env({ action: 'list', company_id: '146' })\n set: gufi_env({ action: 'set', company_id: '146', key: 'API_KEY', value: '...' })\n delete: gufi_env({ action: 'delete', company_id: '146', key: 'API_KEY' })"
|
|
47
36
|
},
|
|
48
|
-
|
|
49
37
|
"gufi_view_pull": {
|
|
50
|
-
"description": "📥 Descargar vista a local para editarla.\n\nDescarga a: ~/gufi-dev/view_<id>/\n\n💜 FLUJO COMPLETO (crear + desarrollar):\n1. CREAR vista con gufi_schema_modify:\n gufi_schema_modify({ company_id: '146', operations: [{ op: 'add_entity', module: 'ventas', entity: { name: 'mi_dashboard', label: 'Mi Dashboard', kind: '
|
|
38
|
+
"description": "📥 Descargar vista a local para editarla.\n\nDescarga a: ~/gufi-dev/view_<id>/\n\n💜 FLUJO COMPLETO (crear + desarrollar):\n1. CREAR vista con gufi_schema_modify:\n gufi_schema_modify({ company_id: '146', operations: [{ op: 'add_entity', module: 'ventas', entity: { name: 'mi_dashboard', label: 'Mi Dashboard', kind: 'custom_view' } }] })\n → Retorna el view_id de la nueva vista\n2. gufi_view_pull({ view_id: <id> }) - Descargar template\n3. Editar archivos con Read/Edit\n4. gufi_view_push({ view_id: <id> }) - Subir\n5. gufi_view_test({ view_id: <id>, company_id: '146' }) - Verificar\n6. gufi_package({ action: 'publish', id: '19' }) - Publicar\n\nEjemplo: gufi_view_pull({ view_id: 13 })"
|
|
51
39
|
},
|
|
52
|
-
|
|
53
40
|
"gufi_view_push": {
|
|
54
41
|
"description": "📤 Subir cambios (sync completo).\n\nSube TODOS los archivos locales. El servidor borra los que no vengan.\n\n💜 AUTOMATIONS: Si existe core/automations.ts, las automations declaradas se registran en __automation_meta__ con trigger_event='CUSTOM'. Solo automations declaradas pueden llamarse con gufi.automation().\n\n⚠️ IMPORTANTE: Después de push, SIEMPRE usar gufi_view_test para verificar que la vista carga sin errores antes de publicar.\n\nFlujo:\n1. Crear scripts: gufi_automation_scripts({ action: 'create', name: 'mi_script' })\n2. Declarar en core/automations.ts: export const automations = [{ name: 'mi_script' }]\n3. gufi_view_push({ view_id: 13 }) → Registra automations\n4. gufi_view_test({ view_id: 13, company_id: '116' }) ← OBLIGATORIO\n5. Si OK → gufi_package({ action: 'publish', id: '19' })\n\nEjemplo: gufi_view_push({ view_id: 13, message: 'Fix bug' })"
|
|
55
42
|
},
|
|
56
|
-
|
|
57
43
|
"gufi_view_test": {
|
|
58
44
|
"description": "🧪 Test view in headless browser. Returns console logs, JS errors, API calls, and screenshot.\n\n⚠️ SIEMPRE ejecutar después de gufi_view_push para verificar que todo funciona.\n\nFlujo típico:\n1. Editar código localmente\n2. gufi_view_push({ view_id: 45 }) - Subir\n3. gufi_view_test({ view_id: 45, company_id: '150' }) - Verificar\n4. Ver errores/screenshot → Arreglar si hay problemas\n5. Repetir hasta que funcione\n6. gufi_package({ action: 'publish' }) - Publicar\n\nAcciones opcionales después de cargar:\n actions: [\n { type: 'click', selector: '.my-button' },\n { type: 'fill', selector: 'input[name=search]', value: 'test' },\n { type: 'wait', selector: '.results' },\n { type: 'delay', ms: 1000 }\n ]\n\nEjemplo: gufi_view_test({ view_id: 45, company_id: '150' })"
|
|
59
45
|
},
|
|
60
|
-
|
|
61
46
|
"gufi_package": {
|
|
62
47
|
"description": "Gestión de packages.\n\nAcciones:\n list: gufi_package({ action: 'list' })\n get: gufi_package({ action: 'get', id: '19' })\n create: gufi_package({ action: 'create', name: 'Mi Package' })\n delete: gufi_package({ action: 'delete', id: '19' })\n add_module: gufi_package({ action: 'add_module', id: '19', module_id: '5', company_id: '146' })\n remove_module: gufi_package({ action: 'remove_module', id: '19', module_id: '5' })\n publish: gufi_package({ action: 'publish', id: '19' })"
|
|
63
48
|
}
|