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 CHANGED
@@ -10,7 +10,7 @@
10
10
  tools/cli/
11
11
  ├── src/
12
12
  │ ├── index.ts # Entry point CLI
13
- │ ├── mcp.ts # MCP Server (12 tools)
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 - 13 Tools
58
+ ## MCP Server - 15 Tools
59
59
 
60
- El servidor MCP está en `src/mcp.ts` y expone **13 tools** para que Claude interactúe con Gufi.
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** | `gufi_automation` | Scripts: list, get, create |
71
- | | `gufi_triggers` | Ver/configurar triggers |
72
- | | `gufi_executions` | Historial de ejecuciones |
73
- | **Data** | `gufi_data` | CRUD: list, get, create, update, delete |
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
- | `gufi_automations` | Listar scripts de automation |
513
- | `gufi_automation` | Ver código de un automation |
514
- | `gufi_automation_create` | Crear/actualizar automation |
515
- | `gufi_entity_automations` | Ver triggers de una entidad |
516
- | `gufi_entity_automations_update` | Actualizar triggers |
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 gufi_automations para ver existentes, crea código, usa gufi_automation_create
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 via triggerClick endpoint
1499
+ // 💜 Execute automation
1500
1500
  console.log(chalk.yellow("\n ⏳ Ejecutando..."));
1501
- const result = await apiRequest("/api/automation-scripts/triggerClick", {
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({
@@ -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 de tipo \`click\` desde la vista:\n`;
370
+ md += `Puedes ejecutar automations desde la vista con \`gufi.automation()\`:\n`;
371
371
  md += "```typescript\n";
372
- md += `await gufi.dataProvider.runClickAutomation?.({\n`;
373
- md += ` company_id: companyId,\n`;
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
- const GUFI_DEV_DIR = path.join(os.homedir(), "gufi-dev");
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 { pullView, pushView, getViewDir, loadViewMeta } from "./lib/sync.js";
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 - always prod for safety
36
- const DEFAULT_ENV = "prod";
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' (default) or 'dev' (localhost:3000 Cloud SQL Dev)
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 'prod' (backwards compatible)
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 prod (backwards compatible)
133
+ // Undefined/null → default to config's currentEnv
58
134
  if (env === undefined || env === null || env === "") {
59
- return "prod";
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[DEFAULT_ENV];
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(DEFAULT_ENV);
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(DEFAULT_ENV);
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: "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.",
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: "gufi_automation",
570
- description: getDesc("gufi_automation"),
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: "gufi_automation_test",
587
- description: "Ejecutar/testear un automation script manualmente.\n\nEjemplo:\ngufi_automation_test({\n company_id: '116',\n script_name: 'send_email',\n entity: 'ventas.facturas',\n trigger_event: 'on_click',\n row: { id: 123, total: 150 },\n input: { email_to: 'test@example.com' } // Para on_click\n})",
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 like 'ventas.facturas')" },
594
- trigger_event: { type: "string", description: "Trigger event: on_create, on_update, on_delete, on_click" },
595
- row: { type: "object", description: "Row data to pass to the script (must include id)" },
596
- input: { type: "object", description: "Input data for on_click triggers (goes to context.input)" },
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", "entity", "trigger_event", "row"],
697
+ required: ["company_id", "script_name", "trigger_event"],
600
698
  },
601
699
  },
602
700
  {
603
- name: "gufi_triggers",
604
- description: getDesc("gufi_triggers"),
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
- config: {
611
- type: "object",
612
- description: "Trigger config to SET (omit to GET current config). Format: { on_create: [...], on_update: [...], on_delete: [...], on_click: [...] }",
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: "gufi_executions",
621
- description: getDesc("gufi_executions"),
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: 15000)" },
836
+ timeout: { type: "number", description: "Navigation timeout in ms (default: 25000)" },
736
837
  actions: {
737
838
  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}]",
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/fill/wait" },
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: true)" },
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 || DEFAULT_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
- async gufi_integrations(params) {
1261
+ // ─────────────────────────────────────────────────────────────────────────
1262
+ // Automations
1263
+ // ─────────────────────────────────────────────────────────────────────────
1264
+ async gufi_automation_integrations(params) {
1147
1265
  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
- };
1266
+ // Read integration docs from centralized docs/dev-guide/
1267
+ const allDocs = getIntegrationDocs();
1199
1268
  if (name) {
1200
- const integration = integrations[name];
1201
- if (!integration) {
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
- available: Object.keys(integrations),
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
- ...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
- }`,
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
- 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),
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
- 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
- });`,
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 gufi_automation_test(params) {
1404
+ async gufi_automation_script_test(params) {
1326
1405
  const { company_id, script_name, entity, trigger_event, row, input, env } = params;
1327
- // Resolve entity to get module_id and table_id
1328
- const schema = await getCompanySchema(company_id, env);
1329
- if (!schema?.modules) {
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
- for (const mod of schema.modules) {
1343
- const modMatch = mod.name?.toLowerCase() === moduleName.toLowerCase() ||
1344
- mod.label?.toLowerCase() === moduleName.toLowerCase();
1345
- if (!modMatch)
1346
- continue;
1347
- moduleId = mod.id;
1348
- for (const ent of mod.entities || []) {
1349
- const entMatch = ent.name?.toLowerCase() === entityName.toLowerCase() ||
1350
- ent.label?.toLowerCase() === entityName.toLowerCase();
1351
- if (entMatch) {
1352
- tableId = ent.id;
1353
- tableName = `m${moduleId}_t${tableId}`;
1354
- break;
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 (same as backend: accepts row and/or input)
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
- return {
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 gufi_triggers(params) {
1387
- const { entity_id, company_id, config, env } = params;
1388
- if (config) {
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(config),
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, config };
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 gufi_executions(params) {
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
- const viewKey = `view_${viewId}`;
1585
- const packageId = view.package_id || 0;
1586
- // Pull using sync.ts
1587
- const result = await pullView(viewId, viewKey, packageId);
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: packageId || null,
1593
- local_path: result.dir,
1594
- files_count: result.fileCount,
1595
- _hint: `📥 View downloaded to ${result.dir}. Use Read/Edit tools to modify files. Then gufi_view_push({ view_id: ${viewId} }) to upload.`,
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 || 15000,
1741
+ timeout: timeout || 25000,
1644
1742
  actions: actions || [],
1645
- capture_screenshot: capture_screenshot !== false,
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: gufi_automations({ company_id: "${viewCompanyId}" })`);
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/company/schema which has full entity info
2044
- const schemaResponse = await apiRequest(`/api/company/schema`, {
2045
- headers: { "X-Company-ID": String(companyId) },
2046
- }, String(companyId));
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
- modules: modulesInfo,
2123
- automations: automations.map((a) => ({
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: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.40",
3
+ "version": "0.1.43",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"