gufi-cli 0.1.2 → 0.1.3

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
@@ -987,16 +987,117 @@ interface GufiProps {
987
987
 
988
988
  ---
989
989
 
990
+ ### 💜 seedData - Datos de Ejemplo para Demo/Testing
991
+
992
+ Las vistas pueden incluir datos de ejemplo que se cargan automáticamente al instalar. Esto es útil para demos, testing, y onboarding de nuevos usuarios.
993
+
994
+ ```typescript
995
+ // metadata/seedData.ts
996
+ export interface SeedDataConfig {
997
+ description: { es: string; en: string };
998
+ data: { [dataSourceKey: string]: Array<Record<string, any>> };
999
+ order: string[]; // Orden de creación (importante para referencias)
1000
+ }
1001
+
1002
+ export const seedData: SeedDataConfig = {
1003
+ description: {
1004
+ es: 'Crea empresas de ejemplo (Singular, FitVending), proyectos y tareas de prueba',
1005
+ en: 'Creates sample companies, projects and test tasks',
1006
+ },
1007
+
1008
+ // Orden de creación - tablas con referencias van después
1009
+ order: ['empresasTable', 'proyectosTable', 'tareasTable'],
1010
+
1011
+ data: {
1012
+ // Empresas/Clientes
1013
+ empresasTable: [
1014
+ {
1015
+ nombre: 'Singular',
1016
+ contacto: 'Contacto Singular',
1017
+ email: 'contacto@singular.es',
1018
+ estado: 'activo'
1019
+ },
1020
+ {
1021
+ nombre: 'FitVending',
1022
+ contacto: 'Equipo FitVending',
1023
+ email: 'info@fitvending.es',
1024
+ estado: 'activo'
1025
+ },
1026
+ ],
1027
+
1028
+ // Proyectos
1029
+ proyectosTable: [
1030
+ {
1031
+ nombre: 'Gufi ERP',
1032
+ descripcion: 'Desarrollo del ERP Gufi',
1033
+ color: '#8B5CF6',
1034
+ estado: 'activo',
1035
+ },
1036
+ ],
1037
+
1038
+ // Tareas - con referencias a otras tablas
1039
+ tareasTable: [
1040
+ {
1041
+ titulo: 'Revisar bug crítico',
1042
+ descripcion: 'Los usuarios reportan problemas',
1043
+ asignado_a: '@currentUser', // 💜 Token especial: usuario actual
1044
+ asignado_por: '@currentUser',
1045
+ prioridad: 'urgente',
1046
+ estado: 'pendiente',
1047
+ fecha_limite: '@today', // 💜 Token especial: fecha de hoy
1048
+ proyecto_id: '@ref:proyectosTable.0', // 💜 Referencia: ID del primer proyecto
1049
+ empresa_id: '@ref:empresasTable.0', // 💜 Referencia: ID de primera empresa
1050
+ },
1051
+ {
1052
+ titulo: 'Preparar demo',
1053
+ descripcion: 'Demo del módulo de facturación',
1054
+ asignado_a: '@currentUser',
1055
+ prioridad: 'alta',
1056
+ fecha_limite: '@tomorrow', // 💜 Token especial: mañana
1057
+ empresa_id: '@ref:empresasTable.1',
1058
+ },
1059
+ ],
1060
+ },
1061
+ };
1062
+ ```
1063
+
1064
+ **Tokens Especiales:**
1065
+ | Token | Descripción |
1066
+ |-------|-------------|
1067
+ | `@currentUser` | ID del usuario actual (para campos users) |
1068
+ | `@today` | Fecha de hoy (YYYY-MM-DD) |
1069
+ | `@tomorrow` | Fecha de mañana |
1070
+ | `@nextWeek` | Fecha dentro de 7 días |
1071
+ | `@ref:tableKey.index` | ID del registro creado en otra tabla |
1072
+
1073
+ **Agregar a featureConfig:**
1074
+ ```typescript
1075
+ // core/dataProvider.ts
1076
+ import { seedData } from '../metadata/seedData';
1077
+
1078
+ export const featureConfig = {
1079
+ dataSources,
1080
+ inputs: featureInputs,
1081
+ seedData, // 💜 Agregar aquí
1082
+ };
1083
+ ```
1084
+
1085
+ **Cargar desde Developer Center:**
1086
+ En la página de edición de vista, el Developer Center muestra un botón "Load Sample Data" que ejecuta el seedData en la company seleccionada.
1087
+
1088
+ ---
1089
+
990
1090
  ### 💜 Checklist para Nueva Vista
991
1091
 
992
1092
  1. [ ] `core/dataProvider.ts` - dataSources + export featureConfig
993
1093
  2. [ ] `metadata/inputs.ts` - featureInputs configurables
994
- 3. [ ] `metadata/help.es.ts` y `help.en.ts` - Documentación
995
- 4. [ ] `index.tsx` - export featureConfig y default component
996
- 5. [ ] Usar `gufi?.context?.viewSpec` para tablas e inputs
997
- 6. [ ] Usar `gufi?.dataProvider` para CRUD
998
- 7. [ ] Usar `gufi?.utils?.toast*` para notificaciones
999
- 8. [ ] Si hay automation: carpeta `automations/` con .js
1094
+ 3. [ ] `metadata/seedData.ts` - Datos de ejemplo (opcional pero recomendado)
1095
+ 4. [ ] `metadata/help.es.ts` y `help.en.ts` - Documentación
1096
+ 5. [ ] `index.tsx` - export featureConfig y default component
1097
+ 6. [ ] Usar `gufi?.context?.viewSpec` para tablas e inputs
1098
+ 7. [ ] Usar `gufi?.dataProvider` para CRUD
1099
+ 8. [ ] Usar `gufi?.utils?.toast*` para notificaciones
1100
+ 9. [ ] Si hay automation: carpeta `automations/` con .js
1000
1101
 
1001
1102
  ### View.tsx - Componente Principal
1002
1103
 
@@ -82,10 +82,11 @@ export async function loginCommand(options) {
82
82
  }
83
83
  const spinner = ora("Iniciando sesión...").start();
84
84
  try {
85
- const { token } = await login(email, password);
86
- setToken(token, email);
85
+ const { token, refreshToken } = await login(email, password);
86
+ setToken(token, email, refreshToken);
87
87
  spinner.succeed(chalk.green(`Sesión iniciada como ${email}`));
88
- console.log(chalk.gray("\n Ahora puedes usar: gufi pull <vista>\n"));
88
+ console.log(chalk.gray("\n Tu sesión se mantendrá activa automáticamente.\n"));
89
+ console.log(chalk.gray(" Ahora puedes usar: gufi pull <vista>\n"));
89
90
  }
90
91
  catch (error) {
91
92
  spinner.fail(chalk.red(error.message || "Error al iniciar sesión"));
package/dist/lib/api.d.ts CHANGED
@@ -25,6 +25,7 @@ export interface Package {
25
25
  }
26
26
  export declare function login(email: string, password: string): Promise<{
27
27
  token: string;
28
+ refreshToken?: string;
28
29
  }>;
29
30
  export declare function listPackages(): Promise<Package[]>;
30
31
  export declare function listViews(packageId: number): Promise<View[]>;
package/dist/lib/api.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Gufi Dev CLI - API Client
3
3
  * Communicates with Gufi Marketplace API
4
4
  */
5
- import { getToken, getApiUrl } from "./config.js";
5
+ import { getToken, getApiUrl, getRefreshToken, setToken, loadConfig } from "./config.js";
6
6
  class ApiError extends Error {
7
7
  status;
8
8
  constructor(status, message) {
@@ -11,8 +11,34 @@ class ApiError extends Error {
11
11
  this.name = "ApiError";
12
12
  }
13
13
  }
14
- async function request(endpoint, options = {}) {
15
- const token = getToken();
14
+ // Auto-refresh the token if expired
15
+ async function refreshAccessToken() {
16
+ const refreshToken = getRefreshToken();
17
+ if (!refreshToken)
18
+ return null;
19
+ try {
20
+ const url = `${getApiUrl()}/api/auth/refresh`;
21
+ const response = await fetch(url, {
22
+ method: "POST",
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ "X-Client": "cli",
26
+ "X-Refresh-Token": refreshToken,
27
+ },
28
+ });
29
+ if (!response.ok)
30
+ return null;
31
+ const data = await response.json();
32
+ const config = loadConfig();
33
+ setToken(data.accessToken, config.email || "", data.refreshToken);
34
+ return data.accessToken;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ async function request(endpoint, options = {}, retryOnExpire = true) {
41
+ let token = getToken();
16
42
  if (!token) {
17
43
  throw new Error("No estás logueado. Ejecuta: gufi login");
18
44
  }
@@ -22,9 +48,17 @@ async function request(endpoint, options = {}) {
22
48
  headers: {
23
49
  "Content-Type": "application/json",
24
50
  Authorization: `Bearer ${token}`,
51
+ "X-Client": "cli",
25
52
  ...options.headers,
26
53
  },
27
54
  });
55
+ // Auto-refresh on 401/403 and retry once
56
+ if ((response.status === 401 || response.status === 403) && retryOnExpire) {
57
+ const newToken = await refreshAccessToken();
58
+ if (newToken) {
59
+ return request(endpoint, options, false); // Retry with new token
60
+ }
61
+ }
28
62
  if (!response.ok) {
29
63
  const text = await response.text();
30
64
  throw new ApiError(response.status, `API Error ${response.status}: ${text}`);
@@ -36,14 +70,17 @@ export async function login(email, password) {
36
70
  const url = `${getApiUrl()}/api/auth/login`;
37
71
  const response = await fetch(url, {
38
72
  method: "POST",
39
- headers: { "Content-Type": "application/json" },
73
+ headers: {
74
+ "Content-Type": "application/json",
75
+ "X-Client": "cli", // 💜 Request refresh token in response
76
+ },
40
77
  body: JSON.stringify({ email, password }),
41
78
  });
42
79
  if (!response.ok) {
43
80
  throw new ApiError(response.status, "Credenciales inválidas");
44
81
  }
45
82
  const data = await response.json();
46
- return { token: data.accessToken };
83
+ return { token: data.accessToken, refreshToken: data.refreshToken };
47
84
  }
48
85
  // ============ Packages ============
49
86
  export async function listPackages() {
@@ -5,6 +5,7 @@
5
5
  export interface GufiConfig {
6
6
  apiUrl: string;
7
7
  token?: string;
8
+ refreshToken?: string;
8
9
  email?: string;
9
10
  currentView?: {
10
11
  id: number;
@@ -17,7 +18,9 @@ export declare function ensureConfigDir(): void;
17
18
  export declare function loadConfig(): GufiConfig;
18
19
  export declare function saveConfig(config: GufiConfig): void;
19
20
  export declare function getToken(): string | undefined;
20
- export declare function setToken(token: string, email: string): void;
21
+ export declare function setToken(token: string, email: string, refreshToken?: string): void;
22
+ export declare function getRefreshToken(): string | undefined;
23
+ export declare function setRefreshToken(refreshToken: string): void;
21
24
  export declare function clearToken(): void;
22
25
  export declare function isLoggedIn(): boolean;
23
26
  export declare function setCurrentView(view: GufiConfig["currentView"]): void;
@@ -35,15 +35,27 @@ export function saveConfig(config) {
35
35
  export function getToken() {
36
36
  return loadConfig().token;
37
37
  }
38
- export function setToken(token, email) {
38
+ export function setToken(token, email, refreshToken) {
39
39
  const config = loadConfig();
40
40
  config.token = token;
41
41
  config.email = email;
42
+ if (refreshToken) {
43
+ config.refreshToken = refreshToken;
44
+ }
45
+ saveConfig(config);
46
+ }
47
+ export function getRefreshToken() {
48
+ return loadConfig().refreshToken;
49
+ }
50
+ export function setRefreshToken(refreshToken) {
51
+ const config = loadConfig();
52
+ config.refreshToken = refreshToken;
42
53
  saveConfig(config);
43
54
  }
44
55
  export function clearToken() {
45
56
  const config = loadConfig();
46
57
  delete config.token;
58
+ delete config.refreshToken;
47
59
  delete config.email;
48
60
  saveConfig(config);
49
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"