gufi-cli 0.1.9 β 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 +24 -2
- package/dist/commands/companies.d.ts +6 -0
- package/dist/commands/companies.js +182 -21
- package/dist/commands/env.js +53 -4
- package/dist/commands/login.js +6 -2
- package/dist/commands/rows.js +53 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -1
- package/dist/lib/api.js +48 -26
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +4 -1
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -1569,6 +1569,28 @@ npm install -g gufi-cli
|
|
|
1569
1569
|
gufi login
|
|
1570
1570
|
```
|
|
1571
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
|
+
|
|
1572
1594
|
### GestiΓ³n de Companies y MΓ³dulos
|
|
1573
1595
|
|
|
1574
1596
|
```bash
|
|
@@ -1718,9 +1740,9 @@ gufi view:status
|
|
|
1718
1740
|
|
|
1719
1741
|
| Error | SoluciΓ³n |
|
|
1720
1742
|
|-------|----------|
|
|
1721
|
-
| "No estΓ‘s logueado" | `gufi login` |
|
|
1743
|
+
| "No estΓ‘s logueado" | `gufi login` (guarda credenciales para auto-login) |
|
|
1722
1744
|
| "MΓ³dulo no encontrado" | Verificar `-c <company_id>` |
|
|
1723
|
-
| "Token expirado" |
|
|
1745
|
+
| "Token expirado" | AutomΓ‘tico: refresh o auto-login con credenciales guardadas |
|
|
1724
1746
|
| "JSON invΓ‘lido" | Validar estructura del JSON |
|
|
1725
1747
|
|
|
1726
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) {
|
|
@@ -472,6 +587,52 @@ export async function automationCommand(automationName, options) {
|
|
|
472
587
|
console.log(chalk.red(`\n β Error: ${err.message}\n`));
|
|
473
588
|
}
|
|
474
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
|
+
}
|
|
475
636
|
async function editAutomationCode(automationName, currentCode, companyId) {
|
|
476
637
|
const fs = await import("fs");
|
|
477
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/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/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,7 +38,7 @@ 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();
|
|
@@ -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();
|