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 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
@@ -10,4 +10,6 @@
10
10
  *
11
11
  * The MCP server exposes all CLI functionality as tools that Claude can call.
12
12
  */
13
- export declare function startMcpServer(): Promise<void>;
13
+ export declare function startMcpServer(options?: {
14
+ company?: string;
15
+ }): Promise<void>;
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: 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 │ │ Marketplace │ │ WebSocket │
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
- ### Marketplace Backend (Port 3003)
77
+ ### Views Backend (Port 3003)
78
78
 
79
- Handles marketplace-specific operations:
79
+ Handles view-specific operations:
80
80
 
81
81
  - Package publishing and versioning
82
82
  - View code storage and retrieval
@@ -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` | Vistas marketplace |
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/ # Marketplace views API
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
- │ └── marketplace/ # processSeedData, etc.
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
- - `marketplace-install-package` - Instala packages
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
- ### Marketplace Server
91
+ ### Views Server
94
92
 
95
- Archivo: `marketplace/src/features/marketplace/views.controller.js`
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
- | Marketplace | 3001 | Compilacion vistas |
220
+ | Views | 3001 | Compilacion vistas |
@@ -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
- | `marketplace_view` | Vista del marketplace | MarketplaceView |
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 (marketplace_view)
161
+ ### Crear Custom View
162
162
 
163
- Las Custom Views son entidades de tipo `marketplace_view`. Se crean igual que cualquier entidad:
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: "marketplace_view" // <-- Esto crea una Custom View
174
+ kind: "custom_view" // <-- Esto crea una Custom View
175
175
  }
176
176
  }]
177
177
  })
@@ -1,4 +1,4 @@
1
- # Vistas Marketplace
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 marketplace_view)
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: "marketplace_view" }
24
+ entity: { name: "mi_dashboard", label: "Mi Dashboard", kind: "custom_view" }
25
25
  }]
26
26
  })
27
27
 
@@ -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='marketplace_view': { op: 'add_entity', module: 'ventas', entity: { name: 'mi_dashboard', label: 'Mi Dashboard', kind: 'marketplace_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)."
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: 'marketplace_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 })"
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.51",
3
+ "version": "0.1.52",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"