gufi-cli 0.1.1 → 0.1.2

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.
@@ -1,85 +0,0 @@
1
- /**
2
- * gufi push - Upload local changes to Gufi
3
- */
4
-
5
- import chalk from "chalk";
6
- import ora from "ora";
7
- import path from "path";
8
- import { pushView, getChangedFiles, loadViewMeta } from "../lib/sync.js";
9
- import { isLoggedIn, getCurrentView } from "../lib/config.js";
10
-
11
- export async function pushCommand(viewDir?: string): Promise<void> {
12
- if (!isLoggedIn()) {
13
- console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
14
- process.exit(1);
15
- }
16
-
17
- console.log(chalk.magenta("\n 🟣 Gufi Push\n"));
18
-
19
- // Determine view directory
20
- const dir = viewDir || getCurrentView()?.localPath || process.cwd();
21
-
22
- // Check if it's a valid Gufi view directory
23
- const meta = loadViewMeta(dir);
24
- if (!meta) {
25
- console.log(chalk.red(" ✗ No es un directorio de vista Gufi válido."));
26
- console.log(chalk.gray(" Usa: gufi pull <vista> primero\n"));
27
- process.exit(1);
28
- }
29
-
30
- console.log(chalk.gray(` Vista: ${meta.viewName}`));
31
- console.log(chalk.gray(` Directorio: ${dir}\n`));
32
-
33
- // Check for changes
34
- const changedFiles = getChangedFiles(dir);
35
-
36
- if (changedFiles.length === 0) {
37
- console.log(chalk.green(" ✓ No hay cambios para subir\n"));
38
- return;
39
- }
40
-
41
- console.log(chalk.gray(" Archivos modificados:"));
42
- changedFiles.forEach((file) => {
43
- console.log(chalk.yellow(` • ${file}`));
44
- });
45
- console.log();
46
-
47
- // Push changes
48
- const spinner = ora("Subiendo cambios...").start();
49
-
50
- try {
51
- const result = await pushView(dir);
52
- spinner.succeed(chalk.green(`${result.pushed} archivo(s) subido(s)`));
53
- console.log(chalk.gray("\n El preview en Gufi se actualizará automáticamente.\n"));
54
- } catch (error: any) {
55
- spinner.fail(chalk.red(error.message));
56
- process.exit(1);
57
- }
58
- }
59
-
60
- export async function statusCommand(viewDir?: string): Promise<void> {
61
- const dir = viewDir || getCurrentView()?.localPath || process.cwd();
62
-
63
- const meta = loadViewMeta(dir);
64
- if (!meta) {
65
- console.log(chalk.red("\n ✗ No es un directorio de vista Gufi válido.\n"));
66
- process.exit(1);
67
- }
68
-
69
- console.log(chalk.magenta("\n 🟣 Gufi Status\n"));
70
- console.log(chalk.gray(` Vista: ${meta.viewName}`));
71
- console.log(chalk.gray(` View ID: ${meta.viewId}`));
72
- console.log(chalk.gray(` Último sync: ${meta.lastSync}\n`));
73
-
74
- const changedFiles = getChangedFiles(dir);
75
-
76
- if (changedFiles.length === 0) {
77
- console.log(chalk.green(" ✓ Todo sincronizado\n"));
78
- } else {
79
- console.log(chalk.yellow(" Archivos modificados:"));
80
- changedFiles.forEach((file) => {
81
- console.log(chalk.yellow(` • ${file}`));
82
- });
83
- console.log(chalk.gray("\n Usa: gufi push para subir cambios\n"));
84
- }
85
- }
@@ -1,89 +0,0 @@
1
- /**
2
- * gufi watch - Auto-sync file changes to Gufi
3
- */
4
-
5
- import chalk from "chalk";
6
- import chokidar from "chokidar";
7
- import path from "path";
8
- import { pushSingleFile, loadViewMeta, getViewDir } from "../lib/sync.js";
9
- import { isLoggedIn, getCurrentView } from "../lib/config.js";
10
-
11
- export async function watchCommand(viewDir?: string): Promise<void> {
12
- if (!isLoggedIn()) {
13
- console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
14
- process.exit(1);
15
- }
16
-
17
- // Determine view directory
18
- const dir = viewDir || getCurrentView()?.localPath || process.cwd();
19
-
20
- const meta = loadViewMeta(dir);
21
- if (!meta) {
22
- console.log(chalk.red("\n ✗ No es un directorio de vista Gufi válido."));
23
- console.log(chalk.gray(" Usa: gufi pull <vista> primero\n"));
24
- process.exit(1);
25
- }
26
-
27
- console.log(chalk.magenta("\n 🟣 Gufi Watch\n"));
28
- console.log(chalk.gray(` Vista: ${meta.viewName}`));
29
- console.log(chalk.gray(` Directorio: ${dir}\n`));
30
- console.log(chalk.green(" ✓ Observando cambios...\n"));
31
- console.log(chalk.gray(" Ctrl+C para salir\n"));
32
-
33
- // Setup file watcher
34
- const watcher = chokidar.watch(dir, {
35
- ignored: [
36
- /(^|[\/\\])\../, // Hidden files
37
- "**/node_modules/**",
38
- "**/.gufi-view.json",
39
- ],
40
- persistent: true,
41
- ignoreInitial: true,
42
- });
43
-
44
- // Debounce map to prevent rapid duplicate syncs
45
- const debounceMap = new Map<string, NodeJS.Timeout>();
46
-
47
- const syncFile = async (filePath: string) => {
48
- const relativePath = path.relative(dir, filePath);
49
-
50
- // Clear existing debounce
51
- const existing = debounceMap.get(relativePath);
52
- if (existing) {
53
- clearTimeout(existing);
54
- }
55
-
56
- // Set new debounce
57
- debounceMap.set(
58
- relativePath,
59
- setTimeout(async () => {
60
- debounceMap.delete(relativePath);
61
-
62
- try {
63
- process.stdout.write(chalk.yellow(` ↑ ${relativePath}...`));
64
- await pushSingleFile(dir, relativePath);
65
- process.stdout.write(chalk.green(" ✓\n"));
66
- } catch (error: any) {
67
- process.stdout.write(chalk.red(` ✗ ${error.message}\n`));
68
- }
69
- }, 300)
70
- );
71
- };
72
-
73
- watcher
74
- .on("change", syncFile)
75
- .on("add", syncFile)
76
- .on("error", (error) => {
77
- console.log(chalk.red(` Error: ${error.message}`));
78
- });
79
-
80
- // Handle graceful shutdown
81
- process.on("SIGINT", () => {
82
- console.log(chalk.gray("\n\n Deteniendo watch...\n"));
83
- watcher.close();
84
- process.exit(0);
85
- });
86
-
87
- // Keep process alive
88
- await new Promise(() => {});
89
- }
package/src/index.ts DELETED
@@ -1,73 +0,0 @@
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
-
15
- import { Command } from "commander";
16
- import { loginCommand, logoutCommand, whoamiCommand } from "./commands/login.js";
17
- import { pullCommand } from "./commands/pull.js";
18
- import { pushCommand, statusCommand } from "./commands/push.js";
19
- import { watchCommand } from "./commands/watch.js";
20
- import { listCommand } from "./commands/list.js";
21
-
22
- const program = new Command();
23
-
24
- program
25
- .name("gufi")
26
- .description("Gufi Developer CLI - Develop Gufi views locally")
27
- .version("0.1.0");
28
-
29
- // Auth commands
30
- program
31
- .command("login")
32
- .description("Login to Gufi")
33
- .option("--api <url>", "Custom API URL")
34
- .action(loginCommand);
35
-
36
- program
37
- .command("logout")
38
- .description("Logout from Gufi")
39
- .action(logoutCommand);
40
-
41
- program
42
- .command("whoami")
43
- .description("Show current logged in user")
44
- .action(whoamiCommand);
45
-
46
- // Sync commands
47
- program
48
- .command("pull [view]")
49
- .description("Download view files from Gufi")
50
- .action(pullCommand);
51
-
52
- program
53
- .command("push")
54
- .description("Upload local changes to Gufi")
55
- .action(pushCommand);
56
-
57
- program
58
- .command("watch")
59
- .description("Watch for file changes and auto-sync")
60
- .action(watchCommand);
61
-
62
- program
63
- .command("status")
64
- .description("Show sync status")
65
- .action(statusCommand);
66
-
67
- program
68
- .command("list")
69
- .alias("ls")
70
- .description("List your packages and views")
71
- .action(listCommand);
72
-
73
- program.parse();
package/src/lib/api.ts DELETED
@@ -1,127 +0,0 @@
1
- /**
2
- * Gufi Dev CLI - API Client
3
- * Communicates with Gufi Marketplace API
4
- */
5
-
6
- import { getToken, getApiUrl } from "./config.js";
7
-
8
- export interface ViewFile {
9
- id?: number;
10
- file_path: string;
11
- content: string;
12
- language: string;
13
- is_entry_point?: boolean;
14
- }
15
-
16
- export interface View {
17
- pk_id: number;
18
- name: string;
19
- description?: string;
20
- view_type: string;
21
- package_id: number;
22
- config?: Record<string, any>;
23
- }
24
-
25
- export interface Package {
26
- pk_id: number;
27
- name: string;
28
- description?: string;
29
- company_id: number;
30
- }
31
-
32
- class ApiError extends Error {
33
- constructor(public status: number, message: string) {
34
- super(message);
35
- this.name = "ApiError";
36
- }
37
- }
38
-
39
- async function request<T>(
40
- endpoint: string,
41
- options: RequestInit = {}
42
- ): Promise<T> {
43
- const token = getToken();
44
- if (!token) {
45
- throw new Error("No estás logueado. Ejecuta: gufi login");
46
- }
47
-
48
- const url = `${getApiUrl()}${endpoint}`;
49
- const response = await fetch(url, {
50
- ...options,
51
- headers: {
52
- "Content-Type": "application/json",
53
- Authorization: `Bearer ${token}`,
54
- ...options.headers,
55
- },
56
- });
57
-
58
- if (!response.ok) {
59
- const text = await response.text();
60
- throw new ApiError(response.status, `API Error ${response.status}: ${text}`);
61
- }
62
-
63
- return response.json();
64
- }
65
-
66
- // ============ Auth ============
67
-
68
- export async function login(email: string, password: string): Promise<{ token: string }> {
69
- const url = `${getApiUrl()}/api/auth/login`;
70
- const response = await fetch(url, {
71
- method: "POST",
72
- headers: { "Content-Type": "application/json" },
73
- body: JSON.stringify({ email, password }),
74
- });
75
-
76
- if (!response.ok) {
77
- throw new ApiError(response.status, "Credenciales inválidas");
78
- }
79
-
80
- const data = await response.json();
81
- return { token: data.accessToken };
82
- }
83
-
84
- // ============ Packages ============
85
-
86
- export async function listPackages(): Promise<Package[]> {
87
- return request<Package[]>("/api/marketplace/developer/packages");
88
- }
89
-
90
- // ============ Views ============
91
-
92
- export async function listViews(packageId: number): Promise<View[]> {
93
- return request<View[]>(`/api/marketplace/packages/${packageId}/views`);
94
- }
95
-
96
- export async function getView(viewId: number): Promise<View> {
97
- return request<View>(`/api/marketplace/views/${viewId}`);
98
- }
99
-
100
- export async function getViewFiles(viewId: number): Promise<ViewFile[]> {
101
- return request<ViewFile[]>(`/api/marketplace/views/${viewId}/files`);
102
- }
103
-
104
- export async function saveViewFiles(viewId: number, files: ViewFile[]): Promise<void> {
105
- await request(`/api/marketplace/views/${viewId}/files`, {
106
- method: "PUT",
107
- body: JSON.stringify({ files }),
108
- });
109
- }
110
-
111
- export async function saveViewFile(viewId: number, file: ViewFile): Promise<void> {
112
- await request(`/api/marketplace/views/${viewId}/files`, {
113
- method: "PUT",
114
- body: JSON.stringify({ files: [file] }),
115
- });
116
- }
117
-
118
- // ============ Validation ============
119
-
120
- export async function validateToken(): Promise<boolean> {
121
- try {
122
- await request("/api/auth/me");
123
- return true;
124
- } catch {
125
- return false;
126
- }
127
- }
package/src/lib/config.ts DELETED
@@ -1,93 +0,0 @@
1
- /**
2
- * Gufi Dev CLI - Config Management
3
- * Stores credentials and settings in ~/.gufi/config.json
4
- */
5
-
6
- import fs from "fs";
7
- import path from "path";
8
- import os from "os";
9
-
10
- export interface GufiConfig {
11
- apiUrl: string;
12
- token?: string;
13
- email?: string;
14
- currentView?: {
15
- id: number;
16
- name: string;
17
- packageId: number;
18
- localPath: string;
19
- };
20
- }
21
-
22
- const CONFIG_DIR = path.join(os.homedir(), ".gufi");
23
- const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
24
-
25
- const DEFAULT_CONFIG: GufiConfig = {
26
- apiUrl: "https://gogufi.com",
27
- };
28
-
29
- export function ensureConfigDir(): void {
30
- if (!fs.existsSync(CONFIG_DIR)) {
31
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
32
- }
33
- }
34
-
35
- export function loadConfig(): GufiConfig {
36
- ensureConfigDir();
37
- if (!fs.existsSync(CONFIG_FILE)) {
38
- return { ...DEFAULT_CONFIG };
39
- }
40
- try {
41
- const data = fs.readFileSync(CONFIG_FILE, "utf-8");
42
- return { ...DEFAULT_CONFIG, ...JSON.parse(data) };
43
- } catch {
44
- return { ...DEFAULT_CONFIG };
45
- }
46
- }
47
-
48
- export function saveConfig(config: GufiConfig): void {
49
- ensureConfigDir();
50
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
51
- }
52
-
53
- export function getToken(): string | undefined {
54
- return loadConfig().token;
55
- }
56
-
57
- export function setToken(token: string, email: string): void {
58
- const config = loadConfig();
59
- config.token = token;
60
- config.email = email;
61
- saveConfig(config);
62
- }
63
-
64
- export function clearToken(): void {
65
- const config = loadConfig();
66
- delete config.token;
67
- delete config.email;
68
- saveConfig(config);
69
- }
70
-
71
- export function isLoggedIn(): boolean {
72
- return !!getToken();
73
- }
74
-
75
- export function setCurrentView(view: GufiConfig["currentView"]): void {
76
- const config = loadConfig();
77
- config.currentView = view;
78
- saveConfig(config);
79
- }
80
-
81
- export function getCurrentView(): GufiConfig["currentView"] | undefined {
82
- return loadConfig().currentView;
83
- }
84
-
85
- export function getApiUrl(): string {
86
- return loadConfig().apiUrl;
87
- }
88
-
89
- export function setApiUrl(url: string): void {
90
- const config = loadConfig();
91
- config.apiUrl = url;
92
- saveConfig(config);
93
- }
package/src/lib/sync.ts DELETED
@@ -1,236 +0,0 @@
1
- /**
2
- * Gufi Dev CLI - Sync Library
3
- * Handles bidirectional file synchronization
4
- */
5
-
6
- import fs from "fs";
7
- import path from "path";
8
- import os from "os";
9
- import { getViewFiles, saveViewFile, type ViewFile } from "./api.js";
10
- import { setCurrentView, getCurrentView } from "./config.js";
11
-
12
- const GUFI_DEV_DIR = path.join(os.homedir(), "gufi-dev");
13
- const META_FILE = ".gufi-view.json";
14
-
15
- export interface ViewMeta {
16
- viewId: number;
17
- viewName: string;
18
- packageId: number;
19
- lastSync: string;
20
- files: Record<string, { hash: string; mtime: number }>;
21
- }
22
-
23
- function ensureDir(dir: string): void {
24
- if (!fs.existsSync(dir)) {
25
- fs.mkdirSync(dir, { recursive: true });
26
- }
27
- }
28
-
29
- function hashContent(content: string): string {
30
- // Simple hash for change detection
31
- let hash = 0;
32
- for (let i = 0; i < content.length; i++) {
33
- const char = content.charCodeAt(i);
34
- hash = ((hash << 5) - hash) + char;
35
- hash = hash & hash;
36
- }
37
- return hash.toString(16);
38
- }
39
-
40
- function getLanguage(filePath: string): string {
41
- const ext = path.extname(filePath).toLowerCase();
42
- const langMap: Record<string, string> = {
43
- ".ts": "typescript",
44
- ".tsx": "typescript",
45
- ".js": "javascript",
46
- ".jsx": "javascript",
47
- ".css": "css",
48
- ".json": "json",
49
- ".md": "markdown",
50
- };
51
- return langMap[ext] || "text";
52
- }
53
-
54
- export function getViewDir(viewName: string): string {
55
- return path.join(GUFI_DEV_DIR, viewName.toLowerCase().replace(/\s+/g, "-"));
56
- }
57
-
58
- export function loadViewMeta(viewDir: string): ViewMeta | null {
59
- const metaPath = path.join(viewDir, META_FILE);
60
- if (!fs.existsSync(metaPath)) return null;
61
- try {
62
- return JSON.parse(fs.readFileSync(metaPath, "utf-8"));
63
- } catch {
64
- return null;
65
- }
66
- }
67
-
68
- function saveViewMeta(viewDir: string, meta: ViewMeta): void {
69
- const metaPath = path.join(viewDir, META_FILE);
70
- fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
71
- }
72
-
73
- /**
74
- * Pull view files from Gufi to local directory
75
- */
76
- export async function pullView(
77
- viewId: number,
78
- viewName: string,
79
- packageId: number
80
- ): Promise<{ dir: string; fileCount: number }> {
81
- const viewDir = getViewDir(viewName);
82
- ensureDir(viewDir);
83
-
84
- const files = await getViewFiles(viewId);
85
- const fileMeta: ViewMeta["files"] = {};
86
-
87
- for (const file of files) {
88
- const filePath = path.join(viewDir, file.file_path.replace(/^\//, ""));
89
- ensureDir(path.dirname(filePath));
90
- fs.writeFileSync(filePath, file.content);
91
-
92
- fileMeta[file.file_path] = {
93
- hash: hashContent(file.content),
94
- mtime: Date.now(),
95
- };
96
- }
97
-
98
- const meta: ViewMeta = {
99
- viewId,
100
- viewName,
101
- packageId,
102
- lastSync: new Date().toISOString(),
103
- files: fileMeta,
104
- };
105
- saveViewMeta(viewDir, meta);
106
-
107
- // Update current view in config
108
- setCurrentView({ id: viewId, name: viewName, packageId, localPath: viewDir });
109
-
110
- return { dir: viewDir, fileCount: files.length };
111
- }
112
-
113
- /**
114
- * Push local files to Gufi
115
- */
116
- export async function pushView(viewDir?: string): Promise<{ pushed: number }> {
117
- const dir = viewDir || getCurrentView()?.localPath;
118
- if (!dir) {
119
- throw new Error("No hay vista activa. Usa: gufi pull <vista>");
120
- }
121
-
122
- const meta = loadViewMeta(dir);
123
- if (!meta) {
124
- throw new Error("No se encontró metadata de vista. ¿Hiciste pull primero?");
125
- }
126
-
127
- const localFiles = getLocalFiles(dir);
128
- let pushed = 0;
129
-
130
- for (const file of localFiles) {
131
- const content = fs.readFileSync(path.join(dir, file), "utf-8");
132
- const hash = hashContent(content);
133
- const filePath = "/" + file;
134
-
135
- // Check if file changed
136
- const oldMeta = meta.files[filePath];
137
- if (oldMeta && oldMeta.hash === hash) {
138
- continue; // No changes
139
- }
140
-
141
- // Push to Gufi
142
- await saveViewFile(meta.viewId, {
143
- file_path: filePath,
144
- content,
145
- language: getLanguage(file),
146
- is_entry_point: file === "index.tsx",
147
- });
148
-
149
- // Update meta
150
- meta.files[filePath] = { hash, mtime: Date.now() };
151
- pushed++;
152
- }
153
-
154
- meta.lastSync = new Date().toISOString();
155
- saveViewMeta(dir, meta);
156
-
157
- return { pushed };
158
- }
159
-
160
- /**
161
- * Get list of local files (excluding meta and hidden)
162
- */
163
- function getLocalFiles(dir: string, prefix = ""): string[] {
164
- const files: string[] = [];
165
- const entries = fs.readdirSync(dir, { withFileTypes: true });
166
-
167
- for (const entry of entries) {
168
- if (entry.name.startsWith(".")) continue; // Skip hidden files
169
-
170
- const fullPath = path.join(prefix, entry.name);
171
-
172
- if (entry.isDirectory()) {
173
- files.push(...getLocalFiles(path.join(dir, entry.name), fullPath));
174
- } else {
175
- files.push(fullPath);
176
- }
177
- }
178
-
179
- return files;
180
- }
181
-
182
- /**
183
- * Check for local changes that need pushing
184
- */
185
- export function getChangedFiles(viewDir: string): string[] {
186
- const meta = loadViewMeta(viewDir);
187
- if (!meta) return [];
188
-
189
- const changed: string[] = [];
190
- const localFiles = getLocalFiles(viewDir);
191
-
192
- for (const file of localFiles) {
193
- const content = fs.readFileSync(path.join(viewDir, file), "utf-8");
194
- const hash = hashContent(content);
195
- const filePath = "/" + file;
196
-
197
- const oldMeta = meta.files[filePath];
198
- if (!oldMeta || oldMeta.hash !== hash) {
199
- changed.push(file);
200
- }
201
- }
202
-
203
- return changed;
204
- }
205
-
206
- /**
207
- * Push a single file
208
- */
209
- export async function pushSingleFile(
210
- viewDir: string,
211
- relativePath: string
212
- ): Promise<void> {
213
- const meta = loadViewMeta(viewDir);
214
- if (!meta) {
215
- throw new Error("No se encontró metadata de vista");
216
- }
217
-
218
- const fullPath = path.join(viewDir, relativePath);
219
- const content = fs.readFileSync(fullPath, "utf-8");
220
- const filePath = "/" + relativePath.replace(/\\/g, "/");
221
-
222
- await saveViewFile(meta.viewId, {
223
- file_path: filePath,
224
- content,
225
- language: getLanguage(relativePath),
226
- is_entry_point: relativePath === "index.tsx",
227
- });
228
-
229
- // Update meta
230
- meta.files[filePath] = {
231
- hash: hashContent(content),
232
- mtime: Date.now(),
233
- };
234
- meta.lastSync = new Date().toISOString();
235
- saveViewMeta(viewDir, meta);
236
- }