gufi-cli 0.1.9 β†’ 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -1569,6 +1569,29 @@ npm install -g gufi-cli
1569
1569
  gufi login
1570
1570
  ```
1571
1571
 
1572
+ ### πŸ’œ AutenticaciΓ³n Persistente (Auto-Login)
1573
+
1574
+ El CLI guarda las credenciales en `~/.gufi/config.json` y hace **auto-login automΓ‘tico** cuando es necesario:
1575
+
1576
+ ```json
1577
+ {
1578
+ "apiUrl": "https://gogufi.com",
1579
+ "email": "user@example.com",
1580
+ "password": "secreto",
1581
+ "token": "eyJ...",
1582
+ "refreshToken": "eyJ..."
1583
+ }
1584
+ ```
1585
+
1586
+ **Flujo automΓ‘tico:**
1587
+ 1. `gufi login` β†’ pide email/password β†’ los guarda permanentemente
1588
+ 2. Cualquier comando β†’ si token expirΓ³ β†’ auto-login con credenciales guardadas
1589
+ 3. `gufi logout` β†’ borra TODO (token + credenciales)
1590
+
1591
+ **Resultado:** Una vez haces `gufi login`, el CLI funciona para siempre sin volver a pedir credenciales. Si el token expira, hace auto-login automΓ‘ticamente.
1592
+
1593
+ **Nota:** Si haces login en el navegador web, el refreshToken del CLI se invalida, pero el CLI hace auto-login automΓ‘ticamente con las credenciales guardadas.
1594
+
1572
1595
  ### GestiΓ³n de Companies y MΓ³dulos
1573
1596
 
1574
1597
  ```bash
@@ -1718,9 +1741,9 @@ gufi view:status
1718
1741
 
1719
1742
  | Error | SoluciΓ³n |
1720
1743
  |-------|----------|
1721
- | "No estΓ‘s logueado" | `gufi login` |
1744
+ | "No estΓ‘s logueado" | `gufi login` (guarda credenciales para auto-login) |
1722
1745
  | "MΓ³dulo no encontrado" | Verificar `-c <company_id>` |
1723
- | "Token expirado" | `gufi login` de nuevo |
1746
+ | "Token expirado" | AutomΓ‘tico: refresh o auto-login con credenciales guardadas |
1724
1747
  | "JSON invΓ‘lido" | Validar estructura del JSON |
1725
1748
 
1726
1749
  ### 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
- async function apiRequest(path, options = {}) {
10
- const token = getToken();
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({ json_definition: newJson }),
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,32 +368,51 @@ export async function moduleUpdateCommand(moduleId, jsonFile, options) {
306
368
  if (options?.company) {
307
369
  headers["X-Company-ID"] = options.company;
308
370
  }
309
- // Validate first
371
+ // Validate JSON structure locally
310
372
  console.log(chalk.gray(" Validando JSON..."));
311
- const validateResult = await apiRequest(`/api/modules/${moduleId}/validate`, {
312
- method: "POST",
373
+ if (!newJson.name && !newJson.displayName) {
374
+ console.log(chalk.red("\n βœ— JSON debe tener 'name' o 'displayName'\n"));
375
+ process.exit(1);
376
+ }
377
+ console.log(chalk.green(" βœ“ JSON vΓ‘lido"));
378
+ // πŸ’œ Step 1: Dry-run para ver quΓ© cambios se harΓ‘n
379
+ console.log(chalk.gray(" Verificando cambios (dry-run)..."));
380
+ const preview = await apiRequest(`/api/company/modules/${moduleId}`, {
381
+ method: "PUT",
313
382
  headers,
314
- body: JSON.stringify({ json_definition: newJson }),
383
+ body: JSON.stringify({ json: newJson, confirm: false }),
315
384
  });
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}`));
385
+ // Mostrar preview de cambios
386
+ const hasChanges = preview.tablesCreated?.length || preview.columnsAdded?.length ||
387
+ preview.columnsRemoved?.length || preview.tablesRemoved?.length;
388
+ if (hasChanges) {
389
+ console.log(chalk.cyan("\n πŸ“‹ Cambios detectados:"));
390
+ if (preview.tablesCreated?.length) {
391
+ console.log(chalk.green(` + Tablas a crear: ${preview.tablesCreated.join(", ")}`));
392
+ }
393
+ if (preview.columnsAdded?.length) {
394
+ console.log(chalk.green(` + Columnas a aΓ±adir: ${preview.columnsAdded.join(", ")}`));
395
+ }
396
+ if (preview.columnsRemoved?.length) {
397
+ console.log(chalk.yellow(` - Columnas a eliminar: ${preview.columnsRemoved.join(", ")}`));
398
+ }
399
+ if (preview.tablesRemoved?.length) {
400
+ console.log(chalk.red(` - Tablas a eliminar: ${preview.tablesRemoved.join(", ")}`));
320
401
  }
321
- console.log();
322
- process.exit(1);
323
402
  }
324
- console.log(chalk.green(" βœ“ JSON vΓ‘lido"));
403
+ else {
404
+ console.log(chalk.gray(" Sin cambios estructurales detectados."));
405
+ }
325
406
  if (options?.dryRun) {
326
- console.log(chalk.gray("\n [DRY RUN] ValidaciΓ³n exitosa. Usa sin --dry-run para aplicar.\n"));
407
+ console.log(chalk.gray("\n [DRY RUN] Vista previa completada. Usa sin --dry-run para aplicar.\n"));
327
408
  return;
328
409
  }
329
- // Apply changes
330
- console.log(chalk.gray(" Aplicando cambios..."));
331
- const result = await apiRequest(`/api/modules/${moduleId}`, {
410
+ // πŸ’œ Step 2: Aplicar cambios (confirm: true)
411
+ console.log(chalk.gray("\n Aplicando cambios..."));
412
+ const result = await apiRequest(`/api/company/modules/${moduleId}`, {
332
413
  method: "PUT",
333
414
  headers,
334
- body: JSON.stringify({ json_definition: newJson }),
415
+ body: JSON.stringify({ json: newJson, confirm: true }),
335
416
  });
336
417
  console.log(chalk.green("\n βœ“ MΓ³dulo actualizado correctamente!\n"));
337
418
  if (result.tablesCreated?.length) {
@@ -353,6 +434,68 @@ export async function moduleUpdateCommand(moduleId, jsonFile, options) {
353
434
  }
354
435
  }
355
436
  // ════════════════════════════════════════════════════════════════════
437
+ // gufi module:create <json_file> --company <id> - Create module from JSON file
438
+ // ════════════════════════════════════════════════════════════════════
439
+ export async function moduleCreateCommand(jsonFile, options) {
440
+ if (!isLoggedIn()) {
441
+ console.log(chalk.red("\n βœ— No estΓ‘s logueado. Usa: gufi login\n"));
442
+ process.exit(1);
443
+ }
444
+ if (!jsonFile) {
445
+ console.log(chalk.red("\n βœ— Uso: gufi module:create <json_file> --company <id>\n"));
446
+ process.exit(1);
447
+ }
448
+ if (!options?.company) {
449
+ console.log(chalk.red("\n βœ— Debes especificar --company <id>\n"));
450
+ console.log(chalk.gray(" Tip: Usa 'gufi companies' para ver tus companies\n"));
451
+ process.exit(1);
452
+ }
453
+ const fs = await import("fs");
454
+ // Read JSON file
455
+ let moduleJson;
456
+ try {
457
+ const content = fs.readFileSync(jsonFile, "utf-8");
458
+ moduleJson = JSON.parse(content);
459
+ }
460
+ catch (e) {
461
+ console.log(chalk.red(`\n βœ— Error leyendo archivo: ${e.message}\n`));
462
+ process.exit(1);
463
+ }
464
+ const moduleName = moduleJson.displayName || moduleJson.label || moduleJson.name || "Nuevo MΓ³dulo";
465
+ console.log(chalk.magenta(`\n 🟣 Creando módulo: ${moduleName}\n`));
466
+ console.log(chalk.gray(` Company: ${options.company}`));
467
+ try {
468
+ const headers = {
469
+ "X-Company-ID": options.company,
470
+ };
471
+ const result = await apiRequest("/api/company/modules", {
472
+ method: "POST",
473
+ headers,
474
+ body: JSON.stringify({ json: moduleJson }),
475
+ });
476
+ const data = result.data || result;
477
+ console.log(chalk.green("\n βœ“ MΓ³dulo creado!\n"));
478
+ console.log(chalk.white(` ID: ${data.moduleId || data.id || "N/A"}`));
479
+ if (data.tablesCreated?.length) {
480
+ console.log(chalk.gray(" Tablas creadas:"));
481
+ for (const t of data.tablesCreated) {
482
+ console.log(chalk.green(` + ${t}`));
483
+ }
484
+ }
485
+ if (data.entities?.length) {
486
+ console.log(chalk.gray(" Entidades:"));
487
+ for (const ent of data.entities) {
488
+ console.log(chalk.white(` - ${ent.name} (${ent.kind})`));
489
+ }
490
+ }
491
+ console.log(chalk.gray("\n πŸ’‘ Usa: gufi modules " + options.company + " para ver todos los mΓ³dulos\n"));
492
+ }
493
+ catch (err) {
494
+ console.log(chalk.red(`\n βœ— Error: ${err.message}\n`));
495
+ process.exit(1);
496
+ }
497
+ }
498
+ // ════════════════════════════════════════════════════════════════════
356
499
  // gufi company:create <name> - Create a new company
357
500
  // ════════════════════════════════════════════════════════════════════
358
501
  export async function companyCreateCommand(name) {
@@ -472,6 +615,52 @@ export async function automationCommand(automationName, options) {
472
615
  console.log(chalk.red(`\n βœ— Error: ${err.message}\n`));
473
616
  }
474
617
  }
618
+ // ════════════════════════════════════════════════════════════════════
619
+ // gufi automation:create <name> <js_file> --company <id> - Create/update automation from file
620
+ // ════════════════════════════════════════════════════════════════════
621
+ export async function automationCreateCommand(name, jsFile, options) {
622
+ if (!isLoggedIn()) {
623
+ console.log(chalk.red("\n βœ— No estΓ‘s logueado. Usa: gufi login\n"));
624
+ process.exit(1);
625
+ }
626
+ if (!name || !jsFile) {
627
+ console.log(chalk.red("\n βœ— Uso: gufi automation:create <nombre> <archivo.js> --company <id>\n"));
628
+ process.exit(1);
629
+ }
630
+ if (!options?.company) {
631
+ console.log(chalk.red("\n βœ— Debes especificar --company <id>\n"));
632
+ process.exit(1);
633
+ }
634
+ const fs = await import("fs");
635
+ let code;
636
+ try {
637
+ code = fs.readFileSync(jsFile, "utf-8");
638
+ }
639
+ catch (e) {
640
+ console.log(chalk.red(`\n βœ— Error leyendo archivo: ${e.message}\n`));
641
+ process.exit(1);
642
+ }
643
+ console.log(chalk.magenta(`\n 🟣 Creando automation: ${name}\n`));
644
+ console.log(chalk.gray(` Company: ${options.company}`));
645
+ try {
646
+ const headers = {
647
+ "X-Company-ID": options.company,
648
+ };
649
+ const result = await apiRequest(`/api/automation-scripts/${encodeURIComponent(name)}`, {
650
+ method: "PUT",
651
+ headers,
652
+ body: JSON.stringify({ code }),
653
+ });
654
+ console.log(chalk.green("\n βœ“ Automation creada!\n"));
655
+ console.log(chalk.white(` Nombre: ${result.name || name}`));
656
+ console.log(chalk.white(` AcciΓ³n: ${result.action || "created/updated"}`));
657
+ console.log(chalk.gray("\n πŸ’‘ Usa: gufi automations -c " + options.company + " para ver la lista\n"));
658
+ }
659
+ catch (err) {
660
+ console.log(chalk.red(`\n βœ— Error: ${err.message}\n`));
661
+ process.exit(1);
662
+ }
663
+ }
475
664
  async function editAutomationCode(automationName, currentCode, companyId) {
476
665
  const fs = await import("fs");
477
666
  const os = await import("os");
@@ -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
- async function apiRequest(endpoint, options = {}) {
9
- const token = getToken();
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
- throw new Error("No estΓ‘s logueado. Ejecuta: gufi login");
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}`);
@@ -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 (7 dΓ­as).\n"));
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) {
@@ -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
- async function apiRequest(endpoint, options = {}) {
10
- const token = getToken();
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
- throw new Error("No estΓ‘s logueado. Ejecuta: gufi login");
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
- if (!refreshToken) {
18
- console.error("[gufi] No refresh token available");
19
- return null;
20
- }
21
- try {
22
- const url = `${getApiUrl()}/api/auth/refresh`;
23
- const response = await fetch(url, {
24
- method: "POST",
25
- headers: {
26
- "Content-Type": "application/json",
27
- "X-Client": "cli",
28
- "X-Refresh-Token": refreshToken,
29
- },
30
- });
31
- if (!response.ok) {
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
- const data = await response.json();
36
- const config = loadConfig();
37
- setToken(data.accessToken, config.email || "", data.refreshToken);
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
- throw new Error("No estΓ‘s logueado. Ejecuta: gufi login");
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, {
@@ -7,6 +7,7 @@ export interface GufiConfig {
7
7
  token?: string;
8
8
  refreshToken?: string;
9
9
  email?: string;
10
+ password?: string;
10
11
  currentView?: {
11
12
  id: number;
12
13
  name: string;
@@ -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
- return !!getToken();
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gufi-cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "CLI for developing Gufi Marketplace views locally with Claude Code",
5
5
  "bin": {
6
6
  "gufi": "./bin/gufi.js"