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.
- package/CLAUDE.md +1390 -0
- package/dist/commands/companies.d.ts +26 -0
- package/dist/commands/companies.js +513 -0
- 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/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,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi companies - List and manage companies
|
|
3
|
+
* gufi modules - List modules of a company
|
|
4
|
+
* gufi module - View/edit module JSON
|
|
5
|
+
*/
|
|
6
|
+
export declare function companiesCommand(): Promise<void>;
|
|
7
|
+
export declare function modulesCommand(companyId?: string): Promise<void>;
|
|
8
|
+
export declare function moduleCommand(moduleId?: string, options?: {
|
|
9
|
+
edit?: boolean;
|
|
10
|
+
company?: string;
|
|
11
|
+
file?: string;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
export declare function moduleUpdateCommand(moduleId?: string, jsonFile?: string, options?: {
|
|
14
|
+
company?: string;
|
|
15
|
+
dryRun?: boolean;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
export declare function companyCreateCommand(name?: string): Promise<void>;
|
|
18
|
+
export declare function automationsCommand(_moduleId?: string, // Ignored - kept for backwards compatibility
|
|
19
|
+
options?: {
|
|
20
|
+
company?: string;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
export declare function automationCommand(automationName?: string, options?: {
|
|
23
|
+
edit?: boolean;
|
|
24
|
+
company?: string;
|
|
25
|
+
file?: string;
|
|
26
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi companies - List and manage companies
|
|
3
|
+
* gufi modules - List modules of a company
|
|
4
|
+
* gufi module - View/edit module JSON
|
|
5
|
+
*/
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { isLoggedIn, getApiUrl, getToken } from "../lib/config.js";
|
|
8
|
+
const API_URL_BASE = "https://gogufi.com";
|
|
9
|
+
async function apiRequest(path, options = {}) {
|
|
10
|
+
const token = getToken();
|
|
11
|
+
const apiUrl = getApiUrl() || API_URL_BASE;
|
|
12
|
+
const res = await fetch(`${apiUrl}${path}`, {
|
|
13
|
+
...options,
|
|
14
|
+
headers: {
|
|
15
|
+
"Authorization": `Bearer ${token}`,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
...options.headers,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
const error = await res.text();
|
|
22
|
+
throw new Error(`API Error ${res.status}: ${error}`);
|
|
23
|
+
}
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
// ════════════════════════════════════════════════════════════════════
|
|
27
|
+
// gufi companies - List user's companies
|
|
28
|
+
// ════════════════════════════════════════════════════════════════════
|
|
29
|
+
export async function companiesCommand() {
|
|
30
|
+
if (!isLoggedIn()) {
|
|
31
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
console.log(chalk.magenta("\n 🟣 Gufi Companies\n"));
|
|
35
|
+
try {
|
|
36
|
+
const data = await apiRequest("/api/companies");
|
|
37
|
+
const companies = data.items || data.data || data || [];
|
|
38
|
+
if (!companies.length) {
|
|
39
|
+
console.log(chalk.gray(" No tienes companies asignadas.\n"));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.log(chalk.gray(" ID\tNombre\t\t\t\tCreado"));
|
|
43
|
+
console.log(chalk.gray(" ─".repeat(35)));
|
|
44
|
+
for (const c of companies) {
|
|
45
|
+
const id = String(c.id).padEnd(4);
|
|
46
|
+
const name = (c.name || "").substring(0, 24).padEnd(24);
|
|
47
|
+
const created = c.created_at ? new Date(c.created_at).toLocaleDateString() : "-";
|
|
48
|
+
console.log(` ${chalk.cyan(id)}\t${name}\t${created}`);
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk.gray("\n 💡 Usa: gufi modules <company_id> para ver módulos\n"));
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ════════════════════════════════════════════════════════════════════
|
|
57
|
+
// gufi modules <company_id> - List modules of a company
|
|
58
|
+
// ════════════════════════════════════════════════════════════════════
|
|
59
|
+
export async function modulesCommand(companyId) {
|
|
60
|
+
if (!isLoggedIn()) {
|
|
61
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
if (!companyId) {
|
|
65
|
+
console.log(chalk.red("\n ✗ Uso: gufi modules <company_id>\n"));
|
|
66
|
+
console.log(chalk.gray(" Tip: Usa 'gufi companies' para ver tus companies\n"));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
console.log(chalk.magenta(`\n 🟣 Módulos de Company ${companyId}\n`));
|
|
70
|
+
try {
|
|
71
|
+
// Get company schema which includes all modules
|
|
72
|
+
const data = await apiRequest("/api/company/schema", {
|
|
73
|
+
headers: { "X-Company-ID": companyId },
|
|
74
|
+
});
|
|
75
|
+
const modules = data.modules || data.data?.modules || [];
|
|
76
|
+
if (!modules.length) {
|
|
77
|
+
console.log(chalk.gray(" No hay módulos en esta company.\n"));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log(chalk.gray(" ID\tNombre\t\t\t\tEntidades"));
|
|
81
|
+
console.log(chalk.gray(" ─".repeat(40)));
|
|
82
|
+
for (const mod of modules) {
|
|
83
|
+
// Count entities across all submodules and find moduleId
|
|
84
|
+
let entityCount = 0;
|
|
85
|
+
let moduleId;
|
|
86
|
+
for (const sub of mod.submodules || []) {
|
|
87
|
+
for (const ent of sub.entities || []) {
|
|
88
|
+
entityCount++;
|
|
89
|
+
if (!moduleId && ent.ui?.moduleId) {
|
|
90
|
+
moduleId = ent.ui.moduleId;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const id = String(moduleId || "?").padEnd(5);
|
|
95
|
+
const label = (mod.displayName || mod.label || mod.name || "").substring(0, 28).padEnd(28);
|
|
96
|
+
console.log(` ${chalk.cyan(id)}\t${label}\t${entityCount} entities`);
|
|
97
|
+
}
|
|
98
|
+
console.log(chalk.gray("\n 💡 Usa: gufi module <module_id> -c " + companyId + " para ver/editar\n"));
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// ════════════════════════════════════════════════════════════════════
|
|
105
|
+
// Helper: Get module from company schema
|
|
106
|
+
// ════════════════════════════════════════════════════════════════════
|
|
107
|
+
async function getModuleFromSchema(moduleId, companyId) {
|
|
108
|
+
const headers = {};
|
|
109
|
+
if (companyId) {
|
|
110
|
+
headers["X-Company-ID"] = companyId;
|
|
111
|
+
}
|
|
112
|
+
const data = await apiRequest("/api/company/schema", { headers });
|
|
113
|
+
const modules = data.modules || data.data?.modules || [];
|
|
114
|
+
// Find module by ID (from entity ui.moduleId) or by name
|
|
115
|
+
const module = modules.find((m) => {
|
|
116
|
+
// Check if any entity has the matching moduleId
|
|
117
|
+
for (const sub of m.submodules || []) {
|
|
118
|
+
for (const ent of sub.entities || []) {
|
|
119
|
+
if (String(ent.ui?.moduleId) === String(moduleId)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Also check by name
|
|
125
|
+
return m.name === moduleId;
|
|
126
|
+
});
|
|
127
|
+
if (!module) {
|
|
128
|
+
throw new Error(`Módulo ${moduleId} no encontrado en esta company`);
|
|
129
|
+
}
|
|
130
|
+
// Attach the moduleId to the module object for convenience
|
|
131
|
+
for (const sub of module.submodules || []) {
|
|
132
|
+
for (const ent of sub.entities || []) {
|
|
133
|
+
if (ent.ui?.moduleId) {
|
|
134
|
+
module._moduleId = ent.ui.moduleId;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (module._moduleId)
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
return module;
|
|
142
|
+
}
|
|
143
|
+
// ════════════════════════════════════════════════════════════════════
|
|
144
|
+
// gufi module <module_id> [--edit] [--company <id>] - View/edit module JSON
|
|
145
|
+
// ════════════════════════════════════════════════════════════════════
|
|
146
|
+
export async function moduleCommand(moduleId, options) {
|
|
147
|
+
if (!isLoggedIn()) {
|
|
148
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
if (!moduleId) {
|
|
152
|
+
console.log(chalk.red("\n ✗ Uso: gufi module <module_id> [--edit] [--company <id>]\n"));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
const companyId = options?.company;
|
|
156
|
+
console.log(chalk.magenta(`\n 🟣 Módulo ${moduleId}\n`));
|
|
157
|
+
try {
|
|
158
|
+
// Get module from company schema
|
|
159
|
+
const module = await getModuleFromSchema(moduleId, companyId);
|
|
160
|
+
const actualModuleId = module._moduleId || moduleId;
|
|
161
|
+
// Show module info
|
|
162
|
+
console.log(chalk.white(` ID: ${actualModuleId}`));
|
|
163
|
+
console.log(chalk.white(` Nombre: ${module.name || "N/A"}`));
|
|
164
|
+
console.log(chalk.white(` Label: ${module.displayName || module.label || module.name}`));
|
|
165
|
+
// Count entities
|
|
166
|
+
let entityCount = 0;
|
|
167
|
+
for (const sub of module.submodules || []) {
|
|
168
|
+
entityCount += (sub.entities || []).length;
|
|
169
|
+
}
|
|
170
|
+
console.log(chalk.white(` Entidades: ${entityCount}`));
|
|
171
|
+
console.log();
|
|
172
|
+
// Build a clean JSON definition for editing
|
|
173
|
+
const jsonDef = {
|
|
174
|
+
name: module.name,
|
|
175
|
+
displayName: module.displayName || module.label,
|
|
176
|
+
icon: module.icon,
|
|
177
|
+
submodules: module.submodules,
|
|
178
|
+
};
|
|
179
|
+
if (options?.edit) {
|
|
180
|
+
// Edit mode - open in editor
|
|
181
|
+
await editModuleJson(String(actualModuleId), jsonDef, companyId);
|
|
182
|
+
}
|
|
183
|
+
else if (options?.file) {
|
|
184
|
+
// Save to file
|
|
185
|
+
const fs = await import("fs");
|
|
186
|
+
fs.writeFileSync(options.file, JSON.stringify(jsonDef, null, 2));
|
|
187
|
+
console.log(chalk.green(` ✓ JSON guardado en: ${options.file}\n`));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Print JSON
|
|
191
|
+
console.log(chalk.gray(" JSON Definition:"));
|
|
192
|
+
console.log(chalk.gray(" ─".repeat(30)));
|
|
193
|
+
console.log(JSON.stringify(jsonDef, null, 2));
|
|
194
|
+
console.log(chalk.gray("\n 💡 Usa: gufi module <id> --edit para editar"));
|
|
195
|
+
console.log(chalk.gray(" 💡 Usa: gufi module <id> --file out.json para guardar\n"));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// ════════════════════════════════════════════════════════════════════
|
|
203
|
+
// Edit module JSON interactively
|
|
204
|
+
// ════════════════════════════════════════════════════════════════════
|
|
205
|
+
async function editModuleJson(moduleId, currentJson, companyId) {
|
|
206
|
+
const fs = await import("fs");
|
|
207
|
+
const os = await import("os");
|
|
208
|
+
const path = await import("path");
|
|
209
|
+
const { spawn } = await import("child_process");
|
|
210
|
+
// Write to temp file
|
|
211
|
+
const tempFile = path.join(os.tmpdir(), `gufi-module-${moduleId}.json`);
|
|
212
|
+
fs.writeFileSync(tempFile, JSON.stringify(currentJson, null, 2));
|
|
213
|
+
console.log(chalk.gray(` Abriendo editor... (${tempFile})`));
|
|
214
|
+
console.log(chalk.gray(" Guarda y cierra el editor para aplicar cambios.\n"));
|
|
215
|
+
// Determine editor
|
|
216
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "nano";
|
|
217
|
+
// Open editor
|
|
218
|
+
const child = spawn(editor, [tempFile], {
|
|
219
|
+
stdio: "inherit",
|
|
220
|
+
shell: true,
|
|
221
|
+
});
|
|
222
|
+
await new Promise((resolve, reject) => {
|
|
223
|
+
child.on("close", (code) => {
|
|
224
|
+
if (code === 0)
|
|
225
|
+
resolve();
|
|
226
|
+
else
|
|
227
|
+
reject(new Error(`Editor exited with code ${code}`));
|
|
228
|
+
});
|
|
229
|
+
child.on("error", reject);
|
|
230
|
+
});
|
|
231
|
+
// Read modified file
|
|
232
|
+
const newContent = fs.readFileSync(tempFile, "utf-8");
|
|
233
|
+
let newJson;
|
|
234
|
+
try {
|
|
235
|
+
newJson = JSON.parse(newContent);
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
console.log(chalk.red("\n ✗ JSON inválido. Cambios no guardados.\n"));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Check if changed
|
|
242
|
+
if (JSON.stringify(newJson) === JSON.stringify(currentJson)) {
|
|
243
|
+
console.log(chalk.gray(" Sin cambios.\n"));
|
|
244
|
+
fs.unlinkSync(tempFile);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// Validate by calling updateModule (dry-run first)
|
|
248
|
+
console.log(chalk.yellow(" ⏳ Validando cambios..."));
|
|
249
|
+
try {
|
|
250
|
+
const headers = {};
|
|
251
|
+
if (companyId) {
|
|
252
|
+
headers["X-Company-ID"] = companyId;
|
|
253
|
+
}
|
|
254
|
+
// Call update endpoint
|
|
255
|
+
const result = await apiRequest(`/api/modules/${moduleId}`, {
|
|
256
|
+
method: "PUT",
|
|
257
|
+
headers,
|
|
258
|
+
body: JSON.stringify({ json_definition: newJson }),
|
|
259
|
+
});
|
|
260
|
+
console.log(chalk.green("\n ✓ Módulo actualizado correctamente!\n"));
|
|
261
|
+
// Show what changed
|
|
262
|
+
if (result.changes) {
|
|
263
|
+
console.log(chalk.gray(" Cambios aplicados:"));
|
|
264
|
+
for (const change of result.changes) {
|
|
265
|
+
console.log(chalk.gray(` - ${change}`));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
console.log(chalk.red(`\n ✗ Error al actualizar: ${err.message}`));
|
|
271
|
+
console.log(chalk.gray(` El archivo temporal se mantiene en: ${tempFile}\n`));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
// Cleanup temp file
|
|
275
|
+
fs.unlinkSync(tempFile);
|
|
276
|
+
}
|
|
277
|
+
// ════════════════════════════════════════════════════════════════════
|
|
278
|
+
// gufi module:update <module_id> <json_file> - Update module from file
|
|
279
|
+
// ════════════════════════════════════════════════════════════════════
|
|
280
|
+
export async function moduleUpdateCommand(moduleId, jsonFile, options) {
|
|
281
|
+
if (!isLoggedIn()) {
|
|
282
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
if (!moduleId || !jsonFile) {
|
|
286
|
+
console.log(chalk.red("\n ✗ Uso: gufi module:update <module_id> <json_file> [--company <id>] [--dry-run]\n"));
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
const fs = await import("fs");
|
|
290
|
+
// Read JSON file
|
|
291
|
+
let newJson;
|
|
292
|
+
try {
|
|
293
|
+
const content = fs.readFileSync(jsonFile, "utf-8");
|
|
294
|
+
newJson = JSON.parse(content);
|
|
295
|
+
}
|
|
296
|
+
catch (e) {
|
|
297
|
+
console.log(chalk.red(`\n ✗ Error leyendo archivo: ${e.message}\n`));
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
console.log(chalk.magenta(`\n 🟣 Actualizando módulo ${moduleId}\n`));
|
|
301
|
+
if (options?.dryRun) {
|
|
302
|
+
console.log(chalk.yellow(" [DRY RUN] Solo validando, no se guardarán cambios.\n"));
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
const headers = {};
|
|
306
|
+
if (options?.company) {
|
|
307
|
+
headers["X-Company-ID"] = options.company;
|
|
308
|
+
}
|
|
309
|
+
// Validate first
|
|
310
|
+
console.log(chalk.gray(" Validando JSON..."));
|
|
311
|
+
const validateResult = await apiRequest(`/api/modules/${moduleId}/validate`, {
|
|
312
|
+
method: "POST",
|
|
313
|
+
headers,
|
|
314
|
+
body: JSON.stringify({ json_definition: newJson }),
|
|
315
|
+
});
|
|
316
|
+
if (validateResult.errors?.length) {
|
|
317
|
+
console.log(chalk.red("\n ✗ Errores de validación:"));
|
|
318
|
+
for (const err of validateResult.errors) {
|
|
319
|
+
console.log(chalk.red(` - ${err}`));
|
|
320
|
+
}
|
|
321
|
+
console.log();
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
console.log(chalk.green(" ✓ JSON válido"));
|
|
325
|
+
if (options?.dryRun) {
|
|
326
|
+
console.log(chalk.gray("\n [DRY RUN] Validación exitosa. Usa sin --dry-run para aplicar.\n"));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// Apply changes
|
|
330
|
+
console.log(chalk.gray(" Aplicando cambios..."));
|
|
331
|
+
const result = await apiRequest(`/api/modules/${moduleId}`, {
|
|
332
|
+
method: "PUT",
|
|
333
|
+
headers,
|
|
334
|
+
body: JSON.stringify({ json_definition: newJson }),
|
|
335
|
+
});
|
|
336
|
+
console.log(chalk.green("\n ✓ Módulo actualizado correctamente!\n"));
|
|
337
|
+
if (result.tablesCreated?.length) {
|
|
338
|
+
console.log(chalk.gray(" Tablas creadas:"));
|
|
339
|
+
for (const t of result.tablesCreated) {
|
|
340
|
+
console.log(chalk.green(` + ${t}`));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (result.columnsAdded?.length) {
|
|
344
|
+
console.log(chalk.gray(" Columnas añadidas:"));
|
|
345
|
+
for (const c of result.columnsAdded) {
|
|
346
|
+
console.log(chalk.green(` + ${c}`));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// ════════════════════════════════════════════════════════════════════
|
|
356
|
+
// gufi company:create <name> - Create a new company
|
|
357
|
+
// ════════════════════════════════════════════════════════════════════
|
|
358
|
+
export async function companyCreateCommand(name) {
|
|
359
|
+
if (!isLoggedIn()) {
|
|
360
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
if (!name) {
|
|
364
|
+
console.log(chalk.red("\n ✗ Uso: gufi company:create <nombre>\n"));
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
console.log(chalk.magenta(`\n 🟣 Creando company: ${name}\n`));
|
|
368
|
+
try {
|
|
369
|
+
const result = await apiRequest("/api/companies", {
|
|
370
|
+
method: "POST",
|
|
371
|
+
body: JSON.stringify({ name }),
|
|
372
|
+
});
|
|
373
|
+
const company = result.data || result;
|
|
374
|
+
console.log(chalk.green(" ✓ Company creada!\n"));
|
|
375
|
+
console.log(chalk.white(` ID: ${company.id}`));
|
|
376
|
+
console.log(chalk.white(` Nombre: ${company.name}`));
|
|
377
|
+
console.log(chalk.white(` Schema: company_${company.id}`));
|
|
378
|
+
console.log(chalk.gray("\n 💡 Usa: gufi modules " + company.id + " para ver módulos\n"));
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// ════════════════════════════════════════════════════════════════════
|
|
385
|
+
// gufi automations - List all automation scripts of a company
|
|
386
|
+
// ════════════════════════════════════════════════════════════════════
|
|
387
|
+
export async function automationsCommand(_moduleId, // Ignored - kept for backwards compatibility
|
|
388
|
+
options) {
|
|
389
|
+
if (!isLoggedIn()) {
|
|
390
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
console.log(chalk.magenta("\n 🟣 Automation Scripts\n"));
|
|
394
|
+
try {
|
|
395
|
+
const headers = {};
|
|
396
|
+
if (options?.company) {
|
|
397
|
+
headers["X-Company-ID"] = options.company;
|
|
398
|
+
}
|
|
399
|
+
const data = await apiRequest("/api/automation-scripts", { headers });
|
|
400
|
+
const automations = Array.isArray(data) ? data : data.data || [];
|
|
401
|
+
if (!automations.length) {
|
|
402
|
+
console.log(chalk.gray(" No hay automation scripts en esta company.\n"));
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
console.log(chalk.gray(" ID\tNombre\t\t\t\tDescripción"));
|
|
406
|
+
console.log(chalk.gray(" ─".repeat(45)));
|
|
407
|
+
for (const auto of automations) {
|
|
408
|
+
const id = String(auto.id).padEnd(4);
|
|
409
|
+
const name = (auto.name || "").substring(0, 28).padEnd(28);
|
|
410
|
+
const desc = (auto.description || "-").substring(0, 30);
|
|
411
|
+
console.log(` ${chalk.cyan(id)}\t${name}\t${desc}`);
|
|
412
|
+
}
|
|
413
|
+
console.log(chalk.gray("\n 💡 Usa: gufi automation <nombre> para ver/editar el código\n"));
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// ════════════════════════════════════════════════════════════════════
|
|
420
|
+
// gufi automation <name> [--edit] - View/edit automation code by name
|
|
421
|
+
// ════════════════════════════════════════════════════════════════════
|
|
422
|
+
export async function automationCommand(automationName, options) {
|
|
423
|
+
if (!isLoggedIn()) {
|
|
424
|
+
console.log(chalk.red("\n ✗ No estás logueado. Usa: gufi login\n"));
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
if (!automationName) {
|
|
428
|
+
console.log(chalk.red("\n ✗ Uso: gufi automation <nombre> [--edit] [--company <id>]\n"));
|
|
429
|
+
console.log(chalk.gray(" Tip: Usa 'gufi automations' para ver la lista\n"));
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
console.log(chalk.magenta(`\n 🟣 Automation: ${automationName}\n`));
|
|
433
|
+
try {
|
|
434
|
+
const headers = {};
|
|
435
|
+
if (options?.company) {
|
|
436
|
+
headers["X-Company-ID"] = options.company;
|
|
437
|
+
}
|
|
438
|
+
// Get all scripts and find by name
|
|
439
|
+
const data = await apiRequest("/api/automation-scripts", { headers });
|
|
440
|
+
const automations = Array.isArray(data) ? data : data.data || [];
|
|
441
|
+
const automation = automations.find((a) => a.name === automationName || String(a.id) === automationName);
|
|
442
|
+
if (!automation) {
|
|
443
|
+
console.log(chalk.red(` ✗ Automation "${automationName}" no encontrada\n`));
|
|
444
|
+
console.log(chalk.gray(" 💡 Usa: gufi automations para ver la lista\n"));
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
// Show info
|
|
448
|
+
console.log(chalk.white(` Nombre: ${automation.name}`));
|
|
449
|
+
console.log(chalk.white(` ID: ${automation.id}`));
|
|
450
|
+
if (automation.description) {
|
|
451
|
+
console.log(chalk.white(` Descripción: ${automation.description}`));
|
|
452
|
+
}
|
|
453
|
+
console.log();
|
|
454
|
+
const jsCode = automation.code || "";
|
|
455
|
+
if (options?.edit) {
|
|
456
|
+
await editAutomationCode(automation.name, jsCode, options.company);
|
|
457
|
+
}
|
|
458
|
+
else if (options?.file) {
|
|
459
|
+
const fs = await import("fs");
|
|
460
|
+
fs.writeFileSync(options.file, jsCode);
|
|
461
|
+
console.log(chalk.green(` ✓ Código guardado en: ${options.file}\n`));
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
console.log(chalk.gray(" Código JavaScript:"));
|
|
465
|
+
console.log(chalk.gray(" ─".repeat(30)));
|
|
466
|
+
console.log(jsCode || "(vacío)");
|
|
467
|
+
console.log(chalk.gray("\n 💡 Usa: gufi automation <nombre> --edit para editar\n"));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
catch (err) {
|
|
471
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}\n`));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
async function editAutomationCode(automationName, currentCode, companyId) {
|
|
475
|
+
const fs = await import("fs");
|
|
476
|
+
const os = await import("os");
|
|
477
|
+
const path = await import("path");
|
|
478
|
+
const { spawn } = await import("child_process");
|
|
479
|
+
// Safe filename
|
|
480
|
+
const safeName = automationName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
481
|
+
const tempFile = path.join(os.tmpdir(), `gufi-automation-${safeName}.js`);
|
|
482
|
+
fs.writeFileSync(tempFile, currentCode);
|
|
483
|
+
console.log(chalk.gray(` Abriendo editor... (${tempFile})`));
|
|
484
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "nano";
|
|
485
|
+
const child = spawn(editor, [tempFile], { stdio: "inherit", shell: true });
|
|
486
|
+
await new Promise((resolve, reject) => {
|
|
487
|
+
child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`Editor exit ${code}`)));
|
|
488
|
+
child.on("error", reject);
|
|
489
|
+
});
|
|
490
|
+
const newCode = fs.readFileSync(tempFile, "utf-8");
|
|
491
|
+
if (newCode === currentCode) {
|
|
492
|
+
console.log(chalk.gray(" Sin cambios.\n"));
|
|
493
|
+
fs.unlinkSync(tempFile);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
console.log(chalk.yellow(" ⏳ Guardando cambios..."));
|
|
497
|
+
try {
|
|
498
|
+
const headers = {};
|
|
499
|
+
if (companyId)
|
|
500
|
+
headers["X-Company-ID"] = companyId;
|
|
501
|
+
await apiRequest(`/api/automation-scripts/${encodeURIComponent(automationName)}`, {
|
|
502
|
+
method: "PUT",
|
|
503
|
+
headers,
|
|
504
|
+
body: JSON.stringify({ code: newCode }),
|
|
505
|
+
});
|
|
506
|
+
console.log(chalk.green("\n ✓ Automation actualizada!\n"));
|
|
507
|
+
fs.unlinkSync(tempFile);
|
|
508
|
+
}
|
|
509
|
+
catch (err) {
|
|
510
|
+
console.log(chalk.red(`\n ✗ Error: ${err.message}`));
|
|
511
|
+
console.log(chalk.gray(` Archivo temporal: ${tempFile}\n`));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
export declare function logsCommand(viewDir?: string): Promise<void>;
|