gufi-cli 0.1.0 → 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,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,66 +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
-
21
- const program = new Command();
22
-
23
- program
24
- .name("gufi")
25
- .description("Gufi Developer CLI - Develop Gufi views locally")
26
- .version("0.1.0");
27
-
28
- // Auth commands
29
- program
30
- .command("login")
31
- .description("Login to Gufi")
32
- .option("--api <url>", "Custom API URL")
33
- .action(loginCommand);
34
-
35
- program
36
- .command("logout")
37
- .description("Logout from Gufi")
38
- .action(logoutCommand);
39
-
40
- program
41
- .command("whoami")
42
- .description("Show current logged in user")
43
- .action(whoamiCommand);
44
-
45
- // Sync commands
46
- program
47
- .command("pull [view]")
48
- .description("Download view files from Gufi")
49
- .action(pullCommand);
50
-
51
- program
52
- .command("push")
53
- .description("Upload local changes to Gufi")
54
- .action(pushCommand);
55
-
56
- program
57
- .command("watch")
58
- .description("Watch for file changes and auto-sync")
59
- .action(watchCommand);
60
-
61
- program
62
- .command("status")
63
- .description("Show sync status")
64
- .action(statusCommand);
65
-
66
- 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
- }
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "declaration": true,
13
- "resolveJsonModule": true
14
- },
15
- "include": ["src/**/*"],
16
- "exclude": ["node_modules", "dist"]
17
- }