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 ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/index.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * gufi login - Authenticate with Gufi
3
+ */
4
+ export declare function loginCommand(options: {
5
+ api?: string;
6
+ }): Promise<void>;
7
+ export declare function logoutCommand(): Promise<void>;
8
+ export declare function whoamiCommand(): Promise<void>;
@@ -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,4 @@
1
+ /**
2
+ * gufi pull - Download view files from Gufi
3
+ */
4
+ export declare function pullCommand(viewIdentifier?: string): Promise<void>;
@@ -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,5 @@
1
+ /**
2
+ * gufi push - Upload local changes to Gufi
3
+ */
4
+ export declare function pushCommand(viewDir?: string): Promise<void>;
5
+ export declare function statusCommand(viewDir?: string): Promise<void>;
@@ -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,4 @@
1
+ /**
2
+ * gufi watch - Auto-sync file changes to Gufi
3
+ */
4
+ export declare function watchCommand(viewDir?: string): Promise<void>;
@@ -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
+ }
@@ -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>;
@@ -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
+ }