gufi-cli 0.1.7 → 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 +204 -8
- package/dist/commands/companies.js +3 -2
- package/dist/commands/list.js +2 -2
- package/dist/commands/login.js +31 -53
- package/dist/commands/pull.js +11 -18
- package/dist/index.js +1 -1
- package/dist/lib/api.js +9 -3
- package/dist/lib/sync.d.ts +5 -2
- package/dist/lib/sync.js +10 -6
- package/package.json +3 -1
package/CLAUDE.md
CHANGED
|
@@ -604,15 +604,16 @@ 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
|
|
607
|
+
Cuando descargas una view con `gufi view:pull <id>`, obtienes:
|
|
608
608
|
|
|
609
609
|
```
|
|
610
|
-
~/gufi-dev/
|
|
610
|
+
~/gufi-dev/view_<id>/
|
|
611
611
|
├── index.tsx # Entry point - exporta featureConfig y default
|
|
612
612
|
├── types.ts # Interfaces TypeScript
|
|
613
613
|
│
|
|
614
614
|
├── core/
|
|
615
|
-
│
|
|
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
|
-
│
|
|
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,198 @@ 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
|
+
### 💜 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
|
+
|
|
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.
|
|
1076
|
+
|
|
1077
|
+
---
|
|
1078
|
+
|
|
885
1079
|
### 💜 automations en Vistas
|
|
886
1080
|
|
|
887
1081
|
Las vistas pueden incluir automations que se ejecutan al hacer click.
|
|
@@ -1475,11 +1669,13 @@ gufi rows:create m360_t16192 --file datos.json
|
|
|
1475
1669
|
### Desarrollo de Views
|
|
1476
1670
|
|
|
1477
1671
|
```bash
|
|
1478
|
-
# Ver tus views del Marketplace
|
|
1672
|
+
# Ver tus views del Marketplace (muestra ID de cada vista)
|
|
1479
1673
|
gufi views
|
|
1674
|
+
# 📦 Gestión de Tareas (ID: 14)
|
|
1675
|
+
# └─ 13 Tasks Manager (custom)
|
|
1480
1676
|
|
|
1481
|
-
# Descargar view
|
|
1482
|
-
gufi view:pull
|
|
1677
|
+
# Descargar view por ID (se guarda en ~/gufi-dev/view_<id>/)
|
|
1678
|
+
gufi view:pull 13
|
|
1483
1679
|
|
|
1484
1680
|
# Auto-sync al guardar archivos
|
|
1485
1681
|
gufi view:watch
|
|
@@ -1516,7 +1712,7 @@ gufi view:status
|
|
|
1516
1712
|
| Ver estructura de una company | `gufi modules <company_id>` |
|
|
1517
1713
|
| Ver/editar JSON de módulo | `gufi module <id> -c <company>` |
|
|
1518
1714
|
| Ver/editar código de automation | `gufi automation <nombre> -c <company>` |
|
|
1519
|
-
| Desarrollar una vista | `gufi pull
|
|
1715
|
+
| Desarrollar una vista | `gufi view:pull <id>`, `gufi view:watch`, `gufi view:logs` |
|
|
1520
1716
|
|
|
1521
1717
|
### Errores comunes
|
|
1522
1718
|
|
|
@@ -370,11 +370,12 @@ export async function companyCreateCommand(name) {
|
|
|
370
370
|
method: "POST",
|
|
371
371
|
body: JSON.stringify({ name }),
|
|
372
372
|
});
|
|
373
|
-
|
|
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:
|
|
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) {
|
package/dist/commands/list.js
CHANGED
|
@@ -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(
|
|
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 <
|
|
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));
|
package/dist/commands/login.js
CHANGED
|
@@ -1,53 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gufi login - Authenticate with Gufi
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
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
|
|
66
|
-
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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) {
|
package/dist/commands/pull.js
CHANGED
|
@@ -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
|
|
16
|
+
let viewKey;
|
|
17
17
|
let packageId;
|
|
18
|
-
//
|
|
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
|
-
|
|
24
|
+
viewKey = `view_${viewId}`;
|
|
25
25
|
packageId = view.package_id;
|
|
26
|
-
spinner.succeed(`Vista: ${
|
|
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,21 +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
|
|
60
|
-
console.log(`
|
|
59
|
+
views.forEach((view) => {
|
|
60
|
+
console.log(` ${chalk.cyan(view.pk_id)} ${view.name || "sin nombre"} ${chalk.gray(`(${view.view_type})`)}`);
|
|
61
61
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
? views.find(v => v.name.toLowerCase().includes(viewIdentifier.toLowerCase()))
|
|
65
|
-
: views[0];
|
|
66
|
-
if (!selectedView) {
|
|
67
|
-
console.log(chalk.yellow(`\n No se encontró vista "${viewIdentifier}"\n`));
|
|
68
|
-
console.log(chalk.gray(" Uso: gufi pull <nombre-vista> o gufi pull <view-id>\n"));
|
|
69
|
-
process.exit(1);
|
|
62
|
+
if (viewIdentifier) {
|
|
63
|
+
console.log(chalk.yellow(`\n "${viewIdentifier}" no es un ID válido.\n`));
|
|
70
64
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
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);
|
|
74
67
|
}
|
|
75
68
|
catch (error) {
|
|
76
69
|
spinner.fail(chalk.red(error.message));
|
|
@@ -80,7 +73,7 @@ export async function pullCommand(viewIdentifier) {
|
|
|
80
73
|
// Pull the view
|
|
81
74
|
const pullSpinner = ora("Descargando archivos...").start();
|
|
82
75
|
try {
|
|
83
|
-
const result = await pullView(viewId,
|
|
76
|
+
const result = await pullView(viewId, viewKey, packageId);
|
|
84
77
|
pullSpinner.succeed(chalk.green(`${result.fileCount} archivos descargados`));
|
|
85
78
|
console.log(chalk.gray(`\n 📁 ${result.dir}\n`));
|
|
86
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.
|
|
47
|
+
.version("0.1.8");
|
|
48
48
|
// ════════════════════════════════════════════════════════════════════
|
|
49
49
|
// 🔐 Auth
|
|
50
50
|
// ════════════════════════════════════════════════════════════════════
|
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/dist/lib/sync.d.ts
CHANGED
|
@@ -12,12 +12,15 @@ export interface ViewMeta {
|
|
|
12
12
|
mtime: number;
|
|
13
13
|
}>;
|
|
14
14
|
}
|
|
15
|
-
export declare function getViewDir(
|
|
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,
|
|
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
|
-
|
|
41
|
-
|
|
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,
|
|
62
|
-
const viewDir = getViewDir(
|
|
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:
|
|
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.
|
|
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"
|
|
@@ -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": {
|