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 +10 -3
- package/dist/commands/companies.d.ts +6 -0
- package/dist/commands/companies.js +211 -30
- package/dist/commands/docs.d.ts +10 -0
- package/dist/commands/docs.js +141 -0
- package/dist/commands/doctor.js +1 -1
- package/dist/index.js +25 -1
- package/dist/lib/columnTypes.d.ts +22 -0
- package/dist/lib/columnTypes.js +74 -0
- package/dist/mcp.js +124 -3
- package/package.json +1 -2
package/CLAUDE.md
CHANGED
|
@@ -11,9 +11,13 @@
|
|
|
11
11
|
## Quick Reference
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
# Contexto y
|
|
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 (
|
|
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,
|
|
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 {
|
|
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 (!
|
|
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:
|
|
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,
|
|
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,
|
|
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
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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"
|
|
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,
|
|
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/
|
|
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.
|
|
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",
|