gufi-cli 0.1.6 → 0.1.8

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
@@ -612,7 +612,8 @@ Cuando descargas una view con `gufi pull`, obtienes:
612
612
  ├── types.ts # Interfaces TypeScript
613
613
 
614
614
  ├── core/
615
- └── dataProvider.ts # 💜 dataSources + featureConfig (MUY IMPORTANTE)
615
+ ├── dataProvider.ts # 💜 dataSources + featureConfig (MUY IMPORTANTE)
616
+ │ └── permissions.ts # 💜 Sistema de permisos dinámico
616
617
 
617
618
  ├── metadata/
618
619
  │ ├── inputs.ts # 💜 Inputs configurables por usuario
@@ -620,7 +621,8 @@ Cuando descargas una view con `gufi pull`, obtienes:
620
621
  │ └── help.en.ts # Documentación en inglés
621
622
 
622
623
  ├── components/
623
- └── MiComponente.tsx # Componentes React
624
+ ├── MiComponente.tsx # Componentes React
625
+ │ └── DevPermissionSwitcher.tsx # 💜 Testing de permisos en dev
624
626
 
625
627
  ├── views/
626
628
  │ └── page.tsx # Página principal
@@ -882,6 +884,158 @@ Para crear tarea, pulsa el botón **+**
882
884
 
883
885
  ---
884
886
 
887
+ ### 💜 permissions - Sistema de Permisos Explícitos (Sin Wildcards)
888
+
889
+ Las vistas usan un sistema de **permisos explícitos** - sin wildcards, sin magia. Cada permiso es un string específico que existe o no en el array de permisos del usuario.
890
+
891
+ > ⚠️ **Importante**: Eliminamos el soporte de wildcards (`*`) porque causaba problemas de integridad entre el estado de la UI y los permisos reales. Siempre usa strings de permisos explícitos.
892
+
893
+ **1. Declarar permisos en `metadata/permissions.ts`:**
894
+ ```typescript
895
+ // metadata/permissions.ts
896
+ export const permissions = [
897
+ // Permisos estáticos - toggles simples
898
+ {
899
+ key: 'send_orders',
900
+ label: { es: 'Enviar pedidos', en: 'Send orders' },
901
+ description: { es: 'Puede enviar pedidos a proveedores', en: 'Can send orders' },
902
+ },
903
+ {
904
+ key: 'view_costs',
905
+ label: { es: 'Ver costos', en: 'View costs' },
906
+ description: { es: 'Puede ver precios de compra', en: 'Can see purchase prices' },
907
+ },
908
+
909
+ // Permisos dinámicos - se resuelven en runtime
910
+ {
911
+ key: 'warehouse:*', // Placeholder, se resuelve a warehouse:madrid, warehouse:barcelona, etc.
912
+ label: { es: 'Acceso a almacén', en: 'Warehouse access' },
913
+ description: { es: 'Almacenes a los que tiene acceso', en: 'Warehouses user can access' },
914
+ dynamic: true, // Marca este como dinámico
915
+ },
916
+ ] as const;
917
+ ```
918
+
919
+ **2. Crear `core/permissions.ts` (sin wildcards):**
920
+ ```typescript
921
+ // core/permissions.ts
922
+ // SIN WILDCARDS - solo permisos explícitos
923
+
924
+ export interface PermissionsConfig {
925
+ userPermissions: string[]; // Permisos del usuario (resueltos desde su rol)
926
+ isDevMode: boolean;
927
+ devPermissions: string[]; // Override en modo dev para testing
928
+ }
929
+
930
+ // Obtener permisos efectivos (dev mode override)
931
+ function getEffectivePermissions(config: PermissionsConfig): string[] {
932
+ const { userPermissions, isDevMode, devPermissions } = config;
933
+ return (isDevMode && devPermissions.length > 0) ? devPermissions : userPermissions;
934
+ }
935
+
936
+ // Check simple - ¿tiene exactamente este permiso?
937
+ export function hasPermission(config: PermissionsConfig, permission: string): boolean {
938
+ const perms = getEffectivePermissions(config);
939
+ return perms.includes(permission);
940
+ }
941
+
942
+ // Helpers específicos de la vista
943
+ export function canSendOrders(config: PermissionsConfig): boolean {
944
+ return hasPermission(config, 'send_orders');
945
+ }
946
+
947
+ export function canViewCosts(config: PermissionsConfig): boolean {
948
+ return hasPermission(config, 'view_costs');
949
+ }
950
+
951
+ // Obtener warehouses permitidos (null si no hay restricciones)
952
+ export function getAllowedWarehouses(config: PermissionsConfig): string[] | null {
953
+ const perms = getEffectivePermissions(config);
954
+
955
+ const warehouses: string[] = [];
956
+ for (const perm of perms) {
957
+ if (perm.startsWith('warehouse:')) {
958
+ warehouses.push(perm.slice('warehouse:'.length));
959
+ }
960
+ }
961
+
962
+ return warehouses.length > 0 ? warehouses : null;
963
+ }
964
+ ```
965
+
966
+ **3. Usar en el componente con inicialización correcta:**
967
+ ```typescript
968
+ import { useState, useEffect, useRef } from 'react';
969
+ import { DevPermissionSwitcher } from '../components/DevPermissionSwitcher';
970
+ import { canSendOrders, getAllowedWarehouses } from '../core/permissions';
971
+
972
+ export default function MiVista({ gufi }) {
973
+ const lang = gufi?.context?.lang || 'es';
974
+ const isDevMode = gufi?.context?.isPreview || window.location.hostname === 'localhost';
975
+
976
+ // 💜 IMPORTANTE: Inicializar vacío, poblar cuando los datos carguen
977
+ const [devPermissions, setDevPermissions] = useState<string[]>([]);
978
+ const devPermissionsInitialized = useRef(false);
979
+
980
+ // Cargar warehouses de tu data source
981
+ const [warehouses, setWarehouses] = useState([]);
982
+
983
+ // Inicializar permisos cuando warehouses carguen (una sola vez)
984
+ useEffect(() => {
985
+ if (isDevMode && !devPermissionsInitialized.current && warehouses.length > 0) {
986
+ devPermissionsInitialized.current = true;
987
+
988
+ // Otorgar todos los permisos explícitos
989
+ const allPerms = [
990
+ 'send_orders',
991
+ 'view_costs',
992
+ ...warehouses.map(w => `warehouse:${w.name.toLowerCase()}`),
993
+ ];
994
+ setDevPermissions(allPerms);
995
+ }
996
+ }, [isDevMode, warehouses]);
997
+
998
+ // Construir config de permisos
999
+ const permissionsConfig = {
1000
+ userPermissions: gufi?.context?.user?.permissions || [],
1001
+ isDevMode,
1002
+ devPermissions,
1003
+ };
1004
+
1005
+ // Usar helpers de permisos
1006
+ const showCosts = canViewCosts(permissionsConfig);
1007
+ const allowedWarehouses = getAllowedWarehouses(permissionsConfig);
1008
+
1009
+ return (
1010
+ <div>
1011
+ {showCosts && <CostsColumn />}
1012
+
1013
+ {/* DevPermissionSwitcher - solo en dev mode */}
1014
+ {isDevMode && (
1015
+ <DevPermissionSwitcher
1016
+ lang={lang}
1017
+ activePermissions={devPermissions}
1018
+ onPermissionsChange={setDevPermissions}
1019
+ dynamicValues={{
1020
+ warehouse: warehouses.map(w => w.name.toLowerCase())
1021
+ }}
1022
+ />
1023
+ )}
1024
+ </div>
1025
+ );
1026
+ }
1027
+ ```
1028
+
1029
+ **DevPermissionSwitcher**: Botón flotante **purple** (💜 Gufi style) que permite:
1030
+ - Toggle individual de permisos on/off
1031
+ - Botón "Todos" en el header para acceso completo
1032
+ - Permisos dinámicos expandibles (ej: warehouses individuales)
1033
+ - Integridad total: UI siempre refleja el estado real
1034
+
1035
+ **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
+
1037
+ ---
1038
+
885
1039
  ### 💜 automations en Vistas
886
1040
 
887
1041
  Las vistas pueden incluir automations que se ejecutan al hacer click.
@@ -1419,6 +1573,29 @@ gufi automation calcular_stock -c 116 --edit
1419
1573
  gufi automation calcular_stock -c 116 --file script.js
1420
1574
  ```
1421
1575
 
1576
+ ### Environment Variables
1577
+
1578
+ ```bash
1579
+ # Ver variables de entorno de la company
1580
+ gufi env
1581
+
1582
+ # Crear/actualizar variable
1583
+ gufi env:set STRIPE_API_KEY sk-live-xxx
1584
+
1585
+ # Eliminar variable
1586
+ gufi env:delete STRIPE_API_KEY
1587
+ ```
1588
+
1589
+ ### Schema (Estructura de Tablas)
1590
+
1591
+ ```bash
1592
+ # Ver todas las tablas de la company
1593
+ gufi schema
1594
+
1595
+ # Filtrar por módulo
1596
+ gufi schema -m 360
1597
+ ```
1598
+
1422
1599
  ### Row CRUD (Datos de Tablas)
1423
1600
 
1424
1601
  ```bash
@@ -0,0 +1,30 @@
1
+ /**
2
+ * gufi env - Manage company environment variables
3
+ * gufi schema - View company table structure
4
+ */
5
+ interface EnvOptions {
6
+ company?: string;
7
+ set?: string;
8
+ delete?: boolean;
9
+ }
10
+ interface SchemaOptions {
11
+ company?: string;
12
+ module?: string;
13
+ }
14
+ /**
15
+ * gufi env - List environment variables
16
+ */
17
+ export declare function envListCommand(options: EnvOptions): Promise<void>;
18
+ /**
19
+ * gufi env:set <key> <value> - Set an environment variable
20
+ */
21
+ export declare function envSetCommand(key: string, value: string, options: EnvOptions): Promise<void>;
22
+ /**
23
+ * gufi env:delete <key> - Delete an environment variable
24
+ */
25
+ export declare function envDeleteCommand(key: string, options: EnvOptions): Promise<void>;
26
+ /**
27
+ * gufi schema - View company table structure
28
+ */
29
+ export declare function schemaCommand(options: SchemaOptions): Promise<void>;
30
+ export {};
@@ -0,0 +1,145 @@
1
+ /**
2
+ * gufi env - Manage company environment variables
3
+ * gufi schema - View company table structure
4
+ */
5
+ import chalk from "chalk";
6
+ import ora from "ora";
7
+ import { getToken, getApiUrl } from "../lib/config.js";
8
+ async function apiRequest(endpoint, options = {}) {
9
+ const token = getToken();
10
+ if (!token) {
11
+ throw new Error("No estás logueado. Ejecuta: gufi login");
12
+ }
13
+ const url = `${getApiUrl()}${endpoint}`;
14
+ const response = await fetch(url, {
15
+ ...options,
16
+ headers: {
17
+ "Content-Type": "application/json",
18
+ Authorization: `Bearer ${token}`,
19
+ "X-Client": "cli",
20
+ ...options.headers,
21
+ },
22
+ });
23
+ if (!response.ok) {
24
+ const text = await response.text();
25
+ throw new Error(`API Error ${response.status}: ${text}`);
26
+ }
27
+ return response.json();
28
+ }
29
+ /**
30
+ * gufi env - List environment variables
31
+ */
32
+ export async function envListCommand(options) {
33
+ const spinner = ora("Cargando variables de entorno...").start();
34
+ try {
35
+ const data = await apiRequest("/api/cli/env");
36
+ spinner.stop();
37
+ console.log(chalk.magenta("\n 🔐 Variables de Entorno\n"));
38
+ if (!data || data.length === 0) {
39
+ console.log(chalk.gray(" No hay variables configuradas\n"));
40
+ console.log(chalk.gray(" Usa: gufi env:set <KEY> <VALUE>\n"));
41
+ return;
42
+ }
43
+ // Show as table
44
+ const maxKeyLen = Math.max(...data.map((v) => v.key.length), 10);
45
+ console.log(chalk.gray(" " + "KEY".padEnd(maxKeyLen + 2) + "VALUE"));
46
+ console.log(chalk.gray(" " + "─".repeat(maxKeyLen + 20)));
47
+ for (const env of data) {
48
+ const maskedValue = env.is_secret
49
+ ? "••••••••"
50
+ : (env.value?.substring(0, 30) + (env.value?.length > 30 ? "..." : ""));
51
+ console.log(" " +
52
+ chalk.cyan(env.key.padEnd(maxKeyLen + 2)) +
53
+ chalk.white(maskedValue));
54
+ }
55
+ console.log(chalk.gray(`\n Total: ${data.length} variables\n`));
56
+ }
57
+ catch (error) {
58
+ spinner.fail(chalk.red(error.message));
59
+ process.exit(1);
60
+ }
61
+ }
62
+ /**
63
+ * gufi env:set <key> <value> - Set an environment variable
64
+ */
65
+ export async function envSetCommand(key, value, options) {
66
+ const spinner = ora(`Guardando ${key}...`).start();
67
+ try {
68
+ await apiRequest("/api/cli/env", {
69
+ method: "POST",
70
+ body: JSON.stringify({ key, value }),
71
+ });
72
+ spinner.succeed(chalk.green(`${key} guardada`));
73
+ console.log();
74
+ }
75
+ catch (error) {
76
+ spinner.fail(chalk.red(error.message));
77
+ process.exit(1);
78
+ }
79
+ }
80
+ /**
81
+ * gufi env:delete <key> - Delete an environment variable
82
+ */
83
+ export async function envDeleteCommand(key, options) {
84
+ const spinner = ora(`Eliminando ${key}...`).start();
85
+ try {
86
+ await apiRequest(`/api/cli/env/${encodeURIComponent(key)}`, {
87
+ method: "DELETE",
88
+ });
89
+ spinner.succeed(chalk.green(`${key} eliminada`));
90
+ console.log();
91
+ }
92
+ catch (error) {
93
+ spinner.fail(chalk.red(error.message));
94
+ process.exit(1);
95
+ }
96
+ }
97
+ /**
98
+ * gufi schema - View company table structure
99
+ */
100
+ export async function schemaCommand(options) {
101
+ const spinner = ora("Cargando schema...").start();
102
+ try {
103
+ const data = await apiRequest("/api/cli/schema");
104
+ spinner.stop();
105
+ console.log(chalk.magenta("\n 📊 Schema de la Company\n"));
106
+ if (!data.modules || data.modules.length === 0) {
107
+ console.log(chalk.gray(" No hay módulos\n"));
108
+ return;
109
+ }
110
+ for (const module of data.modules) {
111
+ // Filter by module if specified
112
+ if (options.module && module.id !== parseInt(options.module)) {
113
+ continue;
114
+ }
115
+ console.log(chalk.cyan(` 📦 ${module.name}`) +
116
+ chalk.gray(` (ID: ${module.id})`));
117
+ if (module.entities && module.entities.length > 0) {
118
+ for (const entity of module.entities) {
119
+ const tableName = `m${module.id}_t${entity.id}`;
120
+ console.log(chalk.white(` └─ ${entity.name}`) +
121
+ chalk.gray(` → ${tableName}`));
122
+ // Show fields if available
123
+ if (entity.fields && entity.fields.length > 0) {
124
+ const fieldList = entity.fields
125
+ .slice(0, 5)
126
+ .map((f) => f.name)
127
+ .join(", ");
128
+ const more = entity.fields.length > 5
129
+ ? ` +${entity.fields.length - 5} más`
130
+ : "";
131
+ console.log(chalk.gray(` campos: ${fieldList}${more}`));
132
+ }
133
+ }
134
+ }
135
+ console.log();
136
+ }
137
+ // Summary
138
+ const totalTables = data.modules.reduce((acc, m) => acc + (m.entities?.length || 0), 0);
139
+ console.log(chalk.gray(` ${data.modules.length} módulos, ${totalTables} tablas\n`));
140
+ }
141
+ catch (error) {
142
+ spinner.fail(chalk.red(error.message));
143
+ process.exit(1);
144
+ }
145
+ }
@@ -1,53 +1,11 @@
1
1
  /**
2
2
  * gufi login - Authenticate with Gufi
3
3
  */
4
- import readline from "readline";
4
+ import prompts from "prompts";
5
5
  import chalk from "chalk";
6
6
  import ora from "ora";
7
7
  import { login, validateToken } from "../lib/api.js";
8
8
  import { setToken, isLoggedIn, clearToken, loadConfig, setApiUrl } from "../lib/config.js";
9
- function prompt(question, hidden = false) {
10
- const rl = readline.createInterface({
11
- input: process.stdin,
12
- output: process.stdout,
13
- });
14
- return new Promise((resolve) => {
15
- if (hidden) {
16
- process.stdout.write(question);
17
- let input = "";
18
- process.stdin.setRawMode?.(true);
19
- process.stdin.resume();
20
- process.stdin.on("data", (char) => {
21
- const c = char.toString();
22
- if (c === "\n" || c === "\r") {
23
- process.stdin.setRawMode?.(false);
24
- process.stdout.write("\n");
25
- rl.close();
26
- resolve(input);
27
- }
28
- else if (c === "\u0003") {
29
- process.exit();
30
- }
31
- else if (c === "\u007F") {
32
- if (input.length > 0) {
33
- input = input.slice(0, -1);
34
- process.stdout.write("\b \b");
35
- }
36
- }
37
- else {
38
- input += c;
39
- process.stdout.write("*");
40
- }
41
- });
42
- }
43
- else {
44
- rl.question(question, (answer) => {
45
- rl.close();
46
- resolve(answer);
47
- });
48
- }
49
- });
50
- }
51
9
  export async function loginCommand(options) {
52
10
  console.log(chalk.magenta("\n 🟣 Gufi Developer CLI\n"));
53
11
  // Set custom API URL if provided
@@ -62,8 +20,13 @@ export async function loginCommand(options) {
62
20
  const valid = await validateToken();
63
21
  if (valid) {
64
22
  spinner.succeed(chalk.green(`Ya estás logueado como ${config.email}`));
65
- const relogin = await prompt("\n¿Quieres iniciar sesión con otra cuenta? (s/N): ");
66
- if (relogin.toLowerCase() !== "s") {
23
+ const { relogin } = await prompts({
24
+ type: "confirm",
25
+ name: "relogin",
26
+ message: "¿Quieres iniciar sesión con otra cuenta?",
27
+ initial: false,
28
+ });
29
+ if (!relogin) {
67
30
  return;
68
31
  }
69
32
  clearToken();
@@ -73,19 +36,34 @@ export async function loginCommand(options) {
73
36
  clearToken();
74
37
  }
75
38
  }
76
- // Get credentials
77
- const email = await prompt(" Email: ");
78
- const password = await prompt(" Password: ", true);
79
- if (!email || !password) {
39
+ // Get credentials using prompts (more robust than readline)
40
+ const response = await prompts([
41
+ {
42
+ type: "text",
43
+ name: "email",
44
+ message: "Email",
45
+ validate: (v) => (v ? true : "Email es requerido"),
46
+ },
47
+ {
48
+ type: "password",
49
+ name: "password",
50
+ message: "Password",
51
+ validate: (v) => (v ? true : "Password es requerido"),
52
+ },
53
+ ]);
54
+ if (!response.email || !response.password) {
80
55
  console.log(chalk.red("\n ✗ Email y password son requeridos\n"));
81
56
  process.exit(1);
82
57
  }
83
58
  const spinner = ora("Iniciando sesión...").start();
84
59
  try {
85
- const { token, refreshToken } = await login(email, password);
86
- setToken(token, email, refreshToken);
87
- spinner.succeed(chalk.green(`Sesión iniciada como ${email}`));
88
- console.log(chalk.gray("\n Tu sesión se mantendrá activa automáticamente.\n"));
60
+ const { token, refreshToken } = await login(response.email, response.password);
61
+ if (!refreshToken) {
62
+ console.log(chalk.yellow("\n ⚠️ No se recibió refresh token - sesión durará 1 hora"));
63
+ }
64
+ setToken(token, response.email, refreshToken);
65
+ spinner.succeed(chalk.green(`Sesión iniciada como ${response.email}`));
66
+ console.log(chalk.gray("\n Tu sesión se mantendrá activa automáticamente (7 días).\n"));
89
67
  console.log(chalk.gray(" Ahora puedes usar: gufi pull <vista>\n"));
90
68
  }
91
69
  catch (error) {
@@ -21,7 +21,7 @@ export async function pullCommand(viewIdentifier) {
21
21
  const spinner = ora("Obteniendo vista...").start();
22
22
  try {
23
23
  const view = await getView(viewId);
24
- viewName = view.name;
24
+ viewName = view.name || `vista-${viewId}`;
25
25
  packageId = view.package_id;
26
26
  spinner.succeed(`Vista: ${viewName}`);
27
27
  }
@@ -57,11 +57,12 @@ export async function pullCommand(viewIdentifier) {
57
57
  }
58
58
  console.log(chalk.gray(" Vistas disponibles:\n"));
59
59
  views.forEach((view, i) => {
60
- console.log(` ${chalk.cyan(i + 1)}. ${view.name} ${chalk.gray(`(${view.view_type})`)}`);
60
+ const name = view.name || `Vista ${view.pk_id}`;
61
+ console.log(` ${chalk.cyan(i + 1)}. ${name} ${chalk.gray(`(${view.view_type})`)}`);
61
62
  });
62
63
  // If viewIdentifier matches a name
63
64
  let selectedView = viewIdentifier
64
- ? views.find(v => v.name.toLowerCase().includes(viewIdentifier.toLowerCase()))
65
+ ? views.find(v => v.name && v.name.toLowerCase().includes(viewIdentifier.toLowerCase()))
65
66
  : views[0];
66
67
  if (!selectedView) {
67
68
  console.log(chalk.yellow(`\n No se encontró vista "${viewIdentifier}"\n`));
@@ -69,7 +70,7 @@ export async function pullCommand(viewIdentifier) {
69
70
  process.exit(1);
70
71
  }
71
72
  viewId = selectedView.pk_id;
72
- viewName = selectedView.name;
73
+ viewName = selectedView.name || `vista-${selectedView.pk_id}`;
73
74
  console.log(chalk.gray(`\n Descargando: ${viewName}\n`));
74
75
  }
75
76
  catch (error) {
package/dist/index.js CHANGED
@@ -39,11 +39,12 @@ import { listCommand } from "./commands/list.js";
39
39
  import { logsCommand } from "./commands/logs.js";
40
40
  import { companiesCommand, modulesCommand, moduleCommand, moduleUpdateCommand, companyCreateCommand, automationsCommand, automationCommand, } from "./commands/companies.js";
41
41
  import { rowsListCommand, rowGetCommand, rowCreateCommand, rowUpdateCommand, rowDeleteCommand, rowDuplicateCommand, rowsBulkCreateCommand, } from "./commands/rows.js";
42
+ import { envListCommand, envSetCommand, envDeleteCommand, schemaCommand, } from "./commands/env.js";
42
43
  const program = new Command();
43
44
  program
44
45
  .name("gufi")
45
46
  .description("🟣 Gufi CLI - Desarrolla módulos, vistas y automations")
46
- .version("0.1.6");
47
+ .version("0.1.7");
47
48
  // ════════════════════════════════════════════════════════════════════
48
49
  // 🔐 Auth
49
50
  // ════════════════════════════════════════════════════════════════════
@@ -75,6 +76,11 @@ program
75
76
  .command("modules <company_id>")
76
77
  .description("Ver módulos de una company")
77
78
  .action(modulesCommand);
79
+ program
80
+ .command("schema")
81
+ .description("Ver estructura de tablas de la company")
82
+ .option("-m, --module <id>", "Filtrar por módulo")
83
+ .action(schemaCommand);
78
84
  program
79
85
  .command("module <module_id>")
80
86
  .description("Ver/editar JSON de un módulo")
@@ -104,6 +110,21 @@ program
104
110
  .option("-f, --file <path>", "Exportar a archivo")
105
111
  .action(automationCommand);
106
112
  // ════════════════════════════════════════════════════════════════════
113
+ // 🔐 Environment Variables
114
+ // ════════════════════════════════════════════════════════════════════
115
+ program
116
+ .command("env")
117
+ .description("Ver variables de entorno de la company")
118
+ .action(envListCommand);
119
+ program
120
+ .command("env:set <key> <value>")
121
+ .description("Crear/actualizar variable de entorno")
122
+ .action(envSetCommand);
123
+ program
124
+ .command("env:delete <key>")
125
+ .description("Eliminar variable de entorno")
126
+ .action(envDeleteCommand);
127
+ // ════════════════════════════════════════════════════════════════════
107
128
  // 📊 Row CRUD - Datos de tablas
108
129
  // ════════════════════════════════════════════════════════════════════
109
130
  program
package/dist/lib/api.js CHANGED
@@ -14,8 +14,10 @@ class ApiError extends Error {
14
14
  // Auto-refresh the token if expired
15
15
  async function refreshAccessToken() {
16
16
  const refreshToken = getRefreshToken();
17
- if (!refreshToken)
17
+ if (!refreshToken) {
18
+ console.error("[gufi] No refresh token available");
18
19
  return null;
20
+ }
19
21
  try {
20
22
  const url = `${getApiUrl()}/api/auth/refresh`;
21
23
  const response = await fetch(url, {
@@ -26,14 +28,18 @@ async function refreshAccessToken() {
26
28
  "X-Refresh-Token": refreshToken,
27
29
  },
28
30
  });
29
- if (!response.ok)
31
+ if (!response.ok) {
32
+ console.error(`[gufi] Refresh failed: ${response.status}`);
30
33
  return null;
34
+ }
31
35
  const data = await response.json();
32
36
  const config = loadConfig();
33
37
  setToken(data.accessToken, config.email || "", data.refreshToken);
38
+ console.log("[gufi] Token refreshed successfully");
34
39
  return data.accessToken;
35
40
  }
36
- catch {
41
+ catch (err) {
42
+ console.error("[gufi] Refresh error:", err);
37
43
  return null;
38
44
  }
39
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"
@@ -22,12 +22,14 @@
22
22
  "start": "node bin/gufi.js"
23
23
  },
24
24
  "dependencies": {
25
+ "@types/prompts": "^2.4.9",
25
26
  "@types/ws": "^8.18.1",
26
27
  "chalk": "^5.3.0",
27
28
  "chokidar": "^3.5.3",
28
29
  "commander": "^12.0.0",
29
30
  "node-fetch": "^3.3.2",
30
31
  "ora": "^8.0.1",
32
+ "prompts": "^2.4.2",
31
33
  "ws": "^8.18.3"
32
34
  },
33
35
  "devDependencies": {