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 +4 -3
- package/README.md +5 -4
- package/dist/index.js +4 -1
- package/dist/mcp.js +202 -38
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -55,9 +55,9 @@ gufi config:prod
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
## MCP Server -
|
|
58
|
+
## MCP Server - 13 Tools
|
|
59
59
|
|
|
60
|
-
El servidor MCP está en `src/mcp.ts` y expone **
|
|
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
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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(
|
|
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
|
-
//
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
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
|
|
1710
|
-
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 = [];
|