gufi-cli 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1491 -0
- package/dist/commands/companies.d.ts +26 -0
- package/dist/commands/companies.js +513 -0
- package/dist/commands/login.js +4 -3
- package/dist/commands/logs.d.ts +8 -0
- package/dist/commands/logs.js +153 -0
- package/dist/commands/push.d.ts +2 -2
- package/dist/commands/push.js +4 -3
- package/dist/index.d.ts +17 -7
- package/dist/index.js +64 -8
- package/dist/lib/api.d.ts +1 -0
- package/dist/lib/api.js +42 -5
- package/dist/lib/config.d.ts +4 -1
- package/dist/lib/config.js +13 -1
- package/package.json +15 -3
- package/src/commands/list.ts +0 -51
- package/src/commands/login.ts +0 -124
- package/src/commands/pull.ts +0 -113
- package/src/commands/push.ts +0 -85
- package/src/commands/watch.ts +0 -89
- package/src/index.ts +0 -73
- package/src/lib/api.ts +0 -127
- package/src/lib/config.ts +0 -93
- package/src/lib/sync.ts +0 -236
- package/tsconfig.json +0 -17
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi logs - Stream console logs from LivePreview in real-time
|
|
3
|
+
*
|
|
4
|
+
* The CLI connects to the Gufi WebSocket server and subscribes to
|
|
5
|
+
* dev-logs for a specific view. When the LivePreview is open in the
|
|
6
|
+
* browser, console logs are forwarded to the CLI.
|
|
7
|
+
*/
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import WebSocket from "ws";
|
|
10
|
+
import { isLoggedIn, getToken, getCurrentView, loadConfig } from "../lib/config.js";
|
|
11
|
+
import { loadViewMeta } from "../lib/sync.js";
|
|
12
|
+
const LOG_COLORS = {
|
|
13
|
+
log: chalk.white,
|
|
14
|
+
info: chalk.blue,
|
|
15
|
+
warn: chalk.yellow,
|
|
16
|
+
error: chalk.red,
|
|
17
|
+
debug: chalk.gray,
|
|
18
|
+
};
|
|
19
|
+
export async function logsCommand(viewDir) {
|
|
20
|
+
if (!isLoggedIn()) {
|
|
21
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
// Determine view directory
|
|
25
|
+
const dir = viewDir || getCurrentView()?.localPath || process.cwd();
|
|
26
|
+
const meta = loadViewMeta(dir);
|
|
27
|
+
if (!meta) {
|
|
28
|
+
console.log(chalk.red("\n ✗ No es un directorio de vista Gufi válido."));
|
|
29
|
+
console.log(chalk.gray(" Usa: gufi pull <vista> primero\n"));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
const apiUrl = config.apiUrl || "https://gogufi.com";
|
|
34
|
+
// Use the WebSocket server (port 4000 in dev, same domain in prod)
|
|
35
|
+
let wsUrl;
|
|
36
|
+
if (apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1")) {
|
|
37
|
+
// Dev: use local WebSocket server
|
|
38
|
+
wsUrl = "ws://localhost:4000";
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Prod: use wss and same domain
|
|
42
|
+
wsUrl = apiUrl.replace(/^https?/, "wss").replace(/:3000$/, ":4000");
|
|
43
|
+
if (!wsUrl.includes(":4000")) {
|
|
44
|
+
wsUrl = wsUrl.replace(/\/?$/, ":4000");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const token = getToken();
|
|
48
|
+
console.log(chalk.magenta("\n 🟣 Gufi Logs\n"));
|
|
49
|
+
console.log(chalk.gray(` Vista: ${meta.viewName} (ID: ${meta.viewId})`));
|
|
50
|
+
console.log(chalk.gray(` Conectando a WebSocket...\n`));
|
|
51
|
+
let ws;
|
|
52
|
+
let reconnectAttempts = 0;
|
|
53
|
+
const maxReconnectAttempts = 5;
|
|
54
|
+
const connect = () => {
|
|
55
|
+
// Connect with token in URL (same as frontend)
|
|
56
|
+
ws = new WebSocket(`${wsUrl}?token=${token}`);
|
|
57
|
+
ws.on("open", () => {
|
|
58
|
+
reconnectAttempts = 0;
|
|
59
|
+
console.log(chalk.green(" ✓ Conectado al WebSocket\n"));
|
|
60
|
+
// Subscribe to dev logs for this view
|
|
61
|
+
ws.send(JSON.stringify({
|
|
62
|
+
type: "dev-logs-subscribe",
|
|
63
|
+
viewId: meta.viewId,
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
ws.on("message", (data) => {
|
|
67
|
+
try {
|
|
68
|
+
const msg = JSON.parse(data.toString());
|
|
69
|
+
// Handle subscription confirmation
|
|
70
|
+
if (msg.type === "dev-logs-subscribed") {
|
|
71
|
+
console.log(chalk.green(` ✓ Suscrito a logs de vista ${msg.viewId}\n`));
|
|
72
|
+
console.log(chalk.gray(" 💡 Abre el Developer Center y activa LivePreview"));
|
|
73
|
+
console.log(chalk.gray(" Los logs de la consola aparecerán aquí en tiempo real\n"));
|
|
74
|
+
console.log(chalk.gray(" Ctrl+C para salir\n"));
|
|
75
|
+
console.log(chalk.gray(" ─".repeat(40) + "\n"));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Handle log messages
|
|
79
|
+
if (msg.type && msg.args) {
|
|
80
|
+
formatAndPrintLog(msg);
|
|
81
|
+
}
|
|
82
|
+
else if (msg.error) {
|
|
83
|
+
console.log(chalk.red(` Error: ${msg.error}`));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
// Raw message, just print it
|
|
88
|
+
console.log(chalk.gray(` ${data.toString()}`));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
ws.on("close", () => {
|
|
92
|
+
if (reconnectAttempts < maxReconnectAttempts) {
|
|
93
|
+
reconnectAttempts++;
|
|
94
|
+
console.log(chalk.yellow(` ⚠ Conexión cerrada. Reintentando (${reconnectAttempts}/${maxReconnectAttempts})...`));
|
|
95
|
+
setTimeout(connect, 2000);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(chalk.red("\n ✗ No se pudo reconectar. Verifica que el servidor esté corriendo.\n"));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
ws.on("error", (err) => {
|
|
103
|
+
if (err.code === "ECONNREFUSED") {
|
|
104
|
+
console.log(chalk.red(`\n ✗ No se puede conectar a ${wsUrl}`));
|
|
105
|
+
console.log(chalk.gray(" Verifica que el servidor WebSocket esté corriendo (puerto 4000)\n"));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.log(chalk.red(` Error: ${err.message}`));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
connect();
|
|
113
|
+
// Handle graceful shutdown
|
|
114
|
+
process.on("SIGINT", () => {
|
|
115
|
+
console.log(chalk.gray("\n\n Desconectando...\n"));
|
|
116
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
117
|
+
ws.send(JSON.stringify({ type: "dev-logs-unsubscribe" }));
|
|
118
|
+
}
|
|
119
|
+
ws?.close();
|
|
120
|
+
process.exit(0);
|
|
121
|
+
});
|
|
122
|
+
// Keep process alive
|
|
123
|
+
await new Promise(() => { });
|
|
124
|
+
}
|
|
125
|
+
function formatAndPrintLog(msg) {
|
|
126
|
+
const colorFn = LOG_COLORS[msg.type] || chalk.white;
|
|
127
|
+
const prefix = getPrefix(msg.type);
|
|
128
|
+
const timestamp = new Date(msg.timestamp).toLocaleTimeString();
|
|
129
|
+
// Format args
|
|
130
|
+
const formatted = msg.args.map(arg => {
|
|
131
|
+
if (typeof arg === "object") {
|
|
132
|
+
try {
|
|
133
|
+
return JSON.stringify(arg, null, 2);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return String(arg);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return String(arg);
|
|
140
|
+
}).join(" ");
|
|
141
|
+
console.log(chalk.gray(` ${timestamp}`) +
|
|
142
|
+
` ${prefix} ` +
|
|
143
|
+
colorFn(formatted));
|
|
144
|
+
}
|
|
145
|
+
function getPrefix(type) {
|
|
146
|
+
switch (type) {
|
|
147
|
+
case "error": return chalk.red("✗");
|
|
148
|
+
case "warn": return chalk.yellow("⚠");
|
|
149
|
+
case "info": return chalk.blue("ℹ");
|
|
150
|
+
case "debug": return chalk.gray("🔍");
|
|
151
|
+
default: return chalk.white("›");
|
|
152
|
+
}
|
|
153
|
+
}
|
package/dist/commands/push.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gufi push - Upload local changes to Gufi
|
|
3
3
|
*/
|
|
4
|
-
export declare function pushCommand(viewDir?: string): Promise<void>;
|
|
5
|
-
export declare function statusCommand(viewDir?: string): Promise<void>;
|
|
4
|
+
export declare function pushCommand(viewDir?: string | object): Promise<void>;
|
|
5
|
+
export declare function statusCommand(viewDir?: string | object): Promise<void>;
|
package/dist/commands/push.js
CHANGED
|
@@ -11,8 +11,8 @@ export async function pushCommand(viewDir) {
|
|
|
11
11
|
process.exit(1);
|
|
12
12
|
}
|
|
13
13
|
console.log(chalk.magenta("\n 🟣 Gufi Push\n"));
|
|
14
|
-
// Determine view directory
|
|
15
|
-
const dir = viewDir || getCurrentView()?.localPath || process.cwd();
|
|
14
|
+
// Determine view directory (Commander passes Command object when no args)
|
|
15
|
+
const dir = (typeof viewDir === 'string' ? viewDir : null) || getCurrentView()?.localPath || process.cwd();
|
|
16
16
|
// Check if it's a valid Gufi view directory
|
|
17
17
|
const meta = loadViewMeta(dir);
|
|
18
18
|
if (!meta) {
|
|
@@ -46,7 +46,8 @@ export async function pushCommand(viewDir) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
export async function statusCommand(viewDir) {
|
|
49
|
-
|
|
49
|
+
// Commander passes Command object when no args
|
|
50
|
+
const dir = (typeof viewDir === 'string' ? viewDir : null) || getCurrentView()?.localPath || process.cwd();
|
|
50
51
|
const meta = loadViewMeta(dir);
|
|
51
52
|
if (!meta) {
|
|
52
53
|
console.log(chalk.red("\n ✗ No es un directorio de vista Gufi válido.\n"));
|
package/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,22 @@
|
|
|
3
3
|
* Gufi Dev CLI - Main Entry Point
|
|
4
4
|
*
|
|
5
5
|
* Commands:
|
|
6
|
-
* gufi login
|
|
7
|
-
* gufi logout
|
|
8
|
-
* gufi whoami
|
|
9
|
-
* gufi pull [view]
|
|
10
|
-
* gufi push
|
|
11
|
-
* gufi watch
|
|
12
|
-
* gufi status
|
|
6
|
+
* gufi login Login to Gufi
|
|
7
|
+
* gufi logout Logout from Gufi
|
|
8
|
+
* gufi whoami Show current user
|
|
9
|
+
* gufi pull [view] Download view files
|
|
10
|
+
* gufi push Upload local changes
|
|
11
|
+
* gufi watch Auto-sync file changes
|
|
12
|
+
* gufi status Show sync status
|
|
13
|
+
* gufi logs [dir] Stream console logs from LivePreview
|
|
14
|
+
*
|
|
15
|
+
* SDK Commands:
|
|
16
|
+
* gufi companies List your companies
|
|
17
|
+
* gufi modules <id> List modules of a company
|
|
18
|
+
* gufi module <id> View/edit module JSON (--edit, --file)
|
|
19
|
+
* gufi module:update Update module from JSON file
|
|
20
|
+
* gufi company:create Create a new company
|
|
21
|
+
* gufi automations List automation scripts
|
|
22
|
+
* gufi automation <name> View/edit automation code (--edit, --file)
|
|
13
23
|
*/
|
|
14
24
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -3,13 +3,23 @@
|
|
|
3
3
|
* Gufi Dev CLI - Main Entry Point
|
|
4
4
|
*
|
|
5
5
|
* Commands:
|
|
6
|
-
* gufi login
|
|
7
|
-
* gufi logout
|
|
8
|
-
* gufi whoami
|
|
9
|
-
* gufi pull [view]
|
|
10
|
-
* gufi push
|
|
11
|
-
* gufi watch
|
|
12
|
-
* gufi status
|
|
6
|
+
* gufi login Login to Gufi
|
|
7
|
+
* gufi logout Logout from Gufi
|
|
8
|
+
* gufi whoami Show current user
|
|
9
|
+
* gufi pull [view] Download view files
|
|
10
|
+
* gufi push Upload local changes
|
|
11
|
+
* gufi watch Auto-sync file changes
|
|
12
|
+
* gufi status Show sync status
|
|
13
|
+
* gufi logs [dir] Stream console logs from LivePreview
|
|
14
|
+
*
|
|
15
|
+
* SDK Commands:
|
|
16
|
+
* gufi companies List your companies
|
|
17
|
+
* gufi modules <id> List modules of a company
|
|
18
|
+
* gufi module <id> View/edit module JSON (--edit, --file)
|
|
19
|
+
* gufi module:update Update module from JSON file
|
|
20
|
+
* gufi company:create Create a new company
|
|
21
|
+
* gufi automations List automation scripts
|
|
22
|
+
* gufi automation <name> View/edit automation code (--edit, --file)
|
|
13
23
|
*/
|
|
14
24
|
import { Command } from "commander";
|
|
15
25
|
import { loginCommand, logoutCommand, whoamiCommand } from "./commands/login.js";
|
|
@@ -17,6 +27,8 @@ import { pullCommand } from "./commands/pull.js";
|
|
|
17
27
|
import { pushCommand, statusCommand } from "./commands/push.js";
|
|
18
28
|
import { watchCommand } from "./commands/watch.js";
|
|
19
29
|
import { listCommand } from "./commands/list.js";
|
|
30
|
+
import { logsCommand } from "./commands/logs.js";
|
|
31
|
+
import { companiesCommand, modulesCommand, moduleCommand, moduleUpdateCommand, companyCreateCommand, automationsCommand, automationCommand, } from "./commands/companies.js";
|
|
20
32
|
const program = new Command();
|
|
21
33
|
program
|
|
22
34
|
.name("gufi")
|
|
@@ -46,7 +58,7 @@ program
|
|
|
46
58
|
.description("Upload local changes to Gufi")
|
|
47
59
|
.action(pushCommand);
|
|
48
60
|
program
|
|
49
|
-
.command("watch")
|
|
61
|
+
.command("watch [dir]")
|
|
50
62
|
.description("Watch for file changes and auto-sync")
|
|
51
63
|
.action(watchCommand);
|
|
52
64
|
program
|
|
@@ -58,4 +70,48 @@ program
|
|
|
58
70
|
.alias("ls")
|
|
59
71
|
.description("List your packages and views")
|
|
60
72
|
.action(listCommand);
|
|
73
|
+
program
|
|
74
|
+
.command("logs [dir]")
|
|
75
|
+
.description("Stream console logs from LivePreview in real-time")
|
|
76
|
+
.action(logsCommand);
|
|
77
|
+
// ════════════════════════════════════════════════════════════════════
|
|
78
|
+
// SDK Commands - Companies, Modules, Automations
|
|
79
|
+
// ════════════════════════════════════════════════════════════════════
|
|
80
|
+
program
|
|
81
|
+
.command("companies")
|
|
82
|
+
.description("List your companies")
|
|
83
|
+
.action(companiesCommand);
|
|
84
|
+
program
|
|
85
|
+
.command("modules <company_id>")
|
|
86
|
+
.description("List modules of a company")
|
|
87
|
+
.action(modulesCommand);
|
|
88
|
+
program
|
|
89
|
+
.command("module <module_id>")
|
|
90
|
+
.description("View or edit a module JSON")
|
|
91
|
+
.option("-e, --edit", "Open in editor to edit")
|
|
92
|
+
.option("-c, --company <id>", "Company ID (if not default)")
|
|
93
|
+
.option("-f, --file <path>", "Save JSON to file")
|
|
94
|
+
.action(moduleCommand);
|
|
95
|
+
program
|
|
96
|
+
.command("module:update <module_id> <json_file>")
|
|
97
|
+
.description("Update module from a JSON file")
|
|
98
|
+
.option("-c, --company <id>", "Company ID (if not default)")
|
|
99
|
+
.option("--dry-run", "Validate without saving")
|
|
100
|
+
.action(moduleUpdateCommand);
|
|
101
|
+
program
|
|
102
|
+
.command("company:create <name>")
|
|
103
|
+
.description("Create a new company")
|
|
104
|
+
.action(companyCreateCommand);
|
|
105
|
+
program
|
|
106
|
+
.command("automations")
|
|
107
|
+
.description("List all automation scripts")
|
|
108
|
+
.option("-c, --company <id>", "Company ID (if not default)")
|
|
109
|
+
.action(automationsCommand);
|
|
110
|
+
program
|
|
111
|
+
.command("automation <name>")
|
|
112
|
+
.description("View or edit an automation by name")
|
|
113
|
+
.option("-e, --edit", "Open in editor to edit")
|
|
114
|
+
.option("-c, --company <id>", "Company ID (if not default)")
|
|
115
|
+
.option("-f, --file <path>", "Save code to file")
|
|
116
|
+
.action(automationCommand);
|
|
61
117
|
program.parse();
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface Package {
|
|
|
25
25
|
}
|
|
26
26
|
export declare function login(email: string, password: string): Promise<{
|
|
27
27
|
token: string;
|
|
28
|
+
refreshToken?: string;
|
|
28
29
|
}>;
|
|
29
30
|
export declare function listPackages(): Promise<Package[]>;
|
|
30
31
|
export declare function listViews(packageId: number): Promise<View[]>;
|
package/dist/lib/api.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Gufi Dev CLI - API Client
|
|
3
3
|
* Communicates with Gufi Marketplace API
|
|
4
4
|
*/
|
|
5
|
-
import { getToken, getApiUrl } from "./config.js";
|
|
5
|
+
import { getToken, getApiUrl, getRefreshToken, setToken, loadConfig } from "./config.js";
|
|
6
6
|
class ApiError extends Error {
|
|
7
7
|
status;
|
|
8
8
|
constructor(status, message) {
|
|
@@ -11,8 +11,34 @@ class ApiError extends Error {
|
|
|
11
11
|
this.name = "ApiError";
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Auto-refresh the token if expired
|
|
15
|
+
async function refreshAccessToken() {
|
|
16
|
+
const refreshToken = getRefreshToken();
|
|
17
|
+
if (!refreshToken)
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
const url = `${getApiUrl()}/api/auth/refresh`;
|
|
21
|
+
const response = await fetch(url, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
"X-Client": "cli",
|
|
26
|
+
"X-Refresh-Token": refreshToken,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok)
|
|
30
|
+
return null;
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
setToken(data.accessToken, config.email || "", data.refreshToken);
|
|
34
|
+
return data.accessToken;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function request(endpoint, options = {}, retryOnExpire = true) {
|
|
41
|
+
let token = getToken();
|
|
16
42
|
if (!token) {
|
|
17
43
|
throw new Error("No estás logueado. Ejecuta: gufi login");
|
|
18
44
|
}
|
|
@@ -22,9 +48,17 @@ async function request(endpoint, options = {}) {
|
|
|
22
48
|
headers: {
|
|
23
49
|
"Content-Type": "application/json",
|
|
24
50
|
Authorization: `Bearer ${token}`,
|
|
51
|
+
"X-Client": "cli",
|
|
25
52
|
...options.headers,
|
|
26
53
|
},
|
|
27
54
|
});
|
|
55
|
+
// Auto-refresh on 401/403 and retry once
|
|
56
|
+
if ((response.status === 401 || response.status === 403) && retryOnExpire) {
|
|
57
|
+
const newToken = await refreshAccessToken();
|
|
58
|
+
if (newToken) {
|
|
59
|
+
return request(endpoint, options, false); // Retry with new token
|
|
60
|
+
}
|
|
61
|
+
}
|
|
28
62
|
if (!response.ok) {
|
|
29
63
|
const text = await response.text();
|
|
30
64
|
throw new ApiError(response.status, `API Error ${response.status}: ${text}`);
|
|
@@ -36,14 +70,17 @@ export async function login(email, password) {
|
|
|
36
70
|
const url = `${getApiUrl()}/api/auth/login`;
|
|
37
71
|
const response = await fetch(url, {
|
|
38
72
|
method: "POST",
|
|
39
|
-
headers: {
|
|
73
|
+
headers: {
|
|
74
|
+
"Content-Type": "application/json",
|
|
75
|
+
"X-Client": "cli", // 💜 Request refresh token in response
|
|
76
|
+
},
|
|
40
77
|
body: JSON.stringify({ email, password }),
|
|
41
78
|
});
|
|
42
79
|
if (!response.ok) {
|
|
43
80
|
throw new ApiError(response.status, "Credenciales inválidas");
|
|
44
81
|
}
|
|
45
82
|
const data = await response.json();
|
|
46
|
-
return { token: data.accessToken };
|
|
83
|
+
return { token: data.accessToken, refreshToken: data.refreshToken };
|
|
47
84
|
}
|
|
48
85
|
// ============ Packages ============
|
|
49
86
|
export async function listPackages() {
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export interface GufiConfig {
|
|
6
6
|
apiUrl: string;
|
|
7
7
|
token?: string;
|
|
8
|
+
refreshToken?: string;
|
|
8
9
|
email?: string;
|
|
9
10
|
currentView?: {
|
|
10
11
|
id: number;
|
|
@@ -17,7 +18,9 @@ export declare function ensureConfigDir(): void;
|
|
|
17
18
|
export declare function loadConfig(): GufiConfig;
|
|
18
19
|
export declare function saveConfig(config: GufiConfig): void;
|
|
19
20
|
export declare function getToken(): string | undefined;
|
|
20
|
-
export declare function setToken(token: string, email: string): void;
|
|
21
|
+
export declare function setToken(token: string, email: string, refreshToken?: string): void;
|
|
22
|
+
export declare function getRefreshToken(): string | undefined;
|
|
23
|
+
export declare function setRefreshToken(refreshToken: string): void;
|
|
21
24
|
export declare function clearToken(): void;
|
|
22
25
|
export declare function isLoggedIn(): boolean;
|
|
23
26
|
export declare function setCurrentView(view: GufiConfig["currentView"]): void;
|
package/dist/lib/config.js
CHANGED
|
@@ -35,15 +35,27 @@ export function saveConfig(config) {
|
|
|
35
35
|
export function getToken() {
|
|
36
36
|
return loadConfig().token;
|
|
37
37
|
}
|
|
38
|
-
export function setToken(token, email) {
|
|
38
|
+
export function setToken(token, email, refreshToken) {
|
|
39
39
|
const config = loadConfig();
|
|
40
40
|
config.token = token;
|
|
41
41
|
config.email = email;
|
|
42
|
+
if (refreshToken) {
|
|
43
|
+
config.refreshToken = refreshToken;
|
|
44
|
+
}
|
|
45
|
+
saveConfig(config);
|
|
46
|
+
}
|
|
47
|
+
export function getRefreshToken() {
|
|
48
|
+
return loadConfig().refreshToken;
|
|
49
|
+
}
|
|
50
|
+
export function setRefreshToken(refreshToken) {
|
|
51
|
+
const config = loadConfig();
|
|
52
|
+
config.refreshToken = refreshToken;
|
|
42
53
|
saveConfig(config);
|
|
43
54
|
}
|
|
44
55
|
export function clearToken() {
|
|
45
56
|
const config = loadConfig();
|
|
46
57
|
delete config.token;
|
|
58
|
+
delete config.refreshToken;
|
|
47
59
|
delete config.email;
|
|
48
60
|
saveConfig(config);
|
|
49
61
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gufi-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "CLI for developing Gufi Marketplace views locally with Claude Code",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gufi": "./bin/gufi.js"
|
|
7
7
|
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"dist",
|
|
11
|
+
"CLAUDE.md"
|
|
12
|
+
],
|
|
8
13
|
"repository": {
|
|
9
14
|
"type": "git",
|
|
10
15
|
"url": "https://github.com/juanbp23/gogufi"
|
|
@@ -17,11 +22,13 @@
|
|
|
17
22
|
"start": "node bin/gufi.js"
|
|
18
23
|
},
|
|
19
24
|
"dependencies": {
|
|
25
|
+
"@types/ws": "^8.18.1",
|
|
20
26
|
"chalk": "^5.3.0",
|
|
21
27
|
"chokidar": "^3.5.3",
|
|
22
28
|
"commander": "^12.0.0",
|
|
23
29
|
"node-fetch": "^3.3.2",
|
|
24
|
-
"ora": "^8.0.1"
|
|
30
|
+
"ora": "^8.0.1",
|
|
31
|
+
"ws": "^8.18.3"
|
|
25
32
|
},
|
|
26
33
|
"devDependencies": {
|
|
27
34
|
"@types/node": "^20.10.0",
|
|
@@ -30,7 +37,12 @@
|
|
|
30
37
|
"engines": {
|
|
31
38
|
"node": ">=18"
|
|
32
39
|
},
|
|
33
|
-
"keywords": [
|
|
40
|
+
"keywords": [
|
|
41
|
+
"gufi",
|
|
42
|
+
"developer",
|
|
43
|
+
"cli",
|
|
44
|
+
"marketplace"
|
|
45
|
+
],
|
|
34
46
|
"author": "Gufi",
|
|
35
47
|
"license": "MIT"
|
|
36
48
|
}
|
package/src/commands/list.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* gufi list - List packages and views
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
import ora from "ora";
|
|
7
|
-
import { listPackages, listViews } from "../lib/api.js";
|
|
8
|
-
import { isLoggedIn } from "../lib/config.js";
|
|
9
|
-
|
|
10
|
-
export async function listCommand(): Promise<void> {
|
|
11
|
-
if (!isLoggedIn()) {
|
|
12
|
-
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
console.log(chalk.magenta("\n 🟣 Gufi List\n"));
|
|
17
|
-
|
|
18
|
-
const spinner = ora("Cargando packages...").start();
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const packages = await listPackages();
|
|
22
|
-
spinner.stop();
|
|
23
|
-
|
|
24
|
-
if (packages.length === 0) {
|
|
25
|
-
console.log(chalk.yellow(" No tienes packages. Crea uno en Developer Center.\n"));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
for (const pkg of packages) {
|
|
30
|
-
console.log(chalk.cyan(`\n 📦 ${pkg.name}`) + chalk.gray(` (ID: ${pkg.pk_id})`));
|
|
31
|
-
console.log(chalk.gray(` ${pkg.description || "Sin descripción"}`));
|
|
32
|
-
|
|
33
|
-
const views = await listViews(pkg.pk_id);
|
|
34
|
-
|
|
35
|
-
if (views.length === 0) {
|
|
36
|
-
console.log(chalk.gray(" └─ Sin vistas"));
|
|
37
|
-
} else {
|
|
38
|
-
views.forEach((view, i) => {
|
|
39
|
-
const prefix = i === views.length - 1 ? "└─" : "├─";
|
|
40
|
-
console.log(chalk.white(` ${prefix} ${view.name}`) + chalk.gray(` (${view.view_type}, ID: ${view.pk_id})`));
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
console.log(chalk.gray("\n Usa: gufi pull <view-id> para descargar una vista\n"));
|
|
46
|
-
|
|
47
|
-
} catch (error: any) {
|
|
48
|
-
spinner.fail(chalk.red(error.message));
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/commands/login.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* gufi login - Authenticate with Gufi
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import readline from "readline";
|
|
6
|
-
import chalk from "chalk";
|
|
7
|
-
import ora from "ora";
|
|
8
|
-
import { login, validateToken } from "../lib/api.js";
|
|
9
|
-
import { setToken, isLoggedIn, clearToken, loadConfig, setApiUrl } from "../lib/config.js";
|
|
10
|
-
|
|
11
|
-
function prompt(question: string, hidden = false): Promise<string> {
|
|
12
|
-
const rl = readline.createInterface({
|
|
13
|
-
input: process.stdin,
|
|
14
|
-
output: process.stdout,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
return new Promise((resolve) => {
|
|
18
|
-
if (hidden) {
|
|
19
|
-
process.stdout.write(question);
|
|
20
|
-
let input = "";
|
|
21
|
-
process.stdin.setRawMode?.(true);
|
|
22
|
-
process.stdin.resume();
|
|
23
|
-
process.stdin.on("data", (char) => {
|
|
24
|
-
const c = char.toString();
|
|
25
|
-
if (c === "\n" || c === "\r") {
|
|
26
|
-
process.stdin.setRawMode?.(false);
|
|
27
|
-
process.stdout.write("\n");
|
|
28
|
-
rl.close();
|
|
29
|
-
resolve(input);
|
|
30
|
-
} else if (c === "\u0003") {
|
|
31
|
-
process.exit();
|
|
32
|
-
} else if (c === "\u007F") {
|
|
33
|
-
if (input.length > 0) {
|
|
34
|
-
input = input.slice(0, -1);
|
|
35
|
-
process.stdout.write("\b \b");
|
|
36
|
-
}
|
|
37
|
-
} else {
|
|
38
|
-
input += c;
|
|
39
|
-
process.stdout.write("*");
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
} else {
|
|
43
|
-
rl.question(question, (answer) => {
|
|
44
|
-
rl.close();
|
|
45
|
-
resolve(answer);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export async function loginCommand(options: { api?: string }): Promise<void> {
|
|
52
|
-
console.log(chalk.magenta("\n 🟣 Gufi Developer CLI\n"));
|
|
53
|
-
|
|
54
|
-
// Set custom API URL if provided
|
|
55
|
-
if (options.api) {
|
|
56
|
-
setApiUrl(options.api);
|
|
57
|
-
console.log(chalk.gray(` API: ${options.api}\n`));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check if already logged in
|
|
61
|
-
if (isLoggedIn()) {
|
|
62
|
-
const config = loadConfig();
|
|
63
|
-
const spinner = ora("Verificando sesión...").start();
|
|
64
|
-
const valid = await validateToken();
|
|
65
|
-
|
|
66
|
-
if (valid) {
|
|
67
|
-
spinner.succeed(chalk.green(`Ya estás logueado como ${config.email}`));
|
|
68
|
-
const relogin = await prompt("\n¿Quieres iniciar sesión con otra cuenta? (s/N): ");
|
|
69
|
-
if (relogin.toLowerCase() !== "s") {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
clearToken();
|
|
73
|
-
} else {
|
|
74
|
-
spinner.warn("Sesión expirada");
|
|
75
|
-
clearToken();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Get credentials
|
|
80
|
-
const email = await prompt(" Email: ");
|
|
81
|
-
const password = await prompt(" Password: ", true);
|
|
82
|
-
|
|
83
|
-
if (!email || !password) {
|
|
84
|
-
console.log(chalk.red("\n ✗ Email y password son requeridos\n"));
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const spinner = ora("Iniciando sesión...").start();
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
const { token } = await login(email, password);
|
|
92
|
-
setToken(token, email);
|
|
93
|
-
spinner.succeed(chalk.green(`Sesión iniciada como ${email}`));
|
|
94
|
-
console.log(chalk.gray("\n Ahora puedes usar: gufi pull <vista>\n"));
|
|
95
|
-
} catch (error: any) {
|
|
96
|
-
spinner.fail(chalk.red(error.message || "Error al iniciar sesión"));
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export async function logoutCommand(): Promise<void> {
|
|
102
|
-
clearToken();
|
|
103
|
-
console.log(chalk.green("\n ✓ Sesión cerrada\n"));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export async function whoamiCommand(): Promise<void> {
|
|
107
|
-
const config = loadConfig();
|
|
108
|
-
|
|
109
|
-
if (!isLoggedIn()) {
|
|
110
|
-
console.log(chalk.yellow("\n No estás logueado. Usa: gufi login\n"));
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const spinner = ora("Verificando...").start();
|
|
115
|
-
const valid = await validateToken();
|
|
116
|
-
|
|
117
|
-
if (valid) {
|
|
118
|
-
spinner.succeed(chalk.green(`Logueado como ${config.email}`));
|
|
119
|
-
console.log(chalk.gray(` API: ${config.apiUrl}\n`));
|
|
120
|
-
} else {
|
|
121
|
-
spinner.fail(chalk.red("Sesión expirada"));
|
|
122
|
-
console.log(chalk.gray(" Usa: gufi login\n"));
|
|
123
|
-
}
|
|
124
|
-
}
|