plazbot-cli 0.2.20 → 0.2.22
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/dist/cli.js +3 -0
- package/dist/commands/auth/index.js +3 -1
- package/dist/commands/auth/status.js +24 -0
- package/dist/commands/workers/deploy.js +127 -0
- package/dist/commands/workers/index.js +18 -0
- package/dist/commands/workers/list.js +81 -0
- package/dist/commands/workers/logs.js +78 -0
- package/dist/commands/workers/remove.js +88 -0
- package/dist/commands/workers/secret.js +151 -0
- package/dist/commands/workers/test.js +109 -0
- package/dist/types/workers.js +2 -0
- package/dist/utils/api.js +25 -0
- package/package.json +2 -2
- package/src/cli.ts +4 -0
- package/src/commands/auth/index.ts +3 -1
- package/src/commands/auth/status.ts +22 -0
- package/src/commands/workers/deploy.ts +144 -0
- package/src/commands/workers/index.ts +16 -0
- package/src/commands/workers/list.ts +93 -0
- package/src/commands/workers/logs.ts +89 -0
- package/src/commands/workers/remove.ts +95 -0
- package/src/commands/workers/secret.ts +179 -0
- package/src/commands/workers/test.ts +133 -0
- package/src/types/workers.ts +86 -0
- package/src/utils/api.ts +29 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.testCommand = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const credentials_1 = require("../../utils/credentials");
|
|
6
|
+
const api_1 = require("../../utils/api");
|
|
7
|
+
const logger_1 = require("../../utils/logger");
|
|
8
|
+
const ui_1 = require("../../utils/ui");
|
|
9
|
+
exports.testCommand = new commander_1.Command('test')
|
|
10
|
+
.description('Ejecutar un worker manualmente para probarlo')
|
|
11
|
+
.argument('<name>', 'Nombre del worker a ejecutar')
|
|
12
|
+
.option('-p, --payload <json>', 'Payload JSON para el worker (ej: \'{"producto":"iPhone"}\')')
|
|
13
|
+
.option('-t, --type <type>', 'Tipo del worker (tool, worker, sync, schedule, webhook)')
|
|
14
|
+
.option('--contact <id>', 'ID del contacto (contexto para tools)')
|
|
15
|
+
.option('--agent <id>', 'ID del agente (contexto para tools)')
|
|
16
|
+
.option('--dev', 'Usar ambiente de desarrollo', false)
|
|
17
|
+
.action(async (name, options) => {
|
|
18
|
+
try {
|
|
19
|
+
const credentials = await (0, credentials_1.getStoredCredentials)();
|
|
20
|
+
const api = (0, api_1.createApiClient)({
|
|
21
|
+
apiKey: credentials.apiKey,
|
|
22
|
+
workspace: credentials.workspace,
|
|
23
|
+
zone: credentials.zone,
|
|
24
|
+
dev: options.dev,
|
|
25
|
+
});
|
|
26
|
+
// Parsear payload
|
|
27
|
+
let payload = {};
|
|
28
|
+
if (options.payload) {
|
|
29
|
+
try {
|
|
30
|
+
payload = JSON.parse(options.payload);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
logger_1.logger.error('El payload no es JSON valido');
|
|
34
|
+
logger_1.logger.dim('Ejemplo: --payload \'{"producto":"iPhone 15"}\'');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
logger_1.logger.title(`Ejecutando worker "${name}"`);
|
|
39
|
+
console.log();
|
|
40
|
+
if (Object.keys(payload).length > 0) {
|
|
41
|
+
logger_1.logger.label('Payload', JSON.stringify(payload, null, 2));
|
|
42
|
+
console.log();
|
|
43
|
+
}
|
|
44
|
+
const spinner = (0, ui_1.createSpinner)('Ejecutando...');
|
|
45
|
+
spinner.start();
|
|
46
|
+
const response = await api.post('/api/worker/execute', {
|
|
47
|
+
workerName: name,
|
|
48
|
+
type: options.type,
|
|
49
|
+
payload,
|
|
50
|
+
triggerSource: 'cli-test',
|
|
51
|
+
agentId: options.agent,
|
|
52
|
+
contactId: options.contact,
|
|
53
|
+
});
|
|
54
|
+
const data = response.data;
|
|
55
|
+
const duration = data.duration ?? 0;
|
|
56
|
+
spinner.stop();
|
|
57
|
+
if (data.success) {
|
|
58
|
+
logger_1.logger.success(`Ejecutado en ${duration}ms`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
logger_1.logger.error(`Fallo en ${duration}ms`);
|
|
62
|
+
}
|
|
63
|
+
console.log();
|
|
64
|
+
// Mostrar resultado
|
|
65
|
+
if (data.result !== undefined && data.result !== null) {
|
|
66
|
+
logger_1.logger.label('Resultado', '');
|
|
67
|
+
const result = data.result;
|
|
68
|
+
// Si es un ToolResponse, mostrar formateado
|
|
69
|
+
if (typeof result === 'object' && 'result' in result) {
|
|
70
|
+
console.log();
|
|
71
|
+
logger_1.logger.label(' result', ui_1.theme.success(String(result.result)));
|
|
72
|
+
if (result.data) {
|
|
73
|
+
logger_1.logger.label(' data', JSON.stringify(result.data, null, 2));
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(result.actions) && result.actions.length > 0) {
|
|
76
|
+
logger_1.logger.label(' actions', '');
|
|
77
|
+
for (const action of result.actions) {
|
|
78
|
+
const a = action;
|
|
79
|
+
logger_1.logger.dim(` - ${a.type}: ${a.value}${a.code ? ` (${a.code})` : ''}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Resultado generico
|
|
85
|
+
console.log(JSON.stringify(result, null, 2));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Mostrar error si hubo
|
|
89
|
+
if (data.error) {
|
|
90
|
+
console.log();
|
|
91
|
+
logger_1.logger.label('Error', ui_1.theme.error(data.error));
|
|
92
|
+
}
|
|
93
|
+
console.log();
|
|
94
|
+
if (options.dev) {
|
|
95
|
+
logger_1.logger.warning('Ambiente: desarrollo');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error.response?.status === 404) {
|
|
100
|
+
logger_1.logger.error(`Worker "${name}" no encontrado o inactivo`);
|
|
101
|
+
logger_1.logger.dim('Verifica que el worker este desplegado con: plazbot workers list');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const message = error.response?.data?.message || error.message || 'Error desconocido';
|
|
105
|
+
logger_1.logger.error(message);
|
|
106
|
+
}
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Utilidades para llamadas directas al API de Plazbot
|
|
3
|
+
// Usado por comandos que no tienen clase SDK (ej: workers)
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.createApiClient = createApiClient;
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
function getBaseUrl(zone, dev) {
|
|
11
|
+
if (dev)
|
|
12
|
+
return 'http://localhost:5090';
|
|
13
|
+
return zone === 'EU' ? 'https://apieu.plazbot.com' : 'https://api.plazbot.com';
|
|
14
|
+
}
|
|
15
|
+
function createApiClient(options) {
|
|
16
|
+
const baseURL = getBaseUrl(options.zone, options.dev);
|
|
17
|
+
return axios_1.default.create({
|
|
18
|
+
baseURL,
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'x-api-key': options.apiKey,
|
|
22
|
+
'x-workspace-id': options.workspace,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plazbot-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
4
|
"description": "CLI para Plazbot SDK",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"marked": "^12.0.2",
|
|
45
45
|
"marked-terminal": "^7.2.1",
|
|
46
46
|
"ora": "^5.4.1",
|
|
47
|
-
"plazbot": "^2.
|
|
47
|
+
"plazbot": "^2.1.4"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^20.0.0",
|
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { portalCommands } from './commands/portal';
|
|
|
4
4
|
import { authCommands } from './commands/auth';
|
|
5
5
|
import { agentCommands } from './commands/agent';
|
|
6
6
|
import { whatsappCommands } from './commands/whatsapp';
|
|
7
|
+
import { workersCommands } from './commands/workers';
|
|
7
8
|
import { showBanner } from './utils/banner';
|
|
8
9
|
|
|
9
10
|
// Configuracion basica del CLI
|
|
@@ -24,6 +25,9 @@ program.addCommand(portalCommands);
|
|
|
24
25
|
// Registrar todos los comandos de WhatsApp
|
|
25
26
|
program.addCommand(whatsappCommands);
|
|
26
27
|
|
|
28
|
+
// Registrar todos los comandos de Workers
|
|
29
|
+
program.addCommand(workersCommands);
|
|
30
|
+
|
|
27
31
|
// Si no se pasan argumentos, mostrar banner
|
|
28
32
|
if (process.argv.length <= 2) {
|
|
29
33
|
showBanner().then(() => {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { loginCommand } from './login';
|
|
3
3
|
import { logoutCommand } from './logout';
|
|
4
|
+
import { statusCommand } from './status';
|
|
4
5
|
|
|
5
6
|
export const authCommands: Command[] = [
|
|
6
7
|
loginCommand,
|
|
7
|
-
logoutCommand
|
|
8
|
+
logoutCommand,
|
|
9
|
+
statusCommand
|
|
8
10
|
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getStoredCredentials } from '../../utils/credentials';
|
|
3
|
+
import { logger } from '../../utils/logger';
|
|
4
|
+
import { theme, section } from '../../utils/ui';
|
|
5
|
+
|
|
6
|
+
export const statusCommand = new Command('status')
|
|
7
|
+
.description('Muestra la sesion activa (workspace, email, zona)')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
try {
|
|
10
|
+
const credentials = await getStoredCredentials();
|
|
11
|
+
|
|
12
|
+
console.log(section('Sesion activa'));
|
|
13
|
+
logger.label('Email', credentials.email);
|
|
14
|
+
logger.label('Workspace', credentials.workspace);
|
|
15
|
+
logger.label('Zona', credentials.zone === 'LA' ? 'Latinoamerica (LA)' : 'Europa (EU)');
|
|
16
|
+
logger.label('API', credentials.zone === 'EU' ? 'https://apieu.plazbot.com' : 'https://api.plazbot.com');
|
|
17
|
+
console.log();
|
|
18
|
+
} catch {
|
|
19
|
+
logger.error("No hay sesion activa. Ejecuta 'plazbot init' para conectarte.");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { getStoredCredentials } from '../../utils/credentials';
|
|
5
|
+
import { createApiClient } from '../../utils/api';
|
|
6
|
+
import { logger } from '../../utils/logger';
|
|
7
|
+
import { createSpinner, createTable, theme } from '../../utils/ui';
|
|
8
|
+
import { WorkerDeployOptions, WorkerDeployResult } from '../../types/workers';
|
|
9
|
+
|
|
10
|
+
export const deployCommand = new Command('deploy')
|
|
11
|
+
.description('Despliega un worker al workspace de Plazbot')
|
|
12
|
+
.argument('[file]', 'Archivo TypeScript del worker (ej: ./workers/mi-tool.ts)')
|
|
13
|
+
.option('-n, --name <name>', 'Nombre del worker (sobreescribe el del archivo)')
|
|
14
|
+
.option('-d, --description <desc>', 'Descripcion del worker')
|
|
15
|
+
.option('--dev', 'Usar ambiente de desarrollo', false)
|
|
16
|
+
.action(async (file: string | undefined, options: WorkerDeployOptions) => {
|
|
17
|
+
try {
|
|
18
|
+
const credentials = await getStoredCredentials();
|
|
19
|
+
|
|
20
|
+
// Buscar archivos de workers
|
|
21
|
+
const files = await resolveWorkerFiles(file);
|
|
22
|
+
|
|
23
|
+
if (files.length === 0) {
|
|
24
|
+
logger.error('No se encontraron archivos de workers para desplegar');
|
|
25
|
+
logger.dim('Uso: plazbot workers deploy ./workers/mi-tool.ts');
|
|
26
|
+
logger.dim(' o: plazbot workers deploy (busca en ./workers/ automaticamente)');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const api = createApiClient({
|
|
31
|
+
apiKey: credentials.apiKey,
|
|
32
|
+
workspace: credentials.workspace,
|
|
33
|
+
zone: credentials.zone,
|
|
34
|
+
dev: options.dev,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
logger.title(`Desplegando ${files.length} worker${files.length > 1 ? 's' : ''}`);
|
|
38
|
+
console.log();
|
|
39
|
+
|
|
40
|
+
const results: { file: string; result?: WorkerDeployResult; error?: string }[] = [];
|
|
41
|
+
|
|
42
|
+
for (const filePath of files) {
|
|
43
|
+
const fileName = path.basename(filePath);
|
|
44
|
+
const spinner = createSpinner(`Desplegando ${fileName}...`);
|
|
45
|
+
spinner.start();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Leer archivo fuente
|
|
49
|
+
const sourceCode = await fs.readFile(filePath, 'utf-8');
|
|
50
|
+
|
|
51
|
+
// Enviar al API
|
|
52
|
+
const response = await api.post('/api/worker/deploy', {
|
|
53
|
+
sourceCode,
|
|
54
|
+
fileName,
|
|
55
|
+
name: files.length === 1 ? options.name : undefined,
|
|
56
|
+
description: files.length === 1 ? options.description : undefined,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const data = response.data as WorkerDeployResult;
|
|
60
|
+
spinner.succeed(`${fileName} → ${data.name} (${data.type})`);
|
|
61
|
+
results.push({ file: fileName, result: data });
|
|
62
|
+
|
|
63
|
+
} catch (error: any) {
|
|
64
|
+
const msg = error.response?.data?.message || error.message || 'Error desconocido';
|
|
65
|
+
spinner.fail(`${fileName}: ${msg}`);
|
|
66
|
+
results.push({ file: fileName, error: msg });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Resumen
|
|
71
|
+
console.log();
|
|
72
|
+
const success = results.filter(r => r.result);
|
|
73
|
+
const failed = results.filter(r => r.error);
|
|
74
|
+
|
|
75
|
+
if (success.length > 0) {
|
|
76
|
+
logger.success(`${success.length} worker${success.length > 1 ? 's' : ''} desplegado${success.length > 1 ? 's' : ''}`);
|
|
77
|
+
|
|
78
|
+
const rows = success.map(r => [
|
|
79
|
+
r.result!.name,
|
|
80
|
+
r.result!.type,
|
|
81
|
+
r.result!.status,
|
|
82
|
+
r.result!.webhookUrl || r.result!.cronExpression || '—',
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
console.log(createTable(
|
|
86
|
+
['Nombre', 'Tipo', 'Estado', 'URL/Cron'],
|
|
87
|
+
rows,
|
|
88
|
+
));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (failed.length > 0) {
|
|
92
|
+
logger.warning(`${failed.length} worker${failed.length > 1 ? 's' : ''} fallaron`);
|
|
93
|
+
failed.forEach(f => logger.dim(` ${f.file}: ${f.error}`));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Mostrar webhook URLs si hay
|
|
97
|
+
const webhooks = success.filter(r => r.result!.webhookUrl);
|
|
98
|
+
if (webhooks.length > 0) {
|
|
99
|
+
console.log();
|
|
100
|
+
logger.title('Webhook URLs');
|
|
101
|
+
webhooks.forEach(w => {
|
|
102
|
+
logger.label(w.result!.name, theme.secondary(w.result!.webhookUrl!));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log();
|
|
107
|
+
|
|
108
|
+
if (options.dev) {
|
|
109
|
+
logger.warning('Ambiente: desarrollo');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const message = error instanceof Error ? error.message : 'Error desconocido';
|
|
114
|
+
logger.error(message);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Resuelve los archivos a desplegar
|
|
120
|
+
async function resolveWorkerFiles(file?: string): Promise<string[]> {
|
|
121
|
+
if (file) {
|
|
122
|
+
// Archivo especifico
|
|
123
|
+
const resolved = path.resolve(file);
|
|
124
|
+
try {
|
|
125
|
+
await fs.access(resolved);
|
|
126
|
+
return [resolved];
|
|
127
|
+
} catch {
|
|
128
|
+
throw new Error(`Archivo no encontrado: ${file}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Buscar en ./workers/ automaticamente
|
|
133
|
+
const workersDir = path.resolve('workers');
|
|
134
|
+
try {
|
|
135
|
+
const entries = await fs.readdir(workersDir);
|
|
136
|
+
const tsFiles = entries
|
|
137
|
+
.filter(e => e.endsWith('.ts') && !e.endsWith('.d.ts'))
|
|
138
|
+
.map(e => path.join(workersDir, e));
|
|
139
|
+
|
|
140
|
+
return tsFiles;
|
|
141
|
+
} catch {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { deployCommand } from './deploy';
|
|
3
|
+
import { listCommand } from './list';
|
|
4
|
+
import { logsCommand } from './logs';
|
|
5
|
+
import { removeCommand } from './remove';
|
|
6
|
+
import { secretCommand } from './secret';
|
|
7
|
+
import { testCommand } from './test';
|
|
8
|
+
|
|
9
|
+
export const workersCommands = new Command('workers')
|
|
10
|
+
.description('Comandos para gestionar workers (tools, syncs, schedules, webhooks)')
|
|
11
|
+
.addCommand(deployCommand)
|
|
12
|
+
.addCommand(listCommand)
|
|
13
|
+
.addCommand(logsCommand)
|
|
14
|
+
.addCommand(removeCommand)
|
|
15
|
+
.addCommand(secretCommand)
|
|
16
|
+
.addCommand(testCommand);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getStoredCredentials } from '../../utils/credentials';
|
|
3
|
+
import { createApiClient } from '../../utils/api';
|
|
4
|
+
import { logger } from '../../utils/logger';
|
|
5
|
+
import { createSpinner, createTable, theme, statusBadge, formatDate } from '../../utils/ui';
|
|
6
|
+
import { WorkerListOptions, WorkerListItem } from '../../types/workers';
|
|
7
|
+
|
|
8
|
+
export const listCommand = new Command('list')
|
|
9
|
+
.description('Lista todos los workers del workspace')
|
|
10
|
+
.option('-t, --type <type>', 'Filtrar por tipo (tool, worker, sync, schedule, webhook)')
|
|
11
|
+
.option('-s, --status <status>', 'Filtrar por estado (active, inactive, error)')
|
|
12
|
+
.option('--dev', 'Usar ambiente de desarrollo', false)
|
|
13
|
+
.action(async (options: WorkerListOptions) => {
|
|
14
|
+
try {
|
|
15
|
+
const credentials = await getStoredCredentials();
|
|
16
|
+
const api = createApiClient({
|
|
17
|
+
apiKey: credentials.apiKey,
|
|
18
|
+
workspace: credentials.workspace,
|
|
19
|
+
zone: credentials.zone,
|
|
20
|
+
dev: options.dev,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const spinner = createSpinner('Obteniendo workers...');
|
|
24
|
+
spinner.start();
|
|
25
|
+
|
|
26
|
+
const params: Record<string, string> = {};
|
|
27
|
+
if (options.type) params.type = options.type;
|
|
28
|
+
if (options.status) params.status = options.status;
|
|
29
|
+
|
|
30
|
+
const response = await api.get('/api/worker', { params });
|
|
31
|
+
const workers: WorkerListItem[] = response.data;
|
|
32
|
+
|
|
33
|
+
spinner.stop();
|
|
34
|
+
|
|
35
|
+
if (!workers || workers.length === 0) {
|
|
36
|
+
logger.info('\n No se encontraron workers en este workspace');
|
|
37
|
+
logger.dim(' Despliega tu primer worker: plazbot workers deploy ./workers/mi-tool.ts');
|
|
38
|
+
console.log();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
logger.title(`Workers (${workers.length})`);
|
|
43
|
+
|
|
44
|
+
const rows = workers.map(w => [
|
|
45
|
+
w.name,
|
|
46
|
+
typeLabel(w.type),
|
|
47
|
+
w.status === 'active' ? theme.success('activo')
|
|
48
|
+
: w.status === 'error' ? theme.error('error')
|
|
49
|
+
: theme.muted('inactivo'),
|
|
50
|
+
`${w.executionCount}`,
|
|
51
|
+
w.errorCount > 0 ? theme.error(`${w.errorCount}`) : theme.muted('0'),
|
|
52
|
+
w.avgDurationMs > 0 ? `${w.avgDurationMs}ms` : '—',
|
|
53
|
+
w.lastExecutedAt ? formatDate(w.lastExecutedAt) : '—',
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
console.log(createTable(
|
|
57
|
+
['Nombre', 'Tipo', 'Estado', 'Ejecuciones', 'Errores', 'Duracion', 'Ultima ejecucion'],
|
|
58
|
+
rows,
|
|
59
|
+
));
|
|
60
|
+
|
|
61
|
+
// Mostrar workers con error
|
|
62
|
+
const errored = workers.filter(w => w.status === 'error');
|
|
63
|
+
if (errored.length > 0) {
|
|
64
|
+
console.log();
|
|
65
|
+
logger.warning('Workers con error:');
|
|
66
|
+
errored.forEach(w => {
|
|
67
|
+
logger.dim(` ${w.name}: ${w.lastError || 'sin detalles'}`);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log();
|
|
72
|
+
|
|
73
|
+
if (options.dev) {
|
|
74
|
+
logger.warning('Ambiente: desarrollo');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const message = error instanceof Error ? error.message : 'Error desconocido';
|
|
79
|
+
logger.error(message);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function typeLabel(type: string): string {
|
|
85
|
+
const labels: Record<string, string> = {
|
|
86
|
+
tool: theme.secondary('tool'),
|
|
87
|
+
worker: theme.primary('worker'),
|
|
88
|
+
sync: theme.accent('sync'),
|
|
89
|
+
schedule: theme.muted('schedule'),
|
|
90
|
+
webhook: theme.warning('webhook'),
|
|
91
|
+
};
|
|
92
|
+
return labels[type] || type;
|
|
93
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getStoredCredentials } from '../../utils/credentials';
|
|
3
|
+
import { createApiClient } from '../../utils/api';
|
|
4
|
+
import { logger } from '../../utils/logger';
|
|
5
|
+
import { createSpinner, createTable, theme, formatDate } from '../../utils/ui';
|
|
6
|
+
import { WorkerLogsOptions, WorkerLogItem } from '../../types/workers';
|
|
7
|
+
|
|
8
|
+
export const logsCommand = new Command('logs')
|
|
9
|
+
.description('Ver logs de ejecucion de un worker')
|
|
10
|
+
.argument('<name>', 'Nombre del worker')
|
|
11
|
+
.option('-l, --limit <number>', 'Numero de logs a mostrar', '20')
|
|
12
|
+
.option('-s, --status <status>', 'Filtrar por estado (success, error, timeout)')
|
|
13
|
+
.option('--dev', 'Usar ambiente de desarrollo', false)
|
|
14
|
+
.action(async (name: string, options: WorkerLogsOptions) => {
|
|
15
|
+
try {
|
|
16
|
+
const credentials = await getStoredCredentials();
|
|
17
|
+
const api = createApiClient({
|
|
18
|
+
apiKey: credentials.apiKey,
|
|
19
|
+
workspace: credentials.workspace,
|
|
20
|
+
zone: credentials.zone,
|
|
21
|
+
dev: options.dev,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const spinner = createSpinner(`Obteniendo logs de "${name}"...`);
|
|
25
|
+
spinner.start();
|
|
26
|
+
|
|
27
|
+
const params: Record<string, string> = {
|
|
28
|
+
limit: String(options.limit || 20),
|
|
29
|
+
};
|
|
30
|
+
if (options.status) params.status = options.status;
|
|
31
|
+
|
|
32
|
+
const response = await api.get(`/api/worker/${encodeURIComponent(name)}/logs`, { params });
|
|
33
|
+
const logs: WorkerLogItem[] = response.data;
|
|
34
|
+
|
|
35
|
+
spinner.stop();
|
|
36
|
+
|
|
37
|
+
if (!logs || logs.length === 0) {
|
|
38
|
+
logger.info(`\n No se encontraron logs para "${name}"`);
|
|
39
|
+
console.log();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
logger.title(`Logs de "${name}" (${logs.length})`);
|
|
44
|
+
|
|
45
|
+
const rows = logs.map(log => [
|
|
46
|
+
formatDate(log.createdAt),
|
|
47
|
+
log.status === 'success' ? theme.success('ok')
|
|
48
|
+
: log.status === 'error' ? theme.error('error')
|
|
49
|
+
: theme.warning('timeout'),
|
|
50
|
+
`${log.durationMs}ms`,
|
|
51
|
+
log.triggerSource || '—',
|
|
52
|
+
log.error ? theme.error(truncate(log.error, 40)) : '—',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
console.log(createTable(
|
|
56
|
+
['Fecha', 'Estado', 'Duracion', 'Origen', 'Error'],
|
|
57
|
+
rows,
|
|
58
|
+
));
|
|
59
|
+
|
|
60
|
+
// Resumen
|
|
61
|
+
const successCount = logs.filter(l => l.status === 'success').length;
|
|
62
|
+
const errorCount = logs.filter(l => l.status === 'error').length;
|
|
63
|
+
const avgDuration = Math.round(logs.reduce((sum, l) => sum + l.durationMs, 0) / logs.length);
|
|
64
|
+
|
|
65
|
+
console.log();
|
|
66
|
+
logger.label('Exitosos', theme.success(`${successCount}`));
|
|
67
|
+
logger.label('Errores', errorCount > 0 ? theme.error(`${errorCount}`) : theme.muted('0'));
|
|
68
|
+
logger.label('Duracion promedio', `${avgDuration}ms`);
|
|
69
|
+
console.log();
|
|
70
|
+
|
|
71
|
+
if (options.dev) {
|
|
72
|
+
logger.warning('Ambiente: desarrollo');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
} catch (error: any) {
|
|
76
|
+
if (error.response?.status === 404) {
|
|
77
|
+
logger.error(`Worker "${name}" no encontrado`);
|
|
78
|
+
} else {
|
|
79
|
+
const message = error instanceof Error ? error.message : 'Error desconocido';
|
|
80
|
+
logger.error(message);
|
|
81
|
+
}
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
function truncate(text: string, max: number): string {
|
|
87
|
+
if (text.length <= max) return text;
|
|
88
|
+
return text.substring(0, max - 3) + '...';
|
|
89
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getStoredCredentials } from '../../utils/credentials';
|
|
3
|
+
import { createApiClient } from '../../utils/api';
|
|
4
|
+
import { logger } from '../../utils/logger';
|
|
5
|
+
import { createSpinner } from '../../utils/ui';
|
|
6
|
+
import { WorkerRemoveOptions } from '../../types/workers';
|
|
7
|
+
import readline from 'readline';
|
|
8
|
+
|
|
9
|
+
export const removeCommand = new Command('remove')
|
|
10
|
+
.description('Elimina un worker del workspace')
|
|
11
|
+
.argument('<name>', 'Nombre del worker a eliminar')
|
|
12
|
+
.option('-f, --force', 'Eliminar sin confirmacion', false)
|
|
13
|
+
.option('--dev', 'Usar ambiente de desarrollo', false)
|
|
14
|
+
.action(async (name: string, options: WorkerRemoveOptions) => {
|
|
15
|
+
try {
|
|
16
|
+
const credentials = await getStoredCredentials();
|
|
17
|
+
const api = createApiClient({
|
|
18
|
+
apiKey: credentials.apiKey,
|
|
19
|
+
workspace: credentials.workspace,
|
|
20
|
+
zone: credentials.zone,
|
|
21
|
+
dev: options.dev,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Verificar que el worker existe
|
|
25
|
+
const spinner = createSpinner(`Buscando worker "${name}"...`);
|
|
26
|
+
spinner.start();
|
|
27
|
+
|
|
28
|
+
let workerInfo: any;
|
|
29
|
+
try {
|
|
30
|
+
const response = await api.get(`/api/worker/${encodeURIComponent(name)}`);
|
|
31
|
+
workerInfo = response.data;
|
|
32
|
+
spinner.stop();
|
|
33
|
+
} catch (error: any) {
|
|
34
|
+
spinner.stop();
|
|
35
|
+
if (error.response?.status === 404) {
|
|
36
|
+
logger.error(`Worker "${name}" no encontrado`);
|
|
37
|
+
} else {
|
|
38
|
+
logger.error(error.response?.data?.message || error.message);
|
|
39
|
+
}
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Mostrar info del worker
|
|
44
|
+
logger.warning(`\n Vas a eliminar el siguiente worker:`);
|
|
45
|
+
logger.divider();
|
|
46
|
+
logger.label('Nombre', workerInfo.name);
|
|
47
|
+
logger.label('Tipo', workerInfo.type);
|
|
48
|
+
logger.label('Estado', workerInfo.status);
|
|
49
|
+
logger.label('Ejecuciones', `${workerInfo.executionCount || 0}`);
|
|
50
|
+
logger.divider();
|
|
51
|
+
|
|
52
|
+
// Confirmacion
|
|
53
|
+
if (!options.force) {
|
|
54
|
+
const confirmed = await askConfirmation(
|
|
55
|
+
`Eliminar worker "${name}"? Esta accion no se puede deshacer (y/N): `
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (!confirmed) {
|
|
59
|
+
logger.info('\n Operacion cancelada');
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const deleteSpinner = createSpinner(`Eliminando "${name}"...`);
|
|
65
|
+
deleteSpinner.start();
|
|
66
|
+
|
|
67
|
+
await api.delete(`/api/worker/${encodeURIComponent(name)}`);
|
|
68
|
+
|
|
69
|
+
deleteSpinner.succeed(`Worker "${name}" eliminado`);
|
|
70
|
+
console.log();
|
|
71
|
+
|
|
72
|
+
if (options.dev) {
|
|
73
|
+
logger.warning('Ambiente: desarrollo');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const message = error instanceof Error ? error.message : 'Error desconocido';
|
|
78
|
+
logger.error(message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function askConfirmation(question: string): Promise<boolean> {
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
const rl = readline.createInterface({
|
|
86
|
+
input: process.stdin,
|
|
87
|
+
output: process.stdout,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
rl.question(question, (answer) => {
|
|
91
|
+
rl.close();
|
|
92
|
+
resolve(answer.toLowerCase() === 'y');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|