gufi-cli 0.1.18 → 0.1.21

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
@@ -11,9 +11,13 @@
11
11
  ## Quick Reference
12
12
 
13
13
  ```bash
14
- # Contexto y diagnóstico
14
+ # Contexto, diagnóstico y documentación
15
15
  gufi context # Genera contexto para Claude
16
16
  gufi doctor # Diagnóstico del sistema
17
+ gufi docs # Ver topics disponibles
18
+ gufi docs fields # Documentación de tipos de campo
19
+ gufi docs errors # Errores comunes y soluciones
20
+ gufi docs --search "currency" # Buscar en toda la documentación
17
21
 
18
22
  # Entornos
19
23
  gufi config # Ver entornos
@@ -68,12 +72,13 @@ El CLI incluye un servidor MCP que permite a Claude interactuar **directamente**
68
72
  claude mcp add --transport stdio gufi -- gufi mcp
69
73
  ```
70
74
 
71
- ### Tools disponibles (42 tools)
75
+ ### Tools disponibles (43 tools)
72
76
 
73
77
  **Contexto e Info (EMPEZAR AQUÍ):**
74
78
  - `gufi_context` - **USAR PRIMERO** - Genera contexto inteligente del proyecto
75
79
  - `gufi_whoami` - Usuario y entorno actual
76
80
  - `gufi_schema` - Estructura de tablas/campos
81
+ - `gufi_docs` - **LEER DOCUMENTACIÓN** - Topics: fields, views, automations, errors, examples
77
82
 
78
83
  **Companies:**
79
84
  - `gufi_companies` - Listar companies
@@ -149,7 +154,7 @@ Claude: [usa gufi_schema para encontrar tabla de clientes]
149
154
  Al crear o actualizar datos, usa los formatos correctos:
150
155
 
151
156
  **Simples:**
152
- - text, textarea, email, url, barcode: `"string"`
157
+ - text, email, url, barcode: `"string"`
153
158
  - number_int: `42` | number_float: `3.14` | percentage: `0.75`
154
159
  - boolean: `true/false`
155
160
  - date: `"2024-01-15"` | datetime: `"2024-01-15T10:30:00Z"` | time: `"14:30:00"`
@@ -185,6 +190,8 @@ gufi.CB.multiselect('a', 'b') // → ['a', 'b']
185
190
  |-----------|---------|
186
191
  | **Contexto para Claude** | `gufi context` |
187
192
  | **Diagnóstico** | `gufi doctor` |
193
+ | **Leer documentación** | `gufi docs fields` |
194
+ | **Buscar en docs** | `gufi docs --search "currency"` |
188
195
  | Ver companies | `gufi companies` |
189
196
  | Ver módulos | `gufi modules 146` |
190
197
  | Editar módulo | `gufi module 360 --edit` |
@@ -62,3 +62,9 @@ export declare function automationsExecutionsCommand(options?: {
62
62
  limit?: string;
63
63
  script?: string;
64
64
  }): Promise<void>;
65
+ export declare function automationExecuteCommand(automationIdOrName?: string, options?: {
66
+ company?: string;
67
+ input?: string;
68
+ wait?: boolean;
69
+ timeout?: string;
70
+ }): Promise<void>;
@@ -5,30 +5,13 @@
5
5
  */
6
6
  import chalk from "chalk";
7
7
  import { isLoggedIn, getApiUrl, getToken, getRefreshToken, setToken, loadConfig } from "../lib/config.js";
8
- import { COLUMN_TYPE_NAMES } from "@gufi/column-types";
9
- // 💜 Valid column types from @gufi/column-types + special types
10
- const VALID_COLUMN_TYPES = new Set([
11
- ...COLUMN_TYPE_NAMES,
12
- "gufi_id", // Special auto-generated ID type
13
- "user", // Alias for users (singular)
14
- "image", // Alias for file with image handling
15
- ]);
16
- // 💜 Suggestions for common mistakes
17
- const TYPE_SUGGESTIONS = {
18
- "number_decimal": "number_precise", // For financial values
19
- "decimal": "number_precise",
20
- "float": "number_float",
21
- "int": "number_int",
22
- "integer": "number_int",
23
- "string": "text",
24
- "varchar": "text",
25
- "bool": "boolean",
26
- "timestamp": "datetime",
27
- "array": "multiselect",
28
- };
8
+ import { getValidColumnTypes, getTypeSuggestions } from "../lib/columnTypes.js";
29
9
  const API_URL_BASE = "https://gogufi.com";
30
- function validateModuleColumnTypes(moduleJson) {
10
+ async function validateModuleColumnTypes(moduleJson) {
31
11
  const errors = [];
12
+ // Fetch valid types and suggestions from API
13
+ const validTypes = await getValidColumnTypes();
14
+ const suggestions = await getTypeSuggestions();
32
15
  // Iterate through submodules
33
16
  for (const submodule of moduleJson.submodules || []) {
34
17
  // Iterate through entities
@@ -40,12 +23,12 @@ function validateModuleColumnTypes(moduleJson) {
40
23
  for (const field of entity.fields) {
41
24
  if (!field.type)
42
25
  continue;
43
- if (!VALID_COLUMN_TYPES.has(field.type)) {
26
+ if (!validTypes.has(field.type)) {
44
27
  errors.push({
45
28
  entity: entity.name || entity.label,
46
29
  field: field.name,
47
30
  invalidType: field.type,
48
- suggestion: TYPE_SUGGESTIONS[field.type],
31
+ suggestion: suggestions[field.type],
49
32
  });
50
33
  }
51
34
  }
@@ -646,14 +629,14 @@ async function editModuleJson(moduleId, currentJson, companyId) {
646
629
  console.log(chalk.red("\n ✗ JSON inválido. Cambios no guardados.\n"));
647
630
  return;
648
631
  }
649
- // 💜 Validate column types before saving
650
- const typeErrors = validateModuleColumnTypes(newJson);
632
+ // 💜 Validate column types before saving (fetches from API)
633
+ const typeErrors = await validateModuleColumnTypes(newJson);
651
634
  if (typeErrors.length > 0) {
652
635
  console.log(chalk.red("\n ✗ Tipos de columna inválidos:\n"));
653
636
  for (const err of typeErrors) {
654
637
  const suggestion = err.suggestion
655
638
  ? chalk.cyan(` → Usa "${err.suggestion}" en su lugar`)
656
- : chalk.gray(` (tipos válidos: text, number_int, number_float, number_precise, currency, select...)`);
639
+ : chalk.gray(` (tipos válidos: text, number_int, number_float, currency, select...)`);
657
640
  console.log(chalk.yellow(` • ${err.entity}.${err.field}: "${err.invalidType}" no existe${suggestion}`));
658
641
  }
659
642
  console.log(chalk.gray(`\n 💡 Ver todos los tipos: https://github.com/juanbp23/gogufi/blob/main/libs/column-types/CLAUDE.md`));
@@ -743,15 +726,15 @@ export async function moduleUpdateCommand(moduleId, jsonFile, options) {
743
726
  process.exit(1);
744
727
  }
745
728
  console.log(chalk.green(" ✓ JSON válido"));
746
- // 💜 Validate column types
729
+ // 💜 Validate column types (fetches from API)
747
730
  console.log(chalk.gray(" Validando tipos de columna..."));
748
- const typeErrors = validateModuleColumnTypes(newJson);
731
+ const typeErrors = await validateModuleColumnTypes(newJson);
749
732
  if (typeErrors.length > 0) {
750
733
  console.log(chalk.red("\n ✗ Tipos de columna inválidos:\n"));
751
734
  for (const err of typeErrors) {
752
735
  const suggestion = err.suggestion
753
736
  ? chalk.cyan(` → Usa "${err.suggestion}" en su lugar`)
754
- : chalk.gray(` (tipos válidos: text, number_int, number_float, number_precise, currency, select...)`);
737
+ : chalk.gray(` (tipos válidos: text, number_int, number_float, currency, select...)`);
755
738
  console.log(chalk.yellow(` • ${err.entity}.${err.field}: "${err.invalidType}" no existe${suggestion}`));
756
739
  }
757
740
  console.log(chalk.gray(`\n 💡 Ver todos los tipos: https://github.com/juanbp23/gogufi/blob/main/libs/column-types/CLAUDE.md\n`));
@@ -1372,6 +1355,204 @@ export async function automationsExecutionsCommand(options) {
1372
1355
  console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
1373
1356
  }
1374
1357
  }
1358
+ // ════════════════════════════════════════════════════════════════════
1359
+ // gufi automation:execute <id_or_name> - Execute an automation script
1360
+ // 💜 Runs via pg-boss queue, returns jobId and polls for result
1361
+ // ════════════════════════════════════════════════════════════════════
1362
+ export async function automationExecuteCommand(automationIdOrName, options) {
1363
+ if (!isLoggedIn()) {
1364
+ console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
1365
+ process.exit(1);
1366
+ }
1367
+ if (!automationIdOrName) {
1368
+ console.log(chalk.red("\n ✗ Uso: gufi automation:execute <id_or_name> [--input '{...}'] [--wait]\n"));
1369
+ console.log(chalk.gray(" Ejemplos:"));
1370
+ console.log(chalk.gray(" gufi automation:execute 26"));
1371
+ console.log(chalk.gray(" gufi automation:execute nayax_sync_machine_products --wait"));
1372
+ console.log(chalk.gray(" gufi automation:execute 26 --input '{\"foo\": \"bar\"}'\n"));
1373
+ process.exit(1);
1374
+ }
1375
+ console.log(chalk.magenta(`\n 🟣 Ejecutando automation: ${automationIdOrName}\n`));
1376
+ try {
1377
+ // 💜 Detect company and get automation info
1378
+ console.log(chalk.gray(" Buscando automation..."));
1379
+ let companyId = options?.company;
1380
+ let automation;
1381
+ let entityId;
1382
+ let moduleId;
1383
+ // 💜 If company specified, search only in that company
1384
+ if (companyId) {
1385
+ const data = await apiRequest("/api/automation-scripts", {
1386
+ headers: { "X-Company-ID": companyId },
1387
+ });
1388
+ const automations = Array.isArray(data) ? data : data.data || [];
1389
+ const isNumericId = /^\d+$/.test(automationIdOrName);
1390
+ automation = automations.find((a) => isNumericId ? a.id === parseInt(automationIdOrName) : a.name === automationIdOrName);
1391
+ }
1392
+ else {
1393
+ // First try by ID (auto-detect company)
1394
+ const isNumericId = /^\d+$/.test(automationIdOrName);
1395
+ if (isNumericId) {
1396
+ const result = await detectCompanyFromAutomation(automationIdOrName);
1397
+ if (result) {
1398
+ automation = result.automation;
1399
+ companyId = result.companyId;
1400
+ }
1401
+ }
1402
+ // If not found by ID, search by name across companies
1403
+ if (!automation) {
1404
+ const companiesData = await apiRequest("/api/companies");
1405
+ const companies = companiesData.items || companiesData.data || companiesData || [];
1406
+ for (const company of companies) {
1407
+ try {
1408
+ const data = await apiRequest("/api/automation-scripts", {
1409
+ headers: { "X-Company-ID": String(company.id) },
1410
+ });
1411
+ const automations = Array.isArray(data) ? data : data.data || [];
1412
+ const found = automations.find((a) => a.name === automationIdOrName);
1413
+ if (found) {
1414
+ automation = found;
1415
+ companyId = String(company.id);
1416
+ break;
1417
+ }
1418
+ }
1419
+ catch {
1420
+ continue;
1421
+ }
1422
+ }
1423
+ }
1424
+ }
1425
+ if (!automation || !companyId) {
1426
+ console.log(chalk.red(` ✗ Automation '${automationIdOrName}' no encontrada\n`));
1427
+ console.log(chalk.gray(" 💡 Usa: gufi automations para ver la lista\n"));
1428
+ process.exit(1);
1429
+ }
1430
+ console.log(chalk.gray(` Company: ${companyId}`));
1431
+ console.log(chalk.white(` Script: ${automation.name}`));
1432
+ // 💜 Find entity where this automation is configured (for module_id and entity_id)
1433
+ const metaData = await apiRequest("/api/automation-meta", {
1434
+ headers: { "X-Company-ID": companyId },
1435
+ });
1436
+ const metas = Array.isArray(metaData) ? metaData : metaData.data || [];
1437
+ const meta = metas.find((m) => m.function_name === automation.name);
1438
+ if (meta) {
1439
+ entityId = meta.entity_id;
1440
+ // Get module_id from entity
1441
+ const schemaData = await apiRequest("/api/company/schema", {
1442
+ headers: { "X-Company-ID": companyId },
1443
+ });
1444
+ const modules = schemaData.modules || schemaData.data?.modules || [];
1445
+ for (const mod of modules) {
1446
+ for (const sub of mod.submodules || []) {
1447
+ for (const ent of sub.entities || []) {
1448
+ if (ent.id === entityId && ent.ui?.moduleId) {
1449
+ moduleId = ent.ui.moduleId;
1450
+ break;
1451
+ }
1452
+ }
1453
+ if (moduleId)
1454
+ break;
1455
+ }
1456
+ if (moduleId)
1457
+ break;
1458
+ }
1459
+ }
1460
+ // 💜 Fallback: use first module if not found
1461
+ if (!moduleId) {
1462
+ const schemaData = await apiRequest("/api/company/schema", {
1463
+ headers: { "X-Company-ID": companyId },
1464
+ });
1465
+ const modules = schemaData.modules || schemaData.data?.modules || [];
1466
+ for (const mod of modules) {
1467
+ for (const sub of mod.submodules || []) {
1468
+ for (const ent of sub.entities || []) {
1469
+ if (ent.ui?.moduleId) {
1470
+ moduleId = ent.ui.moduleId;
1471
+ if (!entityId)
1472
+ entityId = ent.id;
1473
+ break;
1474
+ }
1475
+ }
1476
+ if (moduleId)
1477
+ break;
1478
+ }
1479
+ if (moduleId)
1480
+ break;
1481
+ }
1482
+ }
1483
+ if (!moduleId || !entityId) {
1484
+ console.log(chalk.red(" ✗ No se pudo determinar module_id o entity_id\n"));
1485
+ process.exit(1);
1486
+ }
1487
+ console.log(chalk.gray(` Module: ${moduleId}, Entity: ${entityId}`));
1488
+ // Parse input if provided
1489
+ let input = {};
1490
+ if (options?.input) {
1491
+ try {
1492
+ input = JSON.parse(options.input);
1493
+ }
1494
+ catch (e) {
1495
+ console.log(chalk.red(" ✗ Input JSON inválido\n"));
1496
+ process.exit(1);
1497
+ }
1498
+ }
1499
+ // 💜 Execute automation via triggerClick endpoint
1500
+ console.log(chalk.yellow("\n ⏳ Ejecutando..."));
1501
+ const result = await apiRequest("/api/automation-scripts/triggerClick", {
1502
+ method: "POST",
1503
+ headers: { "X-Company-ID": companyId },
1504
+ body: JSON.stringify({
1505
+ company_id: parseInt(companyId),
1506
+ module_id: moduleId,
1507
+ entity_id: entityId,
1508
+ function_name: automation.name,
1509
+ input,
1510
+ }),
1511
+ });
1512
+ const jobId = result.jobId;
1513
+ console.log(chalk.green(`\n ✓ Automation encolada!`));
1514
+ console.log(chalk.white(` Job ID: ${jobId}`));
1515
+ // 💜 If --wait flag, poll for result
1516
+ if (options?.wait) {
1517
+ const timeoutMs = parseInt(options?.timeout || "60000");
1518
+ console.log(chalk.gray(`\n Esperando resultado (timeout: ${timeoutMs / 1000}s)...`));
1519
+ const startTime = Date.now();
1520
+ let jobStatus;
1521
+ while (Date.now() - startTime < timeoutMs) {
1522
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1523
+ jobStatus = await apiRequest(`/api/automation-jobs/${jobId}`, {
1524
+ headers: { "X-Company-ID": companyId },
1525
+ });
1526
+ if (jobStatus.isCompleted) {
1527
+ console.log(chalk.green("\n ✓ Completado exitosamente!"));
1528
+ if (jobStatus.result) {
1529
+ console.log(chalk.gray(" Resultado:"));
1530
+ console.log(JSON.stringify(jobStatus.result, null, 2));
1531
+ }
1532
+ console.log();
1533
+ return;
1534
+ }
1535
+ if (jobStatus.isFailed) {
1536
+ console.log(chalk.red("\n ✗ Falló!"));
1537
+ console.log(chalk.red(` Error: ${jobStatus.error || "Unknown error"}`));
1538
+ console.log();
1539
+ process.exit(1);
1540
+ }
1541
+ process.stdout.write(".");
1542
+ }
1543
+ console.log(chalk.yellow("\n\n ⏱ Timeout alcanzado. El job sigue ejecutándose en background."));
1544
+ console.log(chalk.gray(` Verifica estado: gufi automation-jobs ${jobId}\n`));
1545
+ }
1546
+ else {
1547
+ console.log(chalk.gray("\n 💡 Usa --wait para esperar el resultado"));
1548
+ console.log(chalk.gray(` 💡 O consulta: gufi automations:executions -s ${automation.name}\n`));
1549
+ }
1550
+ }
1551
+ catch (err) {
1552
+ console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
1553
+ process.exit(1);
1554
+ }
1555
+ }
1375
1556
  async function editEntityAutomations(entityId, currentAutomations, availableScripts, companyId) {
1376
1557
  const fs = await import("fs");
1377
1558
  const os = await import("os");
@@ -0,0 +1,10 @@
1
+ /**
2
+ * gufi docs - Read Gufi documentation
3
+ *
4
+ * Usage:
5
+ * gufi docs # List available topics
6
+ * gufi docs fields # Read about field types
7
+ * gufi docs errors # Read common errors
8
+ * gufi docs --search currency # Search across all docs
9
+ */
10
+ export declare function docsCommand(args: string[]): Promise<void>;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * gufi docs - Read Gufi documentation
3
+ *
4
+ * Usage:
5
+ * gufi docs # List available topics
6
+ * gufi docs fields # Read about field types
7
+ * gufi docs errors # Read common errors
8
+ * gufi docs --search currency # Search across all docs
9
+ */
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ import { fileURLToPath } from "url";
13
+ import chalk from "chalk";
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ // docs/mcp path relative to CLI
17
+ const DOCS_MCP_PATH = path.resolve(__dirname, "../../../../docs/mcp");
18
+ // Topic to file mapping
19
+ const TOPIC_FILES = {
20
+ overview: { file: "00-overview.md", description: "Overview and workflow" },
21
+ architecture: { file: "01-architecture.md", description: "Technical architecture" },
22
+ modules: { file: "02-modules.md", description: "Module system and entities" },
23
+ fields: { file: "03-fields.md", description: "Field types and CB builders" },
24
+ views: { file: "04-views.md", description: "View system and SDK" },
25
+ automations: { file: "05-automations.md", description: "Automations and worker" },
26
+ api: { file: "06-api.md", description: "API endpoints reference" },
27
+ packages: { file: "07-packages.md", description: "Packages and marketplace" },
28
+ errors: { file: "08-common-errors.md", description: "Common errors and solutions" },
29
+ examples: { file: "09-examples.md", description: "Practical code examples" },
30
+ };
31
+ export async function docsCommand(args) {
32
+ const searchIndex = args.indexOf("--search");
33
+ const jsonFlag = args.includes("--json");
34
+ // Search mode
35
+ if (searchIndex !== -1) {
36
+ const searchTerm = args[searchIndex + 1];
37
+ if (!searchTerm) {
38
+ console.error(chalk.red("Error: --search requires a search term"));
39
+ process.exit(1);
40
+ }
41
+ await searchDocs(searchTerm, jsonFlag);
42
+ return;
43
+ }
44
+ // Topic mode
45
+ const topic = args.find((a) => !a.startsWith("--"));
46
+ if (!topic) {
47
+ // List available topics
48
+ if (jsonFlag) {
49
+ console.log(JSON.stringify({ topics: Object.keys(TOPIC_FILES) }));
50
+ }
51
+ else {
52
+ console.log(chalk.bold.magenta("\n📚 Gufi Documentation\n"));
53
+ console.log("Available topics:\n");
54
+ for (const [name, { description }] of Object.entries(TOPIC_FILES)) {
55
+ console.log(` ${chalk.cyan(name.padEnd(14))} ${chalk.gray(description)}`);
56
+ }
57
+ console.log(chalk.gray("\nUsage:"));
58
+ console.log(chalk.gray(" gufi docs <topic> Read a topic"));
59
+ console.log(chalk.gray(" gufi docs --search <term> Search across all docs"));
60
+ console.log(chalk.gray("\nExamples:"));
61
+ console.log(chalk.gray(" gufi docs fields"));
62
+ console.log(chalk.gray(" gufi docs errors"));
63
+ console.log(chalk.gray(' gufi docs --search "currency"'));
64
+ }
65
+ return;
66
+ }
67
+ // Read specific topic
68
+ const topicInfo = TOPIC_FILES[topic.toLowerCase()];
69
+ if (!topicInfo) {
70
+ console.error(chalk.red(`Unknown topic: ${topic}`));
71
+ console.error(chalk.gray(`Available topics: ${Object.keys(TOPIC_FILES).join(", ")}`));
72
+ process.exit(1);
73
+ }
74
+ const filePath = path.join(DOCS_MCP_PATH, topicInfo.file);
75
+ try {
76
+ const content = fs.readFileSync(filePath, "utf-8");
77
+ if (jsonFlag) {
78
+ console.log(JSON.stringify({ topic, content }));
79
+ }
80
+ else {
81
+ console.log(chalk.bold.magenta(`\n📖 ${topic.charAt(0).toUpperCase() + topic.slice(1)}\n`));
82
+ console.log(content);
83
+ }
84
+ }
85
+ catch (err) {
86
+ console.error(chalk.red(`Failed to read documentation: ${err.message}`));
87
+ console.error(chalk.gray("Make sure docs/mcp/ exists in the project root."));
88
+ process.exit(1);
89
+ }
90
+ }
91
+ async function searchDocs(searchTerm, jsonFlag) {
92
+ const term = searchTerm.toLowerCase();
93
+ const results = [];
94
+ for (const [topic, { file }] of Object.entries(TOPIC_FILES)) {
95
+ const filePath = path.join(DOCS_MCP_PATH, file);
96
+ try {
97
+ const content = fs.readFileSync(filePath, "utf-8");
98
+ const lines = content.split("\n");
99
+ const matches = [];
100
+ for (let i = 0; i < lines.length; i++) {
101
+ if (lines[i].toLowerCase().includes(term)) {
102
+ const start = Math.max(0, i - 1);
103
+ const end = Math.min(lines.length - 1, i + 1);
104
+ const context = lines.slice(start, end + 1).join("\n");
105
+ matches.push({ line: i + 1, context });
106
+ }
107
+ }
108
+ if (matches.length > 0) {
109
+ results.push({ topic, matches: matches.slice(0, 3) });
110
+ }
111
+ }
112
+ catch {
113
+ // Skip files that can't be read
114
+ }
115
+ }
116
+ if (jsonFlag) {
117
+ console.log(JSON.stringify({ search: searchTerm, results }));
118
+ return;
119
+ }
120
+ if (results.length === 0) {
121
+ console.log(chalk.yellow(`\nNo matches found for "${searchTerm}"`));
122
+ console.log(chalk.gray("Try a different search term or read a specific topic."));
123
+ return;
124
+ }
125
+ console.log(chalk.bold.magenta(`\n🔍 Search results for "${searchTerm}"\n`));
126
+ for (const { topic, matches } of results) {
127
+ console.log(chalk.cyan.bold(` ${topic}`));
128
+ for (const { line, context } of matches) {
129
+ console.log(chalk.gray(` Line ${line}:`));
130
+ // Highlight search term
131
+ const highlighted = context.replace(new RegExp(`(${searchTerm})`, "gi"), chalk.yellow.bold("$1"));
132
+ console.log(highlighted
133
+ .split("\n")
134
+ .map((l) => ` ${l}`)
135
+ .join("\n"));
136
+ console.log();
137
+ }
138
+ }
139
+ console.log(chalk.gray(`\nFound matches in ${results.length} topics.`));
140
+ console.log(chalk.gray('Use "gufi docs <topic>" to read the full content.'));
141
+ }
@@ -145,7 +145,7 @@ function validateDataSources(content) {
145
145
  function validateInputs(content) {
146
146
  const results = [];
147
147
  // Check for valid input types
148
- const validTypes = ["text", "number", "boolean", "select", "email", "multiselect", "textarea"];
148
+ const validTypes = ["text", "number", "boolean", "select", "email", "multiselect"];
149
149
  const typeMatches = content.matchAll(/type:\s*['"](\w+)['"]/g);
150
150
  const invalidTypes = [];
151
151
  for (const match of typeMatches) {
package/dist/index.js CHANGED
@@ -74,7 +74,7 @@ import { pushCommand, statusCommand } from "./commands/push.js";
74
74
  import { watchCommand } from "./commands/watch.js";
75
75
  // listCommand removed - use 'gufi packages' instead
76
76
  import { logsCommand } from "./commands/logs.js";
77
- import { companiesCommand, modulesCommand, moduleCommand, moduleUpdateCommand, moduleCreateCommand, companyCreateCommand, automationsCommand, automationCommand, automationCreateCommand, entityAutomationsCommand, entityAutomationsUpdateCommand, automationsMetaCommand, automationsExecutionsCommand, } from "./commands/companies.js";
77
+ import { companiesCommand, modulesCommand, moduleCommand, moduleUpdateCommand, moduleCreateCommand, companyCreateCommand, automationsCommand, automationCommand, automationCreateCommand, automationExecuteCommand, entityAutomationsCommand, entityAutomationsUpdateCommand, automationsMetaCommand, automationsExecutionsCommand, } from "./commands/companies.js";
78
78
  import { rowsListCommand, rowGetCommand, rowCreateCommand, rowUpdateCommand, rowDeleteCommand, rowDuplicateCommand, rowsBulkCreateCommand, } from "./commands/rows.js";
79
79
  import { envListCommand, envSetCommand, envDeleteCommand, schemaCommand, } from "./commands/env.js";
80
80
  import { packagesCommand, packageCommand, packageCreateCommand, packageDeleteCommand, packageAddModuleCommand, packageRemoveModuleCommand, packagePublishCommand, packageUnpublishCommand, packageSyncCommand, packageCheckCommand,
@@ -86,6 +86,7 @@ import { configCommand, configLocalCommand, configProdCommand, configSetCommand,
86
86
  import { templatesCommand, viewCreateCommand, } from "./commands/templates.js";
87
87
  import { contextCommand } from "./commands/context.js";
88
88
  import { doctorCommand } from "./commands/doctor.js";
89
+ import { docsCommand } from "./commands/docs.js";
89
90
  import { startMcpServer } from "./mcp.js";
90
91
  const program = new Command();
91
92
  program
@@ -149,6 +150,21 @@ program
149
150
  .option("-c, --company <id>", "Diagnosticar company específica")
150
151
  .option("--verbose", "Mostrar más detalles")
151
152
  .action(doctorCommand);
153
+ program
154
+ .command("docs [topic]")
155
+ .description("Leer documentación de Gufi (topics: fields, views, automations, errors...)")
156
+ .option("--search <term>", "Buscar término en toda la documentación")
157
+ .option("--json", "Output JSON (for Claude/scripts)")
158
+ .action((topic, options) => {
159
+ const args = [];
160
+ if (topic)
161
+ args.push(topic);
162
+ if (options.search)
163
+ args.push("--search", options.search);
164
+ if (options.json)
165
+ args.push("--json");
166
+ docsCommand(args);
167
+ });
152
168
  // ════════════════════════════════════════════════════════════════════
153
169
  // 🏢 Companies & Modules
154
170
  // ════════════════════════════════════════════════════════════════════
@@ -210,6 +226,14 @@ program
210
226
  .description("Crear/actualizar automation desde archivo JS")
211
227
  .option("-c, --company <id>", "ID de company (requerido)")
212
228
  .action(automationCreateCommand);
229
+ program
230
+ .command("automation:execute <id_or_name>")
231
+ .description("Ejecutar automation script")
232
+ .option("-c, --company <id>", "ID de company")
233
+ .option("-i, --input <json>", "Input JSON para el script")
234
+ .option("-w, --wait", "Esperar resultado (polling)")
235
+ .option("-t, --timeout <ms>", "Timeout en ms (default: 60000)")
236
+ .action(automationExecuteCommand);
213
237
  program
214
238
  .command("automations:meta")
215
239
  .description("Ver índice del worker (debugging)")
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 💜 Column Types - Single source of truth from API
3
+ *
4
+ * Types are fetched from /api/cli/column-types at runtime.
5
+ * NO FALLBACK - forces API to be the only source.
6
+ */
7
+ /**
8
+ * Get valid column types (fetches from API with cache)
9
+ */
10
+ export declare function getValidColumnTypes(): Promise<Set<string>>;
11
+ /**
12
+ * Get type suggestions (fetches from API with cache)
13
+ */
14
+ export declare function getTypeSuggestions(): Promise<Record<string, string>>;
15
+ /**
16
+ * Check if a type is valid (async - fetches from API)
17
+ */
18
+ export declare function isValidColumnType(type: string): Promise<boolean>;
19
+ /**
20
+ * Get suggestion for an invalid type (async - fetches from API)
21
+ */
22
+ export declare function getTypeSuggestion(type: string): Promise<string | undefined>;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * 💜 Column Types - Single source of truth from API
3
+ *
4
+ * Types are fetched from /api/cli/column-types at runtime.
5
+ * NO FALLBACK - forces API to be the only source.
6
+ */
7
+ import { getApiUrl } from "./config.js";
8
+ // Cache for column types (fetched from API)
9
+ let cachedTypes = null;
10
+ let cachedSuggestions = null;
11
+ let cacheTimestamp = 0;
12
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
13
+ /**
14
+ * Fetch column types from API
15
+ */
16
+ async function fetchColumnTypes() {
17
+ const apiUrl = getApiUrl();
18
+ const url = `${apiUrl}/api/cli/column-types`;
19
+ const response = await fetch(url, {
20
+ headers: { "X-Client": "cli" },
21
+ });
22
+ if (!response.ok) {
23
+ throw new Error(`Failed to fetch column types: ${response.status}`);
24
+ }
25
+ const data = await response.json();
26
+ return {
27
+ types: new Set(data.type_names || []),
28
+ suggestions: data.suggestions || {},
29
+ };
30
+ }
31
+ /**
32
+ * Get valid column types (fetches from API with cache)
33
+ */
34
+ export async function getValidColumnTypes() {
35
+ const now = Date.now();
36
+ // Return cached if still valid
37
+ if (cachedTypes && now - cacheTimestamp < CACHE_TTL) {
38
+ return cachedTypes;
39
+ }
40
+ const { types, suggestions } = await fetchColumnTypes();
41
+ cachedTypes = types;
42
+ cachedSuggestions = suggestions;
43
+ cacheTimestamp = now;
44
+ return types;
45
+ }
46
+ /**
47
+ * Get type suggestions (fetches from API with cache)
48
+ */
49
+ export async function getTypeSuggestions() {
50
+ const now = Date.now();
51
+ // Return cached if still valid
52
+ if (cachedSuggestions && now - cacheTimestamp < CACHE_TTL) {
53
+ return cachedSuggestions;
54
+ }
55
+ const { types, suggestions } = await fetchColumnTypes();
56
+ cachedTypes = types;
57
+ cachedSuggestions = suggestions;
58
+ cacheTimestamp = now;
59
+ return suggestions;
60
+ }
61
+ /**
62
+ * Check if a type is valid (async - fetches from API)
63
+ */
64
+ export async function isValidColumnType(type) {
65
+ const types = await getValidColumnTypes();
66
+ return types.has(type);
67
+ }
68
+ /**
69
+ * Get suggestion for an invalid type (async - fetches from API)
70
+ */
71
+ export async function getTypeSuggestion(type) {
72
+ const suggestions = await getTypeSuggestions();
73
+ return suggestions[type.toLowerCase()];
74
+ }
package/dist/mcp.js CHANGED
@@ -11,8 +11,16 @@
11
11
  * The MCP server exposes all CLI functionality as tools that Claude can call.
12
12
  */
13
13
  import * as readline from "readline";
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import { fileURLToPath } from "url";
14
17
  import { getToken, getApiUrl, getRefreshToken, setToken, loadConfig, isLoggedIn, getCurrentEnv } from "./lib/config.js";
15
18
  import { getViewFiles, saveViewFiles, } from "./lib/api.js";
19
+ // For ES modules __dirname equivalent
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = path.dirname(__filename);
22
+ // docs/mcp path relative to CLI
23
+ const DOCS_MCP_PATH = path.resolve(__dirname, "../../../docs/mcp");
16
24
  // ════════════════════════════════════════════════════════════════════════════
17
25
  // API Client (reused from CLI)
18
26
  // ════════════════════════════════════════════════════════════════════════════
@@ -172,6 +180,36 @@ Use this to understand the data structure before creating queries or modificatio
172
180
  },
173
181
  },
174
182
  },
183
+ {
184
+ name: "gufi_docs",
185
+ description: `Read documentation about Gufi. Use this when you need detailed information about specific topics.
186
+
187
+ Available topics:
188
+ - "overview" - Overview and workflow (start here)
189
+ - "architecture" - Technical architecture
190
+ - "modules" - Module system and entities
191
+ - "fields" - Field types and CB builders (IMPORTANT for data operations)
192
+ - "views" - View system, SDK, and React components
193
+ - "automations" - Automations and worker
194
+ - "api" - API endpoints reference
195
+ - "packages" - Packages and marketplace
196
+ - "errors" - Common errors and solutions
197
+ - "examples" - Practical code examples
198
+
199
+ You can also search across all docs with the search parameter.
200
+
201
+ Examples:
202
+ - gufi_docs({ topic: "fields" }) - Read about field types
203
+ - gufi_docs({ topic: "errors" }) - Common errors and fixes
204
+ - gufi_docs({ search: "currency" }) - Search for currency info`,
205
+ inputSchema: {
206
+ type: "object",
207
+ properties: {
208
+ topic: { type: "string", description: "Topic to read: overview, architecture, modules, fields, views, automations, api, packages, errors, examples" },
209
+ search: { type: "string", description: "Search term to find across all documentation" },
210
+ },
211
+ },
212
+ },
175
213
  // ─────────────────────────────────────────────────────────────────────────
176
214
  // Companies
177
215
  // ─────────────────────────────────────────────────────────────────────────
@@ -217,7 +255,7 @@ Returns the complete module structure:
217
255
  - fields[]: Array of fields with type, label, required, options, etc.
218
256
 
219
257
  Field types (from @gufi/column-types):
220
- - Text: text, textarea, email, url, barcode
258
+ - Text: text, email, url, barcode
221
259
  - Numbers: number_int, number_float, percentage
222
260
  - Date/Time: date, datetime, time
223
261
  - Boolean: boolean
@@ -472,7 +510,7 @@ Supports:
472
510
  Field formats (from @gufi/column-types):
473
511
 
474
512
  SIMPLE TYPES:
475
- - text/textarea/email/url/barcode: "string value"
513
+ - text/email/url/barcode: "string value"
476
514
  - number_int: 42 (integer)
477
515
  - number_float: 3.14 (decimal)
478
516
  - percentage: 0.75 (decimal, displayed as 75%)
@@ -980,6 +1018,90 @@ const toolHandlers = {
980
1018
  }
981
1019
  return { modules };
982
1020
  },
1021
+ async gufi_docs(params) {
1022
+ // Map topics to file names
1023
+ const topicFiles = {
1024
+ overview: "00-overview.md",
1025
+ architecture: "01-architecture.md",
1026
+ modules: "02-modules.md",
1027
+ fields: "03-fields.md",
1028
+ views: "04-views.md",
1029
+ automations: "05-automations.md",
1030
+ api: "06-api.md",
1031
+ packages: "07-packages.md",
1032
+ errors: "08-common-errors.md",
1033
+ examples: "09-examples.md",
1034
+ };
1035
+ // Read a specific topic
1036
+ if (params.topic) {
1037
+ const fileName = topicFiles[params.topic.toLowerCase()];
1038
+ if (!fileName) {
1039
+ return {
1040
+ error: `Unknown topic: ${params.topic}`,
1041
+ available_topics: Object.keys(topicFiles),
1042
+ };
1043
+ }
1044
+ const filePath = path.join(DOCS_MCP_PATH, fileName);
1045
+ try {
1046
+ const content = fs.readFileSync(filePath, "utf-8");
1047
+ return {
1048
+ topic: params.topic,
1049
+ content,
1050
+ };
1051
+ }
1052
+ catch (err) {
1053
+ return {
1054
+ error: `Failed to read documentation: ${err.message}`,
1055
+ hint: "Documentation may not be installed. Check docs/mcp/ folder.",
1056
+ };
1057
+ }
1058
+ }
1059
+ // Search across all docs
1060
+ if (params.search) {
1061
+ const searchTerm = params.search.toLowerCase();
1062
+ const results = [];
1063
+ for (const [topic, fileName] of Object.entries(topicFiles)) {
1064
+ const filePath = path.join(DOCS_MCP_PATH, fileName);
1065
+ try {
1066
+ const content = fs.readFileSync(filePath, "utf-8");
1067
+ const lines = content.split("\n");
1068
+ const matches = [];
1069
+ for (let i = 0; i < lines.length; i++) {
1070
+ if (lines[i].toLowerCase().includes(searchTerm)) {
1071
+ // Include context (line before and after)
1072
+ const start = Math.max(0, i - 1);
1073
+ const end = Math.min(lines.length - 1, i + 1);
1074
+ const context = lines.slice(start, end + 1).join("\n");
1075
+ matches.push(`Line ${i + 1}:\n${context}`);
1076
+ }
1077
+ }
1078
+ if (matches.length > 0) {
1079
+ results.push({ topic, matches: matches.slice(0, 3) }); // Max 3 matches per file
1080
+ }
1081
+ }
1082
+ catch {
1083
+ // Skip files that can't be read
1084
+ }
1085
+ }
1086
+ if (results.length === 0) {
1087
+ return {
1088
+ search: params.search,
1089
+ results: [],
1090
+ hint: "No matches found. Try a different search term or read a specific topic.",
1091
+ };
1092
+ }
1093
+ return {
1094
+ search: params.search,
1095
+ results,
1096
+ hint: `Found matches in ${results.length} topics. Use gufi_docs({ topic: "..." }) to read the full content.`,
1097
+ };
1098
+ }
1099
+ // No params - return available topics
1100
+ return {
1101
+ available_topics: Object.keys(topicFiles),
1102
+ hint: "Use gufi_docs({ topic: 'fields' }) to read about field types, or gufi_docs({ search: 'currency' }) to search.",
1103
+ };
1104
+ },
983
1105
  // ─────────────────────────────────────────────────────────────────────────
984
1106
  // Companies
985
1107
  // ─────────────────────────────────────────────────────────────────────────
@@ -1673,7 +1795,6 @@ function getGufiConcepts() {
1673
1795
  },
1674
1796
  fieldTypes: {
1675
1797
  text: "string",
1676
- textarea: "string (multiline)",
1677
1798
  email: "string (validated)",
1678
1799
  url: "string (URL)",
1679
1800
  barcode: "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.18",
3
+ "version": "0.1.21",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"
@@ -22,7 +22,6 @@
22
22
  "start": "node bin/gufi.js"
23
23
  },
24
24
  "dependencies": {
25
- "@gufi/column-types": "file:../../libs/column-types",
26
25
  "@types/prompts": "^2.4.9",
27
26
  "@types/ws": "^8.18.1",
28
27
  "chalk": "^5.3.0",