plazbot-cli 0.4.1 → 0.5.1
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/README.md +67 -111
- package/dist/cli.js +26 -11
- package/dist/commands/agent/ai-config.js +264 -63
- package/dist/commands/agent/chat.js +16 -11
- package/dist/commands/agent/copy.js +49 -13
- package/dist/commands/agent/create.js +62 -17
- package/dist/commands/agent/delete.js +50 -39
- package/dist/commands/agent/enable-widget.js +47 -27
- package/dist/commands/agent/export.js +180 -91
- package/dist/commands/agent/files.js +155 -73
- package/dist/commands/agent/get.js +25 -19
- package/dist/commands/agent/index.js +12 -10
- package/dist/commands/agent/list.js +20 -11
- package/dist/commands/agent/monitor.js +103 -57
- package/dist/commands/agent/on-message.js +55 -32
- package/dist/commands/agent/set.js +230 -82
- package/dist/commands/agent/templates.js +259 -156
- package/dist/commands/agent/tools.js +356 -123
- package/dist/commands/agent/update.js +69 -28
- package/dist/commands/agent/validate.js +72 -40
- package/dist/commands/agent/wizard.js +240 -329
- package/dist/commands/auth/browser-login.js +37 -31
- package/dist/commands/auth/login.js +49 -45
- package/dist/commands/auth/logout.js +20 -11
- package/dist/commands/auth/status.js +36 -12
- package/dist/commands/auth/workspace.js +61 -27
- package/dist/commands/portal/add-agent.js +106 -45
- package/dist/commands/portal/add-link.js +25 -28
- package/dist/commands/portal/clear-links.js +39 -19
- package/dist/commands/portal/create.js +66 -37
- package/dist/commands/portal/delete.js +40 -45
- package/dist/commands/portal/get.js +21 -54
- package/dist/commands/portal/index.js +12 -12
- package/dist/commands/portal/list.js +34 -52
- package/dist/commands/portal/shared.js +92 -0
- package/dist/commands/portal/update.js +100 -70
- package/dist/commands/whatsapp/broadcast.js +169 -77
- package/dist/commands/whatsapp/channels.js +167 -65
- package/dist/commands/whatsapp/chat.js +45 -46
- package/dist/commands/whatsapp/connect-flow.js +16 -15
- package/dist/commands/whatsapp/connect.js +36 -21
- package/dist/commands/whatsapp/delete-webhook.js +42 -25
- package/dist/commands/whatsapp/index.js +15 -16
- package/dist/commands/whatsapp/register-webhook.js +43 -26
- package/dist/commands/whatsapp/send-template.js +62 -61
- package/dist/commands/whatsapp/send.js +46 -33
- package/dist/commands/whatsapp/setup.js +62 -52
- package/dist/commands/whatsapp/shared.js +75 -0
- package/dist/commands/whatsapp/widget.js +82 -45
- package/dist/commands/workers/deploy.js +80 -58
- package/dist/commands/workers/index.js +8 -8
- package/dist/commands/workers/list.js +42 -20
- package/dist/commands/workers/logs.js +139 -36
- package/dist/commands/workers/remove.js +45 -47
- package/dist/commands/workers/secret.js +159 -83
- package/dist/commands/workers/test.js +108 -60
- package/dist/schemas/agent.config.schema.json +159 -8
- package/dist/studio/api/sseClient.js +1 -1
- package/dist/studio/api/studioApi.js +8 -0
- package/dist/studio/components/App.js +12 -0
- package/dist/studio/components/Footer.js +1 -1
- package/dist/studio/index.js +74 -14
- package/dist/studio/runOneShot.js +62 -23
- package/dist/studio/runRepl.js +5 -5
- package/dist/studio/slash/handlers.js +11 -8
- package/dist/studio/state/store.js +1 -1
- package/dist/utils/agent-errors.js +14 -11
- package/dist/utils/agent-patch.js +60 -0
- package/dist/utils/banner.js +9 -5
- package/dist/utils/confirm.js +31 -0
- package/dist/utils/credentials.js +13 -3
- package/dist/utils/logger.js +13 -10
- package/dist/utils/output.js +78 -0
- package/dist/utils/phone.js +22 -0
- package/dist/utils/ui.js +14 -11
- package/package.json +3 -3
|
@@ -1,68 +1,105 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { Agent } from 'plazbot';
|
|
3
|
-
import { getStoredCredentials } from '../../utils/credentials.js';
|
|
4
2
|
import { logger } from '../../utils/logger.js';
|
|
5
|
-
import { addExamples } from '../../utils/help.js';
|
|
3
|
+
import { addExamples, addNotes } from '../../utils/help.js';
|
|
6
4
|
import { createSpinner } from '../../utils/ui.js';
|
|
5
|
+
import { jsonOption, isJsonMode, printJson, fail, CliError } from '../../utils/output.js';
|
|
6
|
+
import { phoneOption, normalizePhone } from '../../utils/phone.js';
|
|
7
|
+
import { fetchFullAgent, getAgentClient, updateAgentMerged } from './shared.js';
|
|
7
8
|
const widgetGroup = new Command('widget')
|
|
8
|
-
.description('
|
|
9
|
+
.description('Show or hide the WhatsApp click-to-chat button on an agent web widget');
|
|
9
10
|
addExamples(widgetGroup, [
|
|
10
|
-
{ description: 'Enable the WhatsApp
|
|
11
|
-
command: 'plazbot whatsapp widget enable
|
|
12
|
-
{ description: 'Disable
|
|
13
|
-
command: 'plazbot whatsapp widget disable
|
|
11
|
+
{ description: 'Enable the WhatsApp button for a number',
|
|
12
|
+
command: 'plazbot whatsapp widget enable age_AbcDef123 -p +51912345678' },
|
|
13
|
+
{ description: 'Disable it',
|
|
14
|
+
command: 'plazbot whatsapp widget disable age_AbcDef123' },
|
|
14
15
|
]);
|
|
15
16
|
widgetGroup.command('enable')
|
|
16
|
-
.description('Enable the WhatsApp
|
|
17
|
-
.argument('<agentId>', 'Agent ID')
|
|
18
|
-
.
|
|
17
|
+
.description('Enable the WhatsApp button on an agent web widget')
|
|
18
|
+
.argument('<agentId>', 'Agent ID (e.g. age_AbcDef123)')
|
|
19
|
+
.addOption(phoneOption('WhatsApp number the button opens, e.g. +51912345678 (builds https://wa.me/<number>)'))
|
|
20
|
+
.option('-u, --url <url>', 'Custom click-to-chat URL instead of --phone (e.g. https://wa.me/51912345678?text=Hi)')
|
|
21
|
+
.addOption(jsonOption())
|
|
19
22
|
.option('--dev', 'Use development environment', false)
|
|
20
23
|
.action(async (agentId, options) => {
|
|
21
24
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
if (options.phone && options.url) {
|
|
26
|
+
throw new CliError('Use either --phone or --url, not both.', 'conflicting_options');
|
|
27
|
+
}
|
|
28
|
+
let url;
|
|
29
|
+
if (options.phone) {
|
|
30
|
+
url = `https://wa.me/${normalizePhone(options.phone)}`;
|
|
31
|
+
}
|
|
32
|
+
else if (options.url) {
|
|
33
|
+
url = options.url.trim();
|
|
34
|
+
if (!/^https?:\/\/\S+$/i.test(url)) {
|
|
35
|
+
throw new CliError(`Invalid URL "${options.url}".`, 'invalid_url', 'Use a full URL, e.g. https://wa.me/51912345678');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
throw new CliError('Missing WhatsApp number.', 'missing_phone', 'Pass --phone +51912345678, or a custom link with --url <url>.');
|
|
40
|
+
}
|
|
41
|
+
const { agent, credentials } = await getAgentClient(options.dev);
|
|
42
|
+
const spinner = createSpinner('Enabling WhatsApp button...').start();
|
|
43
|
+
try {
|
|
44
|
+
const current = await fetchFullAgent(agent, agentId);
|
|
45
|
+
await updateAgentMerged(credentials, options.dev, agentId, current, { enableWhatsappWidget: true, urlWhatsappWidget: url });
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
spinner.stop();
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
spinner.succeed('WhatsApp button enabled');
|
|
52
|
+
if (isJsonMode())
|
|
53
|
+
printJson({ id: agentId, enableWhatsappWidget: true, url });
|
|
54
|
+
else
|
|
55
|
+
logger.label('URL', url);
|
|
37
56
|
}
|
|
38
57
|
catch (error) {
|
|
39
|
-
|
|
40
|
-
logger.error(message);
|
|
41
|
-
process.exit(1);
|
|
58
|
+
fail(error);
|
|
42
59
|
}
|
|
43
60
|
});
|
|
44
61
|
widgetGroup.command('disable')
|
|
45
|
-
.description('Disable the WhatsApp
|
|
46
|
-
.argument('<agentId>', 'Agent ID')
|
|
62
|
+
.description('Disable the WhatsApp button on an agent web widget')
|
|
63
|
+
.argument('<agentId>', 'Agent ID (e.g. age_AbcDef123)')
|
|
64
|
+
.addOption(jsonOption())
|
|
47
65
|
.option('--dev', 'Use development environment', false)
|
|
48
66
|
.action(async (agentId, options) => {
|
|
49
67
|
try {
|
|
50
|
-
const credentials = await
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
spinner.succeed('WhatsApp
|
|
68
|
+
const { agent, credentials } = await getAgentClient(options.dev);
|
|
69
|
+
const spinner = createSpinner('Disabling WhatsApp button...').start();
|
|
70
|
+
try {
|
|
71
|
+
const current = await fetchFullAgent(agent, agentId);
|
|
72
|
+
await updateAgentMerged(credentials, options.dev, agentId, current, { enableWhatsappWidget: false });
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
spinner.stop();
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
spinner.succeed('WhatsApp button disabled');
|
|
79
|
+
if (isJsonMode())
|
|
80
|
+
printJson({ id: agentId, enableWhatsappWidget: false });
|
|
61
81
|
}
|
|
62
82
|
catch (error) {
|
|
63
|
-
|
|
64
|
-
logger.error(message);
|
|
65
|
-
process.exit(1);
|
|
83
|
+
fail(error);
|
|
66
84
|
}
|
|
67
85
|
});
|
|
86
|
+
const enableCmd = widgetGroup.commands.find(c => c.name() === 'enable');
|
|
87
|
+
const disableCmd = widgetGroup.commands.find(c => c.name() === 'disable');
|
|
88
|
+
addExamples(enableCmd, [
|
|
89
|
+
{ description: 'Button that opens a chat with your number',
|
|
90
|
+
command: 'plazbot whatsapp widget enable age_AbcDef123 -p +51912345678' },
|
|
91
|
+
{ description: 'Custom link with a pre-filled message',
|
|
92
|
+
command: 'plazbot whatsapp widget enable age_AbcDef123 -u "https://wa.me/51912345678?text=Hi"' },
|
|
93
|
+
{ description: 'Machine-readable output',
|
|
94
|
+
command: 'plazbot whatsapp widget enable age_AbcDef123 -p +51912345678 --json' },
|
|
95
|
+
]);
|
|
96
|
+
addNotes(enableCmd, [
|
|
97
|
+
'Pass one of --phone or --url. Only the widget fields change; the rest of the agent is kept.',
|
|
98
|
+
]);
|
|
99
|
+
addExamples(disableCmd, [
|
|
100
|
+
{ description: 'Hide the WhatsApp button',
|
|
101
|
+
command: 'plazbot whatsapp widget disable age_AbcDef123' },
|
|
102
|
+
{ description: 'Machine-readable output',
|
|
103
|
+
command: 'plazbot whatsapp widget disable age_AbcDef123 --json' },
|
|
104
|
+
]);
|
|
68
105
|
export const widgetCommand = widgetGroup;
|
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { getStoredCredentials } from '../../utils/credentials.js';
|
|
5
5
|
import { createApiClient } from '../../utils/api.js';
|
|
6
6
|
import { logger } from '../../utils/logger.js';
|
|
7
|
-
import { addExamples } from '../../utils/help.js';
|
|
7
|
+
import { addExamples, addNotes } from '../../utils/help.js';
|
|
8
8
|
import { createSpinner, createTable, theme } from '../../utils/ui.js';
|
|
9
|
+
import { jsonOption, isJsonMode, printJson, fail, CliError, describeError } from '../../utils/output.js';
|
|
9
10
|
export const deployCommand = new Command('deploy')
|
|
10
|
-
.description('Deploy a worker
|
|
11
|
-
.argument('[file]', 'Worker TypeScript file (
|
|
12
|
-
.option('-n, --name <name>', 'Worker name
|
|
13
|
-
.option('
|
|
11
|
+
.description('Deploy a worker file, or every worker in ./workers')
|
|
12
|
+
.argument('[file]', 'Worker TypeScript file (default: every *.ts file in ./workers)')
|
|
13
|
+
.option('-n, --name <name>', 'Worker name, overrides the one in the file (single file only)')
|
|
14
|
+
.option('--description <text>', 'Worker description (single file only)')
|
|
15
|
+
.addOption(new Option('-d <text>', 'Alias of --description').hideHelp())
|
|
16
|
+
.addOption(jsonOption())
|
|
14
17
|
.option('--dev', 'Use development environment', false)
|
|
15
18
|
.action(async (file, options) => {
|
|
16
19
|
try {
|
|
17
|
-
const
|
|
18
|
-
// Buscar archivos de workers
|
|
20
|
+
const description = options.description ?? options.d;
|
|
19
21
|
const files = await resolveWorkerFiles(file);
|
|
20
22
|
if (files.length === 0) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
throw new CliError('No worker files found to deploy.', 'no_worker_files', 'Pass a file (plazbot workers deploy ./workers/my-tool.ts) or add *.ts files to ./workers.');
|
|
24
|
+
}
|
|
25
|
+
if (files.length > 1 && (options.name || description)) {
|
|
26
|
+
throw new CliError(`--name and --description only apply to a single file, but ${files.length} files were found in ./workers.`, 'name_with_multiple_files', 'Deploy one file at a time: plazbot workers deploy ./workers/my-tool.ts -n "my-tool"');
|
|
25
27
|
}
|
|
28
|
+
const credentials = await getStoredCredentials();
|
|
26
29
|
const api = createApiClient({
|
|
27
30
|
apiKey: credentials.apiKey,
|
|
28
31
|
workspace: credentials.workspace,
|
|
@@ -30,103 +33,122 @@ export const deployCommand = new Command('deploy')
|
|
|
30
33
|
dev: options.dev,
|
|
31
34
|
});
|
|
32
35
|
logger.title(`Deploying ${files.length} worker${files.length > 1 ? 's' : ''}`);
|
|
33
|
-
console.log();
|
|
34
36
|
const results = [];
|
|
35
37
|
for (const filePath of files) {
|
|
36
38
|
const fileName = path.basename(filePath);
|
|
37
39
|
const spinner = createSpinner(`Deploying ${fileName}...`);
|
|
38
40
|
spinner.start();
|
|
39
41
|
try {
|
|
40
|
-
// Leer archivo fuente
|
|
41
42
|
const sourceCode = await fs.readFile(filePath, 'utf-8');
|
|
42
|
-
// Enviar al API
|
|
43
43
|
const response = await api.post('/api/worker/deploy', {
|
|
44
44
|
sourceCode,
|
|
45
45
|
fileName,
|
|
46
46
|
name: files.length === 1 ? options.name : undefined,
|
|
47
|
-
description: files.length === 1 ?
|
|
47
|
+
description: files.length === 1 ? description : undefined,
|
|
48
48
|
});
|
|
49
49
|
const data = response.data;
|
|
50
|
+
if (data?.success === false || !data?.name) {
|
|
51
|
+
throw new CliError(data?.message || 'The API did not return the deployed worker.', 'deploy_failed');
|
|
52
|
+
}
|
|
50
53
|
spinner.succeed(`${fileName} → ${data.name} (${data.type})`);
|
|
51
|
-
results.push({
|
|
54
|
+
results.push({
|
|
55
|
+
file: fileName,
|
|
56
|
+
name: data.name,
|
|
57
|
+
type: data.type,
|
|
58
|
+
status: 'deployed',
|
|
59
|
+
workerStatus: data.status,
|
|
60
|
+
webhookUrl: data.webhookUrl || undefined,
|
|
61
|
+
cronExpression: data.cronExpression || undefined,
|
|
62
|
+
});
|
|
52
63
|
}
|
|
53
64
|
catch (error) {
|
|
54
|
-
const
|
|
55
|
-
spinner.fail(`${fileName}: ${
|
|
56
|
-
results.push({ file: fileName, error:
|
|
65
|
+
const { message } = describeError(error);
|
|
66
|
+
spinner.fail(`${fileName}: ${message}`);
|
|
67
|
+
results.push({ file: fileName, status: 'failed', error: message });
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
const success = results.filter(r => r.status === 'deployed');
|
|
71
|
+
const failed = results.filter(r => r.status === 'failed');
|
|
72
|
+
if (isJsonMode()) {
|
|
73
|
+
printJson(results);
|
|
74
|
+
if (failed.length > 0)
|
|
75
|
+
process.exit(1);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
63
78
|
if (success.length > 0) {
|
|
64
79
|
logger.success(`${success.length} worker${success.length > 1 ? 's' : ''} deployed`);
|
|
65
|
-
|
|
66
|
-
r.
|
|
67
|
-
r.
|
|
68
|
-
r.
|
|
69
|
-
r.
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
console.log(createTable(['File', 'Name', 'Type', 'Status', 'URL/Cron'], success.map(r => [
|
|
81
|
+
r.file,
|
|
82
|
+
r.name,
|
|
83
|
+
r.type || '—',
|
|
84
|
+
r.workerStatus || '—',
|
|
85
|
+
r.webhookUrl || r.cronExpression || '—',
|
|
86
|
+
])));
|
|
72
87
|
}
|
|
73
|
-
|
|
74
|
-
logger.warning(`${failed.length} worker${failed.length > 1 ? 's' : ''} failed`);
|
|
75
|
-
failed.forEach(f => logger.dim(` ${f.file}: ${f.error}`));
|
|
76
|
-
}
|
|
77
|
-
// Mostrar webhook URLs si hay
|
|
78
|
-
const webhooks = success.filter(r => r.result.webhookUrl);
|
|
88
|
+
const webhooks = success.filter(r => r.webhookUrl);
|
|
79
89
|
if (webhooks.length > 0) {
|
|
80
|
-
console.log();
|
|
81
90
|
logger.title('Webhook URLs');
|
|
82
|
-
webhooks.forEach(w =>
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
webhooks.forEach(w => logger.label(w.name, theme.secondary(w.webhookUrl)));
|
|
92
|
+
}
|
|
93
|
+
if (failed.length > 0) {
|
|
94
|
+
logger.warning(`${failed.length} of ${results.length} worker${results.length > 1 ? 's' : ''} failed to deploy`);
|
|
95
|
+
failed.forEach(f => logger.dim(` ${f.file}: ${f.error}`));
|
|
85
96
|
}
|
|
86
97
|
console.log();
|
|
87
98
|
if (options.dev) {
|
|
88
99
|
logger.warning('Environment: development');
|
|
89
100
|
}
|
|
101
|
+
if (failed.length > 0)
|
|
102
|
+
process.exit(1);
|
|
90
103
|
}
|
|
91
104
|
catch (error) {
|
|
92
|
-
|
|
93
|
-
logger.error(message);
|
|
94
|
-
process.exit(1);
|
|
105
|
+
fail(error);
|
|
95
106
|
}
|
|
96
107
|
});
|
|
97
108
|
addExamples(deployCommand, [
|
|
98
109
|
{ description: 'Deploy a single worker file',
|
|
99
|
-
command: 'plazbot workers deploy ./workers/
|
|
100
|
-
{ description: 'Deploy every
|
|
110
|
+
command: 'plazbot workers deploy ./workers/get-stock.ts' },
|
|
111
|
+
{ description: 'Deploy every *.ts file inside ./workers',
|
|
101
112
|
command: 'plazbot workers deploy' },
|
|
102
113
|
{ description: 'Deploy with a custom name and description',
|
|
103
|
-
command: 'plazbot workers deploy ./workers/cron.ts -n "
|
|
104
|
-
{ description: 'Deploy
|
|
105
|
-
command: 'plazbot workers deploy
|
|
114
|
+
command: 'plazbot workers deploy ./workers/cron.ts -n "daily-report" --description "Generates daily KPIs"' },
|
|
115
|
+
{ description: 'Deploy everything and get per-file results as JSON (CI)',
|
|
116
|
+
command: 'plazbot workers deploy --json' },
|
|
117
|
+
]);
|
|
118
|
+
addNotes(deployCommand, [
|
|
119
|
+
'Without [file], every *.ts file in ./workers is deployed (*.d.ts files are skipped).',
|
|
120
|
+
'--name and --description only work when deploying a single file.',
|
|
121
|
+
'Only the single source file is uploaded: relative imports and npm dependencies are not bundled.',
|
|
122
|
+
'The TypeScript is compiled on the Plazbot server; compile errors show up as a failed file.',
|
|
123
|
+
'Exits with code 1 if any file fails to deploy.',
|
|
106
124
|
]);
|
|
107
125
|
// Resuelve los archivos a desplegar
|
|
108
126
|
async function resolveWorkerFiles(file) {
|
|
109
127
|
if (file) {
|
|
110
|
-
// Archivo especifico
|
|
111
128
|
const resolved = path.resolve(file);
|
|
129
|
+
let isFile = false;
|
|
112
130
|
try {
|
|
113
|
-
await fs.
|
|
114
|
-
return [resolved];
|
|
131
|
+
isFile = (await fs.stat(resolved)).isFile();
|
|
115
132
|
}
|
|
116
133
|
catch {
|
|
117
|
-
|
|
134
|
+
isFile = false;
|
|
135
|
+
}
|
|
136
|
+
if (!isFile) {
|
|
137
|
+
throw new CliError(`File not found: ${file}.`, 'file_not_found', 'Check the path to your worker file.');
|
|
118
138
|
}
|
|
139
|
+
return [resolved];
|
|
119
140
|
}
|
|
120
141
|
// Buscar en ./workers/ automaticamente
|
|
121
142
|
const workersDir = path.resolve('workers');
|
|
143
|
+
let entries;
|
|
122
144
|
try {
|
|
123
|
-
|
|
124
|
-
const tsFiles = entries
|
|
125
|
-
.filter(e => e.endsWith('.ts') && !e.endsWith('.d.ts'))
|
|
126
|
-
.map(e => path.join(workersDir, e));
|
|
127
|
-
return tsFiles;
|
|
145
|
+
entries = await fs.readdir(workersDir);
|
|
128
146
|
}
|
|
129
147
|
catch {
|
|
130
148
|
return [];
|
|
131
149
|
}
|
|
150
|
+
return entries
|
|
151
|
+
.filter(e => e.endsWith('.ts') && !e.endsWith('.d.ts'))
|
|
152
|
+
.sort()
|
|
153
|
+
.map(e => path.join(workersDir, e));
|
|
132
154
|
}
|
|
@@ -7,7 +7,7 @@ import { removeCommand } from './remove.js';
|
|
|
7
7
|
import { secretCommand } from './secret.js';
|
|
8
8
|
import { testCommand } from './test.js';
|
|
9
9
|
export const workersCommands = new Command('workers')
|
|
10
|
-
.description('
|
|
10
|
+
.description('Manage workers (tools, syncs, schedules, webhooks)')
|
|
11
11
|
.addCommand(deployCommand)
|
|
12
12
|
.addCommand(listCommand)
|
|
13
13
|
.addCommand(logsCommand)
|
|
@@ -15,14 +15,14 @@ export const workersCommands = new Command('workers')
|
|
|
15
15
|
.addCommand(secretCommand)
|
|
16
16
|
.addCommand(testCommand);
|
|
17
17
|
addExamples(workersCommands, [
|
|
18
|
-
{ description: 'Deploy every worker file in ./workers
|
|
18
|
+
{ description: 'Deploy every worker file in ./workers',
|
|
19
19
|
command: 'plazbot workers deploy' },
|
|
20
20
|
{ description: 'List active workers',
|
|
21
21
|
command: 'plazbot workers list -s active' },
|
|
22
|
-
{ description: 'Run a worker with
|
|
23
|
-
command: 'plazbot workers test
|
|
24
|
-
{ description: 'Save a secret
|
|
25
|
-
command: 'plazbot workers secret set STRIPE_API_KEY
|
|
26
|
-
{ description: '
|
|
27
|
-
command: 'plazbot workers logs
|
|
22
|
+
{ description: 'Run a worker with JSON data',
|
|
23
|
+
command: 'plazbot workers test get-stock -d \'{"product":"iPhone 15"}\'' },
|
|
24
|
+
{ description: 'Save a secret from stdin (available as plz.env.STRIPE_API_KEY)',
|
|
25
|
+
command: 'echo "$STRIPE_KEY" | plazbot workers secret set STRIPE_API_KEY' },
|
|
26
|
+
{ description: 'Follow the executions of a worker in real time',
|
|
27
|
+
command: 'plazbot workers logs get-stock --follow' },
|
|
28
28
|
]);
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
2
|
import { getStoredCredentials } from '../../utils/credentials.js';
|
|
3
3
|
import { createApiClient } from '../../utils/api.js';
|
|
4
4
|
import { logger } from '../../utils/logger.js';
|
|
5
5
|
import { addExamples } from '../../utils/help.js';
|
|
6
6
|
import { createSpinner, createTable, theme, formatDate } from '../../utils/ui.js';
|
|
7
|
+
import { jsonOption, isJsonMode, printJson, fail } from '../../utils/output.js';
|
|
8
|
+
const WORKER_TYPES = ['tool', 'worker', 'sync', 'schedule', 'webhook'];
|
|
9
|
+
const WORKER_STATUSES = ['active', 'inactive', 'error'];
|
|
7
10
|
export const listCommand = new Command('list')
|
|
8
|
-
.description('List
|
|
9
|
-
.
|
|
10
|
-
.
|
|
11
|
+
.description('List the workers in the workspace')
|
|
12
|
+
.addOption(new Option('-t, --type <type>', 'Filter by type').choices(WORKER_TYPES))
|
|
13
|
+
.addOption(new Option('-s, --status <status>', 'Filter by status').choices(WORKER_STATUSES))
|
|
14
|
+
.addOption(jsonOption())
|
|
11
15
|
.option('--dev', 'Use development environment', false)
|
|
12
16
|
.action(async (options) => {
|
|
13
17
|
try {
|
|
@@ -25,12 +29,36 @@ export const listCommand = new Command('list')
|
|
|
25
29
|
params.type = options.type;
|
|
26
30
|
if (options.status)
|
|
27
31
|
params.status = options.status;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
let workers;
|
|
33
|
+
try {
|
|
34
|
+
const response = await api.get('/api/worker', { params });
|
|
35
|
+
workers = Array.isArray(response.data) ? response.data : [];
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
spinner.stop();
|
|
39
|
+
}
|
|
40
|
+
if (isJsonMode()) {
|
|
41
|
+
printJson(workers.map(w => ({
|
|
42
|
+
id: w.id,
|
|
43
|
+
name: w.name,
|
|
44
|
+
type: w.type,
|
|
45
|
+
status: w.status,
|
|
46
|
+
description: w.description ?? null,
|
|
47
|
+
cronExpression: w.cronExpression ?? null,
|
|
48
|
+
webhookUrl: w.webhookUrl ?? null,
|
|
49
|
+
executionCount: w.executionCount ?? 0,
|
|
50
|
+
errorCount: w.errorCount ?? 0,
|
|
51
|
+
avgDurationMs: w.avgDurationMs ?? 0,
|
|
52
|
+
lastExecutedAt: w.lastExecutedAt ?? null,
|
|
53
|
+
lastError: w.lastError ?? null,
|
|
54
|
+
createdAt: w.createdAt,
|
|
55
|
+
updatedAt: w.updatedAt,
|
|
56
|
+
})));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (workers.length === 0) {
|
|
32
60
|
logger.info('\n No workers found in this workspace');
|
|
33
|
-
logger.dim('
|
|
61
|
+
logger.dim('Deploy your first worker: plazbot workers deploy ./workers/my-tool.ts');
|
|
34
62
|
console.log();
|
|
35
63
|
return;
|
|
36
64
|
}
|
|
@@ -41,20 +69,16 @@ export const listCommand = new Command('list')
|
|
|
41
69
|
w.status === 'active' ? theme.success('active')
|
|
42
70
|
: w.status === 'error' ? theme.error('error')
|
|
43
71
|
: theme.muted('inactive'),
|
|
44
|
-
`${w.executionCount}`,
|
|
72
|
+
`${w.executionCount ?? 0}`,
|
|
45
73
|
w.errorCount > 0 ? theme.error(`${w.errorCount}`) : theme.muted('0'),
|
|
46
74
|
w.avgDurationMs > 0 ? `${w.avgDurationMs}ms` : '—',
|
|
47
75
|
w.lastExecutedAt ? formatDate(w.lastExecutedAt) : '—',
|
|
48
76
|
]);
|
|
49
77
|
console.log(createTable(['Name', 'Type', 'Status', 'Executions', 'Errors', 'Duration', 'Last execution'], rows));
|
|
50
|
-
// Mostrar workers con error
|
|
51
78
|
const errored = workers.filter(w => w.status === 'error');
|
|
52
79
|
if (errored.length > 0) {
|
|
53
|
-
console.log();
|
|
54
80
|
logger.warning('Workers with errors:');
|
|
55
|
-
errored.forEach(w => {
|
|
56
|
-
logger.dim(` ${w.name}: ${w.lastError || 'no details'}`);
|
|
57
|
-
});
|
|
81
|
+
errored.forEach(w => logger.dim(` ${w.name}: ${w.lastError || 'no details'}`));
|
|
58
82
|
}
|
|
59
83
|
console.log();
|
|
60
84
|
if (options.dev) {
|
|
@@ -62,9 +86,7 @@ export const listCommand = new Command('list')
|
|
|
62
86
|
}
|
|
63
87
|
}
|
|
64
88
|
catch (error) {
|
|
65
|
-
|
|
66
|
-
logger.error(message);
|
|
67
|
-
process.exit(1);
|
|
89
|
+
fail(error);
|
|
68
90
|
}
|
|
69
91
|
});
|
|
70
92
|
addExamples(listCommand, [
|
|
@@ -74,8 +96,8 @@ addExamples(listCommand, [
|
|
|
74
96
|
command: 'plazbot workers list -t schedule' },
|
|
75
97
|
{ description: 'Show only workers with errors',
|
|
76
98
|
command: 'plazbot workers list -s error' },
|
|
77
|
-
{ description: '
|
|
78
|
-
command: 'plazbot workers list -t webhook -s active' },
|
|
99
|
+
{ description: 'Get active webhooks as JSON',
|
|
100
|
+
command: 'plazbot workers list -t webhook -s active --json' },
|
|
79
101
|
]);
|
|
80
102
|
function typeLabel(type) {
|
|
81
103
|
const labels = {
|