gufi-cli 0.1.38 → 0.1.40

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/CLAUDE.md CHANGED
@@ -55,9 +55,9 @@ gufi config:prod
55
55
  }
56
56
  ```
57
57
 
58
- ## MCP Server - 12 Tools
58
+ ## MCP Server - 13 Tools
59
59
 
60
- El servidor MCP está en `src/mcp.ts` y expone **12 tools** para que Claude interactúe con Gufi.
60
+ El servidor MCP está en `src/mcp.ts` y expone **13 tools** para que Claude interactúe con Gufi.
61
61
 
62
62
  ### Tools organizados por categoría
63
63
 
@@ -66,7 +66,7 @@ El servidor MCP está en `src/mcp.ts` y expone **12 tools** para que Claude inte
66
66
  | **Context** | `gufi_context` | Schema completo (company, view, package) |
67
67
  | | `gufi_whoami` | Usuario, entorno, empresas |
68
68
  | | `gufi_docs` | Documentación (`docs/mcp/`) |
69
- | **Schema** | `gufi_schema_modify` | Operaciones atomicas (add/update/remove) |
69
+ | **Schema** | `gufi_schema_modify` | Operaciones atómicas (add/update/remove) |
70
70
  | **Automations** | `gufi_automation` | Scripts: list, get, create |
71
71
  | | `gufi_triggers` | Ver/configurar triggers |
72
72
  | | `gufi_executions` | Historial de ejecuciones |
@@ -74,6 +74,7 @@ El servidor MCP está en `src/mcp.ts` y expone **12 tools** para que Claude inte
74
74
  | **Env** | `gufi_env` | Variables: list, set, delete |
75
75
  | **Views** | `gufi_view_pull` | Descargar vista a local |
76
76
  | | `gufi_view_push` | Subir cambios a draft |
77
+ | | `gufi_view_test` | Test en headless browser (errores, screenshot) |
77
78
  | **Packages** | `gufi_package` | list, get, create, delete, add_module, remove_module, publish |
78
79
 
79
80
  ### Añadir un nuevo tool MCP
package/README.md CHANGED
@@ -173,11 +173,12 @@ const { rows } = await api.query(...); // ERROR!
173
173
  ### Llamar automations desde vistas
174
174
 
175
175
  ```tsx
176
- await dataProvider.runClickAutomation?.({
177
- company_id, module_id, table_id: entityId,
178
- function_name: "my_automation",
179
- input: { record_id: 123 },
176
+ // 💜 gufi.automation() - Simple y directo
177
+ const result = await gufi.automation('my_automation', {
178
+ record_id: 123
180
179
  });
180
+
181
+ // La automation tiene acceso a api.stripe.*, api.nayax.*, etc.
181
182
  ```
182
183
 
183
184
  ## Row CRUD (Datos)
package/dist/index.js CHANGED
@@ -82,11 +82,14 @@ import { doctorCommand } from "./commands/doctor.js";
82
82
  import { docsCommand } from "./commands/docs.js";
83
83
  import { startMcpServer } from "./mcp.js";
84
84
  import { claudeCommand } from "./commands/claude.js";
85
+ import { createRequire } from "module";
86
+ const require = createRequire(import.meta.url);
87
+ const pkg = require("../package.json");
85
88
  const program = new Command();
86
89
  program
87
90
  .name("gufi")
88
91
  .description("🟣 Gufi CLI - Desarrolla módulos, vistas y automations")
89
- .version("0.1.8");
92
+ .version(pkg.version);
90
93
  // ════════════════════════════════════════════════════════════════════
91
94
  // 🔐 Auth
92
95
  // ════════════════════════════════════════════════════════════════════
package/dist/mcp.js CHANGED
@@ -552,6 +552,16 @@ const TOOLS = [
552
552
  },
553
553
  },
554
554
  },
555
+ {
556
+ name: "gufi_integrations",
557
+ description: "List available integrations (Stripe, Nayax, etc.) and their methods. Use this to see what api.* methods are available in automations. Credentials are passed from automations using env.* variables.",
558
+ inputSchema: {
559
+ type: "object",
560
+ properties: {
561
+ name: { type: "string", description: "Integration name to get detailed info (optional)" },
562
+ },
563
+ },
564
+ },
555
565
  // ─────────────────────────────────────────────────────────────────────────
556
566
  // Automations (Business Logic)
557
567
  // ─────────────────────────────────────────────────────────────────────────
@@ -713,6 +723,35 @@ Example: gufi_view_push({ view_id: 13, message: "Fixed bug in chart" })`,
713
723
  required: [],
714
724
  },
715
725
  },
726
+ // 🧪 Test view in headless browser
727
+ {
728
+ name: "gufi_view_test",
729
+ description: getDesc("gufi_view_test"),
730
+ inputSchema: {
731
+ type: "object",
732
+ properties: {
733
+ view_id: { type: "number", description: "View ID to test" },
734
+ company_id: { type: "string", description: "Company ID for authentication context" },
735
+ timeout: { type: "number", description: "Navigation timeout in ms (default: 15000)" },
736
+ actions: {
737
+ type: "array",
738
+ description: "Optional actions to perform after page load: [{type:'click',selector:'.btn'}, {type:'fill',selector:'input',value:'text'}, {type:'wait',selector:'.loaded'}, {type:'delay',ms:1000}]",
739
+ items: {
740
+ type: "object",
741
+ properties: {
742
+ type: { type: "string", description: "Action type: click, fill, wait, delay" },
743
+ selector: { type: "string", description: "CSS selector for click/fill/wait" },
744
+ value: { type: "string", description: "Value for fill action" },
745
+ ms: { type: "number", description: "Milliseconds for delay action" },
746
+ },
747
+ },
748
+ },
749
+ capture_screenshot: { type: "boolean", description: "Include base64 screenshot (default: true)" },
750
+ env: ENV_PARAM,
751
+ },
752
+ required: ["view_id", "company_id"],
753
+ },
754
+ },
716
755
  // ─────────────────────────────────────────────────────────────────────────
717
756
  // Packages (Marketplace Distribution)
718
757
  // ─────────────────────────────────────────────────────────────────────────
@@ -1104,6 +1143,102 @@ const toolHandlers = {
1104
1143
  hint: "Use gufi_docs({ topic: 'fields' }) to read about field types, or gufi_docs({ search: 'currency' }) to search.",
1105
1144
  };
1106
1145
  },
1146
+ async gufi_integrations(params) {
1147
+ const { name } = params;
1148
+ // Static list of available integrations and their methods
1149
+ // Credentials are passed from automations using env.* - no configuration status to check
1150
+ const integrations = {
1151
+ stripe: {
1152
+ name: "stripe",
1153
+ description: "Stripe payment processing - Create checkout sessions, handle payments",
1154
+ category: "payments",
1155
+ docs: "https://stripe.com/docs/api",
1156
+ methods: [
1157
+ { name: "createCheckoutSession", description: "Create a Stripe Checkout Session for payment", params: ["secretKey", "lineItems", "customerEmail?", "successUrl?", "cancelUrl?"] },
1158
+ ],
1159
+ },
1160
+ nayax: {
1161
+ name: "nayax",
1162
+ description: "Nayax vending machine telemetry - Sync machines, products, sales, alerts",
1163
+ category: "vending",
1164
+ docs: "https://developers.nayax.com/",
1165
+ methods: [
1166
+ { name: "sync_machines", description: "Sync all machines from Nayax", params: ["token", "tableName"] },
1167
+ { name: "sync_products", description: "Sync products from Nayax", params: ["token", "tableName"] },
1168
+ { name: "sync_last_sales", description: "Sync recent sales", params: ["token", "tableName", "machinesTable", "productsTable"] },
1169
+ { name: "sync_alerts", description: "Sync machine alerts", params: ["token", "tableName", "machinesTable", "productsTable"] },
1170
+ { name: "sync_machine_products", description: "Sync planograms", params: ["token", "tableName", "machinesTable", "productsTable"] },
1171
+ { name: "sync_pick_list", description: "Sync low stock items", params: ["token", "tableName", "machinesTable", "productsTable"] },
1172
+ { name: "sync_single_machine_capacity", description: "Sync and calculate capacity for one machine", params: ["machineId", "token", "machineProductsTable", "machinesTable", "productsTable"] },
1173
+ ],
1174
+ },
1175
+ ourvend: {
1176
+ name: "ourvend",
1177
+ description: "OurVend (RedPanda) vending system - Sync groups, machines, products, sales",
1178
+ category: "vending",
1179
+ docs: "https://ourvend.com/api",
1180
+ methods: [
1181
+ { name: "sync_groups", description: "Sync machine groups", params: ["user", "password", "tableName"] },
1182
+ { name: "sync_machines", description: "Sync machines", params: ["user", "password", "tableName", "groupsTable"] },
1183
+ { name: "sync_products", description: "Sync products", params: ["user", "password", "tableName"] },
1184
+ { name: "sync_sales", description: "Sync sales", params: ["user", "password", "tableName", "machinesTable", "productsTable", "groupsTable", "dateFrom?", "dateTo?"] },
1185
+ ],
1186
+ },
1187
+ tns: {
1188
+ name: "tns",
1189
+ description: "TNS Colombian accounting - Sync products and categories",
1190
+ category: "accounting",
1191
+ docs: "https://tns.com.co/api",
1192
+ methods: [
1193
+ { name: "sync_products", description: "Sync products from TNS", params: ["productsTable", "categoriesTable", "env?"] },
1194
+ { name: "sync_products_from_sales", description: "Sync products that appear in sales", params: ["productsTable", "categoriesTable", "salesTable", "env?"] },
1195
+ { name: "sync_all_products", description: "Sync all products from catalog", params: ["productsTable", "categoriesTable", "env?"] },
1196
+ ],
1197
+ },
1198
+ };
1199
+ if (name) {
1200
+ const integration = integrations[name];
1201
+ if (!integration) {
1202
+ return {
1203
+ error: `Integration '${name}' not found`,
1204
+ available: Object.keys(integrations),
1205
+ };
1206
+ }
1207
+ return {
1208
+ ...integration,
1209
+ methods: integration.methods.map((m) => ({
1210
+ ...m,
1211
+ usage: `api.${name}.${m.name}({ ${m.params.map(p => p.replace("?", "")).join(", ")} })`,
1212
+ })),
1213
+ usage_note: "Credentials (tokens, passwords, keys) are passed from automations using env.* variables. Name your env vars however you prefer.",
1214
+ example: `
1215
+ // In automation:
1216
+ async function my_automation(context, api, logger) {
1217
+ const { env } = context;
1218
+ const result = await api.${name}.${integration.methods[0].name}({
1219
+ ${integration.methods[0].params.filter(p => !p.endsWith("?")).map(p => `${p}: env.MY_${p.toUpperCase()}`).join(",\n ")}
1220
+ });
1221
+ return result;
1222
+ }`,
1223
+ };
1224
+ }
1225
+ // List all integrations
1226
+ return {
1227
+ integrations: Object.values(integrations).map((i) => ({
1228
+ name: i.name,
1229
+ description: i.description,
1230
+ category: i.category,
1231
+ methods: i.methods.map((m) => m.name),
1232
+ })),
1233
+ usage_note: "Credentials are passed from automations using env.* variables. Name your env vars however you prefer.",
1234
+ example: `
1235
+ // In automation, use api.{integration}.{method}():
1236
+ const result = await api.stripe.createCheckoutSession({
1237
+ secretKey: env.MY_STRIPE_KEY, // Your env var name
1238
+ lineItems: [{ name: 'Product', amount: 1999, quantity: 1 }],
1239
+ });`,
1240
+ };
1241
+ },
1107
1242
  // ─────────────────────────────────────────────────────────────────────────
1108
1243
  // Automations
1109
1244
  // ─────────────────────────────────────────────────────────────────────────
@@ -1498,6 +1633,62 @@ const toolHandlers = {
1498
1633
  : "Changes saved (no package - goes directly to production)",
1499
1634
  };
1500
1635
  },
1636
+ async gufi_view_test(params) {
1637
+ const { view_id, company_id, timeout, actions, capture_screenshot, env } = params;
1638
+ const result = await apiRequest("/api/cli/view-test", {
1639
+ method: "POST",
1640
+ body: JSON.stringify({
1641
+ view_id,
1642
+ company_id,
1643
+ timeout: timeout || 15000,
1644
+ actions: actions || [],
1645
+ capture_screenshot: capture_screenshot !== false,
1646
+ }),
1647
+ }, company_id, true, env);
1648
+ // Format response for Claude readability
1649
+ const response = {
1650
+ success: result.success,
1651
+ view_id: result.view_id,
1652
+ url: result.url,
1653
+ duration_ms: result.duration_ms,
1654
+ };
1655
+ // Add console output if there are errors/warnings
1656
+ if (result.console?.errors?.length > 0) {
1657
+ response.console_errors = result.console.errors;
1658
+ }
1659
+ if (result.console?.warnings?.length > 0) {
1660
+ response.console_warnings = result.console.warnings;
1661
+ }
1662
+ if (result.console?.logs?.length > 0) {
1663
+ response.console_logs = result.console.logs;
1664
+ }
1665
+ // Add JS/network errors
1666
+ if (result.errors?.length > 0) {
1667
+ response.errors = result.errors;
1668
+ }
1669
+ // Add failed API calls
1670
+ const failedCalls = (result.api_calls || []).filter((c) => c.status >= 400);
1671
+ if (failedCalls.length > 0) {
1672
+ response.failed_api_calls = failedCalls;
1673
+ }
1674
+ // Add DOM info
1675
+ response.dom = result.dom;
1676
+ // Add screenshot (Claude can view base64 images)
1677
+ if (result.screenshot) {
1678
+ response.screenshot = result.screenshot;
1679
+ }
1680
+ // Add hint based on results
1681
+ if (!result.success) {
1682
+ response._hint = `View test failed: ${result.error}. Check console_errors and errors for details.`;
1683
+ }
1684
+ else if (result.errors?.length > 0 || result.console?.errors?.length > 0) {
1685
+ response._hint = "View loaded but has errors. Check console_errors and errors arrays.";
1686
+ }
1687
+ else {
1688
+ response._hint = "View loaded successfully. Check screenshot to verify visual appearance.";
1689
+ }
1690
+ return response;
1691
+ },
1501
1692
  // gufi_view_create removed - views are created from UI, edited via pull/push
1502
1693
  // ─────────────────────────────────────────────────────────────────────────
1503
1694
  // Packages
@@ -1683,46 +1874,19 @@ async function generateViewContextMcp(viewId, includeConcepts, env) {
1683
1874
  }
1684
1875
  const config = view.config || {};
1685
1876
  const viewCompanyId = config.view_company_id;
1686
- // Try to get featureConfig from marketplace backend
1687
- let dataSources = {};
1688
- let inputs = {};
1689
- try {
1690
- // Marketplace endpoint is at /api/marketplace/views/:id/feature-config
1691
- const featureConfigResponse = await apiRequest(`/api/marketplace/views/${viewId}/feature-config`, {}, undefined, true, env);
1692
- const featureConfig = featureConfigResponse.data || featureConfigResponse;
1693
- // If we have dataSources from featureConfig, use them
1694
- if (featureConfig && Array.isArray(featureConfig.dataSources)) {
1695
- featureConfig.dataSources.forEach((ds) => {
1696
- if (ds.key && ds.table) {
1697
- dataSources[ds.key] = ds.table;
1698
- }
1699
- });
1700
- }
1701
- // Inputs from featureConfig (if available)
1702
- if (featureConfig && Array.isArray(featureConfig.inputs)) {
1703
- featureConfig.inputs.forEach((inp) => {
1704
- if (inp.key) {
1705
- inputs[inp.key] = inp;
1706
- }
1707
- });
1877
+ // Extract dataSources and inputs from config
1878
+ const dataSources = {};
1879
+ const inputs = {};
1880
+ Object.entries(config).forEach(([key, value]) => {
1881
+ if (["view_company_id", "category", "is_public", "slug"].includes(key))
1882
+ return;
1883
+ if (key.endsWith("Table") || key.endsWith("table")) {
1884
+ dataSources[key] = value;
1708
1885
  }
1709
- else if (featureConfig && typeof featureConfig.inputs === 'object') {
1710
- inputs = featureConfig.inputs;
1886
+ else {
1887
+ inputs[key] = value;
1711
1888
  }
1712
- }
1713
- catch (err) {
1714
- // If featureConfig endpoint fails, fallback to parsing config
1715
- Object.entries(config).forEach(([key, value]) => {
1716
- if (["view_company_id", "category", "is_public", "slug"].includes(key))
1717
- return;
1718
- if (key.endsWith("Table") || key.endsWith("table")) {
1719
- dataSources[key] = value;
1720
- }
1721
- else {
1722
- inputs[key] = value;
1723
- }
1724
- });
1725
- }
1889
+ });
1726
1890
  // Load modules and automations if we have view_company_id
1727
1891
  let modules = [];
1728
1892
  let automations = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"