gufi-cli 0.1.48 → 0.1.50

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.
@@ -114,6 +114,11 @@ const ALLOWED_PATTERNS = [
114
114
  // CSRF token reading from cookies - legitimate security practice
115
115
  /csrf-token/i,
116
116
  /getCsrfToken/,
117
+ // Auth cookie management - legitimate for ecommerce auth
118
+ /strong_store_auth/,
119
+ /setAuthCookie/,
120
+ /clearAuthCookie/,
121
+ /document\.cookie/,
117
122
  ];
118
123
  /**
119
124
  * Scan a file for suspicious patterns
package/dist/mcp.js CHANGED
@@ -502,22 +502,24 @@ const SCHEMA_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
502
502
  const PHYSICAL_TABLE_RE = /^m\d+_t\d+$/;
503
503
  /**
504
504
  * Get company schema (cached)
505
+ * Returns { schema, error } - error is set if fetch failed
505
506
  */
506
507
  async function getCompanySchema(companyId, env) {
507
508
  // Include env in cache key to avoid mixing schemas from different environments
508
509
  const cacheKey = `${companyId}:${env || 'prod'}`;
509
510
  const cached = schemaCache.get(cacheKey);
510
511
  if (cached && Date.now() - cached.timestamp < SCHEMA_CACHE_TTL) {
511
- return cached.schema;
512
+ return { schema: cached.schema };
512
513
  }
513
514
  try {
514
515
  const data = await apiRequest("/api/cli/schema", {}, companyId, true, env);
515
516
  schemaCache.set(cacheKey, { schema: data, timestamp: Date.now() });
516
- return data;
517
+ return { schema: data };
517
518
  }
518
519
  catch (err) {
519
- console.error(`[MCP] Failed to get schema for company ${companyId}:`, err.message);
520
- return null;
520
+ const errorMsg = err.message;
521
+ console.error(`[MCP] Failed to get schema for company ${companyId}:`, errorMsg);
522
+ return { schema: null, error: errorMsg };
521
523
  }
522
524
  }
523
525
  /**
@@ -538,19 +540,20 @@ async function resolveTableName(tableName, companyId, env) {
538
540
  throw new Error(`Invalid table name: "${tableName}". Use physical (m123_t456) or logical (module.entity) format`);
539
541
  }
540
542
  const [moduleName, entityName] = parts;
541
- const schema = await getCompanySchema(companyId, env);
543
+ const { schema, error } = await getCompanySchema(companyId, env);
542
544
  if (!schema?.modules) {
543
- throw new Error(`Cannot resolve logical table name "${tableName}": schema not available. Use physical name (m123_t456) instead`);
545
+ const reason = error || "unknown error";
546
+ throw new Error(`Cannot resolve logical table name "${tableName}": ${reason}`);
544
547
  }
545
548
  // Find module and entity
546
549
  for (const mod of schema.modules) {
547
550
  const modMatch = mod.name?.toLowerCase() === moduleName.toLowerCase() ||
548
- mod.label?.toLowerCase() === moduleName.toLowerCase();
551
+ mod.display_name?.toLowerCase() === moduleName.toLowerCase();
549
552
  if (!modMatch)
550
553
  continue;
551
554
  for (const ent of mod.entities || []) {
552
555
  const entMatch = ent.name?.toLowerCase() === entityName.toLowerCase() ||
553
- ent.label?.toLowerCase() === entityName.toLowerCase();
556
+ ent.display_name?.toLowerCase() === entityName.toLowerCase();
554
557
  if (entMatch) {
555
558
  // Found! Build physical name from entity ID
556
559
  const moduleId = mod.id;
@@ -856,20 +859,20 @@ Solo Admin y Consultant pueden gestionar endpoints.`,
856
859
  name: "gufi_view_pull",
857
860
  description: `📥 Download view files to local directory for editing.
858
861
 
859
- Downloads to: ~/gufi-dev/view_<id>/
862
+ Downloads to: ~/gufi-dev/company_<id>/view_<id>/
860
863
 
861
864
  After pulling, use Read/Edit tools to work with local files.
862
865
  Then use gufi_view_push to upload changes.
863
866
 
864
- Example: gufi_view_pull({ view_id: 13 })`,
867
+ Example: gufi_view_pull({ view_id: 13, company_id: '150' })`,
865
868
  inputSchema: {
866
869
  type: "object",
867
870
  properties: {
868
871
  view_id: { type: "number", description: "View ID to pull" },
869
- company_id: { type: "string", description: "Company ID (required for company-specific views)" },
872
+ company_id: { type: "string", description: "Company ID (required)" },
870
873
  env: ENV_PARAM,
871
874
  },
872
- required: ["view_id"],
875
+ required: ["view_id", "company_id"],
873
876
  },
874
877
  },
875
878
  // 📤 Push local changes to draft
@@ -941,25 +944,9 @@ Examples:
941
944
  },
942
945
  },
943
946
  // ─────────────────────────────────────────────────────────────────────────
944
- // Packages (Marketplace Distribution)
947
+ // 💜 DISABLED: Packages (not actively used, keeping MCP simple)
945
948
  // ─────────────────────────────────────────────────────────────────────────
946
- {
947
- name: "gufi_package",
948
- description: getDesc("gufi_package"),
949
- inputSchema: {
950
- type: "object",
951
- properties: {
952
- action: { type: "string", description: "Action: list, get, create, delete, add_module, remove_module, publish" },
953
- id: { type: "string", description: "Package ID (for get, delete, add_module, remove_module, publish)" },
954
- name: { type: "string", description: "Package name (for create)" },
955
- description: { type: "string", description: "Package description (for create)" },
956
- module_id: { type: "string", description: "Module ID (for add_module, remove_module)" },
957
- company_id: { type: "string", description: "Source company ID (for add_module)" },
958
- env: ENV_PARAM,
959
- },
960
- required: ["action"],
961
- },
962
- },
949
+ // gufi_package → Re-enable when marketplace packages become priority
963
950
  ];
964
951
  // ════════════════════════════════════════════════════════════════════════════
965
952
  // Tool Handlers
@@ -1469,9 +1456,9 @@ const toolHandlers = {
1469
1456
  let tableName = null;
1470
1457
  // Resolve entity if provided (optional for standalone scripts)
1471
1458
  if (entity) {
1472
- const schema = await getCompanySchema(company_id, env);
1459
+ const { schema, error } = await getCompanySchema(company_id, env);
1473
1460
  if (!schema?.modules) {
1474
- throw new Error("Cannot get schema. Make sure company_id is correct.");
1461
+ throw new Error(`Cannot get schema: ${error || "unknown error"}`);
1475
1462
  }
1476
1463
  // Parse entity (module.entity format)
1477
1464
  const parts = entity.split(".");
@@ -1482,13 +1469,13 @@ const toolHandlers = {
1482
1469
  // Find module and entity IDs
1483
1470
  for (const mod of schema.modules) {
1484
1471
  const modMatch = mod.name?.toLowerCase() === moduleName.toLowerCase() ||
1485
- mod.label?.toLowerCase() === moduleName.toLowerCase();
1472
+ mod.display_name?.toLowerCase() === moduleName.toLowerCase();
1486
1473
  if (!modMatch)
1487
1474
  continue;
1488
1475
  moduleId = mod.id;
1489
1476
  for (const ent of mod.entities || []) {
1490
1477
  const entMatch = ent.name?.toLowerCase() === entityName.toLowerCase() ||
1491
- ent.label?.toLowerCase() === entityName.toLowerCase();
1478
+ ent.display_name?.toLowerCase() === entityName.toLowerCase();
1492
1479
  if (entMatch) {
1493
1480
  tableId = ent.id;
1494
1481
  tableName = `m${moduleId}_t${tableId}`;
@@ -1801,7 +1788,9 @@ const toolHandlers = {
1801
1788
  // ─────────────────────────────────────────────────────────────────────────
1802
1789
  async gufi_view_pull(params) {
1803
1790
  const viewId = params.view_id;
1804
- const companyId = params.company_id;
1791
+ const companyId = params.company_id != null ? String(params.company_id) : undefined;
1792
+ if (!companyId)
1793
+ throw new Error("company_id is required for view pull");
1805
1794
  const env = params.env;
1806
1795
  const useLocal = canWriteLocal();
1807
1796
  // Get view info
@@ -1814,9 +1803,14 @@ const toolHandlers = {
1814
1803
  const filesResponse = await apiRequest(`/api/marketplace/views/${viewId}/files`, {}, companyId, true, env);
1815
1804
  const files = Array.isArray(filesResponse) ? filesResponse : (filesResponse.data || []);
1816
1805
  if (useLocal) {
1817
- // 💜 CLI: Save to ~/gufi-dev/view_<id>/
1818
- const viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
1806
+ // 💜 CLI: Save to ~/gufi-dev/company_<id>/view_<id>/
1807
+ const viewDir = path.join(LOCAL_VIEWS_DIR, `company_${companyId}`, `view_${viewId}`);
1819
1808
  fs.mkdirSync(viewDir, { recursive: true });
1809
+ // 💜 Migrate: if old path exists (~/gufi-dev/view_<id>/), remove it
1810
+ const oldViewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
1811
+ if (fs.existsSync(oldViewDir)) {
1812
+ fs.rmSync(oldViewDir, { recursive: true, force: true });
1813
+ }
1820
1814
  // 💜 Get latest snapshot for version tracking
1821
1815
  let latestSnapshot;
1822
1816
  try {
@@ -1839,9 +1833,10 @@ const toolHandlers = {
1839
1833
  const meta = {
1840
1834
  viewId,
1841
1835
  viewName: `view_${viewId}`,
1836
+ company_id: Number(companyId),
1842
1837
  packageId: view.package_id || 0,
1843
1838
  lastSync: new Date().toISOString(),
1844
- lastPulledSnapshot: latestSnapshot, // 💜 For version conflict detection
1839
+ lastPulledSnapshot: latestSnapshot,
1845
1840
  files: fileMeta,
1846
1841
  };
1847
1842
  fs.writeFileSync(path.join(viewDir, ".gufi-view.json"), JSON.stringify(meta, null, 2));
@@ -1853,14 +1848,23 @@ const toolHandlers = {
1853
1848
  local_path: viewDir,
1854
1849
  storage: "local",
1855
1850
  files_count: files.length,
1856
- _hint: `📥 View downloaded to ${viewDir}/. Use Read/Edit tools to modify files. Then gufi_view_push({ view_id: ${viewId}, env: '${env || 'prod'}' }) to upload.`,
1851
+ _hint: `📥 View downloaded to ${viewDir}/. Use Read/Edit tools to modify files. Then gufi_view_push({ view_id: ${viewId}, company_id: '${companyId}' }) to upload.`,
1857
1852
  };
1858
1853
  }
1859
1854
  else {
1860
1855
  // 💜 Web: Save to Claude Workspace BD
1856
+ // Get latest snapshot for version tracking
1857
+ let latestSnapshot;
1858
+ try {
1859
+ const snapshotResp = await apiRequest(`/api/marketplace/views/${viewId}/latest-snapshot`, {}, companyId, true, env);
1860
+ latestSnapshot = snapshotResp.latest_snapshot;
1861
+ }
1862
+ catch {
1863
+ // If endpoint not available, continue without snapshot tracking
1864
+ }
1861
1865
  const saveResponse = await apiRequest(`/api/claude/workspace/view`, {
1862
1866
  method: "POST",
1863
- body: JSON.stringify({ view_id: viewId, files }),
1867
+ body: JSON.stringify({ view_id: viewId, files, snapshot: latestSnapshot, company_id: companyId }),
1864
1868
  }, companyId, true, env);
1865
1869
  const saveResult = saveResponse.data || saveResponse;
1866
1870
  const viewFolder = saveResult?.folder || `views/view_${viewId}`;
@@ -1872,26 +1876,65 @@ const toolHandlers = {
1872
1876
  local_path: `~/workspace/${viewFolder}`,
1873
1877
  storage: "workspace",
1874
1878
  files_count: files.length,
1875
- _hint: `📥 View downloaded to Claude Workspace. Use Read/Edit tools to modify files. Then gufi_view_push({ view_id: ${viewId} }) to upload.`,
1879
+ _hint: `📥 View downloaded to Claude Workspace. Use Read/Edit tools to modify files. Then gufi_view_push({ view_id: ${viewId}, company_id: '${companyId}' }) to upload.`,
1876
1880
  };
1877
1881
  }
1878
1882
  },
1879
1883
  async gufi_view_push(params) {
1880
1884
  const viewId = params.view_id;
1885
+ let companyId = params.company_id != null ? String(params.company_id) : undefined;
1881
1886
  const env = params.env;
1882
1887
  const useLocal = canWriteLocal();
1883
1888
  if (!viewId) {
1884
1889
  throw new Error("view_id is required");
1885
1890
  }
1891
+ // 💜 Try to get company_id from view metadata if not provided
1892
+ if (!companyId && useLocal) {
1893
+ // Try new path first (company_*/view_*)
1894
+ const companyDirs = fs.existsSync(LOCAL_VIEWS_DIR)
1895
+ ? fs.readdirSync(LOCAL_VIEWS_DIR).filter(d => d.startsWith("company_"))
1896
+ : [];
1897
+ for (const cd of companyDirs) {
1898
+ const metaPath = path.join(LOCAL_VIEWS_DIR, cd, `view_${viewId}`, ".gufi-view.json");
1899
+ if (fs.existsSync(metaPath)) {
1900
+ try {
1901
+ const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
1902
+ if (meta.company_id)
1903
+ companyId = String(meta.company_id);
1904
+ }
1905
+ catch { /* ignore */ }
1906
+ break;
1907
+ }
1908
+ }
1909
+ // Fallback: old path (view_*)
1910
+ if (!companyId) {
1911
+ const oldMetaPath = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`, ".gufi-view.json");
1912
+ if (fs.existsSync(oldMetaPath)) {
1913
+ try {
1914
+ const meta = JSON.parse(fs.readFileSync(oldMetaPath, "utf-8"));
1915
+ if (meta.company_id)
1916
+ companyId = String(meta.company_id);
1917
+ }
1918
+ catch { /* ignore */ }
1919
+ }
1920
+ }
1921
+ }
1922
+ if (!companyId) {
1923
+ throw new Error("company_id is required. Pass it as parameter or pull the view first.");
1924
+ }
1886
1925
  let files = [];
1887
- let lastPulledSnapshot; // 💜 For version conflict detection
1926
+ let lastPulledSnapshot;
1888
1927
  if (useLocal) {
1889
- // 💜 CLI: Read from ~/gufi-dev/view_<id>/
1890
- const viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
1928
+ // 💜 CLI: Read from ~/gufi-dev/company_<id>/view_<id>/ (or legacy ~/gufi-dev/view_<id>/)
1929
+ let viewDir = path.join(LOCAL_VIEWS_DIR, `company_${companyId}`, `view_${viewId}`);
1891
1930
  if (!fs.existsSync(viewDir)) {
1892
- throw new Error(`View directory not found: ${viewDir}. Run gufi_view_pull first.`);
1931
+ // Fallback to old path
1932
+ viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
1893
1933
  }
1894
- // 💜 Read metadata for version tracking (backend handles the check)
1934
+ if (!fs.existsSync(viewDir)) {
1935
+ throw new Error(`View directory not found. Run gufi_view_pull({ view_id: ${viewId}, company_id: '${companyId}' }) first.`);
1936
+ }
1937
+ // 💜 Read metadata for version tracking
1895
1938
  const metaPath = path.join(viewDir, ".gufi-view.json");
1896
1939
  if (fs.existsSync(metaPath)) {
1897
1940
  try {
@@ -1933,13 +1976,26 @@ const toolHandlers = {
1933
1976
  }
1934
1977
  else {
1935
1978
  // 💜 Web: Read from Claude Workspace BD
1936
- const workspaceResponse = await apiRequest(`/api/claude/workspace/view/${viewId}/files`, {}, undefined, true, env);
1979
+ const workspaceResponse = await apiRequest(`/api/claude/workspace/view/${viewId}/files`, {}, companyId, true, env);
1937
1980
  files = workspaceResponse.data || workspaceResponse || [];
1981
+ // 💜 Also read metadata for version tracking
1982
+ try {
1983
+ const metaResponse = await apiRequest(`/api/claude/workspace/view/${viewId}/meta`, {}, companyId, true, env);
1984
+ // sendData wraps response in { data: [...] } format
1985
+ const meta = metaResponse.data?.[0] || metaResponse;
1986
+ lastPulledSnapshot = meta.lastPulledSnapshot;
1987
+ if (!companyId && meta.company_id) {
1988
+ companyId = String(meta.company_id);
1989
+ }
1990
+ }
1991
+ catch {
1992
+ // Metadata not found - view wasn't pulled through workspace
1993
+ }
1938
1994
  }
1939
1995
  // 💜 VERSION CHECK: Always verify before push (don't rely on backend alone)
1940
1996
  // This prevents overwriting changes from other developers
1941
1997
  try {
1942
- const snapshotResp = await apiRequest(`/api/marketplace/views/${viewId}/latest-snapshot`, {}, undefined, true, env);
1998
+ const snapshotResp = await apiRequest(`/api/marketplace/views/${viewId}/latest-snapshot`, {}, companyId, true, env);
1943
1999
  const serverSnapshot = snapshotResp.latest_snapshot;
1944
2000
  // If server has versions but we don't have a local snapshot → conflict
1945
2001
  // (means we pulled with an old CLI that didn't track versions)
@@ -1981,7 +2037,7 @@ const toolHandlers = {
1981
2037
  result = await apiRequest(`/api/marketplace/views/${viewId}/files/bulk`, {
1982
2038
  method: "POST",
1983
2039
  body: JSON.stringify({ files, message: params.message, sync: true, lastPulledSnapshot }),
1984
- }, undefined, true, env);
2040
+ }, companyId, true, env);
1985
2041
  }
1986
2042
  catch (err) {
1987
2043
  // 💜 Handle version conflict from backend
@@ -1999,7 +2055,9 @@ const toolHandlers = {
1999
2055
  }
2000
2056
  // 💜 Update local metadata with new snapshot number (for version tracking)
2001
2057
  if (useLocal && result.snapshot) {
2002
- const viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
2058
+ let viewDir = path.join(LOCAL_VIEWS_DIR, `company_${companyId}`, `view_${viewId}`);
2059
+ if (!fs.existsSync(viewDir))
2060
+ viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
2003
2061
  const metaPath = path.join(viewDir, ".gufi-view.json");
2004
2062
  if (fs.existsSync(metaPath)) {
2005
2063
  try {
@@ -2015,11 +2073,36 @@ const toolHandlers = {
2015
2073
  }
2016
2074
  // Get view info for package
2017
2075
  let packageInfo = null;
2018
- const viewResponse = await apiRequest(`/api/marketplace/views/${viewId}`, {}, undefined, true, env);
2076
+ const viewResponse = await apiRequest(`/api/marketplace/views/${viewId}`, {}, companyId, true, env);
2019
2077
  const view = viewResponse.data || viewResponse;
2020
2078
  if (view?.package_id) {
2021
2079
  packageInfo = { id: view.package_id, publish_cmd: `gufi package:publish ${view.package_id}` };
2022
2080
  }
2081
+ // 💜 Extract and save publicAutomations from core/automations.ts
2082
+ const automationsFile = files.find(f => f.file_path === '/core/automations.ts' || f.file_path === 'core/automations.ts');
2083
+ if (automationsFile && view?.company_id && view?.entity_id) {
2084
+ try {
2085
+ // Parse publicAutomations from the file content
2086
+ const content = automationsFile.content;
2087
+ const publicMatch = content.match(/export\s+const\s+publicAutomations\s*=\s*\[([\s\S]*?)\]/);
2088
+ if (publicMatch) {
2089
+ // Extract string values from the array
2090
+ const arrContent = publicMatch[1];
2091
+ const publicAutomations = [...arrContent.matchAll(/['"]([^'"]+)['"]/g)].map(m => m[1]);
2092
+ if (publicAutomations.length > 0) {
2093
+ // Update entity config with publicAutomations
2094
+ await apiRequest(`/api/entities/${view.entity_id}/config`, {
2095
+ method: "PATCH",
2096
+ body: JSON.stringify({ publicAutomations }),
2097
+ }, String(view.company_id), true, env);
2098
+ }
2099
+ }
2100
+ }
2101
+ catch (err) {
2102
+ // Non-fatal: log but continue
2103
+ console.error("Warning: Could not update publicAutomations:", err);
2104
+ }
2105
+ }
2023
2106
  // Build response
2024
2107
  const response = {
2025
2108
  success: true,
@@ -2512,7 +2595,7 @@ function findTableInModulesMcp(modules, tableName) {
2512
2595
  fields: (ent.fields || []).map((f) => ({
2513
2596
  name: f.name,
2514
2597
  type: f.type,
2515
- label: f.label,
2598
+ display_name: f.display_name,
2516
2599
  required: f.required || false,
2517
2600
  options: f.options?.map((o) => o.value || o),
2518
2601
  })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"