gufi-cli 0.1.49 → 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.
- package/dist/lib/security.js +5 -0
- package/dist/mcp.js +78 -39
- package/package.json +1 -1
package/dist/lib/security.js
CHANGED
|
@@ -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
|
-
|
|
520
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
@@ -1453,9 +1456,9 @@ const toolHandlers = {
|
|
|
1453
1456
|
let tableName = null;
|
|
1454
1457
|
// Resolve entity if provided (optional for standalone scripts)
|
|
1455
1458
|
if (entity) {
|
|
1456
|
-
const schema = await getCompanySchema(company_id, env);
|
|
1459
|
+
const { schema, error } = await getCompanySchema(company_id, env);
|
|
1457
1460
|
if (!schema?.modules) {
|
|
1458
|
-
throw new Error(
|
|
1461
|
+
throw new Error(`Cannot get schema: ${error || "unknown error"}`);
|
|
1459
1462
|
}
|
|
1460
1463
|
// Parse entity (module.entity format)
|
|
1461
1464
|
const parts = entity.split(".");
|
|
@@ -1466,13 +1469,13 @@ const toolHandlers = {
|
|
|
1466
1469
|
// Find module and entity IDs
|
|
1467
1470
|
for (const mod of schema.modules) {
|
|
1468
1471
|
const modMatch = mod.name?.toLowerCase() === moduleName.toLowerCase() ||
|
|
1469
|
-
mod.
|
|
1472
|
+
mod.display_name?.toLowerCase() === moduleName.toLowerCase();
|
|
1470
1473
|
if (!modMatch)
|
|
1471
1474
|
continue;
|
|
1472
1475
|
moduleId = mod.id;
|
|
1473
1476
|
for (const ent of mod.entities || []) {
|
|
1474
1477
|
const entMatch = ent.name?.toLowerCase() === entityName.toLowerCase() ||
|
|
1475
|
-
ent.
|
|
1478
|
+
ent.display_name?.toLowerCase() === entityName.toLowerCase();
|
|
1476
1479
|
if (entMatch) {
|
|
1477
1480
|
tableId = ent.id;
|
|
1478
1481
|
tableName = `m${moduleId}_t${tableId}`;
|
|
@@ -1785,7 +1788,9 @@ const toolHandlers = {
|
|
|
1785
1788
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1786
1789
|
async gufi_view_pull(params) {
|
|
1787
1790
|
const viewId = params.view_id;
|
|
1788
|
-
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");
|
|
1789
1794
|
const env = params.env;
|
|
1790
1795
|
const useLocal = canWriteLocal();
|
|
1791
1796
|
// Get view info
|
|
@@ -1798,9 +1803,14 @@ const toolHandlers = {
|
|
|
1798
1803
|
const filesResponse = await apiRequest(`/api/marketplace/views/${viewId}/files`, {}, companyId, true, env);
|
|
1799
1804
|
const files = Array.isArray(filesResponse) ? filesResponse : (filesResponse.data || []);
|
|
1800
1805
|
if (useLocal) {
|
|
1801
|
-
// 💜 CLI: Save to ~/gufi-dev/view_<id>/
|
|
1802
|
-
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}`);
|
|
1803
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
|
+
}
|
|
1804
1814
|
// 💜 Get latest snapshot for version tracking
|
|
1805
1815
|
let latestSnapshot;
|
|
1806
1816
|
try {
|
|
@@ -1823,9 +1833,10 @@ const toolHandlers = {
|
|
|
1823
1833
|
const meta = {
|
|
1824
1834
|
viewId,
|
|
1825
1835
|
viewName: `view_${viewId}`,
|
|
1836
|
+
company_id: Number(companyId),
|
|
1826
1837
|
packageId: view.package_id || 0,
|
|
1827
1838
|
lastSync: new Date().toISOString(),
|
|
1828
|
-
lastPulledSnapshot: latestSnapshot,
|
|
1839
|
+
lastPulledSnapshot: latestSnapshot,
|
|
1829
1840
|
files: fileMeta,
|
|
1830
1841
|
};
|
|
1831
1842
|
fs.writeFileSync(path.join(viewDir, ".gufi-view.json"), JSON.stringify(meta, null, 2));
|
|
@@ -1837,7 +1848,7 @@ const toolHandlers = {
|
|
|
1837
1848
|
local_path: viewDir,
|
|
1838
1849
|
storage: "local",
|
|
1839
1850
|
files_count: files.length,
|
|
1840
|
-
_hint: `📥 View downloaded to ${viewDir}/. Use Read/Edit tools to modify files. Then gufi_view_push({ view_id: ${viewId},
|
|
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.`,
|
|
1841
1852
|
};
|
|
1842
1853
|
}
|
|
1843
1854
|
else {
|
|
@@ -1871,33 +1882,59 @@ const toolHandlers = {
|
|
|
1871
1882
|
},
|
|
1872
1883
|
async gufi_view_push(params) {
|
|
1873
1884
|
const viewId = params.view_id;
|
|
1874
|
-
let companyId = params.company_id;
|
|
1885
|
+
let companyId = params.company_id != null ? String(params.company_id) : undefined;
|
|
1875
1886
|
const env = params.env;
|
|
1876
1887
|
const useLocal = canWriteLocal();
|
|
1888
|
+
if (!viewId) {
|
|
1889
|
+
throw new Error("view_id is required");
|
|
1890
|
+
}
|
|
1877
1891
|
// 💜 Try to get company_id from view metadata if not provided
|
|
1878
|
-
if (!companyId &&
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
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 */ }
|
|
1885
1919
|
}
|
|
1886
|
-
catch { /* ignore */ }
|
|
1887
1920
|
}
|
|
1888
1921
|
}
|
|
1889
|
-
if (!
|
|
1890
|
-
throw new Error("
|
|
1922
|
+
if (!companyId) {
|
|
1923
|
+
throw new Error("company_id is required. Pass it as parameter or pull the view first.");
|
|
1891
1924
|
}
|
|
1892
1925
|
let files = [];
|
|
1893
|
-
let lastPulledSnapshot;
|
|
1926
|
+
let lastPulledSnapshot;
|
|
1894
1927
|
if (useLocal) {
|
|
1895
|
-
// 💜 CLI: Read from ~/gufi-dev/view_<id>/
|
|
1896
|
-
|
|
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}`);
|
|
1930
|
+
if (!fs.existsSync(viewDir)) {
|
|
1931
|
+
// Fallback to old path
|
|
1932
|
+
viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
|
|
1933
|
+
}
|
|
1897
1934
|
if (!fs.existsSync(viewDir)) {
|
|
1898
|
-
throw new Error(`View directory not found: ${
|
|
1935
|
+
throw new Error(`View directory not found. Run gufi_view_pull({ view_id: ${viewId}, company_id: '${companyId}' }) first.`);
|
|
1899
1936
|
}
|
|
1900
|
-
// 💜 Read metadata for version tracking
|
|
1937
|
+
// 💜 Read metadata for version tracking
|
|
1901
1938
|
const metaPath = path.join(viewDir, ".gufi-view.json");
|
|
1902
1939
|
if (fs.existsSync(metaPath)) {
|
|
1903
1940
|
try {
|
|
@@ -2018,7 +2055,9 @@ const toolHandlers = {
|
|
|
2018
2055
|
}
|
|
2019
2056
|
// 💜 Update local metadata with new snapshot number (for version tracking)
|
|
2020
2057
|
if (useLocal && result.snapshot) {
|
|
2021
|
-
|
|
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}`);
|
|
2022
2061
|
const metaPath = path.join(viewDir, ".gufi-view.json");
|
|
2023
2062
|
if (fs.existsSync(metaPath)) {
|
|
2024
2063
|
try {
|
|
@@ -2556,7 +2595,7 @@ function findTableInModulesMcp(modules, tableName) {
|
|
|
2556
2595
|
fields: (ent.fields || []).map((f) => ({
|
|
2557
2596
|
name: f.name,
|
|
2558
2597
|
type: f.type,
|
|
2559
|
-
|
|
2598
|
+
display_name: f.display_name,
|
|
2560
2599
|
required: f.required || false,
|
|
2561
2600
|
options: f.options?.map((o) => o.value || o),
|
|
2562
2601
|
})),
|