gufi-cli 0.1.0
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/bin/gufi.js +2 -0
- package/dist/commands/login.d.ts +8 -0
- package/dist/commands/login.js +115 -0
- package/dist/commands/pull.d.ts +4 -0
- package/dist/commands/pull.js +96 -0
- package/dist/commands/push.d.ts +5 -0
- package/dist/commands/push.js +70 -0
- package/dist/commands/watch.d.ts +4 -0
- package/dist/commands/watch.js +73 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +55 -0
- package/dist/lib/api.d.ts +35 -0
- package/dist/lib/api.js +83 -0
- package/dist/lib/config.d.ts +26 -0
- package/dist/lib/config.js +68 -0
- package/dist/lib/sync.d.ts +37 -0
- package/dist/lib/sync.js +188 -0
- package/package.json +36 -0
- package/src/commands/login.ts +124 -0
- package/src/commands/pull.ts +113 -0
- package/src/commands/push.ts +85 -0
- package/src/commands/watch.ts +89 -0
- package/src/index.ts +66 -0
- package/src/lib/api.ts +127 -0
- package/src/lib/config.ts +93 -0
- package/src/lib/sync.ts +236 -0
- package/tsconfig.json +17 -0
package/bin/gufi.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi login - Authenticate with Gufi
|
|
3
|
+
*/
|
|
4
|
+
import readline from "readline";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import { login, validateToken } from "../lib/api.js";
|
|
8
|
+
import { setToken, isLoggedIn, clearToken, loadConfig, setApiUrl } from "../lib/config.js";
|
|
9
|
+
function prompt(question, hidden = false) {
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout,
|
|
13
|
+
});
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
if (hidden) {
|
|
16
|
+
process.stdout.write(question);
|
|
17
|
+
let input = "";
|
|
18
|
+
process.stdin.setRawMode?.(true);
|
|
19
|
+
process.stdin.resume();
|
|
20
|
+
process.stdin.on("data", (char) => {
|
|
21
|
+
const c = char.toString();
|
|
22
|
+
if (c === "\n" || c === "\r") {
|
|
23
|
+
process.stdin.setRawMode?.(false);
|
|
24
|
+
process.stdout.write("\n");
|
|
25
|
+
rl.close();
|
|
26
|
+
resolve(input);
|
|
27
|
+
}
|
|
28
|
+
else if (c === "\u0003") {
|
|
29
|
+
process.exit();
|
|
30
|
+
}
|
|
31
|
+
else if (c === "\u007F") {
|
|
32
|
+
if (input.length > 0) {
|
|
33
|
+
input = input.slice(0, -1);
|
|
34
|
+
process.stdout.write("\b \b");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
input += c;
|
|
39
|
+
process.stdout.write("*");
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
rl.question(question, (answer) => {
|
|
45
|
+
rl.close();
|
|
46
|
+
resolve(answer);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export async function loginCommand(options) {
|
|
52
|
+
console.log(chalk.magenta("\n 🟣 Gufi Developer CLI\n"));
|
|
53
|
+
// Set custom API URL if provided
|
|
54
|
+
if (options.api) {
|
|
55
|
+
setApiUrl(options.api);
|
|
56
|
+
console.log(chalk.gray(` API: ${options.api}\n`));
|
|
57
|
+
}
|
|
58
|
+
// Check if already logged in
|
|
59
|
+
if (isLoggedIn()) {
|
|
60
|
+
const config = loadConfig();
|
|
61
|
+
const spinner = ora("Verificando sesión...").start();
|
|
62
|
+
const valid = await validateToken();
|
|
63
|
+
if (valid) {
|
|
64
|
+
spinner.succeed(chalk.green(`Ya estás logueado como ${config.email}`));
|
|
65
|
+
const relogin = await prompt("\n¿Quieres iniciar sesión con otra cuenta? (s/N): ");
|
|
66
|
+
if (relogin.toLowerCase() !== "s") {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
clearToken();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
spinner.warn("Sesión expirada");
|
|
73
|
+
clearToken();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Get credentials
|
|
77
|
+
const email = await prompt(" Email: ");
|
|
78
|
+
const password = await prompt(" Password: ", true);
|
|
79
|
+
if (!email || !password) {
|
|
80
|
+
console.log(chalk.red("\n ✗ Email y password son requeridos\n"));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const spinner = ora("Iniciando sesión...").start();
|
|
84
|
+
try {
|
|
85
|
+
const { token } = await login(email, password);
|
|
86
|
+
setToken(token, email);
|
|
87
|
+
spinner.succeed(chalk.green(`Sesión iniciada como ${email}`));
|
|
88
|
+
console.log(chalk.gray("\n Ahora puedes usar: gufi pull <vista>\n"));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
spinner.fail(chalk.red(error.message || "Error al iniciar sesión"));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export async function logoutCommand() {
|
|
96
|
+
clearToken();
|
|
97
|
+
console.log(chalk.green("\n ✓ Sesión cerrada\n"));
|
|
98
|
+
}
|
|
99
|
+
export async function whoamiCommand() {
|
|
100
|
+
const config = loadConfig();
|
|
101
|
+
if (!isLoggedIn()) {
|
|
102
|
+
console.log(chalk.yellow("\n No estás logueado. Usa: gufi login\n"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const spinner = ora("Verificando...").start();
|
|
106
|
+
const valid = await validateToken();
|
|
107
|
+
if (valid) {
|
|
108
|
+
spinner.succeed(chalk.green(`Logueado como ${config.email}`));
|
|
109
|
+
console.log(chalk.gray(` API: ${config.apiUrl}\n`));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
spinner.fail(chalk.red("Sesión expirada"));
|
|
113
|
+
console.log(chalk.gray(" Usa: gufi login\n"));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi pull - Download view files from Gufi
|
|
3
|
+
*/
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import { listPackages, listViews, getView } from "../lib/api.js";
|
|
7
|
+
import { pullView } from "../lib/sync.js";
|
|
8
|
+
import { isLoggedIn } from "../lib/config.js";
|
|
9
|
+
export async function pullCommand(viewIdentifier) {
|
|
10
|
+
if (!isLoggedIn()) {
|
|
11
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
console.log(chalk.magenta("\n 🟣 Gufi Pull\n"));
|
|
15
|
+
let viewId;
|
|
16
|
+
let viewName;
|
|
17
|
+
let packageId;
|
|
18
|
+
// If view ID provided directly
|
|
19
|
+
if (viewIdentifier && /^\d+$/.test(viewIdentifier)) {
|
|
20
|
+
viewId = parseInt(viewIdentifier);
|
|
21
|
+
const spinner = ora("Obteniendo vista...").start();
|
|
22
|
+
try {
|
|
23
|
+
const view = await getView(viewId);
|
|
24
|
+
viewName = view.name;
|
|
25
|
+
packageId = view.package_id;
|
|
26
|
+
spinner.succeed(`Vista: ${viewName}`);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
spinner.fail(chalk.red(`No se encontró la vista ${viewId}`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Interactive selection
|
|
35
|
+
const spinner = ora("Cargando packages...").start();
|
|
36
|
+
try {
|
|
37
|
+
const packages = await listPackages();
|
|
38
|
+
spinner.stop();
|
|
39
|
+
if (packages.length === 0) {
|
|
40
|
+
console.log(chalk.yellow(" No tienes packages. Crea uno en Developer Center.\n"));
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
console.log(chalk.gray(" Tus packages:\n"));
|
|
44
|
+
packages.forEach((pkg, i) => {
|
|
45
|
+
console.log(` ${chalk.cyan(i + 1)}. ${pkg.name}`);
|
|
46
|
+
});
|
|
47
|
+
// For now, use first package (can improve with interactive selection later)
|
|
48
|
+
const pkg = packages[0];
|
|
49
|
+
packageId = pkg.pk_id;
|
|
50
|
+
console.log(chalk.gray(`\n Usando package: ${pkg.name}\n`));
|
|
51
|
+
const viewsSpinner = ora("Cargando vistas...").start();
|
|
52
|
+
const views = await listViews(packageId);
|
|
53
|
+
viewsSpinner.stop();
|
|
54
|
+
if (views.length === 0) {
|
|
55
|
+
console.log(chalk.yellow(" No hay vistas en este package.\n"));
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
console.log(chalk.gray(" Vistas disponibles:\n"));
|
|
59
|
+
views.forEach((view, i) => {
|
|
60
|
+
console.log(` ${chalk.cyan(i + 1)}. ${view.name} ${chalk.gray(`(${view.view_type})`)}`);
|
|
61
|
+
});
|
|
62
|
+
// If viewIdentifier matches a name
|
|
63
|
+
let selectedView = viewIdentifier
|
|
64
|
+
? views.find(v => v.name.toLowerCase().includes(viewIdentifier.toLowerCase()))
|
|
65
|
+
: views[0];
|
|
66
|
+
if (!selectedView) {
|
|
67
|
+
console.log(chalk.yellow(`\n No se encontró vista "${viewIdentifier}"\n`));
|
|
68
|
+
console.log(chalk.gray(" Uso: gufi pull <nombre-vista> o gufi pull <view-id>\n"));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
viewId = selectedView.pk_id;
|
|
72
|
+
viewName = selectedView.name;
|
|
73
|
+
console.log(chalk.gray(`\n Descargando: ${viewName}\n`));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
spinner.fail(chalk.red(error.message));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Pull the view
|
|
81
|
+
const pullSpinner = ora("Descargando archivos...").start();
|
|
82
|
+
try {
|
|
83
|
+
const result = await pullView(viewId, viewName, packageId);
|
|
84
|
+
pullSpinner.succeed(chalk.green(`${result.fileCount} archivos descargados`));
|
|
85
|
+
console.log(chalk.gray(`\n 📁 ${result.dir}\n`));
|
|
86
|
+
console.log(chalk.gray(" Comandos útiles:"));
|
|
87
|
+
console.log(chalk.cyan(" cd " + result.dir));
|
|
88
|
+
console.log(chalk.cyan(" gufi watch") + chalk.gray(" # Auto-sync cambios"));
|
|
89
|
+
console.log(chalk.cyan(" claude") + chalk.gray(" # Abrir Claude Code"));
|
|
90
|
+
console.log();
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
pullSpinner.fail(chalk.red(error.message));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi push - Upload local changes to Gufi
|
|
3
|
+
*/
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import { pushView, getChangedFiles, loadViewMeta } from "../lib/sync.js";
|
|
7
|
+
import { isLoggedIn, getCurrentView } from "../lib/config.js";
|
|
8
|
+
export async function pushCommand(viewDir) {
|
|
9
|
+
if (!isLoggedIn()) {
|
|
10
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
console.log(chalk.magenta("\n 🟣 Gufi Push\n"));
|
|
14
|
+
// Determine view directory
|
|
15
|
+
const dir = viewDir || getCurrentView()?.localPath || process.cwd();
|
|
16
|
+
// Check if it's a valid Gufi view directory
|
|
17
|
+
const meta = loadViewMeta(dir);
|
|
18
|
+
if (!meta) {
|
|
19
|
+
console.log(chalk.red(" ✗ No es un directorio de vista Gufi válido."));
|
|
20
|
+
console.log(chalk.gray(" Usa: gufi pull <vista> primero\n"));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
console.log(chalk.gray(` Vista: ${meta.viewName}`));
|
|
24
|
+
console.log(chalk.gray(` Directorio: ${dir}\n`));
|
|
25
|
+
// Check for changes
|
|
26
|
+
const changedFiles = getChangedFiles(dir);
|
|
27
|
+
if (changedFiles.length === 0) {
|
|
28
|
+
console.log(chalk.green(" ✓ No hay cambios para subir\n"));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log(chalk.gray(" Archivos modificados:"));
|
|
32
|
+
changedFiles.forEach((file) => {
|
|
33
|
+
console.log(chalk.yellow(` • ${file}`));
|
|
34
|
+
});
|
|
35
|
+
console.log();
|
|
36
|
+
// Push changes
|
|
37
|
+
const spinner = ora("Subiendo cambios...").start();
|
|
38
|
+
try {
|
|
39
|
+
const result = await pushView(dir);
|
|
40
|
+
spinner.succeed(chalk.green(`${result.pushed} archivo(s) subido(s)`));
|
|
41
|
+
console.log(chalk.gray("\n El preview en Gufi se actualizará automáticamente.\n"));
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
spinner.fail(chalk.red(error.message));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function statusCommand(viewDir) {
|
|
49
|
+
const dir = viewDir || getCurrentView()?.localPath || process.cwd();
|
|
50
|
+
const meta = loadViewMeta(dir);
|
|
51
|
+
if (!meta) {
|
|
52
|
+
console.log(chalk.red("\n ✗ No es un directorio de vista Gufi válido.\n"));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
console.log(chalk.magenta("\n 🟣 Gufi Status\n"));
|
|
56
|
+
console.log(chalk.gray(` Vista: ${meta.viewName}`));
|
|
57
|
+
console.log(chalk.gray(` View ID: ${meta.viewId}`));
|
|
58
|
+
console.log(chalk.gray(` Último sync: ${meta.lastSync}\n`));
|
|
59
|
+
const changedFiles = getChangedFiles(dir);
|
|
60
|
+
if (changedFiles.length === 0) {
|
|
61
|
+
console.log(chalk.green(" ✓ Todo sincronizado\n"));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(chalk.yellow(" Archivos modificados:"));
|
|
65
|
+
changedFiles.forEach((file) => {
|
|
66
|
+
console.log(chalk.yellow(` • ${file}`));
|
|
67
|
+
});
|
|
68
|
+
console.log(chalk.gray("\n Usa: gufi push para subir cambios\n"));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi watch - Auto-sync file changes to Gufi
|
|
3
|
+
*/
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import chokidar from "chokidar";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { pushSingleFile, loadViewMeta } from "../lib/sync.js";
|
|
8
|
+
import { isLoggedIn, getCurrentView } from "../lib/config.js";
|
|
9
|
+
export async function watchCommand(viewDir) {
|
|
10
|
+
if (!isLoggedIn()) {
|
|
11
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
// Determine view directory
|
|
15
|
+
const dir = viewDir || getCurrentView()?.localPath || process.cwd();
|
|
16
|
+
const meta = loadViewMeta(dir);
|
|
17
|
+
if (!meta) {
|
|
18
|
+
console.log(chalk.red("\n ✗ No es un directorio de vista Gufi válido."));
|
|
19
|
+
console.log(chalk.gray(" Usa: gufi pull <vista> primero\n"));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
console.log(chalk.magenta("\n 🟣 Gufi Watch\n"));
|
|
23
|
+
console.log(chalk.gray(` Vista: ${meta.viewName}`));
|
|
24
|
+
console.log(chalk.gray(` Directorio: ${dir}\n`));
|
|
25
|
+
console.log(chalk.green(" ✓ Observando cambios...\n"));
|
|
26
|
+
console.log(chalk.gray(" Ctrl+C para salir\n"));
|
|
27
|
+
// Setup file watcher
|
|
28
|
+
const watcher = chokidar.watch(dir, {
|
|
29
|
+
ignored: [
|
|
30
|
+
/(^|[\/\\])\../, // Hidden files
|
|
31
|
+
"**/node_modules/**",
|
|
32
|
+
"**/.gufi-view.json",
|
|
33
|
+
],
|
|
34
|
+
persistent: true,
|
|
35
|
+
ignoreInitial: true,
|
|
36
|
+
});
|
|
37
|
+
// Debounce map to prevent rapid duplicate syncs
|
|
38
|
+
const debounceMap = new Map();
|
|
39
|
+
const syncFile = async (filePath) => {
|
|
40
|
+
const relativePath = path.relative(dir, filePath);
|
|
41
|
+
// Clear existing debounce
|
|
42
|
+
const existing = debounceMap.get(relativePath);
|
|
43
|
+
if (existing) {
|
|
44
|
+
clearTimeout(existing);
|
|
45
|
+
}
|
|
46
|
+
// Set new debounce
|
|
47
|
+
debounceMap.set(relativePath, setTimeout(async () => {
|
|
48
|
+
debounceMap.delete(relativePath);
|
|
49
|
+
try {
|
|
50
|
+
process.stdout.write(chalk.yellow(` ↑ ${relativePath}...`));
|
|
51
|
+
await pushSingleFile(dir, relativePath);
|
|
52
|
+
process.stdout.write(chalk.green(" ✓\n"));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
process.stdout.write(chalk.red(` ✗ ${error.message}\n`));
|
|
56
|
+
}
|
|
57
|
+
}, 300));
|
|
58
|
+
};
|
|
59
|
+
watcher
|
|
60
|
+
.on("change", syncFile)
|
|
61
|
+
.on("add", syncFile)
|
|
62
|
+
.on("error", (error) => {
|
|
63
|
+
console.log(chalk.red(` Error: ${error.message}`));
|
|
64
|
+
});
|
|
65
|
+
// Handle graceful shutdown
|
|
66
|
+
process.on("SIGINT", () => {
|
|
67
|
+
console.log(chalk.gray("\n\n Deteniendo watch...\n"));
|
|
68
|
+
watcher.close();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
});
|
|
71
|
+
// Keep process alive
|
|
72
|
+
await new Promise(() => { });
|
|
73
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gufi Dev CLI - Main Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
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
|
+
*/
|
|
14
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gufi Dev CLI - Main Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
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
|
+
*/
|
|
14
|
+
import { Command } from "commander";
|
|
15
|
+
import { loginCommand, logoutCommand, whoamiCommand } from "./commands/login.js";
|
|
16
|
+
import { pullCommand } from "./commands/pull.js";
|
|
17
|
+
import { pushCommand, statusCommand } from "./commands/push.js";
|
|
18
|
+
import { watchCommand } from "./commands/watch.js";
|
|
19
|
+
const program = new Command();
|
|
20
|
+
program
|
|
21
|
+
.name("gufi")
|
|
22
|
+
.description("Gufi Developer CLI - Develop Gufi views locally")
|
|
23
|
+
.version("0.1.0");
|
|
24
|
+
// Auth commands
|
|
25
|
+
program
|
|
26
|
+
.command("login")
|
|
27
|
+
.description("Login to Gufi")
|
|
28
|
+
.option("--api <url>", "Custom API URL")
|
|
29
|
+
.action(loginCommand);
|
|
30
|
+
program
|
|
31
|
+
.command("logout")
|
|
32
|
+
.description("Logout from Gufi")
|
|
33
|
+
.action(logoutCommand);
|
|
34
|
+
program
|
|
35
|
+
.command("whoami")
|
|
36
|
+
.description("Show current logged in user")
|
|
37
|
+
.action(whoamiCommand);
|
|
38
|
+
// Sync commands
|
|
39
|
+
program
|
|
40
|
+
.command("pull [view]")
|
|
41
|
+
.description("Download view files from Gufi")
|
|
42
|
+
.action(pullCommand);
|
|
43
|
+
program
|
|
44
|
+
.command("push")
|
|
45
|
+
.description("Upload local changes to Gufi")
|
|
46
|
+
.action(pushCommand);
|
|
47
|
+
program
|
|
48
|
+
.command("watch")
|
|
49
|
+
.description("Watch for file changes and auto-sync")
|
|
50
|
+
.action(watchCommand);
|
|
51
|
+
program
|
|
52
|
+
.command("status")
|
|
53
|
+
.description("Show sync status")
|
|
54
|
+
.action(statusCommand);
|
|
55
|
+
program.parse();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gufi Dev CLI - API Client
|
|
3
|
+
* Communicates with Gufi Marketplace API
|
|
4
|
+
*/
|
|
5
|
+
export interface ViewFile {
|
|
6
|
+
id?: number;
|
|
7
|
+
file_path: string;
|
|
8
|
+
content: string;
|
|
9
|
+
language: string;
|
|
10
|
+
is_entry_point?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface View {
|
|
13
|
+
pk_id: number;
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
view_type: string;
|
|
17
|
+
package_id: number;
|
|
18
|
+
config?: Record<string, any>;
|
|
19
|
+
}
|
|
20
|
+
export interface Package {
|
|
21
|
+
pk_id: number;
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
company_id: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function login(email: string, password: string): Promise<{
|
|
27
|
+
token: string;
|
|
28
|
+
}>;
|
|
29
|
+
export declare function listPackages(): Promise<Package[]>;
|
|
30
|
+
export declare function listViews(packageId: number): Promise<View[]>;
|
|
31
|
+
export declare function getView(viewId: number): Promise<View>;
|
|
32
|
+
export declare function getViewFiles(viewId: number): Promise<ViewFile[]>;
|
|
33
|
+
export declare function saveViewFiles(viewId: number, files: ViewFile[]): Promise<void>;
|
|
34
|
+
export declare function saveViewFile(viewId: number, file: ViewFile): Promise<void>;
|
|
35
|
+
export declare function validateToken(): Promise<boolean>;
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gufi Dev CLI - API Client
|
|
3
|
+
* Communicates with Gufi Marketplace API
|
|
4
|
+
*/
|
|
5
|
+
import { getToken, getApiUrl } from "./config.js";
|
|
6
|
+
class ApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
constructor(status, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.name = "ApiError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function request(endpoint, options = {}) {
|
|
15
|
+
const token = getToken();
|
|
16
|
+
if (!token) {
|
|
17
|
+
throw new Error("No estás logueado. Ejecuta: gufi login");
|
|
18
|
+
}
|
|
19
|
+
const url = `${getApiUrl()}${endpoint}`;
|
|
20
|
+
const response = await fetch(url, {
|
|
21
|
+
...options,
|
|
22
|
+
headers: {
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
Authorization: `Bearer ${token}`,
|
|
25
|
+
...options.headers,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const text = await response.text();
|
|
30
|
+
throw new ApiError(response.status, `API Error ${response.status}: ${text}`);
|
|
31
|
+
}
|
|
32
|
+
return response.json();
|
|
33
|
+
}
|
|
34
|
+
// ============ Auth ============
|
|
35
|
+
export async function login(email, password) {
|
|
36
|
+
const url = `${getApiUrl()}/api/auth/login`;
|
|
37
|
+
const response = await fetch(url, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/json" },
|
|
40
|
+
body: JSON.stringify({ email, password }),
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new ApiError(response.status, "Credenciales inválidas");
|
|
44
|
+
}
|
|
45
|
+
const data = await response.json();
|
|
46
|
+
return { token: data.accessToken };
|
|
47
|
+
}
|
|
48
|
+
// ============ Packages ============
|
|
49
|
+
export async function listPackages() {
|
|
50
|
+
return request("/api/marketplace/developer/packages");
|
|
51
|
+
}
|
|
52
|
+
// ============ Views ============
|
|
53
|
+
export async function listViews(packageId) {
|
|
54
|
+
return request(`/api/marketplace/packages/${packageId}/views`);
|
|
55
|
+
}
|
|
56
|
+
export async function getView(viewId) {
|
|
57
|
+
return request(`/api/marketplace/views/${viewId}`);
|
|
58
|
+
}
|
|
59
|
+
export async function getViewFiles(viewId) {
|
|
60
|
+
return request(`/api/marketplace/views/${viewId}/files`);
|
|
61
|
+
}
|
|
62
|
+
export async function saveViewFiles(viewId, files) {
|
|
63
|
+
await request(`/api/marketplace/views/${viewId}/files`, {
|
|
64
|
+
method: "PUT",
|
|
65
|
+
body: JSON.stringify({ files }),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export async function saveViewFile(viewId, file) {
|
|
69
|
+
await request(`/api/marketplace/views/${viewId}/files`, {
|
|
70
|
+
method: "PUT",
|
|
71
|
+
body: JSON.stringify({ files: [file] }),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// ============ Validation ============
|
|
75
|
+
export async function validateToken() {
|
|
76
|
+
try {
|
|
77
|
+
await request("/api/auth/me");
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gufi Dev CLI - Config Management
|
|
3
|
+
* Stores credentials and settings in ~/.gufi/config.json
|
|
4
|
+
*/
|
|
5
|
+
export interface GufiConfig {
|
|
6
|
+
apiUrl: string;
|
|
7
|
+
token?: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
currentView?: {
|
|
10
|
+
id: number;
|
|
11
|
+
name: string;
|
|
12
|
+
packageId: number;
|
|
13
|
+
localPath: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare function ensureConfigDir(): void;
|
|
17
|
+
export declare function loadConfig(): GufiConfig;
|
|
18
|
+
export declare function saveConfig(config: GufiConfig): void;
|
|
19
|
+
export declare function getToken(): string | undefined;
|
|
20
|
+
export declare function setToken(token: string, email: string): void;
|
|
21
|
+
export declare function clearToken(): void;
|
|
22
|
+
export declare function isLoggedIn(): boolean;
|
|
23
|
+
export declare function setCurrentView(view: GufiConfig["currentView"]): void;
|
|
24
|
+
export declare function getCurrentView(): GufiConfig["currentView"] | undefined;
|
|
25
|
+
export declare function getApiUrl(): string;
|
|
26
|
+
export declare function setApiUrl(url: string): void;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gufi Dev CLI - Config Management
|
|
3
|
+
* Stores credentials and settings in ~/.gufi/config.json
|
|
4
|
+
*/
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
const CONFIG_DIR = path.join(os.homedir(), ".gufi");
|
|
9
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
apiUrl: "https://gogufi.com",
|
|
12
|
+
};
|
|
13
|
+
export function ensureConfigDir() {
|
|
14
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
15
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function loadConfig() {
|
|
19
|
+
ensureConfigDir();
|
|
20
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
21
|
+
return { ...DEFAULT_CONFIG };
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const data = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
25
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(data) };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { ...DEFAULT_CONFIG };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function saveConfig(config) {
|
|
32
|
+
ensureConfigDir();
|
|
33
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
34
|
+
}
|
|
35
|
+
export function getToken() {
|
|
36
|
+
return loadConfig().token;
|
|
37
|
+
}
|
|
38
|
+
export function setToken(token, email) {
|
|
39
|
+
const config = loadConfig();
|
|
40
|
+
config.token = token;
|
|
41
|
+
config.email = email;
|
|
42
|
+
saveConfig(config);
|
|
43
|
+
}
|
|
44
|
+
export function clearToken() {
|
|
45
|
+
const config = loadConfig();
|
|
46
|
+
delete config.token;
|
|
47
|
+
delete config.email;
|
|
48
|
+
saveConfig(config);
|
|
49
|
+
}
|
|
50
|
+
export function isLoggedIn() {
|
|
51
|
+
return !!getToken();
|
|
52
|
+
}
|
|
53
|
+
export function setCurrentView(view) {
|
|
54
|
+
const config = loadConfig();
|
|
55
|
+
config.currentView = view;
|
|
56
|
+
saveConfig(config);
|
|
57
|
+
}
|
|
58
|
+
export function getCurrentView() {
|
|
59
|
+
return loadConfig().currentView;
|
|
60
|
+
}
|
|
61
|
+
export function getApiUrl() {
|
|
62
|
+
return loadConfig().apiUrl;
|
|
63
|
+
}
|
|
64
|
+
export function setApiUrl(url) {
|
|
65
|
+
const config = loadConfig();
|
|
66
|
+
config.apiUrl = url;
|
|
67
|
+
saveConfig(config);
|
|
68
|
+
}
|