gufi-cli 0.1.40 → 0.1.43
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 +12 -8
- package/README.md +6 -7
- package/dist/commands/companies.js +2 -2
- package/dist/commands/context.js +3 -5
- package/dist/commands/integrations.d.ts +10 -0
- package/dist/commands/integrations.js +53 -0
- package/dist/index.js +8 -0
- package/dist/lib/sync.js +2 -1
- package/dist/mcp.js +289 -251
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
tools/cli/
|
|
11
11
|
├── src/
|
|
12
12
|
│ ├── index.ts # Entry point CLI
|
|
13
|
-
│ ├── mcp.ts # MCP Server (
|
|
13
|
+
│ ├── mcp.ts # MCP Server (15 tools)
|
|
14
14
|
│ ├── commands/ # Comandos CLI
|
|
15
15
|
│ │ ├── context.ts # gufi context
|
|
16
16
|
│ │ ├── pull.ts # gufi view:pull
|
|
@@ -55,9 +55,9 @@ gufi config:prod
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
## MCP Server -
|
|
58
|
+
## MCP Server - 15 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 **15 tools** para que Claude interactúe con Gufi.
|
|
61
61
|
|
|
62
62
|
### Tools organizados por categoría
|
|
63
63
|
|
|
@@ -67,10 +67,12 @@ El servidor MCP está en `src/mcp.ts` y expone **13 tools** para que Claude inte
|
|
|
67
67
|
| | `gufi_whoami` | Usuario, entorno, empresas |
|
|
68
68
|
| | `gufi_docs` | Documentación (`docs/mcp/`) |
|
|
69
69
|
| **Schema** | `gufi_schema_modify` | Operaciones atómicas (add/update/remove) |
|
|
70
|
-
| **Automations** | `
|
|
71
|
-
| | `
|
|
72
|
-
| | `
|
|
73
|
-
|
|
|
70
|
+
| **Automations** | `gufi_automation_scripts` | Scripts: list, get, create, update, delete |
|
|
71
|
+
| | `gufi_automation_script_test` | Testear script manualmente |
|
|
72
|
+
| | `gufi_automation_meta` | Ver/configurar triggers por entidad |
|
|
73
|
+
| | `gufi_automation_integrations` | Integraciones built-in (Stripe, Nayax, etc.) |
|
|
74
|
+
| | `gufi_automation_executions` | Historial de ejecuciones |
|
|
75
|
+
| **Data** | `gufi_data` | CRUD: list, get, create, update, delete, aggregate |
|
|
74
76
|
| **Env** | `gufi_env` | Variables: list, set, delete |
|
|
75
77
|
| **Views** | `gufi_view_pull` | Descargar vista a local |
|
|
76
78
|
| | `gufi_view_push` | Subir cambios a draft |
|
|
@@ -165,12 +167,14 @@ export async function miComando(args: string[], flags: Flags) {
|
|
|
165
167
|
```
|
|
166
168
|
gufi view:pull <id> → ~/gufi-dev/view_<id>/
|
|
167
169
|
# Editar archivos locales
|
|
168
|
-
gufi view:push → Sync completo
|
|
170
|
+
gufi view:push → Sync completo + registra automations
|
|
169
171
|
gufi package:publish <id> → Publicar
|
|
170
172
|
```
|
|
171
173
|
|
|
172
174
|
**Push = sync completo**: Sube TODOS los archivos, servidor borra los que no vengan.
|
|
173
175
|
|
|
176
|
+
**Automations**: Si existe `core/automations.ts`, push detecta las automations declaradas y las registra en `__automation_meta__` con `trigger_event='CUSTOM'`.
|
|
177
|
+
|
|
174
178
|
## Config (~/.gufi/)
|
|
175
179
|
|
|
176
180
|
```
|
package/README.md
CHANGED
|
@@ -509,12 +509,11 @@ El MCP expone todas las funcionalidades del CLI como tools que Claude puede usar
|
|
|
509
509
|
| `gufi_module_update` | Actualizar módulo desde JSON |
|
|
510
510
|
| `gufi_module_create` | Crear módulo nuevo |
|
|
511
511
|
| **Automations** | |
|
|
512
|
-
| `
|
|
513
|
-
| `
|
|
514
|
-
| `
|
|
515
|
-
| `
|
|
516
|
-
| `
|
|
517
|
-
| `gufi_automations_executions` | Ver historial de ejecuciones |
|
|
512
|
+
| `gufi_automation_scripts` | CRUD de scripts (list, get, create, update, delete) |
|
|
513
|
+
| `gufi_automation_meta` | Ver/configurar triggers por entidad |
|
|
514
|
+
| `gufi_automation_integrations` | Ver integraciones built-in (Stripe, Nayax, etc.) |
|
|
515
|
+
| `gufi_automation_script_test` | Testear un script manualmente |
|
|
516
|
+
| `gufi_automation_executions` | Ver historial de ejecuciones |
|
|
518
517
|
| **Datos** | |
|
|
519
518
|
| `gufi_rows` | Listar registros de una tabla |
|
|
520
519
|
| `gufi_row` | Ver un registro específico |
|
|
@@ -546,7 +545,7 @@ Una vez configurado, puedes hablar naturalmente con Claude:
|
|
|
546
545
|
→ Claude usa gufi_module para leer, modifica, y usa gufi_module_update
|
|
547
546
|
|
|
548
547
|
"Crea un automation que envíe email cuando un pedido pase a 'enviado'"
|
|
549
|
-
→ Claude usa
|
|
548
|
+
→ Claude usa gufi_automation_scripts para ver existentes, crea código, configura con gufi_automation_meta
|
|
550
549
|
|
|
551
550
|
"¿Cuántos registros hay en la tabla de clientes?"
|
|
552
551
|
→ Claude usa gufi_rows para consultar
|
|
@@ -1496,9 +1496,9 @@ export async function automationExecuteCommand(automationIdOrName, options) {
|
|
|
1496
1496
|
process.exit(1);
|
|
1497
1497
|
}
|
|
1498
1498
|
}
|
|
1499
|
-
// 💜 Execute automation
|
|
1499
|
+
// 💜 Execute automation
|
|
1500
1500
|
console.log(chalk.yellow("\n ⏳ Ejecutando..."));
|
|
1501
|
-
const result = await apiRequest("/api/automation-scripts/
|
|
1501
|
+
const result = await apiRequest("/api/automation-scripts/run", {
|
|
1502
1502
|
method: "POST",
|
|
1503
1503
|
headers: { "X-Company-ID": companyId },
|
|
1504
1504
|
body: JSON.stringify({
|
package/dist/commands/context.js
CHANGED
|
@@ -367,12 +367,10 @@ function generateRelevantConcepts(dataSources, inputs, automations) {
|
|
|
367
367
|
// automations concept
|
|
368
368
|
if (automations.length > 0) {
|
|
369
369
|
md += `### Llamar Automations\n`;
|
|
370
|
-
md += `Puedes ejecutar automations
|
|
370
|
+
md += `Puedes ejecutar automations desde la vista con \`gufi.automation()\`:\n`;
|
|
371
371
|
md += "```typescript\n";
|
|
372
|
-
md += `await gufi.
|
|
373
|
-
md += `
|
|
374
|
-
md += ` function_name: "${automations[0]?.function_name || "mi_automation"}",\n`;
|
|
375
|
-
md += ` input: { /* parámetros */ },\n`;
|
|
372
|
+
md += `const result = await gufi.automation("${automations[0]?.function_name || "mi_automation"}", {\n`;
|
|
373
|
+
md += ` // input parameters\n`;
|
|
376
374
|
md += `});\n`;
|
|
377
375
|
md += "```\n\n";
|
|
378
376
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi integrations - List available integrations and their methods
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* gufi integrations # List all integrations with methods
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* List all integrations with their methods
|
|
9
|
+
*/
|
|
10
|
+
export declare function integrationsCommand(): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi integrations - List available integrations and their methods
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* gufi integrations # List all integrations with methods
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
// Path to integrations folder
|
|
14
|
+
const INTEGRATIONS_PATH = path.resolve(__dirname, "../../../../backend/src/features/automations/core/api/integrations");
|
|
15
|
+
/**
|
|
16
|
+
* List all integrations with their methods
|
|
17
|
+
*/
|
|
18
|
+
export async function integrationsCommand() {
|
|
19
|
+
const integrations = await loadIntegrations();
|
|
20
|
+
console.log(chalk.bold.magenta("\n🔌 Gufi Integrations\n"));
|
|
21
|
+
for (const [name, integration] of Object.entries(integrations)) {
|
|
22
|
+
const methodCount = Object.keys(integration.methods).length;
|
|
23
|
+
console.log(` ${chalk.cyan(name.padEnd(12))} ${chalk.gray(integration.meta.description)} ${chalk.yellow(`(${methodCount} methods)`)}`);
|
|
24
|
+
for (const [methodName, method] of Object.entries(integration.methods)) {
|
|
25
|
+
console.log(` ${chalk.white("•")} ${methodName} - ${chalk.gray(method.description)}`);
|
|
26
|
+
}
|
|
27
|
+
console.log();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function loadIntegrations() {
|
|
31
|
+
const integrations = {};
|
|
32
|
+
const dirs = fs.readdirSync(INTEGRATIONS_PATH, { withFileTypes: true });
|
|
33
|
+
for (const dir of dirs) {
|
|
34
|
+
if (!dir.isDirectory())
|
|
35
|
+
continue;
|
|
36
|
+
const integrationPath = path.join(INTEGRATIONS_PATH, dir.name, "integration.js");
|
|
37
|
+
if (!fs.existsSync(integrationPath))
|
|
38
|
+
continue;
|
|
39
|
+
try {
|
|
40
|
+
const module = await import(`file://${integrationPath}`);
|
|
41
|
+
if (module.meta && module.methods) {
|
|
42
|
+
integrations[module.meta.name] = {
|
|
43
|
+
meta: module.meta,
|
|
44
|
+
methods: module.methods,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.error(chalk.yellow(`Warning: Could not load ${dir.name}: ${err.message}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return integrations;
|
|
53
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -80,6 +80,7 @@ import { configCommand, configLocalCommand, configProdCommand, configSetCommand,
|
|
|
80
80
|
import { contextCommand } from "./commands/context.js";
|
|
81
81
|
import { doctorCommand } from "./commands/doctor.js";
|
|
82
82
|
import { docsCommand } from "./commands/docs.js";
|
|
83
|
+
import { integrationsCommand } from "./commands/integrations.js";
|
|
83
84
|
import { startMcpServer } from "./mcp.js";
|
|
84
85
|
import { claudeCommand } from "./commands/claude.js";
|
|
85
86
|
import { createRequire } from "module";
|
|
@@ -163,6 +164,13 @@ program
|
|
|
163
164
|
docsCommand(args);
|
|
164
165
|
});
|
|
165
166
|
// ════════════════════════════════════════════════════════════════════
|
|
167
|
+
// 🔌 Integrations
|
|
168
|
+
// ════════════════════════════════════════════════════════════════════
|
|
169
|
+
program
|
|
170
|
+
.command("integrations")
|
|
171
|
+
.description("Listar integraciones disponibles con sus métodos")
|
|
172
|
+
.action(integrationsCommand);
|
|
173
|
+
// ════════════════════════════════════════════════════════════════════
|
|
166
174
|
// 🏢 Companies & Modules
|
|
167
175
|
// ════════════════════════════════════════════════════════════════════
|
|
168
176
|
program
|
package/dist/lib/sync.js
CHANGED
|
@@ -8,7 +8,8 @@ import os from "os";
|
|
|
8
8
|
import crypto from "crypto";
|
|
9
9
|
import { getViewFiles, saveViewFiles } from "./api.js";
|
|
10
10
|
import { setCurrentView, getCurrentView } from "./config.js";
|
|
11
|
-
|
|
11
|
+
// 💜 Views go to workspace/views/ for unified workspace
|
|
12
|
+
const GUFI_DEV_DIR = path.join(os.homedir(), "workspace", "views");
|
|
12
13
|
const META_FILE = ".gufi-view.json";
|
|
13
14
|
function ensureDir(dir) {
|
|
14
15
|
if (!fs.existsSync(dir)) {
|
package/dist/mcp.js
CHANGED
|
@@ -14,13 +14,81 @@ import * as readline from "readline";
|
|
|
14
14
|
import * as fs from "fs";
|
|
15
15
|
import * as path from "path";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
|
-
import { getToken, getRefreshToken, loadConfig, isLoggedIn, getTokenForEnv, getRefreshTokenForEnv, setTokenForEnv, getCredentialsForEnv } from "./lib/config.js";
|
|
18
|
-
import {
|
|
17
|
+
import { getToken, getRefreshToken, loadConfig, isLoggedIn, getTokenForEnv, getRefreshTokenForEnv, setTokenForEnv, getCredentialsForEnv, getCurrentEnv } from "./lib/config.js";
|
|
18
|
+
import { getViewFiles, } from "./lib/api.js";
|
|
19
|
+
import { pushView, getViewDir, loadViewMeta } from "./lib/sync.js";
|
|
19
20
|
// For ES modules __dirname equivalent
|
|
20
21
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
22
|
const __dirname = path.dirname(__filename);
|
|
22
23
|
// docs/mcp path relative to CLI
|
|
23
24
|
const DOCS_MCP_PATH = path.resolve(__dirname, "../../../docs/mcp");
|
|
25
|
+
// docs/dev-guide path for integration docs
|
|
26
|
+
const DOCS_DEV_GUIDE_PATH = path.resolve(__dirname, "../../../docs/dev-guide");
|
|
27
|
+
/**
|
|
28
|
+
* Parse frontmatter from markdown content
|
|
29
|
+
*/
|
|
30
|
+
function parseFrontmatter(content) {
|
|
31
|
+
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
|
32
|
+
const match = content.match(frontmatterRegex);
|
|
33
|
+
if (!match) {
|
|
34
|
+
return { frontmatter: {}, body: content };
|
|
35
|
+
}
|
|
36
|
+
const [, frontmatterStr, body] = match;
|
|
37
|
+
const frontmatter = {};
|
|
38
|
+
frontmatterStr.split('\n').forEach(line => {
|
|
39
|
+
const colonIndex = line.indexOf(':');
|
|
40
|
+
if (colonIndex > 0) {
|
|
41
|
+
const key = line.slice(0, colonIndex).trim();
|
|
42
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
43
|
+
// Remove quotes
|
|
44
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
45
|
+
value = value.slice(1, -1);
|
|
46
|
+
}
|
|
47
|
+
frontmatter[key] = value;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return { frontmatter, body: body.trim() };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get all integration docs from docs/dev-guide/
|
|
54
|
+
* Returns docs that have group: integrations in frontmatter
|
|
55
|
+
*/
|
|
56
|
+
function getIntegrationDocs() {
|
|
57
|
+
try {
|
|
58
|
+
if (!fs.existsSync(DOCS_DEV_GUIDE_PATH)) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const files = fs.readdirSync(DOCS_DEV_GUIDE_PATH).filter(f => f.endsWith('.md'));
|
|
62
|
+
const integrations = [];
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
const filePath = path.join(DOCS_DEV_GUIDE_PATH, file);
|
|
65
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
66
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
67
|
+
// Only include docs with group: integrations
|
|
68
|
+
if (frontmatter.group === 'integrations') {
|
|
69
|
+
integrations.push({
|
|
70
|
+
id: frontmatter.id || file.replace('.md', ''),
|
|
71
|
+
title: frontmatter.title || '',
|
|
72
|
+
description: frontmatter.description || '',
|
|
73
|
+
content: body,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Sort by filename order (2-06, 2-07, etc.)
|
|
78
|
+
return integrations;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error('[MCP] Error reading integration docs:', err);
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get a specific integration doc by name/id
|
|
87
|
+
*/
|
|
88
|
+
function getIntegrationDoc(name) {
|
|
89
|
+
const docs = getIntegrationDocs();
|
|
90
|
+
return docs.find(d => d.id === name || d.id === name.toLowerCase()) || null;
|
|
91
|
+
}
|
|
24
92
|
// ════════════════════════════════════════════════════════════════════════════
|
|
25
93
|
// Environment Configuration (stateless - passed per request)
|
|
26
94
|
// ════════════════════════════════════════════════════════════════════════════
|
|
@@ -32,11 +100,19 @@ const ENV_URLS = {
|
|
|
32
100
|
dev: "http://localhost:3000",
|
|
33
101
|
local: "http://localhost:3000", // Alias for backwards compatibility
|
|
34
102
|
};
|
|
35
|
-
// Default environment -
|
|
36
|
-
|
|
103
|
+
// 💜 Default environment - read from config file (set by Gufi AI based on gufiEnv)
|
|
104
|
+
function getDefaultEnv() {
|
|
105
|
+
try {
|
|
106
|
+
const configEnv = getCurrentEnv();
|
|
107
|
+
return configEnv === "local" ? "dev" : "prod";
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return "prod"; // Fallback if config not available
|
|
111
|
+
}
|
|
112
|
+
}
|
|
37
113
|
/**
|
|
38
114
|
* Get API URL for the specified environment.
|
|
39
|
-
* @param env - 'prod'
|
|
115
|
+
* @param env - 'prod' or 'dev' (localhost:3000). If not specified, uses config's currentEnv.
|
|
40
116
|
*
|
|
41
117
|
* Uses resolveEnv() which is STRICT - invalid values throw errors.
|
|
42
118
|
*/
|
|
@@ -49,14 +125,14 @@ function getApiUrl(env) {
|
|
|
49
125
|
* 'local' is treated as alias for 'dev'
|
|
50
126
|
*
|
|
51
127
|
* IMPORTANT: This function is STRICT to prevent accidental prod modifications.
|
|
52
|
-
* - undefined/null → defaults to '
|
|
128
|
+
* - undefined/null → defaults to config's currentEnv (set by Gufi AI)
|
|
53
129
|
* - 'prod', 'dev', 'local' → valid values
|
|
54
130
|
* - Any other value → THROWS ERROR (prevents typos like 'dve' from hitting prod)
|
|
55
131
|
*/
|
|
56
132
|
function resolveEnv(env) {
|
|
57
|
-
// Undefined/null → default to
|
|
133
|
+
// Undefined/null → default to config's currentEnv
|
|
58
134
|
if (env === undefined || env === null || env === "") {
|
|
59
|
-
return
|
|
135
|
+
return getDefaultEnv();
|
|
60
136
|
}
|
|
61
137
|
// Valid values
|
|
62
138
|
if (env === "dev" || env === "local")
|
|
@@ -70,7 +146,7 @@ function resolveEnv(env) {
|
|
|
70
146
|
}
|
|
71
147
|
// Keep for backwards compatibility (some internal functions may use this)
|
|
72
148
|
function getSessionApiUrl() {
|
|
73
|
-
return ENV_URLS[
|
|
149
|
+
return ENV_URLS[getDefaultEnv()];
|
|
74
150
|
}
|
|
75
151
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
76
152
|
const viewFilesCache = new Map();
|
|
@@ -183,7 +259,7 @@ async function trackAnalytics(data) {
|
|
|
183
259
|
// API Client (reused from CLI)
|
|
184
260
|
// ════════════════════════════════════════════════════════════════════════════
|
|
185
261
|
async function autoLogin() {
|
|
186
|
-
return autoLoginWithEnv(
|
|
262
|
+
return autoLoginWithEnv(getDefaultEnv());
|
|
187
263
|
}
|
|
188
264
|
async function autoLoginWithEnv(env) {
|
|
189
265
|
const resolvedEnv = resolveEnv(env);
|
|
@@ -210,7 +286,7 @@ async function autoLoginWithEnv(env) {
|
|
|
210
286
|
}
|
|
211
287
|
}
|
|
212
288
|
async function refreshOrLogin() {
|
|
213
|
-
return refreshOrLoginWithEnv(
|
|
289
|
+
return refreshOrLoginWithEnv(getDefaultEnv());
|
|
214
290
|
}
|
|
215
291
|
async function refreshOrLoginWithEnv(env) {
|
|
216
292
|
const resolvedEnv = resolveEnv(env);
|
|
@@ -552,22 +628,23 @@ const TOOLS = [
|
|
|
552
628
|
},
|
|
553
629
|
},
|
|
554
630
|
},
|
|
631
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
632
|
+
// Automations (Business Logic)
|
|
633
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
555
634
|
{
|
|
556
|
-
name: "
|
|
557
|
-
description: "
|
|
635
|
+
name: "gufi_automation_integrations",
|
|
636
|
+
description: getDesc("gufi_automation_integrations"),
|
|
558
637
|
inputSchema: {
|
|
559
638
|
type: "object",
|
|
560
639
|
properties: {
|
|
561
640
|
name: { type: "string", description: "Integration name to get detailed info (optional)" },
|
|
641
|
+
env: ENV_PARAM,
|
|
562
642
|
},
|
|
563
643
|
},
|
|
564
644
|
},
|
|
565
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
566
|
-
// Automations (Business Logic)
|
|
567
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
568
645
|
{
|
|
569
|
-
name: "
|
|
570
|
-
description: getDesc("
|
|
646
|
+
name: "gufi_automation_scripts",
|
|
647
|
+
description: getDesc("gufi_automation_scripts"),
|
|
571
648
|
inputSchema: {
|
|
572
649
|
type: "object",
|
|
573
650
|
properties: {
|
|
@@ -583,33 +660,54 @@ const TOOLS = [
|
|
|
583
660
|
},
|
|
584
661
|
},
|
|
585
662
|
{
|
|
586
|
-
name: "
|
|
587
|
-
description:
|
|
663
|
+
name: "gufi_automation_script_test",
|
|
664
|
+
description: `Ejecutar/testear un automation script manualmente.
|
|
665
|
+
|
|
666
|
+
Ejemplos:
|
|
667
|
+
# Con entidad (la mayoría de casos)
|
|
668
|
+
gufi_automation_script_test({
|
|
669
|
+
company_id: '116',
|
|
670
|
+
script_name: 'send_email',
|
|
671
|
+
entity: 'ventas.facturas', // nombre lógico, se resuelve automáticamente
|
|
672
|
+
trigger_event: 'on_click',
|
|
673
|
+
row: { id: 123, total: 150 },
|
|
674
|
+
input: { email_to: 'test@example.com' }
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
# Script standalone (sin entidad vinculada)
|
|
678
|
+
gufi_automation_script_test({
|
|
679
|
+
company_id: '116',
|
|
680
|
+
script_name: 'daily_report',
|
|
681
|
+
trigger_event: 'scheduled',
|
|
682
|
+
input: { date: '2024-01-15' }
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
IMPORTANTE: Usa nombres lógicos (module.entity) NO IDs físicos.`,
|
|
588
686
|
inputSchema: {
|
|
589
687
|
type: "object",
|
|
590
688
|
properties: {
|
|
591
689
|
company_id: { type: "string", description: "Company ID" },
|
|
592
690
|
script_name: { type: "string", description: "Script name to execute" },
|
|
593
|
-
entity: { type: "string", description: "Entity name (module.entity format
|
|
594
|
-
trigger_event: { type: "string", description: "Trigger event: on_create, on_update, on_delete, on_click" },
|
|
595
|
-
row: { type: "object", description: "Row data
|
|
596
|
-
input: { type: "object", description: "Input data
|
|
691
|
+
entity: { type: "string", description: "Entity name (module.entity format). Optional for standalone scripts." },
|
|
692
|
+
trigger_event: { type: "string", description: "Trigger event: on_create, on_update, on_delete, on_click, scheduled" },
|
|
693
|
+
row: { type: "object", description: "Row data (goes to context.row). Include 'id' field." },
|
|
694
|
+
input: { type: "object", description: "Input data (goes to context.input). For on_click or standalone scripts." },
|
|
597
695
|
env: ENV_PARAM,
|
|
598
696
|
},
|
|
599
|
-
required: ["company_id", "script_name", "
|
|
697
|
+
required: ["company_id", "script_name", "trigger_event"],
|
|
600
698
|
},
|
|
601
699
|
},
|
|
602
700
|
{
|
|
603
|
-
name: "
|
|
604
|
-
description: getDesc("
|
|
701
|
+
name: "gufi_automation_meta",
|
|
702
|
+
description: getDesc("gufi_automation_meta"),
|
|
605
703
|
inputSchema: {
|
|
606
704
|
type: "object",
|
|
607
705
|
properties: {
|
|
608
706
|
entity_id: { type: "string", description: "Entity ID" },
|
|
609
707
|
company_id: { type: "string", description: "Company ID" },
|
|
610
|
-
|
|
611
|
-
type: "
|
|
612
|
-
description: "
|
|
708
|
+
automations: {
|
|
709
|
+
type: "array",
|
|
710
|
+
description: "Array of automations to SET (omit to GET current). Each: { trigger: 'insert'|'update'|'delete'|'click'|'scheduled'|'custom', function_name: 'script_name' }. NOTA: CUSTOM se registra automáticamente al hacer view_push con core/automations.ts",
|
|
613
711
|
},
|
|
614
712
|
env: ENV_PARAM,
|
|
615
713
|
},
|
|
@@ -617,8 +715,8 @@ const TOOLS = [
|
|
|
617
715
|
},
|
|
618
716
|
},
|
|
619
717
|
{
|
|
620
|
-
name: "
|
|
621
|
-
description: getDesc("
|
|
718
|
+
name: "gufi_automation_executions",
|
|
719
|
+
description: getDesc("gufi_automation_executions"),
|
|
622
720
|
inputSchema: {
|
|
623
721
|
type: "object",
|
|
624
722
|
properties: {
|
|
@@ -710,6 +808,9 @@ Example: gufi_view_pull({ view_id: 13 })`,
|
|
|
710
808
|
Pushes changed files from ~/gufi-dev/view_<id>/ to Gufi draft.
|
|
711
809
|
Creates a snapshot for version history (last 10 kept).
|
|
712
810
|
|
|
811
|
+
⚠️ IMPORTANT: If core/automations.ts exists, declared automations are registered in __automation_meta__.
|
|
812
|
+
Only automations declared in core/automations.ts can be called via gufi.automation().
|
|
813
|
+
|
|
713
814
|
After pushing, use: gufi package:publish <package_id> to publish.
|
|
714
815
|
|
|
715
816
|
Example: gufi_view_push({ view_id: 13, message: "Fixed bug in chart" })`,
|
|
@@ -732,21 +833,35 @@ Example: gufi_view_push({ view_id: 13, message: "Fixed bug in chart" })`,
|
|
|
732
833
|
properties: {
|
|
733
834
|
view_id: { type: "number", description: "View ID to test" },
|
|
734
835
|
company_id: { type: "string", description: "Company ID for authentication context" },
|
|
735
|
-
timeout: { type: "number", description: "Navigation timeout in ms (default:
|
|
836
|
+
timeout: { type: "number", description: "Navigation timeout in ms (default: 25000)" },
|
|
736
837
|
actions: {
|
|
737
838
|
type: "array",
|
|
738
|
-
description:
|
|
839
|
+
description: `Actions to perform. IMPORTANT workflow:
|
|
840
|
+
1. First use 'explore' to list all interactive elements
|
|
841
|
+
2. Then use 'click' with text selector: {type:'click', selector:'text:Button Text'}
|
|
842
|
+
3. Use 'closeModals' to dismiss popups before interacting
|
|
843
|
+
|
|
844
|
+
Examples:
|
|
845
|
+
- Explore: {type:'explore'}
|
|
846
|
+
- Click by text: {type:'click', selector:'text:Submit'}
|
|
847
|
+
- Click by CSS: {type:'click', selector:'.my-button'}
|
|
848
|
+
- Close modals: {type:'closeModals'}
|
|
849
|
+
- Wait: {type:'delay', ms:1000}`,
|
|
739
850
|
items: {
|
|
740
851
|
type: "object",
|
|
741
852
|
properties: {
|
|
742
|
-
type: { type: "string", description: "Action type: click, fill, wait, delay" },
|
|
743
|
-
selector: { type: "string", description: "CSS selector for click
|
|
744
|
-
value: { type: "string", description: "Value for fill action" },
|
|
853
|
+
type: { type: "string", description: "Action type: explore (list elements), click, closeModals, fill, clear, select, wait, waitForText, delay, scroll, hover, screenshot, getText, eval" },
|
|
854
|
+
selector: { type: "string", description: "CSS selector OR 'text:Button Text' for click by visible text" },
|
|
855
|
+
value: { type: "string", description: "Value for fill/select action" },
|
|
856
|
+
text: { type: "string", description: "Text to wait for (waitForText)" },
|
|
745
857
|
ms: { type: "number", description: "Milliseconds for delay action" },
|
|
858
|
+
y: { type: "number", description: "Pixels to scroll (scroll without selector)" },
|
|
859
|
+
timeout: { type: "number", description: "Timeout for wait actions (default 5000)" },
|
|
860
|
+
code: { type: "string", description: "JavaScript code to evaluate (eval action)" },
|
|
746
861
|
},
|
|
747
862
|
},
|
|
748
863
|
},
|
|
749
|
-
capture_screenshot: { type: "boolean", description: "Include base64 screenshot (default:
|
|
864
|
+
capture_screenshot: { type: "boolean", description: "Include base64 screenshot (default: false)" },
|
|
750
865
|
env: ENV_PARAM,
|
|
751
866
|
},
|
|
752
867
|
required: ["view_id", "company_id"],
|
|
@@ -899,7 +1014,7 @@ const toolHandlers = {
|
|
|
899
1014
|
// Context & Info
|
|
900
1015
|
// ─────────────────────────────────────────────────────────────────────────
|
|
901
1016
|
async gufi_whoami(params) {
|
|
902
|
-
const env = params.env ||
|
|
1017
|
+
const env = params.env || getDefaultEnv();
|
|
903
1018
|
const config = loadConfig();
|
|
904
1019
|
const loggedIn = isLoggedIn();
|
|
905
1020
|
let companies = [];
|
|
@@ -1143,106 +1258,70 @@ const toolHandlers = {
|
|
|
1143
1258
|
hint: "Use gufi_docs({ topic: 'fields' }) to read about field types, or gufi_docs({ search: 'currency' }) to search.",
|
|
1144
1259
|
};
|
|
1145
1260
|
},
|
|
1146
|
-
|
|
1261
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1262
|
+
// Automations
|
|
1263
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1264
|
+
async gufi_automation_integrations(params) {
|
|
1147
1265
|
const { name } = params;
|
|
1148
|
-
//
|
|
1149
|
-
|
|
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
|
-
};
|
|
1266
|
+
// Read integration docs from centralized docs/dev-guide/
|
|
1267
|
+
const allDocs = getIntegrationDocs();
|
|
1199
1268
|
if (name) {
|
|
1200
|
-
|
|
1201
|
-
|
|
1269
|
+
// Get specific integration doc
|
|
1270
|
+
const doc = getIntegrationDoc(name);
|
|
1271
|
+
if (!doc) {
|
|
1272
|
+
// List available integrations on error
|
|
1273
|
+
const available = allDocs
|
|
1274
|
+
.filter(d => d.id !== 'integrations-overview')
|
|
1275
|
+
.map(d => d.id);
|
|
1202
1276
|
return {
|
|
1203
1277
|
error: `Integration '${name}' not found`,
|
|
1204
|
-
|
|
1278
|
+
available_integrations: available,
|
|
1279
|
+
hint: "Use one of the available integration names, or use gufi_automation_integrations() to see all",
|
|
1205
1280
|
};
|
|
1206
1281
|
}
|
|
1207
1282
|
return {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
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
|
-
}`,
|
|
1283
|
+
name: doc.id,
|
|
1284
|
+
title: doc.title,
|
|
1285
|
+
description: doc.description,
|
|
1286
|
+
documentation: doc.content,
|
|
1287
|
+
usage: {
|
|
1288
|
+
step1: "Set up credentials in Environment Variables",
|
|
1289
|
+
step2: `In automation script: api.${doc.id}.methodName({ params })`,
|
|
1290
|
+
step3: "See documentation above for methods and examples",
|
|
1291
|
+
},
|
|
1223
1292
|
};
|
|
1224
1293
|
}
|
|
1225
1294
|
// List all integrations
|
|
1295
|
+
const integrations = allDocs.filter(d => d.id !== 'integrations-overview');
|
|
1296
|
+
const overview = allDocs.find(d => d.id === 'integrations-overview');
|
|
1226
1297
|
return {
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
description:
|
|
1230
|
-
|
|
1231
|
-
|
|
1298
|
+
overview: overview ? {
|
|
1299
|
+
title: overview.title,
|
|
1300
|
+
description: overview.description,
|
|
1301
|
+
content: overview.content,
|
|
1302
|
+
} : {
|
|
1303
|
+
description: "Connect automations to external services",
|
|
1304
|
+
usage: "api.{integration}.{method}() or api.http() for custom",
|
|
1305
|
+
credentials: "Use env.* variables for API keys",
|
|
1306
|
+
},
|
|
1307
|
+
integrations: integrations.map(d => ({
|
|
1308
|
+
name: d.id,
|
|
1309
|
+
title: d.title,
|
|
1310
|
+
description: d.description,
|
|
1232
1311
|
})),
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
}
|
|
1312
|
+
custom_http: {
|
|
1313
|
+
description: "For APIs without built-in integration, use api.http()",
|
|
1314
|
+
example: `await api.http({
|
|
1315
|
+
url: 'https://api.example.com/endpoint',
|
|
1316
|
+
method: 'POST',
|
|
1317
|
+
headers: { Authorization: 'Bearer ' + env.API_KEY },
|
|
1318
|
+
body: { data: 'value' }
|
|
1319
|
+
})`,
|
|
1320
|
+
},
|
|
1321
|
+
hint: "Use gufi_automation_integrations({ name: 'stripe' }) for complete documentation of an integration",
|
|
1240
1322
|
};
|
|
1241
1323
|
},
|
|
1242
|
-
|
|
1243
|
-
// Automations
|
|
1244
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
1245
|
-
async gufi_automation(params) {
|
|
1324
|
+
async gufi_automation_scripts(params) {
|
|
1246
1325
|
const { action, company_id, id, name, code, description, env } = params;
|
|
1247
1326
|
switch (action) {
|
|
1248
1327
|
case "list": {
|
|
@@ -1322,45 +1401,51 @@ const result = await api.stripe.createCheckoutSession({
|
|
|
1322
1401
|
throw new Error(`Unknown action: ${action}. Use: list, get, create, update, delete`);
|
|
1323
1402
|
}
|
|
1324
1403
|
},
|
|
1325
|
-
async
|
|
1404
|
+
async gufi_automation_script_test(params) {
|
|
1326
1405
|
const { company_id, script_name, entity, trigger_event, row, input, env } = params;
|
|
1327
|
-
//
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
throw new Error("Cannot get schema. Make sure company_id is correct.");
|
|
1331
|
-
}
|
|
1332
|
-
// Parse entity (module.entity format)
|
|
1333
|
-
const parts = entity.split(".");
|
|
1334
|
-
if (parts.length !== 2) {
|
|
1335
|
-
throw new Error(`Invalid entity format: "${entity}". Use module.entity (e.g., 'ventas.facturas')`);
|
|
1406
|
+
// Validate: need at least row or input
|
|
1407
|
+
if (!row && !input) {
|
|
1408
|
+
throw new Error("Must provide 'row' or 'input' (or both)");
|
|
1336
1409
|
}
|
|
1337
|
-
const [moduleName, entityName] = parts;
|
|
1338
|
-
// Find module and entity IDs
|
|
1339
1410
|
let moduleId = null;
|
|
1340
1411
|
let tableId = null;
|
|
1341
1412
|
let tableName = null;
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
if (!
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1413
|
+
// Resolve entity if provided (optional for standalone scripts)
|
|
1414
|
+
if (entity) {
|
|
1415
|
+
const schema = await getCompanySchema(company_id, env);
|
|
1416
|
+
if (!schema?.modules) {
|
|
1417
|
+
throw new Error("Cannot get schema. Make sure company_id is correct.");
|
|
1418
|
+
}
|
|
1419
|
+
// Parse entity (module.entity format)
|
|
1420
|
+
const parts = entity.split(".");
|
|
1421
|
+
if (parts.length !== 2) {
|
|
1422
|
+
throw new Error(`Invalid entity format: "${entity}". Use module.entity (e.g., 'ventas.facturas')`);
|
|
1423
|
+
}
|
|
1424
|
+
const [moduleName, entityName] = parts;
|
|
1425
|
+
// Find module and entity IDs
|
|
1426
|
+
for (const mod of schema.modules) {
|
|
1427
|
+
const modMatch = mod.name?.toLowerCase() === moduleName.toLowerCase() ||
|
|
1428
|
+
mod.label?.toLowerCase() === moduleName.toLowerCase();
|
|
1429
|
+
if (!modMatch)
|
|
1430
|
+
continue;
|
|
1431
|
+
moduleId = mod.id;
|
|
1432
|
+
for (const ent of mod.entities || []) {
|
|
1433
|
+
const entMatch = ent.name?.toLowerCase() === entityName.toLowerCase() ||
|
|
1434
|
+
ent.label?.toLowerCase() === entityName.toLowerCase();
|
|
1435
|
+
if (entMatch) {
|
|
1436
|
+
tableId = ent.id;
|
|
1437
|
+
tableName = `m${moduleId}_t${tableId}`;
|
|
1438
|
+
break;
|
|
1439
|
+
}
|
|
1355
1440
|
}
|
|
1441
|
+
if (tableId)
|
|
1442
|
+
break;
|
|
1443
|
+
}
|
|
1444
|
+
if (!moduleId || !tableId) {
|
|
1445
|
+
throw new Error(`Entity "${entity}" not found in schema`);
|
|
1356
1446
|
}
|
|
1357
|
-
if (tableId)
|
|
1358
|
-
break;
|
|
1359
|
-
}
|
|
1360
|
-
if (!moduleId || !tableId) {
|
|
1361
|
-
throw new Error(`Entity "${entity}" not found in schema`);
|
|
1362
1447
|
}
|
|
1363
|
-
// Call test endpoint
|
|
1448
|
+
// Call test endpoint
|
|
1364
1449
|
const response = await apiRequest("/api/automation-scripts/test", {
|
|
1365
1450
|
method: "POST",
|
|
1366
1451
|
body: JSON.stringify({
|
|
@@ -1370,29 +1455,37 @@ const result = await api.stripe.createCheckoutSession({
|
|
|
1370
1455
|
trigger_event,
|
|
1371
1456
|
table_id: tableId,
|
|
1372
1457
|
table_name: tableName,
|
|
1373
|
-
row,
|
|
1458
|
+
row: row || null,
|
|
1374
1459
|
input: input || null,
|
|
1375
1460
|
}),
|
|
1376
1461
|
headers: { "X-Company-ID": company_id },
|
|
1377
1462
|
}, company_id, true, env);
|
|
1378
|
-
|
|
1463
|
+
const result = {
|
|
1379
1464
|
success: true,
|
|
1380
1465
|
script_name,
|
|
1381
|
-
entity,
|
|
1382
1466
|
trigger_event,
|
|
1383
|
-
result: response.data || response,
|
|
1384
1467
|
};
|
|
1468
|
+
if (entity) {
|
|
1469
|
+
result.entity = entity;
|
|
1470
|
+
result.resolved_table = tableName;
|
|
1471
|
+
}
|
|
1472
|
+
// Include script output if available
|
|
1473
|
+
const scriptOutput = response.data || response;
|
|
1474
|
+
if (scriptOutput && Object.keys(scriptOutput).length > 0) {
|
|
1475
|
+
result.output = scriptOutput;
|
|
1476
|
+
}
|
|
1477
|
+
return result;
|
|
1385
1478
|
},
|
|
1386
|
-
async
|
|
1387
|
-
const { entity_id, company_id,
|
|
1388
|
-
if (
|
|
1389
|
-
// SET triggers
|
|
1479
|
+
async gufi_automation_meta(params) {
|
|
1480
|
+
const { entity_id, company_id, automations, env } = params;
|
|
1481
|
+
if (automations) {
|
|
1482
|
+
// SET triggers - backend expects array: [{ trigger: 'insert', function_name: 'my_script' }]
|
|
1390
1483
|
await apiRequest(`/api/entities/${entity_id}/automations`, {
|
|
1391
1484
|
method: "PUT",
|
|
1392
|
-
body: JSON.stringify(
|
|
1485
|
+
body: JSON.stringify(automations),
|
|
1393
1486
|
headers: { "X-Company-ID": company_id },
|
|
1394
1487
|
}, company_id, true, env);
|
|
1395
|
-
return { success: true, entity_id,
|
|
1488
|
+
return { success: true, entity_id, automations };
|
|
1396
1489
|
}
|
|
1397
1490
|
else {
|
|
1398
1491
|
// GET triggers
|
|
@@ -1407,7 +1500,7 @@ const result = await api.stripe.createCheckoutSession({
|
|
|
1407
1500
|
};
|
|
1408
1501
|
}
|
|
1409
1502
|
},
|
|
1410
|
-
async
|
|
1503
|
+
async gufi_automation_executions(params) {
|
|
1411
1504
|
let endpoint = `/api/automation-executions?limit=${params.limit || 20}`;
|
|
1412
1505
|
if (params.script_name) {
|
|
1413
1506
|
endpoint += `&script=${encodeURIComponent(params.script_name)}`;
|
|
@@ -1581,18 +1674,23 @@ const result = await api.stripe.createCheckoutSession({
|
|
|
1581
1674
|
if (!view || !view.pk_id) {
|
|
1582
1675
|
return { error: `View ${viewId} not found` };
|
|
1583
1676
|
}
|
|
1584
|
-
|
|
1585
|
-
const
|
|
1586
|
-
//
|
|
1587
|
-
const
|
|
1677
|
+
// 💜 Get view files (returns ViewFile[] directly)
|
|
1678
|
+
const files = await getViewFiles(viewId);
|
|
1679
|
+
// 💜 Save to user's workspace via API (saves to views/view_<id>/ subfolder)
|
|
1680
|
+
const saveResponse = await apiRequest(`/api/claude/workspace/view`, {
|
|
1681
|
+
method: "POST",
|
|
1682
|
+
body: JSON.stringify({ view_id: viewId, files }),
|
|
1683
|
+
}, companyId, true, env);
|
|
1684
|
+
const saveResult = saveResponse.data || saveResponse;
|
|
1685
|
+
const viewFolder = saveResult?.folder || `views/view_${viewId}`;
|
|
1588
1686
|
return {
|
|
1589
1687
|
success: true,
|
|
1590
1688
|
view_id: viewId,
|
|
1591
1689
|
name: view.name,
|
|
1592
|
-
package_id:
|
|
1593
|
-
local_path:
|
|
1594
|
-
files_count:
|
|
1595
|
-
_hint: `📥 View downloaded to
|
|
1690
|
+
package_id: view.package_id || null,
|
|
1691
|
+
local_path: `~/workspace/${viewFolder}`,
|
|
1692
|
+
files_count: files.length,
|
|
1693
|
+
_hint: `📥 View downloaded to workspace/${viewFolder}/. Use Read/Edit tools to modify files. Then gufi_view_push({ view_id: ${viewId} }) to upload.`,
|
|
1596
1694
|
};
|
|
1597
1695
|
},
|
|
1598
1696
|
async gufi_view_push(params) {
|
|
@@ -1640,9 +1738,9 @@ const result = await api.stripe.createCheckoutSession({
|
|
|
1640
1738
|
body: JSON.stringify({
|
|
1641
1739
|
view_id,
|
|
1642
1740
|
company_id,
|
|
1643
|
-
timeout: timeout ||
|
|
1741
|
+
timeout: timeout || 25000,
|
|
1644
1742
|
actions: actions || [],
|
|
1645
|
-
capture_screenshot: capture_screenshot
|
|
1743
|
+
capture_screenshot: capture_screenshot === true,
|
|
1646
1744
|
}),
|
|
1647
1745
|
}, company_id, true, env);
|
|
1648
1746
|
// Format response for Claude readability
|
|
@@ -1673,6 +1771,10 @@ const result = await api.stripe.createCheckoutSession({
|
|
|
1673
1771
|
}
|
|
1674
1772
|
// Add DOM info
|
|
1675
1773
|
response.dom = result.dom;
|
|
1774
|
+
// Add action results (eval results, getText, etc.)
|
|
1775
|
+
if (result.actions?.length > 0) {
|
|
1776
|
+
response.actions = result.actions;
|
|
1777
|
+
}
|
|
1676
1778
|
// Add screenshot (Claude can view base64 images)
|
|
1677
1779
|
if (result.screenshot) {
|
|
1678
1780
|
response.screenshot = result.screenshot;
|
|
@@ -1937,7 +2039,7 @@ async function generateViewContextMcp(viewId, includeConcepts, env) {
|
|
|
1937
2039
|
}
|
|
1938
2040
|
// If has automations, suggest viewing them
|
|
1939
2041
|
if (autoCount > 0 && viewCompanyId) {
|
|
1940
|
-
nextActions.push(`See automations:
|
|
2042
|
+
nextActions.push(`See automations: gufi_automation_scripts({ action: "list", company_id: "${viewCompanyId}" })`);
|
|
1941
2043
|
}
|
|
1942
2044
|
// Always: docs for field types
|
|
1943
2045
|
nextActions.push(`Field types reference: gufi_docs({ topic: "fields" })`);
|
|
@@ -2040,89 +2142,25 @@ async function generatePackageContextMcp(packageId, includeConcepts, env) {
|
|
|
2040
2142
|
return result;
|
|
2041
2143
|
}
|
|
2042
2144
|
async function generateCompanyContextMcp(companyId, includeConcepts) {
|
|
2043
|
-
// Use /api/
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
const modulesRaw = schemaResponse.modules || schemaResponse.data?.modules || [];
|
|
2145
|
+
// 💜 Use centralized /api/schema/export-claude endpoint
|
|
2146
|
+
// This returns the same complete text as "Copy All for Claude" in the frontend
|
|
2147
|
+
const exportText = await apiRequest(`/api/schema/export-claude`, {}, String(companyId));
|
|
2148
|
+
// Get automation scripts for summary
|
|
2048
2149
|
let automations = [];
|
|
2049
2150
|
try {
|
|
2050
2151
|
const automationsResponse = await apiRequest(`/api/automation-scripts`, {}, String(companyId));
|
|
2051
2152
|
automations = Array.isArray(automationsResponse) ? automationsResponse : automationsResponse.data || [];
|
|
2052
2153
|
}
|
|
2053
2154
|
catch { }
|
|
2054
|
-
const modulesInfo = [];
|
|
2055
|
-
for (const mod of modulesRaw) {
|
|
2056
|
-
// Find moduleId from entities
|
|
2057
|
-
let moduleId;
|
|
2058
|
-
for (const sub of mod.submodules || []) {
|
|
2059
|
-
for (const ent of sub.entities || []) {
|
|
2060
|
-
if (ent.ui?.moduleId) {
|
|
2061
|
-
moduleId = ent.ui.moduleId;
|
|
2062
|
-
break;
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
if (moduleId)
|
|
2066
|
-
break;
|
|
2067
|
-
}
|
|
2068
|
-
const entities = [];
|
|
2069
|
-
for (const sub of mod.submodules || []) {
|
|
2070
|
-
for (const ent of sub.entities || []) {
|
|
2071
|
-
const entityId = ent.ui?.entityId || ent.pk_id || ent.id;
|
|
2072
|
-
entities.push({
|
|
2073
|
-
name: ent.name,
|
|
2074
|
-
label: ent.label,
|
|
2075
|
-
table: moduleId && entityId ? `m${moduleId}_t${entityId}` : null,
|
|
2076
|
-
fieldCount: ent.fields?.length || 0,
|
|
2077
|
-
fields: (ent.fields || []).slice(0, 10).map((f) => ({
|
|
2078
|
-
name: f.name,
|
|
2079
|
-
type: f.type,
|
|
2080
|
-
required: f.required || false,
|
|
2081
|
-
})),
|
|
2082
|
-
});
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
modulesInfo.push({
|
|
2086
|
-
id: moduleId,
|
|
2087
|
-
name: mod.name || mod.displayName,
|
|
2088
|
-
displayName: mod.displayName || mod.label,
|
|
2089
|
-
entities,
|
|
2090
|
-
});
|
|
2091
|
-
}
|
|
2092
|
-
// Build summary and next_actions
|
|
2093
|
-
const totalEntities = modulesInfo.reduce((acc, m) => acc + (m.entities?.length || 0), 0);
|
|
2094
|
-
const summary = [
|
|
2095
|
-
`Company ID: ${companyId}`,
|
|
2096
|
-
`${modulesInfo.length} module(s)`,
|
|
2097
|
-
`${totalEntities} table(s)`,
|
|
2098
|
-
`${automations.length} automation(s)`,
|
|
2099
|
-
].join(" | ");
|
|
2100
|
-
const nextActions = [];
|
|
2101
|
-
// Suggest viewing data from first table
|
|
2102
|
-
if (modulesInfo.length > 0 && modulesInfo[0].entities?.length > 0) {
|
|
2103
|
-
const firstTable = modulesInfo[0].entities[0].table;
|
|
2104
|
-
if (firstTable) {
|
|
2105
|
-
nextActions.push(`View data: gufi_rows({ table: "${firstTable}", limit: 5 })`);
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
// If has modules, suggest editing one
|
|
2109
|
-
if (modulesInfo.length > 0 && modulesInfo[0].id) {
|
|
2110
|
-
nextActions.push(`Edit module structure: gufi_module({ module_id: "${modulesInfo[0].id}" })`);
|
|
2111
|
-
}
|
|
2112
|
-
// If has automations, suggest viewing them
|
|
2113
|
-
if (automations.length > 0) {
|
|
2114
|
-
nextActions.push(`View automation code: gufi_automation({ automation_id: "${automations[0].id}" })`);
|
|
2115
|
-
}
|
|
2116
|
-
nextActions.push(`Field types reference: gufi_docs({ topic: "fields" })`);
|
|
2117
2155
|
const result = {
|
|
2118
2156
|
type: "company",
|
|
2119
|
-
summary,
|
|
2120
|
-
next_actions: nextActions,
|
|
2121
2157
|
company_id: companyId,
|
|
2122
|
-
|
|
2123
|
-
|
|
2158
|
+
// The full schema export with ALL details (fields, automations, permissions, etc.)
|
|
2159
|
+
schema: exportText,
|
|
2160
|
+
// Also provide structured data for programmatic access
|
|
2161
|
+
automation_scripts: automations.map((a) => ({
|
|
2124
2162
|
id: a.id,
|
|
2125
|
-
name: a.name,
|
|
2163
|
+
name: a.name || a.function_name,
|
|
2126
2164
|
description: a.description,
|
|
2127
2165
|
})),
|
|
2128
2166
|
commands: {
|