gufi-cli 0.1.8 → 0.1.10
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 +72 -8
- package/dist/commands/companies.d.ts +6 -0
- package/dist/commands/companies.js +185 -23
- package/dist/commands/env.js +53 -4
- package/dist/commands/list.js +2 -2
- package/dist/commands/login.js +6 -2
- package/dist/commands/pull.js +11 -19
- package/dist/commands/rows.js +53 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +13 -2
- package/dist/lib/api.js +48 -26
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +4 -1
- package/dist/lib/sync.d.ts +5 -2
- package/dist/lib/sync.js +10 -6
- package/package.json +1 -1
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
|
|
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
|
│
|
|
@@ -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
|
---
|
|
@@ -1529,6 +1569,28 @@ npm install -g gufi-cli
|
|
|
1529
1569
|
gufi login
|
|
1530
1570
|
```
|
|
1531
1571
|
|
|
1572
|
+
### 💜 Autenticación Persistente
|
|
1573
|
+
|
|
1574
|
+
El CLI guarda las credenciales en `~/.gufi/config.json` para auto-login automático:
|
|
1575
|
+
|
|
1576
|
+
```json
|
|
1577
|
+
{
|
|
1578
|
+
"apiUrl": "https://gogufi.com",
|
|
1579
|
+
"token": "eyJ...",
|
|
1580
|
+
"refreshToken": "eyJ...",
|
|
1581
|
+
"email": "user@example.com",
|
|
1582
|
+
"password": "secreto"
|
|
1583
|
+
}
|
|
1584
|
+
```
|
|
1585
|
+
|
|
1586
|
+
**Flujo de autenticación:**
|
|
1587
|
+
1. Si hay token válido → lo usa
|
|
1588
|
+
2. Si token expirado + refreshToken válido → hace refresh automático
|
|
1589
|
+
3. Si ambos expirados → auto-login con email/password guardados
|
|
1590
|
+
4. Si no hay credenciales → pide `gufi login`
|
|
1591
|
+
|
|
1592
|
+
**Importante:** Para persistencia completa, `gufi login` guarda email y password. Si haces login desde el navegador web, el refreshToken del CLI se invalida, pero el CLI hace auto-login automáticamente con las credenciales guardadas.
|
|
1593
|
+
|
|
1532
1594
|
### Gestión de Companies y Módulos
|
|
1533
1595
|
|
|
1534
1596
|
```bash
|
|
@@ -1629,11 +1691,13 @@ gufi rows:create m360_t16192 --file datos.json
|
|
|
1629
1691
|
### Desarrollo de Views
|
|
1630
1692
|
|
|
1631
1693
|
```bash
|
|
1632
|
-
# Ver tus views del Marketplace
|
|
1694
|
+
# Ver tus views del Marketplace (muestra ID de cada vista)
|
|
1633
1695
|
gufi views
|
|
1696
|
+
# 📦 Gestión de Tareas (ID: 14)
|
|
1697
|
+
# └─ 13 Tasks Manager (custom)
|
|
1634
1698
|
|
|
1635
|
-
# Descargar view
|
|
1636
|
-
gufi view:pull
|
|
1699
|
+
# Descargar view por ID (se guarda en ~/gufi-dev/view_<id>/)
|
|
1700
|
+
gufi view:pull 13
|
|
1637
1701
|
|
|
1638
1702
|
# Auto-sync al guardar archivos
|
|
1639
1703
|
gufi view:watch
|
|
@@ -1670,15 +1734,15 @@ gufi view:status
|
|
|
1670
1734
|
| Ver estructura de una company | `gufi modules <company_id>` |
|
|
1671
1735
|
| Ver/editar JSON de módulo | `gufi module <id> -c <company>` |
|
|
1672
1736
|
| Ver/editar código de automation | `gufi automation <nombre> -c <company>` |
|
|
1673
|
-
| Desarrollar una vista | `gufi pull
|
|
1737
|
+
| Desarrollar una vista | `gufi view:pull <id>`, `gufi view:watch`, `gufi view:logs` |
|
|
1674
1738
|
|
|
1675
1739
|
### Errores comunes
|
|
1676
1740
|
|
|
1677
1741
|
| Error | Solución |
|
|
1678
1742
|
|-------|----------|
|
|
1679
|
-
| "No estás logueado" | `gufi login` |
|
|
1743
|
+
| "No estás logueado" | `gufi login` (guarda credenciales para auto-login) |
|
|
1680
1744
|
| "Módulo no encontrado" | Verificar `-c <company_id>` |
|
|
1681
|
-
| "Token expirado" |
|
|
1745
|
+
| "Token expirado" | Automático: refresh o auto-login con credenciales guardadas |
|
|
1682
1746
|
| "JSON inválido" | Validar estructura del JSON |
|
|
1683
1747
|
|
|
1684
1748
|
### Conceptos clave
|
|
@@ -14,6 +14,9 @@ export declare function moduleUpdateCommand(moduleId?: string, jsonFile?: string
|
|
|
14
14
|
company?: string;
|
|
15
15
|
dryRun?: boolean;
|
|
16
16
|
}): Promise<void>;
|
|
17
|
+
export declare function moduleCreateCommand(jsonFile?: string, options?: {
|
|
18
|
+
company?: string;
|
|
19
|
+
}): Promise<void>;
|
|
17
20
|
export declare function companyCreateCommand(name?: string): Promise<void>;
|
|
18
21
|
export declare function automationsCommand(_moduleId?: string, // Ignored - kept for backwards compatibility
|
|
19
22
|
options?: {
|
|
@@ -24,3 +27,6 @@ export declare function automationCommand(automationName?: string, options?: {
|
|
|
24
27
|
company?: string;
|
|
25
28
|
file?: string;
|
|
26
29
|
}): Promise<void>;
|
|
30
|
+
export declare function automationCreateCommand(name?: string, jsFile?: string, options?: {
|
|
31
|
+
company?: string;
|
|
32
|
+
}): Promise<void>;
|
|
@@ -4,19 +4,81 @@
|
|
|
4
4
|
* gufi module - View/edit module JSON
|
|
5
5
|
*/
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
-
import { isLoggedIn, getApiUrl, getToken } from "../lib/config.js";
|
|
7
|
+
import { isLoggedIn, getApiUrl, getToken, getRefreshToken, setToken, loadConfig } from "../lib/config.js";
|
|
8
8
|
const API_URL_BASE = "https://gogufi.com";
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// 💜 Auto-login with saved credentials
|
|
10
|
+
async function autoLogin() {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
if (!config.email || !config.password) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
console.log(chalk.gray(" [gufi] Auto-login..."));
|
|
17
|
+
const apiUrl = getApiUrl() || API_URL_BASE;
|
|
18
|
+
const response = await fetch(`${apiUrl}/api/auth/login`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: { "Content-Type": "application/json", "X-Client": "cli" },
|
|
21
|
+
body: JSON.stringify({ email: config.email, password: config.password }),
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok)
|
|
24
|
+
return undefined;
|
|
25
|
+
const data = await response.json();
|
|
26
|
+
setToken(data.accessToken, config.email, data.refreshToken);
|
|
27
|
+
console.log(chalk.green(" [gufi] Auto-login exitoso"));
|
|
28
|
+
return data.accessToken;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// 💜 Refresh token or auto-login
|
|
35
|
+
async function refreshOrLogin() {
|
|
36
|
+
const refreshToken = getRefreshToken();
|
|
37
|
+
const apiUrl = getApiUrl() || API_URL_BASE;
|
|
38
|
+
if (refreshToken) {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(`${apiUrl}/api/auth/refresh`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: { "Content-Type": "application/json", "X-Client": "cli", "X-Refresh-Token": refreshToken },
|
|
43
|
+
});
|
|
44
|
+
if (response.ok) {
|
|
45
|
+
const data = await response.json();
|
|
46
|
+
const config = loadConfig();
|
|
47
|
+
setToken(data.accessToken, config.email || "", data.refreshToken);
|
|
48
|
+
return data.accessToken;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch { }
|
|
52
|
+
}
|
|
53
|
+
return autoLogin();
|
|
54
|
+
}
|
|
55
|
+
// 💜 API request with auto-login and refresh
|
|
56
|
+
async function apiRequest(path, options = {}, retry = true) {
|
|
57
|
+
let token = getToken();
|
|
11
58
|
const apiUrl = getApiUrl() || API_URL_BASE;
|
|
59
|
+
// If no token, try auto-login
|
|
60
|
+
if (!token) {
|
|
61
|
+
token = await autoLogin();
|
|
62
|
+
if (!token) {
|
|
63
|
+
throw new Error("No estás logueado. Usa: gufi login");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
12
66
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
13
67
|
...options,
|
|
14
68
|
headers: {
|
|
15
69
|
"Authorization": `Bearer ${token}`,
|
|
16
70
|
"Content-Type": "application/json",
|
|
71
|
+
"X-Client": "cli",
|
|
17
72
|
...options.headers,
|
|
18
73
|
},
|
|
19
74
|
});
|
|
75
|
+
// On 401/403, try refresh and retry once
|
|
76
|
+
if ((res.status === 401 || res.status === 403) && retry) {
|
|
77
|
+
const newToken = await refreshOrLogin();
|
|
78
|
+
if (newToken) {
|
|
79
|
+
return apiRequest(path, options, false);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
20
82
|
if (!res.ok) {
|
|
21
83
|
const error = await res.text();
|
|
22
84
|
throw new Error(`API Error ${res.status}: ${error}`);
|
|
@@ -251,11 +313,11 @@ async function editModuleJson(moduleId, currentJson, companyId) {
|
|
|
251
313
|
if (companyId) {
|
|
252
314
|
headers["X-Company-ID"] = companyId;
|
|
253
315
|
}
|
|
254
|
-
// Call update endpoint
|
|
255
|
-
const result = await apiRequest(`/api/modules/${moduleId}`, {
|
|
316
|
+
// Call update endpoint (confirm: true to actually save)
|
|
317
|
+
const result = await apiRequest(`/api/company/modules/${moduleId}`, {
|
|
256
318
|
method: "PUT",
|
|
257
319
|
headers,
|
|
258
|
-
body: JSON.stringify({
|
|
320
|
+
body: JSON.stringify({ json: newJson, confirm: true }),
|
|
259
321
|
});
|
|
260
322
|
console.log(chalk.green("\n ✓ Módulo actualizado correctamente!\n"));
|
|
261
323
|
// Show what changed
|
|
@@ -306,19 +368,10 @@ export async function moduleUpdateCommand(moduleId, jsonFile, options) {
|
|
|
306
368
|
if (options?.company) {
|
|
307
369
|
headers["X-Company-ID"] = options.company;
|
|
308
370
|
}
|
|
309
|
-
// Validate
|
|
371
|
+
// Validate JSON structure locally
|
|
310
372
|
console.log(chalk.gray(" Validando JSON..."));
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
headers,
|
|
314
|
-
body: JSON.stringify({ json_definition: newJson }),
|
|
315
|
-
});
|
|
316
|
-
if (validateResult.errors?.length) {
|
|
317
|
-
console.log(chalk.red("\n ✗ Errores de validación:"));
|
|
318
|
-
for (const err of validateResult.errors) {
|
|
319
|
-
console.log(chalk.red(` - ${err}`));
|
|
320
|
-
}
|
|
321
|
-
console.log();
|
|
373
|
+
if (!newJson.name && !newJson.displayName) {
|
|
374
|
+
console.log(chalk.red("\n ✗ JSON debe tener 'name' o 'displayName'\n"));
|
|
322
375
|
process.exit(1);
|
|
323
376
|
}
|
|
324
377
|
console.log(chalk.green(" ✓ JSON válido"));
|
|
@@ -326,12 +379,12 @@ export async function moduleUpdateCommand(moduleId, jsonFile, options) {
|
|
|
326
379
|
console.log(chalk.gray("\n [DRY RUN] Validación exitosa. Usa sin --dry-run para aplicar.\n"));
|
|
327
380
|
return;
|
|
328
381
|
}
|
|
329
|
-
// Apply changes
|
|
382
|
+
// Apply changes (confirm: true to actually save)
|
|
330
383
|
console.log(chalk.gray(" Aplicando cambios..."));
|
|
331
|
-
const result = await apiRequest(`/api/modules/${moduleId}`, {
|
|
384
|
+
const result = await apiRequest(`/api/company/modules/${moduleId}`, {
|
|
332
385
|
method: "PUT",
|
|
333
386
|
headers,
|
|
334
|
-
body: JSON.stringify({
|
|
387
|
+
body: JSON.stringify({ json: newJson, confirm: true }),
|
|
335
388
|
});
|
|
336
389
|
console.log(chalk.green("\n ✓ Módulo actualizado correctamente!\n"));
|
|
337
390
|
if (result.tablesCreated?.length) {
|
|
@@ -353,6 +406,68 @@ export async function moduleUpdateCommand(moduleId, jsonFile, options) {
|
|
|
353
406
|
}
|
|
354
407
|
}
|
|
355
408
|
// ════════════════════════════════════════════════════════════════════
|
|
409
|
+
// gufi module:create <json_file> --company <id> - Create module from JSON file
|
|
410
|
+
// ════════════════════════════════════════════════════════════════════
|
|
411
|
+
export async function moduleCreateCommand(jsonFile, options) {
|
|
412
|
+
if (!isLoggedIn()) {
|
|
413
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
if (!jsonFile) {
|
|
417
|
+
console.log(chalk.red("\n ✗ Uso: gufi module:create <json_file> --company <id>\n"));
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
if (!options?.company) {
|
|
421
|
+
console.log(chalk.red("\n ✗ Debes especificar --company <id>\n"));
|
|
422
|
+
console.log(chalk.gray(" Tip: Usa 'gufi companies' para ver tus companies\n"));
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
const fs = await import("fs");
|
|
426
|
+
// Read JSON file
|
|
427
|
+
let moduleJson;
|
|
428
|
+
try {
|
|
429
|
+
const content = fs.readFileSync(jsonFile, "utf-8");
|
|
430
|
+
moduleJson = JSON.parse(content);
|
|
431
|
+
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
console.log(chalk.red(`\n ✗ Error leyendo archivo: ${e.message}\n`));
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
const moduleName = moduleJson.displayName || moduleJson.label || moduleJson.name || "Nuevo Módulo";
|
|
437
|
+
console.log(chalk.magenta(`\n 🟣 Creando módulo: ${moduleName}\n`));
|
|
438
|
+
console.log(chalk.gray(` Company: ${options.company}`));
|
|
439
|
+
try {
|
|
440
|
+
const headers = {
|
|
441
|
+
"X-Company-ID": options.company,
|
|
442
|
+
};
|
|
443
|
+
const result = await apiRequest("/api/company/modules", {
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers,
|
|
446
|
+
body: JSON.stringify({ json: moduleJson }),
|
|
447
|
+
});
|
|
448
|
+
const data = result.data || result;
|
|
449
|
+
console.log(chalk.green("\n ✓ Módulo creado!\n"));
|
|
450
|
+
console.log(chalk.white(` ID: ${data.moduleId || data.id || "N/A"}`));
|
|
451
|
+
if (data.tablesCreated?.length) {
|
|
452
|
+
console.log(chalk.gray(" Tablas creadas:"));
|
|
453
|
+
for (const t of data.tablesCreated) {
|
|
454
|
+
console.log(chalk.green(` + ${t}`));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (data.entities?.length) {
|
|
458
|
+
console.log(chalk.gray(" Entidades:"));
|
|
459
|
+
for (const ent of data.entities) {
|
|
460
|
+
console.log(chalk.white(` - ${ent.name} (${ent.kind})`));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
console.log(chalk.gray("\n 💡 Usa: gufi modules " + options.company + " para ver todos los módulos\n"));
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// ════════════════════════════════════════════════════════════════════
|
|
356
471
|
// gufi company:create <name> - Create a new company
|
|
357
472
|
// ════════════════════════════════════════════════════════════════════
|
|
358
473
|
export async function companyCreateCommand(name) {
|
|
@@ -370,11 +485,12 @@ export async function companyCreateCommand(name) {
|
|
|
370
485
|
method: "POST",
|
|
371
486
|
body: JSON.stringify({ name }),
|
|
372
487
|
});
|
|
373
|
-
|
|
488
|
+
// API returns { company: {...}, message: "..." }
|
|
489
|
+
const company = result.company || result.data || result;
|
|
374
490
|
console.log(chalk.green(" ✓ Company creada!\n"));
|
|
375
491
|
console.log(chalk.white(` ID: ${company.id}`));
|
|
376
492
|
console.log(chalk.white(` Nombre: ${company.name}`));
|
|
377
|
-
console.log(chalk.white(` Schema:
|
|
493
|
+
console.log(chalk.white(` Schema: ${company.schema || 'company_' + company.id}`));
|
|
378
494
|
console.log(chalk.gray("\n 💡 Usa: gufi modules " + company.id + " para ver módulos\n"));
|
|
379
495
|
}
|
|
380
496
|
catch (err) {
|
|
@@ -471,6 +587,52 @@ export async function automationCommand(automationName, options) {
|
|
|
471
587
|
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
472
588
|
}
|
|
473
589
|
}
|
|
590
|
+
// ════════════════════════════════════════════════════════════════════
|
|
591
|
+
// gufi automation:create <name> <js_file> --company <id> - Create/update automation from file
|
|
592
|
+
// ════════════════════════════════════════════════════════════════════
|
|
593
|
+
export async function automationCreateCommand(name, jsFile, options) {
|
|
594
|
+
if (!isLoggedIn()) {
|
|
595
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
if (!name || !jsFile) {
|
|
599
|
+
console.log(chalk.red("\n ✗ Uso: gufi automation:create <nombre> <archivo.js> --company <id>\n"));
|
|
600
|
+
process.exit(1);
|
|
601
|
+
}
|
|
602
|
+
if (!options?.company) {
|
|
603
|
+
console.log(chalk.red("\n ✗ Debes especificar --company <id>\n"));
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
const fs = await import("fs");
|
|
607
|
+
let code;
|
|
608
|
+
try {
|
|
609
|
+
code = fs.readFileSync(jsFile, "utf-8");
|
|
610
|
+
}
|
|
611
|
+
catch (e) {
|
|
612
|
+
console.log(chalk.red(`\n ✗ Error leyendo archivo: ${e.message}\n`));
|
|
613
|
+
process.exit(1);
|
|
614
|
+
}
|
|
615
|
+
console.log(chalk.magenta(`\n 🟣 Creando automation: ${name}\n`));
|
|
616
|
+
console.log(chalk.gray(` Company: ${options.company}`));
|
|
617
|
+
try {
|
|
618
|
+
const headers = {
|
|
619
|
+
"X-Company-ID": options.company,
|
|
620
|
+
};
|
|
621
|
+
const result = await apiRequest(`/api/automation-scripts/${encodeURIComponent(name)}`, {
|
|
622
|
+
method: "PUT",
|
|
623
|
+
headers,
|
|
624
|
+
body: JSON.stringify({ code }),
|
|
625
|
+
});
|
|
626
|
+
console.log(chalk.green("\n ✓ Automation creada!\n"));
|
|
627
|
+
console.log(chalk.white(` Nombre: ${result.name || name}`));
|
|
628
|
+
console.log(chalk.white(` Acción: ${result.action || "created/updated"}`));
|
|
629
|
+
console.log(chalk.gray("\n 💡 Usa: gufi automations -c " + options.company + " para ver la lista\n"));
|
|
630
|
+
}
|
|
631
|
+
catch (err) {
|
|
632
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
633
|
+
process.exit(1);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
474
636
|
async function editAutomationCode(automationName, currentCode, companyId) {
|
|
475
637
|
const fs = await import("fs");
|
|
476
638
|
const os = await import("os");
|
package/dist/commands/env.js
CHANGED
|
@@ -4,11 +4,55 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import ora from "ora";
|
|
7
|
-
import { getToken, getApiUrl } from "../lib/config.js";
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { getToken, getApiUrl, getRefreshToken, setToken, loadConfig } from "../lib/config.js";
|
|
8
|
+
// 💜 Auto-login with saved credentials
|
|
9
|
+
async function autoLogin() {
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
if (!config.email || !config.password)
|
|
12
|
+
return undefined;
|
|
13
|
+
try {
|
|
14
|
+
const response = await fetch(`${getApiUrl()}/api/auth/login`, {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: { "Content-Type": "application/json", "X-Client": "cli" },
|
|
17
|
+
body: JSON.stringify({ email: config.email, password: config.password }),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok)
|
|
20
|
+
return undefined;
|
|
21
|
+
const data = await response.json();
|
|
22
|
+
setToken(data.accessToken, config.email, data.refreshToken);
|
|
23
|
+
return data.accessToken;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// 💜 Refresh token or auto-login
|
|
30
|
+
async function refreshOrLogin() {
|
|
31
|
+
const refreshToken = getRefreshToken();
|
|
32
|
+
if (refreshToken) {
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(`${getApiUrl()}/api/auth/refresh`, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json", "X-Client": "cli", "X-Refresh-Token": refreshToken },
|
|
37
|
+
});
|
|
38
|
+
if (response.ok) {
|
|
39
|
+
const data = await response.json();
|
|
40
|
+
const config = loadConfig();
|
|
41
|
+
setToken(data.accessToken, config.email || "", data.refreshToken);
|
|
42
|
+
return data.accessToken;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
}
|
|
47
|
+
return autoLogin();
|
|
48
|
+
}
|
|
49
|
+
// 💜 API request with auto-login and refresh
|
|
50
|
+
async function apiRequest(endpoint, options = {}, retry = true) {
|
|
51
|
+
let token = getToken();
|
|
10
52
|
if (!token) {
|
|
11
|
-
|
|
53
|
+
token = await autoLogin();
|
|
54
|
+
if (!token)
|
|
55
|
+
throw new Error("No estás logueado. Ejecuta: gufi login");
|
|
12
56
|
}
|
|
13
57
|
const url = `${getApiUrl()}${endpoint}`;
|
|
14
58
|
const response = await fetch(url, {
|
|
@@ -20,6 +64,11 @@ async function apiRequest(endpoint, options = {}) {
|
|
|
20
64
|
...options.headers,
|
|
21
65
|
},
|
|
22
66
|
});
|
|
67
|
+
if ((response.status === 401 || response.status === 403) && retry) {
|
|
68
|
+
const newToken = await refreshOrLogin();
|
|
69
|
+
if (newToken)
|
|
70
|
+
return apiRequest(endpoint, options, false);
|
|
71
|
+
}
|
|
23
72
|
if (!response.ok) {
|
|
24
73
|
const text = await response.text();
|
|
25
74
|
throw new Error(`API Error ${response.status}: ${text}`);
|
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
|
@@ -5,7 +5,7 @@ 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
|
-
import { setToken, isLoggedIn, clearToken, loadConfig, setApiUrl } from "../lib/config.js";
|
|
8
|
+
import { setToken, isLoggedIn, clearToken, loadConfig, setApiUrl, saveConfig } from "../lib/config.js";
|
|
9
9
|
export async function loginCommand(options) {
|
|
10
10
|
console.log(chalk.magenta("\n 🟣 Gufi Developer CLI\n"));
|
|
11
11
|
// Set custom API URL if provided
|
|
@@ -62,8 +62,12 @@ export async function loginCommand(options) {
|
|
|
62
62
|
console.log(chalk.yellow("\n ⚠️ No se recibió refresh token - sesión durará 1 hora"));
|
|
63
63
|
}
|
|
64
64
|
setToken(token, response.email, refreshToken);
|
|
65
|
+
// 💜 Persist credentials for auto-login when token expires
|
|
66
|
+
const config = loadConfig();
|
|
67
|
+
config.password = response.password;
|
|
68
|
+
saveConfig(config);
|
|
65
69
|
spinner.succeed(chalk.green(`Sesión iniciada como ${response.email}`));
|
|
66
|
-
console.log(chalk.gray("\n Tu sesión se mantendrá activa automáticamente
|
|
70
|
+
console.log(chalk.gray("\n Tu sesión se mantendrá activa automáticamente.\n"));
|
|
67
71
|
console.log(chalk.gray(" Ahora puedes usar: gufi pull <vista>\n"));
|
|
68
72
|
}
|
|
69
73
|
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,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
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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,
|
|
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/commands/rows.js
CHANGED
|
@@ -5,11 +5,55 @@
|
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import ora from "ora";
|
|
7
7
|
import fs from "fs";
|
|
8
|
-
import { getToken, getApiUrl } from "../lib/config.js";
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { getToken, getApiUrl, loadConfig, getRefreshToken, setToken } from "../lib/config.js";
|
|
9
|
+
// 💜 Auto-login with saved credentials
|
|
10
|
+
async function autoLogin() {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
if (!config.email || !config.password)
|
|
13
|
+
return undefined;
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch(`${getApiUrl()}/api/auth/login`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/json", "X-Client": "cli" },
|
|
18
|
+
body: JSON.stringify({ email: config.email, password: config.password }),
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok)
|
|
21
|
+
return undefined;
|
|
22
|
+
const data = await response.json();
|
|
23
|
+
setToken(data.accessToken, config.email, data.refreshToken);
|
|
24
|
+
return data.accessToken;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// 💜 Refresh token or auto-login
|
|
31
|
+
async function refreshOrLogin() {
|
|
32
|
+
const refreshToken = getRefreshToken();
|
|
33
|
+
if (refreshToken) {
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(`${getApiUrl()}/api/auth/refresh`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json", "X-Client": "cli", "X-Refresh-Token": refreshToken },
|
|
38
|
+
});
|
|
39
|
+
if (response.ok) {
|
|
40
|
+
const data = await response.json();
|
|
41
|
+
const config = loadConfig();
|
|
42
|
+
setToken(data.accessToken, config.email || "", data.refreshToken);
|
|
43
|
+
return data.accessToken;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
}
|
|
48
|
+
return autoLogin();
|
|
49
|
+
}
|
|
50
|
+
// 💜 API request with auto-login and refresh
|
|
51
|
+
async function apiRequest(endpoint, options = {}, retry = true) {
|
|
52
|
+
let token = getToken();
|
|
11
53
|
if (!token) {
|
|
12
|
-
|
|
54
|
+
token = await autoLogin();
|
|
55
|
+
if (!token)
|
|
56
|
+
throw new Error("No estás logueado. Ejecuta: gufi login");
|
|
13
57
|
}
|
|
14
58
|
const url = `${getApiUrl()}${endpoint}`;
|
|
15
59
|
const response = await fetch(url, {
|
|
@@ -21,6 +65,11 @@ async function apiRequest(endpoint, options = {}) {
|
|
|
21
65
|
...options.headers,
|
|
22
66
|
},
|
|
23
67
|
});
|
|
68
|
+
if ((response.status === 401 || response.status === 403) && retry) {
|
|
69
|
+
const newToken = await refreshOrLogin();
|
|
70
|
+
if (newToken)
|
|
71
|
+
return apiRequest(endpoint, options, false);
|
|
72
|
+
}
|
|
24
73
|
if (!response.ok) {
|
|
25
74
|
const text = await response.text();
|
|
26
75
|
throw new Error(`API Error ${response.status}: ${text}`);
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* gufi modules <id> List modules of a company
|
|
18
18
|
* gufi module <id> View/edit module JSON (--edit, --file)
|
|
19
19
|
* gufi module:update Update module from JSON file
|
|
20
|
+
* gufi module:create Create module from JSON file
|
|
20
21
|
* gufi company:create Create a new company
|
|
21
22
|
* gufi automations List automation scripts
|
|
22
23
|
* gufi automation <name> View/edit automation code (--edit, --file)
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* gufi modules <id> List modules of a company
|
|
18
18
|
* gufi module <id> View/edit module JSON (--edit, --file)
|
|
19
19
|
* gufi module:update Update module from JSON file
|
|
20
|
+
* gufi module:create Create module from JSON file
|
|
20
21
|
* gufi company:create Create a new company
|
|
21
22
|
* gufi automations List automation scripts
|
|
22
23
|
* gufi automation <name> View/edit automation code (--edit, --file)
|
|
@@ -37,14 +38,14 @@ import { pushCommand, statusCommand } from "./commands/push.js";
|
|
|
37
38
|
import { watchCommand } from "./commands/watch.js";
|
|
38
39
|
import { listCommand } from "./commands/list.js";
|
|
39
40
|
import { logsCommand } from "./commands/logs.js";
|
|
40
|
-
import { companiesCommand, modulesCommand, moduleCommand, moduleUpdateCommand, companyCreateCommand, automationsCommand, automationCommand, } from "./commands/companies.js";
|
|
41
|
+
import { companiesCommand, modulesCommand, moduleCommand, moduleUpdateCommand, moduleCreateCommand, companyCreateCommand, automationsCommand, automationCommand, automationCreateCommand, } from "./commands/companies.js";
|
|
41
42
|
import { rowsListCommand, rowGetCommand, rowCreateCommand, rowUpdateCommand, rowDeleteCommand, rowDuplicateCommand, rowsBulkCreateCommand, } from "./commands/rows.js";
|
|
42
43
|
import { envListCommand, envSetCommand, envDeleteCommand, schemaCommand, } from "./commands/env.js";
|
|
43
44
|
const program = new Command();
|
|
44
45
|
program
|
|
45
46
|
.name("gufi")
|
|
46
47
|
.description("🟣 Gufi CLI - Desarrolla módulos, vistas y automations")
|
|
47
|
-
.version("0.1.
|
|
48
|
+
.version("0.1.8");
|
|
48
49
|
// ════════════════════════════════════════════════════════════════════
|
|
49
50
|
// 🔐 Auth
|
|
50
51
|
// ════════════════════════════════════════════════════════════════════
|
|
@@ -94,6 +95,11 @@ program
|
|
|
94
95
|
.option("-c, --company <id>", "ID de company")
|
|
95
96
|
.option("--dry-run", "Validar sin guardar")
|
|
96
97
|
.action(moduleUpdateCommand);
|
|
98
|
+
program
|
|
99
|
+
.command("module:create <json_file>")
|
|
100
|
+
.description("Crear módulo desde archivo JSON")
|
|
101
|
+
.option("-c, --company <id>", "ID de company (requerido)")
|
|
102
|
+
.action(moduleCreateCommand);
|
|
97
103
|
// ════════════════════════════════════════════════════════════════════
|
|
98
104
|
// ⚡ Automations
|
|
99
105
|
// ════════════════════════════════════════════════════════════════════
|
|
@@ -109,6 +115,11 @@ program
|
|
|
109
115
|
.option("-c, --company <id>", "ID de company")
|
|
110
116
|
.option("-f, --file <path>", "Exportar a archivo")
|
|
111
117
|
.action(automationCommand);
|
|
118
|
+
program
|
|
119
|
+
.command("automation:create <name> <js_file>")
|
|
120
|
+
.description("Crear/actualizar automation desde archivo JS")
|
|
121
|
+
.option("-c, --company <id>", "ID de company (requerido)")
|
|
122
|
+
.action(automationCreateCommand);
|
|
112
123
|
// ════════════════════════════════════════════════════════════════════
|
|
113
124
|
// 🔐 Environment Variables
|
|
114
125
|
// ════════════════════════════════════════════════════════════════════
|
package/dist/lib/api.js
CHANGED
|
@@ -11,42 +11,64 @@ class ApiError extends Error {
|
|
|
11
11
|
this.name = "ApiError";
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
// 💜 Auto-login with saved credentials
|
|
15
|
+
async function autoLogin() {
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
if (!config.email || !config.password) {
|
|
18
|
+
console.error("[gufi] No saved credentials for auto-login");
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
console.log("[gufi] Auto-login with saved credentials...");
|
|
23
|
+
const { token, refreshToken } = await login(config.email, config.password);
|
|
24
|
+
setToken(token, config.email, refreshToken);
|
|
25
|
+
console.log("[gufi] Auto-login successful");
|
|
26
|
+
return token;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.error("[gufi] Auto-login failed:", err);
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
14
33
|
// Auto-refresh the token if expired
|
|
15
34
|
async function refreshAccessToken() {
|
|
16
35
|
const refreshToken = getRefreshToken();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
// Try refresh token first
|
|
37
|
+
if (refreshToken) {
|
|
38
|
+
try {
|
|
39
|
+
const url = `${getApiUrl()}/api/auth/refresh`;
|
|
40
|
+
const response = await fetch(url, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
"X-Client": "cli",
|
|
45
|
+
"X-Refresh-Token": refreshToken,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
if (response.ok) {
|
|
49
|
+
const data = await response.json();
|
|
50
|
+
const config = loadConfig();
|
|
51
|
+
setToken(data.accessToken, config.email || "", data.refreshToken);
|
|
52
|
+
console.log("[gufi] Token refreshed successfully");
|
|
53
|
+
return data.accessToken;
|
|
54
|
+
}
|
|
32
55
|
console.error(`[gufi] Refresh failed: ${response.status}`);
|
|
33
|
-
return null;
|
|
34
56
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.log("[gufi] Token refreshed successfully");
|
|
39
|
-
return data.accessToken;
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
console.error("[gufi] Refresh error:", err);
|
|
43
|
-
return null;
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error("[gufi] Refresh error:", err);
|
|
59
|
+
}
|
|
44
60
|
}
|
|
61
|
+
// 💜 Fallback: auto-login with saved credentials
|
|
62
|
+
return autoLogin();
|
|
45
63
|
}
|
|
46
64
|
async function request(endpoint, options = {}, retryOnExpire = true) {
|
|
47
65
|
let token = getToken();
|
|
66
|
+
// 💜 If no token, try auto-login with saved credentials
|
|
48
67
|
if (!token) {
|
|
49
|
-
|
|
68
|
+
token = await autoLogin();
|
|
69
|
+
if (!token) {
|
|
70
|
+
throw new Error("No estás logueado. Usa: gufi login");
|
|
71
|
+
}
|
|
50
72
|
}
|
|
51
73
|
const url = `${getApiUrl()}${endpoint}`;
|
|
52
74
|
const response = await fetch(url, {
|
package/dist/lib/config.d.ts
CHANGED
package/dist/lib/config.js
CHANGED
|
@@ -57,10 +57,13 @@ export function clearToken() {
|
|
|
57
57
|
delete config.token;
|
|
58
58
|
delete config.refreshToken;
|
|
59
59
|
delete config.email;
|
|
60
|
+
delete config.password; // 💜 Also clear saved credentials on logout
|
|
60
61
|
saveConfig(config);
|
|
61
62
|
}
|
|
62
63
|
export function isLoggedIn() {
|
|
63
|
-
|
|
64
|
+
// 💜 True if has token OR has saved credentials for auto-login
|
|
65
|
+
const config = loadConfig();
|
|
66
|
+
return !!getToken() || !!(config.email && config.password);
|
|
64
67
|
}
|
|
65
68
|
export function setCurrentView(view) {
|
|
66
69
|
const config = loadConfig();
|
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
|
/**
|