gufi-cli 0.1.8 → 0.1.9

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
@@ -604,10 +604,10 @@ SELECT id, name, code FROM marketplace.views WHERE package_id = 14;
604
604
 
605
605
  ### Estructura de Archivos de una View
606
606
 
607
- Cuando descargas una view con `gufi pull`, obtienes:
607
+ Cuando descargas una view con `gufi view:pull <id>`, obtienes:
608
608
 
609
609
  ```
610
- ~/gufi-dev/mi-vista/
610
+ ~/gufi-dev/view_<id>/
611
611
  ├── index.tsx # Entry point - exporta featureConfig y default
612
612
  ├── types.ts # Interfaces TypeScript
613
613
 
@@ -1032,6 +1032,46 @@ export default function MiVista({ gufi }) {
1032
1032
  - Permisos dinámicos expandibles (ej: warehouses individuales)
1033
1033
  - Integridad total: UI siempre refleja el estado real
1034
1034
 
1035
+ ### 💜 DevPermissionSwitcher AUTOMÁTICO (LivePreviewPage)
1036
+
1037
+ **¡Ya no necesitas incluir DevPermissionSwitcher en tu código!** LivePreviewPage detecta automáticamente si tu vista tiene `featureConfig.permissions` y muestra el botón DEV.
1038
+
1039
+ **Cómo funciona:**
1040
+ 1. Declara permisos en `metadata/permissions.ts`
1041
+ 2. Expórtalos en `featureConfig`:
1042
+ ```typescript
1043
+ // core/dataProvider.ts
1044
+ import { permissions } from '../metadata/permissions';
1045
+
1046
+ export const featureConfig = {
1047
+ dataSources,
1048
+ inputs: featureInputs,
1049
+ permissions, // 💜 Solo añadir esto
1050
+ };
1051
+ ```
1052
+ 3. LivePreviewPage lo detecta y muestra el botón DEV automáticamente
1053
+
1054
+ **Usar devPermissions del contexto:**
1055
+ ```typescript
1056
+ export default function MiVista({ gufi }) {
1057
+ // 💜 LivePreviewPage provee devPermissions automáticamente
1058
+ const effectiveDevPermissions = gufi?.context?.devPermissions || [];
1059
+
1060
+ const permissionsConfig = {
1061
+ userPermissions: gufi?.context?.userPermissions || [],
1062
+ isDevMode: gufi?.context?.isPreview || gufi?.context?.isDev,
1063
+ devPermissions: effectiveDevPermissions,
1064
+ };
1065
+
1066
+ // Usar helpers de permisos normalmente
1067
+ const canSend = hasPermission(permissionsConfig, 'send_orders');
1068
+ }
1069
+ ```
1070
+
1071
+ **Cuándo incluir DevPermissionSwitcher manualmente:**
1072
+ - Solo si la vista se ejecuta fuera de LivePreviewPage (ej: vista nativa del frontend)
1073
+ - Para vistas del marketplace en Developer Center → **NO necesario**, es automático
1074
+
1035
1075
  **Principio clave**: Inicializar con `[]` vacío, luego usar `useEffect` para otorgar todos los permisos cuando los valores dinámicos (como warehouses) terminen de cargar. Esto garantiza integridad UI.
1036
1076
 
1037
1077
  ---
@@ -1629,11 +1669,13 @@ gufi rows:create m360_t16192 --file datos.json
1629
1669
  ### Desarrollo de Views
1630
1670
 
1631
1671
  ```bash
1632
- # Ver tus views del Marketplace
1672
+ # Ver tus views del Marketplace (muestra ID de cada vista)
1633
1673
  gufi views
1674
+ # 📦 Gestión de Tareas (ID: 14)
1675
+ # └─ 13 Tasks Manager (custom)
1634
1676
 
1635
- # Descargar view para editar localmente
1636
- gufi view:pull "Stock Overview"
1677
+ # Descargar view por ID (se guarda en ~/gufi-dev/view_<id>/)
1678
+ gufi view:pull 13
1637
1679
 
1638
1680
  # Auto-sync al guardar archivos
1639
1681
  gufi view:watch
@@ -1670,7 +1712,7 @@ gufi view:status
1670
1712
  | Ver estructura de una company | `gufi modules <company_id>` |
1671
1713
  | Ver/editar JSON de módulo | `gufi module <id> -c <company>` |
1672
1714
  | Ver/editar código de automation | `gufi automation <nombre> -c <company>` |
1673
- | Desarrollar una vista | `gufi pull`, `gufi watch`, `gufi logs` |
1715
+ | Desarrollar una vista | `gufi view:pull <id>`, `gufi view:watch`, `gufi view:logs` |
1674
1716
 
1675
1717
  ### Errores comunes
1676
1718
 
@@ -370,11 +370,12 @@ export async function companyCreateCommand(name) {
370
370
  method: "POST",
371
371
  body: JSON.stringify({ name }),
372
372
  });
373
- const company = result.data || result;
373
+ // API returns { company: {...}, message: "..." }
374
+ const company = result.company || result.data || result;
374
375
  console.log(chalk.green(" ✓ Company creada!\n"));
375
376
  console.log(chalk.white(` ID: ${company.id}`));
376
377
  console.log(chalk.white(` Nombre: ${company.name}`));
377
- console.log(chalk.white(` Schema: company_${company.id}`));
378
+ console.log(chalk.white(` Schema: ${company.schema || 'company_' + company.id}`));
378
379
  console.log(chalk.gray("\n 💡 Usa: gufi modules " + company.id + " para ver módulos\n"));
379
380
  }
380
381
  catch (err) {
@@ -29,11 +29,11 @@ export async function listCommand() {
29
29
  else {
30
30
  views.forEach((view, i) => {
31
31
  const prefix = i === views.length - 1 ? "└─" : "├─";
32
- console.log(chalk.white(` ${prefix} ${view.name}`) + chalk.gray(` (${view.view_type}, ID: ${view.pk_id})`));
32
+ console.log(` ${prefix} ${chalk.cyan(view.pk_id)} ${view.name} ${chalk.gray(`(${view.view_type})`)}`);
33
33
  });
34
34
  }
35
35
  }
36
- console.log(chalk.gray("\n Usa: gufi pull <view-id> para descargar una vista\n"));
36
+ console.log(chalk.gray("\n Usa: ") + chalk.cyan("gufi view:pull <id>") + chalk.gray(" para descargar una vista\n"));
37
37
  }
38
38
  catch (error) {
39
39
  spinner.fail(chalk.red(error.message));
@@ -13,17 +13,17 @@ export async function pullCommand(viewIdentifier) {
13
13
  }
14
14
  console.log(chalk.magenta("\n 🟣 Gufi Pull\n"));
15
15
  let viewId;
16
- let viewName;
16
+ let viewKey;
17
17
  let packageId;
18
- // If view ID provided directly
18
+ // 💜 Solo acepta ID numérico
19
19
  if (viewIdentifier && /^\d+$/.test(viewIdentifier)) {
20
20
  viewId = parseInt(viewIdentifier);
21
21
  const spinner = ora("Obteniendo vista...").start();
22
22
  try {
23
23
  const view = await getView(viewId);
24
- viewName = view.name || `vista-${viewId}`;
24
+ viewKey = `view_${viewId}`;
25
25
  packageId = view.package_id;
26
- spinner.succeed(`Vista: ${viewName}`);
26
+ spinner.succeed(`Vista ${chalk.cyan(viewId)}: ${view.name || "sin nombre"}`);
27
27
  }
28
28
  catch (error) {
29
29
  spinner.fail(chalk.red(`No se encontró la vista ${viewId}`));
@@ -56,22 +56,14 @@ export async function pullCommand(viewIdentifier) {
56
56
  process.exit(0);
57
57
  }
58
58
  console.log(chalk.gray(" Vistas disponibles:\n"));
59
- views.forEach((view, i) => {
60
- const name = view.name || `Vista ${view.pk_id}`;
61
- console.log(` ${chalk.cyan(i + 1)}. ${name} ${chalk.gray(`(${view.view_type})`)}`);
59
+ views.forEach((view) => {
60
+ console.log(` ${chalk.cyan(view.pk_id)} ${view.name || "sin nombre"} ${chalk.gray(`(${view.view_type})`)}`);
62
61
  });
63
- // If viewIdentifier matches a name
64
- let selectedView = viewIdentifier
65
- ? views.find(v => v.name && v.name.toLowerCase().includes(viewIdentifier.toLowerCase()))
66
- : views[0];
67
- if (!selectedView) {
68
- console.log(chalk.yellow(`\n No se encontró vista "${viewIdentifier}"\n`));
69
- console.log(chalk.gray(" Uso: gufi pull <nombre-vista> o gufi pull <view-id>\n"));
70
- process.exit(1);
62
+ if (viewIdentifier) {
63
+ console.log(chalk.yellow(`\n "${viewIdentifier}" no es un ID válido.\n`));
71
64
  }
72
- viewId = selectedView.pk_id;
73
- viewName = selectedView.name || `vista-${selectedView.pk_id}`;
74
- console.log(chalk.gray(`\n Descargando: ${viewName}\n`));
65
+ console.log(chalk.gray("\n Uso: ") + chalk.cyan("gufi view:pull <id>") + chalk.gray(" (ej: gufi view:pull 13)\n"));
66
+ process.exit(0);
75
67
  }
76
68
  catch (error) {
77
69
  spinner.fail(chalk.red(error.message));
@@ -81,7 +73,7 @@ export async function pullCommand(viewIdentifier) {
81
73
  // Pull the view
82
74
  const pullSpinner = ora("Descargando archivos...").start();
83
75
  try {
84
- const result = await pullView(viewId, viewName, packageId);
76
+ const result = await pullView(viewId, viewKey, packageId);
85
77
  pullSpinner.succeed(chalk.green(`${result.fileCount} archivos descargados`));
86
78
  console.log(chalk.gray(`\n 📁 ${result.dir}\n`));
87
79
  console.log(chalk.gray(" Comandos útiles:"));
package/dist/index.js CHANGED
@@ -44,7 +44,7 @@ const program = new Command();
44
44
  program
45
45
  .name("gufi")
46
46
  .description("🟣 Gufi CLI - Desarrolla módulos, vistas y automations")
47
- .version("0.1.7");
47
+ .version("0.1.8");
48
48
  // ════════════════════════════════════════════════════════════════════
49
49
  // 🔐 Auth
50
50
  // ════════════════════════════════════════════════════════════════════
@@ -12,12 +12,15 @@ export interface ViewMeta {
12
12
  mtime: number;
13
13
  }>;
14
14
  }
15
- export declare function getViewDir(viewName: string): string;
15
+ export declare function getViewDir(viewKey: string): string;
16
16
  export declare function loadViewMeta(viewDir: string): ViewMeta | null;
17
17
  /**
18
18
  * Pull view files from Gufi to local directory
19
+ * @param viewId - The view ID
20
+ * @param viewKey - The unique identifier in view_<id> format (e.g., view_123)
21
+ * @param packageId - The package ID
19
22
  */
20
- export declare function pullView(viewId: number, viewName: string, packageId: number): Promise<{
23
+ export declare function pullView(viewId: number, viewKey: string, packageId: number): Promise<{
21
24
  dir: string;
22
25
  fileCount: number;
23
26
  }>;
package/dist/lib/sync.js CHANGED
@@ -37,8 +37,9 @@ function getLanguage(filePath) {
37
37
  };
38
38
  return langMap[ext] || "text";
39
39
  }
40
- export function getViewDir(viewName) {
41
- return path.join(GUFI_DEV_DIR, viewName.toLowerCase().replace(/\s+/g, "-"));
40
+ // 💜 viewKey is now always in view_<id> format (e.g., view_123)
41
+ export function getViewDir(viewKey) {
42
+ return path.join(GUFI_DEV_DIR, viewKey.toLowerCase());
42
43
  }
43
44
  export function loadViewMeta(viewDir) {
44
45
  const metaPath = path.join(viewDir, META_FILE);
@@ -57,9 +58,12 @@ function saveViewMeta(viewDir, meta) {
57
58
  }
58
59
  /**
59
60
  * Pull view files from Gufi to local directory
61
+ * @param viewId - The view ID
62
+ * @param viewKey - The unique identifier in view_<id> format (e.g., view_123)
63
+ * @param packageId - The package ID
60
64
  */
61
- export async function pullView(viewId, viewName, packageId) {
62
- const viewDir = getViewDir(viewName);
65
+ export async function pullView(viewId, viewKey, packageId) {
66
+ const viewDir = getViewDir(viewKey);
63
67
  ensureDir(viewDir);
64
68
  const files = await getViewFiles(viewId);
65
69
  const fileMeta = {};
@@ -74,14 +78,14 @@ export async function pullView(viewId, viewName, packageId) {
74
78
  }
75
79
  const meta = {
76
80
  viewId,
77
- viewName,
81
+ viewName: viewKey, // 💜 Store viewKey as viewName for backwards compat
78
82
  packageId,
79
83
  lastSync: new Date().toISOString(),
80
84
  files: fileMeta,
81
85
  };
82
86
  saveViewMeta(viewDir, meta);
83
87
  // Update current view in config
84
- setCurrentView({ id: viewId, name: viewName, packageId, localPath: viewDir });
88
+ setCurrentView({ id: viewId, name: viewKey, packageId, localPath: viewDir });
85
89
  return { dir: viewDir, fileCount: files.length };
86
90
  }
87
91
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"