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 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
- - `summary`: Resumen ejecutivo
67
- - `next_actions`: Pasos sugeridos
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` | Genera contexto del proyecto para Claude |
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 |
@@ -53,33 +53,39 @@ function countResults(sections) {
53
53
  // ════════════════════════════════════════════════════════════════════
54
54
  function validateFeatureConfig(content, filePath) {
55
55
  const results = [];
56
- // Check export default
57
- if (!content.includes("export default")) {
58
- results.push({
59
- status: "error",
60
- message: "Falta export default del componente",
61
- details: filePath,
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 { featureConfig }",
74
- details: "Necesario para que Developer Center detecte la configuraciΓ³n",
65
+ message: "Falta export default del componente",
66
+ details: filePath,
75
67
  });
76
68
  }
77
69
  else {
78
- results.push({ status: "ok", message: "featureConfig re-exportado" });
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
- const indexFile = files.find((f) => f.file_path === "index.tsx");
549
- if (indexFile) {
550
- viewResults.push(...validateFeatureConfig(indexFile.content, "index.tsx"));
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
- viewResults.push(...validateDataSources(dpFile.content));
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
  }
@@ -2,14 +2,10 @@
2
2
  * gufi push - Upload local changes to Gufi
3
3
  * πŸ’œ Now with featureConfig validation!
4
4
  */
5
- interface PushFlags {
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 {};
@@ -60,17 +60,12 @@ function scanFilesInDirectory(dir, changedFiles) {
60
60
  }));
61
61
  return scanFilesForXSS(filesToScan);
62
62
  }
63
- export async function pushCommand(viewDir, flags = {}) {
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, { force: forceMode });
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
- .option("-f, --force", "Forzar push de todos los archivos (ignora detecciΓ³n de cambios)")
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")
@@ -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
@@ -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, options?: {
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, options = {}) {
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
- if (!force) {
108
- const storedMeta = meta.files[filePath];
109
- if (storedMeta && storedMeta.hash === localHash) {
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
- full: { type: "boolean", description: "Include detailed explanations of Gufi concepts (viewSpec, dataSources, CB builders, etc.)" },
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), params.full);
1111
+ return generateViewContextMcp(parseInt(params.view_id), wantsFull);
1200
1112
  }
1201
1113
  else if (params.package_id) {
1202
- return generatePackageContextMcp(parseInt(params.package_id), params.full);
1114
+ return generatePackageContextMcp(parseInt(params.package_id), wantsFull);
1203
1115
  }
1204
1116
  else if (params.company_id) {
1205
- return generateCompanyContextMcp(parseInt(params.company_id), params.full);
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, params.full);
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
- async gufi_schema(params) {
1263
- // Use the new /api/schema/readable endpoint for FLAT format (default)
1264
- // This returns human-readable text optimized for Claude comprehension
1265
- if (params.format === "json") {
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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"