gufi-cli 0.1.27 β 0.1.29
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 +7 -5
- package/README.md +1 -2
- package/dist/commands/doctor.js +92 -25
- package/dist/commands/push.d.ts +1 -5
- package/dist/commands/push.js +2 -7
- package/dist/index.js +1 -2
- package/dist/lib/security.js +10 -2
- package/dist/lib/sync.d.ts +1 -3
- package/dist/lib/sync.js +4 -7
- package/dist/mcp.js +239 -212
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -62,14 +62,16 @@ El servidor MCP estΓ‘ en `src/mcp.ts` y expone 44 tools para que Claude interact
|
|
|
62
62
|
|
|
63
63
|
### Tools principales
|
|
64
64
|
|
|
65
|
-
- **`gufi_context`** - Genera contexto inteligente:
|
|
66
|
-
- `
|
|
67
|
-
- `
|
|
65
|
+
- **`gufi_context`** - π¨ THE ONE TOOL FOR CONTEXT - Genera contexto inteligente:
|
|
66
|
+
- `{ company_id }` - Overview de empresa
|
|
67
|
+
- `{ company_id, detail: "full" }` - Schema completo Claude-optimizado (reemplaza gufi_schema)
|
|
68
|
+
- `{ company_id, module_id }` - Contexto de mΓ³dulo especΓfico
|
|
69
|
+
- `{ company_id, entity_id }` - Contexto de entidad especΓfica
|
|
70
|
+
- `{ package_id }` - Contexto de package
|
|
71
|
+
- `{ view_id }` - Contexto de vista
|
|
68
72
|
- Auto-detecta si estΓ‘ en codebase, vista local, o muestra packages
|
|
69
|
-
- **Usar con**: `{ package_id }`, `{ view_id }`, `{ company_id }`
|
|
70
73
|
|
|
71
74
|
- **`gufi_docs`** - Lee documentaciΓ³n de `docs/mcp/`
|
|
72
|
-
- **`gufi_schema`** - Schema de BD (mΓ³dulos, tablas, campos)
|
|
73
75
|
- **`gufi_rows/row/row_create/row_update`** - CRUD de datos
|
|
74
76
|
- **`gufi_module/module_update`** - GestiΓ³n de mΓ³dulos JSON
|
|
75
77
|
- **`gufi_automation/automation_create`** - GestiΓ³n de automations
|
package/README.md
CHANGED
|
@@ -498,9 +498,8 @@ El MCP expone todas las funcionalidades del CLI como tools que Claude puede usar
|
|
|
498
498
|
| Tool | DescripciΓ³n |
|
|
499
499
|
|------|-------------|
|
|
500
500
|
| **Contexto** | |
|
|
501
|
-
| `gufi_context` |
|
|
501
|
+
| `gufi_context` | THE ONE TOOL - Contexto unificado (schema, mΓ³dulos, vistas, packages) |
|
|
502
502
|
| `gufi_whoami` | Ver usuario y entorno actual |
|
|
503
|
-
| `gufi_schema` | Ver estructura de tablas |
|
|
504
503
|
| **Companies** | |
|
|
505
504
|
| `gufi_companies` | Listar companies del usuario |
|
|
506
505
|
| `gufi_company_create` | Crear nueva company |
|
package/dist/commands/doctor.js
CHANGED
|
@@ -53,33 +53,39 @@ function countResults(sections) {
|
|
|
53
53
|
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
54
54
|
function validateFeatureConfig(content, filePath) {
|
|
55
55
|
const results = [];
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
results.push({ status: "ok", message: "export default presente" });
|
|
66
|
-
}
|
|
67
|
-
// Check featureConfig export
|
|
68
|
-
if (!content.includes("export { featureConfig }") && !content.includes("export {featureConfig}")) {
|
|
69
|
-
// Check if it's a re-export
|
|
70
|
-
if (!content.match(/export\s*{\s*featureConfig\s*}\s*from/)) {
|
|
56
|
+
// π These checks only apply to the ROOT index.tsx (main entry point)
|
|
57
|
+
// Paths like /components/index.tsx or /core/index.tsx are NOT the main entry
|
|
58
|
+
const isIndexFile = filePath === "index.tsx" || filePath === "/index.tsx";
|
|
59
|
+
if (isIndexFile) {
|
|
60
|
+
// Check export default (supports both `export default` and `export { default }` re-export)
|
|
61
|
+
const hasExportDefault = content.includes("export default") || content.match(/export\s*{\s*default\s*}\s*from/);
|
|
62
|
+
if (!hasExportDefault) {
|
|
71
63
|
results.push({
|
|
72
64
|
status: "error",
|
|
73
|
-
message: "Falta export
|
|
74
|
-
details:
|
|
65
|
+
message: "Falta export default del componente",
|
|
66
|
+
details: filePath,
|
|
75
67
|
});
|
|
76
68
|
}
|
|
77
69
|
else {
|
|
78
|
-
results.push({ status: "ok", message: "
|
|
70
|
+
results.push({ status: "ok", message: "export default presente" });
|
|
71
|
+
}
|
|
72
|
+
// Check featureConfig export
|
|
73
|
+
if (!content.includes("export { featureConfig }") && !content.includes("export {featureConfig}")) {
|
|
74
|
+
// Check if it's a re-export
|
|
75
|
+
if (!content.match(/export\s*{\s*featureConfig\s*}\s*from/)) {
|
|
76
|
+
results.push({
|
|
77
|
+
status: "error",
|
|
78
|
+
message: "Falta export { featureConfig }",
|
|
79
|
+
details: "Necesario para que Developer Center detecte la configuraciΓ³n",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
results.push({ status: "ok", message: "featureConfig re-exportado" });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
results.push({ status: "ok", message: "featureConfig exportado" });
|
|
79
88
|
}
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
results.push({ status: "ok", message: "featureConfig exportado" });
|
|
83
89
|
}
|
|
84
90
|
// Check for hardcoded table IDs
|
|
85
91
|
const hardcodedIds = content.match(/['"](m\d+_t\d+)['"]/g);
|
|
@@ -106,6 +112,54 @@ function validateFeatureConfig(content, filePath) {
|
|
|
106
112
|
details: "Usar gufi.dataProvider en lugar de importar directamente",
|
|
107
113
|
});
|
|
108
114
|
}
|
|
115
|
+
// π Check for deprecated useCompanySchemaStore
|
|
116
|
+
if (content.includes("useCompanySchemaStore")) {
|
|
117
|
+
results.push({
|
|
118
|
+
status: "error",
|
|
119
|
+
message: "useCompanySchemaStore estΓ‘ deprecado",
|
|
120
|
+
details: "Usar useSchemaStore de @/stores/useSchemaStore o gufi.context.schema",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// π Check for runAnalyticsSpec (not available in marketplace SDK)
|
|
124
|
+
if (content.includes("runAnalyticsSpec")) {
|
|
125
|
+
results.push({
|
|
126
|
+
status: "error",
|
|
127
|
+
message: "runAnalyticsSpec no disponible en SDK",
|
|
128
|
+
details: "Reemplazar con dataProvider.getList() + filtros, o usar agregaciΓ³n SQL en backend",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// π Check for optional chaining on gufi (gufi?.)
|
|
132
|
+
if (content.match(/gufi\?\./)) {
|
|
133
|
+
results.push({
|
|
134
|
+
status: "warning",
|
|
135
|
+
message: "gufi?. con optional chaining - gufi debe ser REQUIRED",
|
|
136
|
+
details: "Quitar ?. y usar gufi. directamente. gufi: FullGufiContext en props (no opcional)",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// π Check for optional gufi prop (gufi?:)
|
|
140
|
+
if (content.match(/gufi\?: /)) {
|
|
141
|
+
results.push({
|
|
142
|
+
status: "error",
|
|
143
|
+
message: "gufi prop es opcional - debe ser REQUIRED",
|
|
144
|
+
details: "Cambiar 'gufi?:' a 'gufi:' en la interface. gufi siempre se provee a vistas marketplace",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// π Check for direct notification import
|
|
148
|
+
if (content.match(/import.*from\s+['"]@\/shared\/notifications/)) {
|
|
149
|
+
results.push({
|
|
150
|
+
status: "warning",
|
|
151
|
+
message: "Import directo de notifications",
|
|
152
|
+
details: "Usar gufi.utils.toastSuccess/toastError/toastInfo en su lugar",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// π Check for direct language import
|
|
156
|
+
if (content.match(/import.*from\s+['"]@\/shared\/language/)) {
|
|
157
|
+
results.push({
|
|
158
|
+
status: "warning",
|
|
159
|
+
message: "Import directo de language",
|
|
160
|
+
details: "Usar gufi.context.lang y gufi.utils.tUI en su lugar",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
109
163
|
return results;
|
|
110
164
|
}
|
|
111
165
|
function validateDataSources(content) {
|
|
@@ -545,13 +599,26 @@ export async function doctorCommand(options) {
|
|
|
545
599
|
const files = await getViewFiles(parseInt(options.view, 10));
|
|
546
600
|
const viewResults = [];
|
|
547
601
|
viewResults.push({ status: "ok", message: `Vista: ${view.name}` });
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
602
|
+
// π Analyze ALL code files, not just index.tsx
|
|
603
|
+
const codeFiles = files.filter((f) => f.file_path.match(/\.(tsx?|jsx?)$/));
|
|
604
|
+
viewResults.push({ status: "ok", message: `${codeFiles.length} archivos de cΓ³digo` });
|
|
605
|
+
for (const file of codeFiles) {
|
|
606
|
+
const fileResults = validateFeatureConfig(file.content, file.file_path);
|
|
607
|
+
// Only add non-ok results to avoid clutter
|
|
608
|
+
const issues = fileResults.filter(r => r.status !== "ok");
|
|
609
|
+
if (issues.length > 0) {
|
|
610
|
+
viewResults.push({
|
|
611
|
+
status: "warning",
|
|
612
|
+
message: `Archivo: ${file.file_path}`,
|
|
613
|
+
});
|
|
614
|
+
viewResults.push(...issues);
|
|
615
|
+
}
|
|
551
616
|
}
|
|
552
617
|
const dpFile = files.find((f) => f.file_path === "core/dataProvider.ts");
|
|
553
618
|
if (dpFile) {
|
|
554
|
-
|
|
619
|
+
const dpResults = validateDataSources(dpFile.content);
|
|
620
|
+
const dpIssues = dpResults.filter(r => r.status !== "ok");
|
|
621
|
+
viewResults.push(...dpIssues);
|
|
555
622
|
}
|
|
556
623
|
allSections.push({ title: `Vista ${options.view}`, results: viewResults });
|
|
557
624
|
}
|
package/dist/commands/push.d.ts
CHANGED
|
@@ -2,14 +2,10 @@
|
|
|
2
2
|
* gufi push - Upload local changes to Gufi
|
|
3
3
|
* π Now with featureConfig validation!
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
force?: boolean;
|
|
7
|
-
}
|
|
8
|
-
export declare function pushCommand(viewDir?: string | object, flags?: PushFlags): Promise<void>;
|
|
5
|
+
export declare function pushCommand(viewDir?: string | object): Promise<void>;
|
|
9
6
|
/**
|
|
10
7
|
* gufi view:errors - Validate view files without pushing
|
|
11
8
|
* π Check for errors BEFORE you push!
|
|
12
9
|
*/
|
|
13
10
|
export declare function viewErrorsCommand(viewIdArg?: string | number): Promise<void>;
|
|
14
11
|
export declare function statusCommand(viewDir?: string | object): Promise<void>;
|
|
15
|
-
export {};
|
package/dist/commands/push.js
CHANGED
|
@@ -60,17 +60,12 @@ function scanFilesInDirectory(dir, changedFiles) {
|
|
|
60
60
|
}));
|
|
61
61
|
return scanFilesForXSS(filesToScan);
|
|
62
62
|
}
|
|
63
|
-
export async function pushCommand(viewDir
|
|
63
|
+
export async function pushCommand(viewDir) {
|
|
64
64
|
if (!isLoggedIn()) {
|
|
65
65
|
console.log(chalk.red("\n β No estΓ‘s logueado. Usa: gufi login\n"));
|
|
66
66
|
process.exit(1);
|
|
67
67
|
}
|
|
68
68
|
console.log(chalk.magenta("\n π£ Gufi Push\n"));
|
|
69
|
-
// π Check for --force flag
|
|
70
|
-
const forceMode = flags.force || false;
|
|
71
|
-
if (forceMode) {
|
|
72
|
-
console.log(chalk.yellow(" β‘ Modo forzado: subiendo todos los archivos\n"));
|
|
73
|
-
}
|
|
74
69
|
// Determine view directory:
|
|
75
70
|
// 1. Explicit path argument
|
|
76
71
|
// 2. Current directory if it has .gufi-view.json
|
|
@@ -129,7 +124,7 @@ export async function pushCommand(viewDir, flags = {}) {
|
|
|
129
124
|
// π Push changes (simple hash comparison)
|
|
130
125
|
const spinner = ora("Subiendo cambios...").start();
|
|
131
126
|
try {
|
|
132
|
-
const result = await pushView(dir
|
|
127
|
+
const result = await pushView(dir);
|
|
133
128
|
spinner.succeed(chalk.green(`${result.pushed} archivo(s) subido(s)`));
|
|
134
129
|
}
|
|
135
130
|
catch (error) {
|
package/dist/index.js
CHANGED
|
@@ -337,8 +337,7 @@ program
|
|
|
337
337
|
program
|
|
338
338
|
.command("view:push [path]")
|
|
339
339
|
.description("Subir cambios locales a Gufi (con validaciΓ³n)")
|
|
340
|
-
.
|
|
341
|
-
.action((path, options) => pushCommand(path, { force: options.force }));
|
|
340
|
+
.action((path) => pushCommand(path));
|
|
342
341
|
program
|
|
343
342
|
.command("view:watch [path]")
|
|
344
343
|
.description("Auto-sync al guardar archivos")
|
package/dist/lib/security.js
CHANGED
|
@@ -77,9 +77,10 @@ const SUSPICIOUS_PATTERNS = [
|
|
|
77
77
|
message: "Location redirect detected - verify this is intentional",
|
|
78
78
|
severity: "warning",
|
|
79
79
|
},
|
|
80
|
-
// Script injection
|
|
80
|
+
// Script injection (only real JSX script tags, not strings for print windows)
|
|
81
|
+
// Note: <script in template literals for print windows is allowed
|
|
81
82
|
{
|
|
82
|
-
pattern: /<script/gi,
|
|
83
|
+
pattern: /<script(?![^`]*`)/gi,
|
|
83
84
|
message: "Script tag detected - not allowed in views",
|
|
84
85
|
severity: "error",
|
|
85
86
|
},
|
|
@@ -103,6 +104,13 @@ const ALLOWED_PATTERNS = [
|
|
|
103
104
|
// Common false positives
|
|
104
105
|
/useViewPreferences/, // Our localStorage wrapper
|
|
105
106
|
/@\/shared\/hooks/, // Our hooks that safely use storage
|
|
107
|
+
// Print window patterns - legitimate use for printing barcodes/labels
|
|
108
|
+
/printWindow/i,
|
|
109
|
+
/window\.print/i,
|
|
110
|
+
/JsBarcode/,
|
|
111
|
+
// Script tags inside template literals (for print windows)
|
|
112
|
+
/^\s*<script>\s*$/,
|
|
113
|
+
/^\s*<\/script>\s*$/,
|
|
106
114
|
];
|
|
107
115
|
/**
|
|
108
116
|
* Scan a file for suspicious patterns
|
package/dist/lib/sync.d.ts
CHANGED
|
@@ -28,9 +28,7 @@ export declare function pullView(viewId: number, viewKey: string, packageId: num
|
|
|
28
28
|
* Simple logic: compare local content hash with stored hash from last pull/push
|
|
29
29
|
* No server calls, no mtime fallbacks - just hash comparison
|
|
30
30
|
*/
|
|
31
|
-
export declare function pushView(viewDir?: string
|
|
32
|
-
force?: boolean;
|
|
33
|
-
}): Promise<{
|
|
31
|
+
export declare function pushView(viewDir?: string): Promise<{
|
|
34
32
|
pushed: number;
|
|
35
33
|
}>;
|
|
36
34
|
/**
|
package/dist/lib/sync.js
CHANGED
|
@@ -87,8 +87,7 @@ export async function pullView(viewId, viewKey, packageId) {
|
|
|
87
87
|
* Simple logic: compare local content hash with stored hash from last pull/push
|
|
88
88
|
* No server calls, no mtime fallbacks - just hash comparison
|
|
89
89
|
*/
|
|
90
|
-
export async function pushView(viewDir
|
|
91
|
-
const { force = false } = options;
|
|
90
|
+
export async function pushView(viewDir) {
|
|
92
91
|
const dir = viewDir || getCurrentView()?.localPath;
|
|
93
92
|
if (!dir) {
|
|
94
93
|
throw new Error("No hay vista activa. Usa: gufi pull <vista>");
|
|
@@ -104,11 +103,9 @@ export async function pushView(viewDir, options = {}) {
|
|
|
104
103
|
const localHash = hashContent(content);
|
|
105
104
|
const filePath = "/" + file;
|
|
106
105
|
// Simple change detection: hash differs from stored hash?
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
continue; // No changes - hash matches what we had
|
|
111
|
-
}
|
|
106
|
+
const storedMeta = meta.files[filePath];
|
|
107
|
+
if (storedMeta && storedMeta.hash === localHash) {
|
|
108
|
+
continue; // No changes - hash matches what we had
|
|
112
109
|
}
|
|
113
110
|
// Push to Gufi
|
|
114
111
|
await saveViewFile(meta.viewId, {
|
package/dist/mcp.js
CHANGED
|
@@ -208,118 +208,6 @@ function normalizeFileFields(data) {
|
|
|
208
208
|
return normalized;
|
|
209
209
|
}
|
|
210
210
|
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
211
|
-
// Relation Field Validation
|
|
212
|
-
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
213
|
-
/**
|
|
214
|
-
* Extract entity ID from table name (e.g., "m617_t20413" β "20413")
|
|
215
|
-
*/
|
|
216
|
-
function extractEntityIdFromTable(tableName) {
|
|
217
|
-
const match = tableName.match(/m\d+_t(\d+)/);
|
|
218
|
-
return match ? match[1] : null;
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Cache for entity schemas to avoid repeated API calls
|
|
222
|
-
*/
|
|
223
|
-
const entitySchemaCache = new Map();
|
|
224
|
-
const CACHE_TTL = 60000; // 1 minute
|
|
225
|
-
/**
|
|
226
|
-
* Get entity schema with caching
|
|
227
|
-
*/
|
|
228
|
-
async function getEntitySchema(entityId, companyId) {
|
|
229
|
-
const cacheKey = `${companyId}:${entityId}`;
|
|
230
|
-
const cached = entitySchemaCache.get(cacheKey);
|
|
231
|
-
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
232
|
-
return cached.fields;
|
|
233
|
-
}
|
|
234
|
-
try {
|
|
235
|
-
const data = await apiRequest(`/api/schema/entities/${entityId}`, {
|
|
236
|
-
headers: { "X-Company-ID": companyId },
|
|
237
|
-
}, companyId);
|
|
238
|
-
const fields = data.fields || [];
|
|
239
|
-
entitySchemaCache.set(cacheKey, { fields, timestamp: Date.now() });
|
|
240
|
-
return fields;
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
return [];
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Resolve a reference path (e.g., "ecommerce.clientes") to a table name
|
|
248
|
-
*/
|
|
249
|
-
async function resolveRefToTable(ref, companyId) {
|
|
250
|
-
try {
|
|
251
|
-
const schemaData = await apiRequest("/api/company/schema", {
|
|
252
|
-
headers: { "X-Company-ID": companyId },
|
|
253
|
-
}, companyId);
|
|
254
|
-
const modules = schemaData.modules || schemaData.data?.modules || [];
|
|
255
|
-
const [moduleName, entityName] = ref.split(".");
|
|
256
|
-
for (const mod of modules) {
|
|
257
|
-
if (mod.name !== moduleName)
|
|
258
|
-
continue;
|
|
259
|
-
for (const sub of mod.submodules || []) {
|
|
260
|
-
for (const ent of sub.entities || []) {
|
|
261
|
-
if (ent.name === entityName) {
|
|
262
|
-
return ent.ui?.tableName || `m${ent.ui?.moduleId}_t${ent.ui?.entityId}`;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
// Ignore errors, validation is best-effort
|
|
270
|
-
}
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Validate relation fields in data - checks if referenced IDs exist
|
|
275
|
-
* Returns { valid: true } or { valid: false, errors: [...] }
|
|
276
|
-
*/
|
|
277
|
-
async function validateRelationFields(tableName, companyId, data) {
|
|
278
|
-
const errors = [];
|
|
279
|
-
// Extract entity ID from table name
|
|
280
|
-
const entityId = extractEntityIdFromTable(tableName);
|
|
281
|
-
if (!entityId) {
|
|
282
|
-
return { valid: true, errors: [] }; // Can't validate without entity ID
|
|
283
|
-
}
|
|
284
|
-
// Get entity schema to find relation fields
|
|
285
|
-
const fields = await getEntitySchema(entityId, companyId);
|
|
286
|
-
if (!fields.length) {
|
|
287
|
-
return { valid: true, errors: [] }; // Can't validate without schema
|
|
288
|
-
}
|
|
289
|
-
// Find relation fields
|
|
290
|
-
const relationFields = fields.filter((f) => f.type === "relation" && f.settings?.ref);
|
|
291
|
-
for (const field of relationFields) {
|
|
292
|
-
const fieldName = field.name;
|
|
293
|
-
const value = data[fieldName];
|
|
294
|
-
// Skip if no value provided
|
|
295
|
-
if (value === undefined || value === null || value === "")
|
|
296
|
-
continue;
|
|
297
|
-
// Get the ID to validate
|
|
298
|
-
const refId = typeof value === "object" ? value.id : value;
|
|
299
|
-
if (!refId || typeof refId !== "number")
|
|
300
|
-
continue;
|
|
301
|
-
// Resolve the reference to a table name
|
|
302
|
-
const refTable = await resolveRefToTable(field.settings.ref, companyId);
|
|
303
|
-
if (!refTable)
|
|
304
|
-
continue;
|
|
305
|
-
// Check if the ID exists in the referenced table
|
|
306
|
-
try {
|
|
307
|
-
const result = await apiRequest(`/api/tables/${refTable}/getOne`, {
|
|
308
|
-
method: "POST",
|
|
309
|
-
body: JSON.stringify({ id: refId }),
|
|
310
|
-
}, companyId);
|
|
311
|
-
if (!result || !result.data) {
|
|
312
|
-
errors.push(`Relation '${fieldName}': ID ${refId} does not exist in ${field.settings.ref}`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
catch (e) {
|
|
316
|
-
// If we get an error, the ID probably doesn't exist
|
|
317
|
-
errors.push(`Relation '${fieldName}': ID ${refId} not found in ${field.settings.ref} (${e.message || 'not found'})`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return { valid: errors.length === 0, errors };
|
|
321
|
-
}
|
|
322
|
-
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
323
211
|
// Tool Definitions - Descriptions loaded from docs/mcp/tool-descriptions.json
|
|
324
212
|
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
325
213
|
// Load tool descriptions from centralized documentation
|
|
@@ -354,7 +242,9 @@ const TOOLS = [
|
|
|
354
242
|
package_id: { type: "string", description: "Package ID to get context for" },
|
|
355
243
|
view_id: { type: "string", description: "View ID to get context for" },
|
|
356
244
|
company_id: { type: "string", description: "Company ID to get context for" },
|
|
357
|
-
|
|
245
|
+
module_id: { type: "string", description: "Module ID to get detailed context for (requires company_id)" },
|
|
246
|
+
entity_id: { type: "string", description: "Entity ID to get detailed context for (requires company_id)" },
|
|
247
|
+
detail: { type: "string", description: "Level of detail: 'summary' (default) or 'full' (Claude-optimized complete export with all fields, automations, webhooks, etc.)" },
|
|
358
248
|
},
|
|
359
249
|
},
|
|
360
250
|
},
|
|
@@ -363,17 +253,7 @@ const TOOLS = [
|
|
|
363
253
|
description: getDesc("gufi_whoami"),
|
|
364
254
|
inputSchema: { type: "object", properties: {} },
|
|
365
255
|
},
|
|
366
|
-
|
|
367
|
-
name: "gufi_schema",
|
|
368
|
-
description: getDesc("gufi_schema"),
|
|
369
|
-
inputSchema: {
|
|
370
|
-
type: "object",
|
|
371
|
-
properties: {
|
|
372
|
-
company_id: { type: "string", description: "Company ID (optional if user has only one company)" },
|
|
373
|
-
format: { type: "string", description: "Output format: 'flat' (text, default) or 'json' (structured)" },
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
},
|
|
256
|
+
// gufi_schema REMOVED - functionality merged into gufi_context with detail: "full"
|
|
377
257
|
{
|
|
378
258
|
name: "gufi_schema_modify",
|
|
379
259
|
description: getDesc("gufi_schema_modify"),
|
|
@@ -767,6 +647,18 @@ const TOOLS = [
|
|
|
767
647
|
required: ["view_id"],
|
|
768
648
|
},
|
|
769
649
|
},
|
|
650
|
+
{
|
|
651
|
+
name: "gufi_view_read",
|
|
652
|
+
description: "π Read the full content of view files. Returns actual code, not just metadata.\n\nUse this to:\n- Debug view issues by reading the code\n- Understand view structure before making changes\n- Find bugs or incorrect imports\n\nExample: gufi_view_read({ view_id: 3 }) β Returns all files with content\ngufi_view_read({ view_id: 3, file_path: 'views/page.tsx' }) β Returns specific file",
|
|
653
|
+
inputSchema: {
|
|
654
|
+
type: "object",
|
|
655
|
+
properties: {
|
|
656
|
+
view_id: { type: "number", description: "View ID" },
|
|
657
|
+
file_path: { type: "string", description: "Optional: specific file path to read (e.g., 'views/page.tsx')" },
|
|
658
|
+
},
|
|
659
|
+
required: ["view_id"],
|
|
660
|
+
},
|
|
661
|
+
},
|
|
770
662
|
{
|
|
771
663
|
name: "gufi_view_create",
|
|
772
664
|
description: getDesc("gufi_view_create"),
|
|
@@ -781,6 +673,17 @@ const TOOLS = [
|
|
|
781
673
|
required: ["name", "template"],
|
|
782
674
|
},
|
|
783
675
|
},
|
|
676
|
+
{
|
|
677
|
+
name: "gufi_view_diagnose",
|
|
678
|
+
description: "π Diagnose common issues in a marketplace view.\n\nAutomatically detects:\n- Deprecated store imports (useCompanySchemaStore)\n- Forbidden @/stores imports\n- Unavailable SDK functions (runAnalyticsSpec)\n- Optional chaining on gufi (gufi?.xxx)\n- Missing gufi prop patterns\n- Hardcoded table names\n\nReturns actionable fixes for each issue found.",
|
|
679
|
+
inputSchema: {
|
|
680
|
+
type: "object",
|
|
681
|
+
properties: {
|
|
682
|
+
view_id: { type: "number", description: "View ID to diagnose" },
|
|
683
|
+
},
|
|
684
|
+
required: ["view_id"],
|
|
685
|
+
},
|
|
686
|
+
},
|
|
784
687
|
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
785
688
|
// Packages (Marketplace Distribution)
|
|
786
689
|
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -1195,14 +1098,27 @@ const toolHandlers = {
|
|
|
1195
1098
|
},
|
|
1196
1099
|
async gufi_context(params) {
|
|
1197
1100
|
// Generate intelligent context based on what's requested
|
|
1101
|
+
const wantsFull = params.detail === "full";
|
|
1102
|
+
// Module-specific context (uses export-claude endpoint)
|
|
1103
|
+
if (params.module_id && params.company_id) {
|
|
1104
|
+
return getClaudeOptimizedContext(params.company_id, params.module_id, undefined, wantsFull);
|
|
1105
|
+
}
|
|
1106
|
+
// Entity-specific context (uses export-claude endpoint)
|
|
1107
|
+
if (params.entity_id && params.company_id) {
|
|
1108
|
+
return getClaudeOptimizedContext(params.company_id, undefined, params.entity_id, wantsFull);
|
|
1109
|
+
}
|
|
1198
1110
|
if (params.view_id) {
|
|
1199
|
-
return generateViewContextMcp(parseInt(params.view_id),
|
|
1111
|
+
return generateViewContextMcp(parseInt(params.view_id), wantsFull);
|
|
1200
1112
|
}
|
|
1201
1113
|
else if (params.package_id) {
|
|
1202
|
-
return generatePackageContextMcp(parseInt(params.package_id),
|
|
1114
|
+
return generatePackageContextMcp(parseInt(params.package_id), wantsFull);
|
|
1203
1115
|
}
|
|
1204
1116
|
else if (params.company_id) {
|
|
1205
|
-
|
|
1117
|
+
// Company context - use Claude-optimized export when detail is "full"
|
|
1118
|
+
if (wantsFull) {
|
|
1119
|
+
return getClaudeOptimizedContext(params.company_id, undefined, undefined, true);
|
|
1120
|
+
}
|
|
1121
|
+
return generateCompanyContextMcp(parseInt(params.company_id), false);
|
|
1206
1122
|
}
|
|
1207
1123
|
else {
|
|
1208
1124
|
// Auto-detect context from current directory
|
|
@@ -1237,7 +1153,7 @@ const toolHandlers = {
|
|
|
1237
1153
|
// 2. Check if we're in a view directory (.gufi-view.json)
|
|
1238
1154
|
const viewMeta = loadViewMetaFromCwd();
|
|
1239
1155
|
if (viewMeta) {
|
|
1240
|
-
return generateViewContextMcp(viewMeta.viewId,
|
|
1156
|
+
return generateViewContextMcp(viewMeta.viewId, wantsFull);
|
|
1241
1157
|
}
|
|
1242
1158
|
// 3. Fallback: Return packages overview
|
|
1243
1159
|
const response = await developerRequest("/my-packages");
|
|
@@ -1259,70 +1175,10 @@ const toolHandlers = {
|
|
|
1259
1175
|
};
|
|
1260
1176
|
}
|
|
1261
1177
|
},
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
// JSON format for programmatic use
|
|
1267
|
-
const data = await apiRequest("/api/company/schema", {
|
|
1268
|
-
headers: params.company_id ? { "X-Company-ID": params.company_id } : {},
|
|
1269
|
-
}, params.company_id);
|
|
1270
|
-
const modulesRaw = data.modules || data.data?.modules || [];
|
|
1271
|
-
const modules = [];
|
|
1272
|
-
for (const mod of modulesRaw) {
|
|
1273
|
-
let moduleId;
|
|
1274
|
-
const entities = [];
|
|
1275
|
-
for (const sub of mod.submodules || []) {
|
|
1276
|
-
for (const ent of sub.entities || []) {
|
|
1277
|
-
if (ent.ui?.moduleId)
|
|
1278
|
-
moduleId = ent.ui.moduleId;
|
|
1279
|
-
const entityId = ent.ui?.entityId || ent.pk_id || ent.id;
|
|
1280
|
-
entities.push({
|
|
1281
|
-
id: entityId,
|
|
1282
|
-
name: ent.name,
|
|
1283
|
-
label: ent.label,
|
|
1284
|
-
table: moduleId && entityId ? `m${moduleId}_t${entityId}` : null,
|
|
1285
|
-
fields: (ent.fields || []).map((f) => ({
|
|
1286
|
-
name: f.name,
|
|
1287
|
-
type: f.type,
|
|
1288
|
-
label: f.label,
|
|
1289
|
-
required: f.required,
|
|
1290
|
-
})),
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
modules.push({
|
|
1295
|
-
id: moduleId,
|
|
1296
|
-
name: mod.name || mod.displayName,
|
|
1297
|
-
entities,
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
return { format: "json", modules };
|
|
1301
|
-
}
|
|
1302
|
-
// Default: FLAT text format optimized for Claude comprehension
|
|
1303
|
-
const url = `${getApiUrl()}/api/schema/readable`;
|
|
1304
|
-
let token = getToken();
|
|
1305
|
-
if (!token) {
|
|
1306
|
-
token = await autoLogin();
|
|
1307
|
-
if (!token)
|
|
1308
|
-
throw new Error("Not logged in. Run: gufi login");
|
|
1309
|
-
}
|
|
1310
|
-
const headers = {
|
|
1311
|
-
Authorization: `Bearer ${token}`,
|
|
1312
|
-
"X-Client": "mcp",
|
|
1313
|
-
};
|
|
1314
|
-
if (params.company_id) {
|
|
1315
|
-
headers["X-Company-ID"] = params.company_id;
|
|
1316
|
-
}
|
|
1317
|
-
const response = await fetch(url, { headers });
|
|
1318
|
-
if (!response.ok) {
|
|
1319
|
-
const text = await response.text();
|
|
1320
|
-
throw new Error(`API Error ${response.status}: ${text}`);
|
|
1321
|
-
}
|
|
1322
|
-
// Return as plain text for maximum readability
|
|
1323
|
-
const schema = await response.text();
|
|
1324
|
-
return { format: "flat", schema };
|
|
1325
|
-
},
|
|
1178
|
+
// gufi_schema REMOVED - functionality merged into gufi_context with detail: "full"
|
|
1179
|
+
// Use: gufi_context({ company_id: "X", detail: "full" })
|
|
1180
|
+
// gufi_context({ company_id: "X", module_id: "Y", detail: "full" })
|
|
1181
|
+
// gufi_context({ company_id: "X", entity_id: "Z", detail: "full" })
|
|
1326
1182
|
async gufi_schema_modify(params) {
|
|
1327
1183
|
// Use the new /api/schema/operations endpoint for semantic operations
|
|
1328
1184
|
const result = await apiRequest("/api/schema/operations", {
|
|
@@ -1789,11 +1645,6 @@ const toolHandlers = {
|
|
|
1789
1645
|
case "create": {
|
|
1790
1646
|
if (!params.data)
|
|
1791
1647
|
throw new Error("data is required for 'create' action");
|
|
1792
|
-
// π Validate relation fields before creating
|
|
1793
|
-
const createValidation = await validateRelationFields(table, company_id, params.data);
|
|
1794
|
-
if (!createValidation.valid) {
|
|
1795
|
-
throw new Error(`Relation validation failed:\n${createValidation.errors.join("\n")}`);
|
|
1796
|
-
}
|
|
1797
1648
|
const result = await apiRequest(`/api/tables/${table}`, {
|
|
1798
1649
|
method: "POST",
|
|
1799
1650
|
body: JSON.stringify(params.data),
|
|
@@ -1805,11 +1656,6 @@ const toolHandlers = {
|
|
|
1805
1656
|
throw new Error("id is required for 'update' action");
|
|
1806
1657
|
if (!params.data)
|
|
1807
1658
|
throw new Error("data is required for 'update' action");
|
|
1808
|
-
// π Validate relation fields before updating
|
|
1809
|
-
const updateValidation = await validateRelationFields(table, company_id, params.data);
|
|
1810
|
-
if (!updateValidation.valid) {
|
|
1811
|
-
throw new Error(`Relation validation failed:\n${updateValidation.errors.join("\n")}`);
|
|
1812
|
-
}
|
|
1813
1659
|
const result = await apiRequest(`/api/tables/${table}/${params.id}`, {
|
|
1814
1660
|
method: "PUT",
|
|
1815
1661
|
body: JSON.stringify(params.data),
|
|
@@ -1869,11 +1715,6 @@ const toolHandlers = {
|
|
|
1869
1715
|
async gufi_row_create(params) {
|
|
1870
1716
|
// Normalize file fields (auto-generate id, mime, uploaded_at)
|
|
1871
1717
|
const normalizedData = normalizeFileFields(params.data);
|
|
1872
|
-
// π Validate relation fields before creating
|
|
1873
|
-
const validation = await validateRelationFields(params.table, params.company_id, normalizedData);
|
|
1874
|
-
if (!validation.valid) {
|
|
1875
|
-
throw new Error(`Relation validation failed:\n${validation.errors.join("\n")}`);
|
|
1876
|
-
}
|
|
1877
1718
|
const result = await apiRequest(`/api/tables/${params.table}`, {
|
|
1878
1719
|
method: "POST",
|
|
1879
1720
|
body: JSON.stringify(normalizedData),
|
|
@@ -1888,11 +1729,6 @@ const toolHandlers = {
|
|
|
1888
1729
|
async gufi_row_update(params) {
|
|
1889
1730
|
// Normalize file fields (auto-generate id, mime, uploaded_at)
|
|
1890
1731
|
const normalizedData = normalizeFileFields(params.data);
|
|
1891
|
-
// π Validate relation fields before updating
|
|
1892
|
-
const validation = await validateRelationFields(params.table, params.company_id, normalizedData);
|
|
1893
|
-
if (!validation.valid) {
|
|
1894
|
-
throw new Error(`Relation validation failed:\n${validation.errors.join("\n")}`);
|
|
1895
|
-
}
|
|
1896
1732
|
const result = await apiRequest(`/api/tables/${params.table}/${params.id}`, {
|
|
1897
1733
|
method: "PUT",
|
|
1898
1734
|
body: JSON.stringify(normalizedData),
|
|
@@ -1979,6 +1815,140 @@ const toolHandlers = {
|
|
|
1979
1815
|
: `β ${response.errors?.length || 0} errors, ${response.warnings?.length || 0} warnings`,
|
|
1980
1816
|
};
|
|
1981
1817
|
},
|
|
1818
|
+
async gufi_view_read(params) {
|
|
1819
|
+
const files = await getViewFiles(params.view_id);
|
|
1820
|
+
// If specific file requested, return just that file
|
|
1821
|
+
if (params.file_path) {
|
|
1822
|
+
const file = files.find((f) => f.file_path === params.file_path || f.file_path === `/${params.file_path}` || `/${f.file_path}` === params.file_path);
|
|
1823
|
+
if (!file) {
|
|
1824
|
+
return {
|
|
1825
|
+
error: `File not found: ${params.file_path}`,
|
|
1826
|
+
available_files: files.map((f) => f.file_path),
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
return {
|
|
1830
|
+
view_id: params.view_id,
|
|
1831
|
+
file_path: file.file_path,
|
|
1832
|
+
language: file.language,
|
|
1833
|
+
content: file.content,
|
|
1834
|
+
size: file.content.length,
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
// Return all files with content
|
|
1838
|
+
return {
|
|
1839
|
+
view_id: params.view_id,
|
|
1840
|
+
total_files: files.length,
|
|
1841
|
+
files: files.map((f) => ({
|
|
1842
|
+
path: f.file_path,
|
|
1843
|
+
language: f.language,
|
|
1844
|
+
is_entry_point: f.is_entry_point,
|
|
1845
|
+
content: f.content,
|
|
1846
|
+
size: f.content.length,
|
|
1847
|
+
})),
|
|
1848
|
+
};
|
|
1849
|
+
},
|
|
1850
|
+
async gufi_view_diagnose(params) {
|
|
1851
|
+
const files = await getViewFiles(params.view_id);
|
|
1852
|
+
const issues = [];
|
|
1853
|
+
// Patterns to detect
|
|
1854
|
+
const patterns = [
|
|
1855
|
+
{
|
|
1856
|
+
regex: /useCompanySchemaStore/g,
|
|
1857
|
+
issue: "Deprecated store import: useCompanySchemaStore",
|
|
1858
|
+
severity: "error",
|
|
1859
|
+
fix: "Use useSchemaStore from @/stores/useSchemaStore or gufi.context.schema",
|
|
1860
|
+
},
|
|
1861
|
+
{
|
|
1862
|
+
regex: /@\/stores\/(?!useSchemaStore|useAuthStore|usePermissionsStore)/g,
|
|
1863
|
+
issue: "Forbidden store import in marketplace view",
|
|
1864
|
+
severity: "error",
|
|
1865
|
+
fix: "Use gufi.context or gufi.utils instead of direct store imports",
|
|
1866
|
+
},
|
|
1867
|
+
{
|
|
1868
|
+
regex: /runAnalyticsSpec/g,
|
|
1869
|
+
issue: "runAnalyticsSpec is not available in marketplace SDK",
|
|
1870
|
+
severity: "error",
|
|
1871
|
+
fix: "Replace with dataProvider.getList() with filters, or use SQL aggregation on backend",
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
regex: /gufi\?\./g,
|
|
1875
|
+
issue: "Optional chaining on gufi - gufi should be REQUIRED",
|
|
1876
|
+
severity: "warning",
|
|
1877
|
+
fix: "Remove ?. and use gufi. directly. Ensure gufi: FullGufiContext in props (not optional)",
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
regex: /gufi\?: /g,
|
|
1881
|
+
issue: "gufi prop is optional - should be REQUIRED",
|
|
1882
|
+
severity: "error",
|
|
1883
|
+
fix: "Change 'gufi?:' to 'gufi:' in interface. gufi is always provided to marketplace views",
|
|
1884
|
+
},
|
|
1885
|
+
{
|
|
1886
|
+
regex: /['"]m\d+_t\d+['"]/g,
|
|
1887
|
+
issue: "Hardcoded physical table ID",
|
|
1888
|
+
severity: "warning",
|
|
1889
|
+
fix: "Use logical table names like 'module.entity' instead of physical IDs",
|
|
1890
|
+
},
|
|
1891
|
+
{
|
|
1892
|
+
regex: /import.*from\s+['"]@\/shared\/notifications/g,
|
|
1893
|
+
issue: "Direct notification import - use gufi.utils instead",
|
|
1894
|
+
severity: "warning",
|
|
1895
|
+
fix: "Use gufi.utils.toastSuccess/toastError/toastInfo instead",
|
|
1896
|
+
},
|
|
1897
|
+
{
|
|
1898
|
+
regex: /import.*from\s+['"]@\/shared\/language/g,
|
|
1899
|
+
issue: "Direct language import - use gufi.context instead",
|
|
1900
|
+
severity: "warning",
|
|
1901
|
+
fix: "Use gufi.context.lang and gufi.utils.tUI instead",
|
|
1902
|
+
},
|
|
1903
|
+
];
|
|
1904
|
+
for (const file of files) {
|
|
1905
|
+
if (!file.file_path.match(/\.(tsx?|jsx?)$/))
|
|
1906
|
+
continue;
|
|
1907
|
+
const lines = file.content.split("\n");
|
|
1908
|
+
for (const pattern of patterns) {
|
|
1909
|
+
// Reset regex
|
|
1910
|
+
pattern.regex.lastIndex = 0;
|
|
1911
|
+
let match;
|
|
1912
|
+
while ((match = pattern.regex.exec(file.content)) !== null) {
|
|
1913
|
+
// Find line number
|
|
1914
|
+
const beforeMatch = file.content.slice(0, match.index);
|
|
1915
|
+
const lineNumber = beforeMatch.split("\n").length;
|
|
1916
|
+
// π Skip if line is a comment (ignore false positives from documentation)
|
|
1917
|
+
const line = lines[lineNumber - 1] || "";
|
|
1918
|
+
const trimmedLine = line.trim();
|
|
1919
|
+
if (trimmedLine.startsWith("//") || trimmedLine.startsWith("*") || trimmedLine.startsWith("/*")) {
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
issues.push({
|
|
1923
|
+
file: file.file_path,
|
|
1924
|
+
line: lineNumber,
|
|
1925
|
+
issue: pattern.issue,
|
|
1926
|
+
severity: pattern.severity,
|
|
1927
|
+
fix: pattern.fix,
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
// Group issues by severity
|
|
1933
|
+
const errors = issues.filter(i => i.severity === "error");
|
|
1934
|
+
const warnings = issues.filter(i => i.severity === "warning");
|
|
1935
|
+
return {
|
|
1936
|
+
view_id: params.view_id,
|
|
1937
|
+
total_issues: issues.length,
|
|
1938
|
+
errors: errors.length,
|
|
1939
|
+
warnings: warnings.length,
|
|
1940
|
+
status: errors.length > 0 ? "β Has blocking issues" : warnings.length > 0 ? "β οΈ Has warnings" : "β
No issues found",
|
|
1941
|
+
issues: issues.map(i => ({
|
|
1942
|
+
...i,
|
|
1943
|
+
location: `${i.file}:${i.line}`,
|
|
1944
|
+
})),
|
|
1945
|
+
summary: errors.length > 0
|
|
1946
|
+
? `Found ${errors.length} error(s) that will cause runtime failures`
|
|
1947
|
+
: warnings.length > 0
|
|
1948
|
+
? `Found ${warnings.length} warning(s) that should be reviewed`
|
|
1949
|
+
: "View follows marketplace best practices",
|
|
1950
|
+
};
|
|
1951
|
+
},
|
|
1982
1952
|
async gufi_view_create(params) {
|
|
1983
1953
|
// Generate safe function name
|
|
1984
1954
|
const safeName = params.name.replace(/[^a-zA-Z0-9]/g, "");
|
|
@@ -2670,6 +2640,63 @@ export const featureConfig: FeatureConfig = {
|
|
|
2670
2640
|
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
2671
2641
|
// Context Generation Helpers
|
|
2672
2642
|
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
2643
|
+
/**
|
|
2644
|
+
* Get Claude-optimized context using the export-claude backend endpoint.
|
|
2645
|
+
* This returns the full schema in a format designed for Claude's comprehension.
|
|
2646
|
+
*
|
|
2647
|
+
* @param companyId - Company ID (required)
|
|
2648
|
+
* @param moduleId - Optional module ID to filter by
|
|
2649
|
+
* @param entityId - Optional entity ID to filter by
|
|
2650
|
+
* @param fullText - If true, return as plain text. Otherwise return structured.
|
|
2651
|
+
*/
|
|
2652
|
+
async function getClaudeOptimizedContext(companyId, moduleId, entityId, fullText = true) {
|
|
2653
|
+
// Build query params
|
|
2654
|
+
const params = new URLSearchParams();
|
|
2655
|
+
if (moduleId)
|
|
2656
|
+
params.set("module_id", moduleId);
|
|
2657
|
+
if (entityId)
|
|
2658
|
+
params.set("entity_id", entityId);
|
|
2659
|
+
const queryString = params.toString();
|
|
2660
|
+
const url = `${getApiUrl()}/api/schema/export-claude${queryString ? "?" + queryString : ""}`;
|
|
2661
|
+
let token = getToken();
|
|
2662
|
+
if (!token) {
|
|
2663
|
+
token = await autoLogin();
|
|
2664
|
+
if (!token)
|
|
2665
|
+
throw new Error("Not logged in. Run: gufi login");
|
|
2666
|
+
}
|
|
2667
|
+
const headers = {
|
|
2668
|
+
Authorization: `Bearer ${token}`,
|
|
2669
|
+
"X-Client": "mcp",
|
|
2670
|
+
"X-Company-ID": companyId,
|
|
2671
|
+
};
|
|
2672
|
+
const response = await fetch(url, { headers });
|
|
2673
|
+
if (!response.ok) {
|
|
2674
|
+
const text = await response.text();
|
|
2675
|
+
throw new Error(`API Error ${response.status}: ${text}`);
|
|
2676
|
+
}
|
|
2677
|
+
// Return as plain text for maximum readability
|
|
2678
|
+
const schema = await response.text();
|
|
2679
|
+
if (fullText) {
|
|
2680
|
+
return {
|
|
2681
|
+
type: entityId ? "entity_context" : moduleId ? "module_context" : "company_context",
|
|
2682
|
+
format: "claude_optimized",
|
|
2683
|
+
company_id: companyId,
|
|
2684
|
+
module_id: moduleId || null,
|
|
2685
|
+
entity_id: entityId || null,
|
|
2686
|
+
schema,
|
|
2687
|
+
hint: "This is the complete schema in Claude-optimized format. Use it to understand the data structure.",
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
// Parse and return structured (for summary mode)
|
|
2691
|
+
return {
|
|
2692
|
+
type: entityId ? "entity_context" : moduleId ? "module_context" : "company_context",
|
|
2693
|
+
format: "claude_optimized",
|
|
2694
|
+
company_id: companyId,
|
|
2695
|
+
module_id: moduleId || null,
|
|
2696
|
+
entity_id: entityId || null,
|
|
2697
|
+
schema,
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2673
2700
|
async function generateViewContextMcp(viewId, includeConcepts) {
|
|
2674
2701
|
// Use apiRequest for view details
|
|
2675
2702
|
const viewResponse = await apiRequest(`/api/marketplace/views/${viewId}`);
|