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,156 +1,232 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
2
3
|
import { getStoredCredentials } from '../../utils/credentials.js';
|
|
3
4
|
import { createApiClient } from '../../utils/api.js';
|
|
4
5
|
import { logger } from '../../utils/logger.js';
|
|
5
|
-
import { addExamples } from '../../utils/help.js';
|
|
6
|
+
import { addExamples, addNotes } from '../../utils/help.js';
|
|
6
7
|
import { createSpinner, createTable, theme, formatDate } from '../../utils/ui.js';
|
|
7
|
-
import
|
|
8
|
+
import { jsonOption, isJsonMode, printJson, fail, CliError } from '../../utils/output.js';
|
|
9
|
+
import { addYesOption, confirmAction } from '../../utils/confirm.js';
|
|
10
|
+
const SECRET_KEY_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
11
|
+
function validateKey(key) {
|
|
12
|
+
if (!SECRET_KEY_RE.test(key)) {
|
|
13
|
+
throw new CliError(`Invalid secret name "${key}". Use UPPER_SNAKE_CASE: uppercase letters, digits and underscores, starting with a letter.`, 'invalid_secret_key', 'Example: STRIPE_API_KEY');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function apiFor(options, credentials) {
|
|
17
|
+
return createApiClient({
|
|
18
|
+
apiKey: credentials.apiKey,
|
|
19
|
+
workspace: credentials.workspace,
|
|
20
|
+
zone: credentials.zone,
|
|
21
|
+
dev: options.dev,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// Reads the whole stdin (piped input) and strips the trailing newline added by echo.
|
|
25
|
+
async function readStdin() {
|
|
26
|
+
const chunks = [];
|
|
27
|
+
for await (const chunk of process.stdin) {
|
|
28
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
29
|
+
}
|
|
30
|
+
return Buffer.concat(chunks).toString('utf-8').replace(/\r?\n$/, '');
|
|
31
|
+
}
|
|
32
|
+
async function resolveSecretValue(key, value) {
|
|
33
|
+
if (value !== undefined)
|
|
34
|
+
return value;
|
|
35
|
+
if (!process.stdin.isTTY) {
|
|
36
|
+
const piped = await readStdin();
|
|
37
|
+
if (!piped) {
|
|
38
|
+
throw new CliError('No secret value received on stdin.', 'missing_secret_value', `Example: echo "$VALUE" | plazbot workers secret set ${key}`);
|
|
39
|
+
}
|
|
40
|
+
return piped;
|
|
41
|
+
}
|
|
42
|
+
if (isJsonMode()) {
|
|
43
|
+
throw new CliError('The secret value is required in --json mode.', 'missing_secret_value', `Pipe it through stdin: echo "$VALUE" | plazbot workers secret set ${key} --json`);
|
|
44
|
+
}
|
|
45
|
+
const { secret } = await inquirer.prompt([{
|
|
46
|
+
type: 'password',
|
|
47
|
+
name: 'secret',
|
|
48
|
+
message: `Value for ${key}:`,
|
|
49
|
+
mask: '*',
|
|
50
|
+
validate: (v) => v.length > 0 || 'The value cannot be empty',
|
|
51
|
+
}]);
|
|
52
|
+
return secret;
|
|
53
|
+
}
|
|
8
54
|
// ── Comando padre: secret ───────────────────────────────────────────────────
|
|
9
55
|
export const secretCommand = new Command('secret')
|
|
10
|
-
.description('Manage worker secrets (
|
|
56
|
+
.description('Manage worker secrets (available as plz.env.KEY)')
|
|
11
57
|
.addCommand(secretSetCommand())
|
|
12
58
|
.addCommand(secretListCommand())
|
|
13
59
|
.addCommand(secretRemoveCommand());
|
|
14
60
|
addExamples(secretCommand, [
|
|
15
|
-
{ description: 'Save (
|
|
16
|
-
command: 'plazbot workers secret set STRIPE_API_KEY
|
|
17
|
-
{ description: '
|
|
61
|
+
{ description: 'Save a secret (prompts for the value with hidden input)',
|
|
62
|
+
command: 'plazbot workers secret set STRIPE_API_KEY' },
|
|
63
|
+
{ description: 'Save a secret from stdin (scripts/CI)',
|
|
64
|
+
command: 'echo "$STRIPE_KEY" | plazbot workers secret set STRIPE_API_KEY' },
|
|
65
|
+
{ description: 'List every secret (values are never shown)',
|
|
18
66
|
command: 'plazbot workers secret list' },
|
|
19
|
-
{ description: 'Delete a secret with confirmation prompt',
|
|
20
|
-
command: 'plazbot workers secret remove STRIPE_API_KEY' },
|
|
21
67
|
{ description: 'Delete a secret without confirmation',
|
|
22
|
-
command: 'plazbot workers secret remove STRIPE_API_KEY
|
|
68
|
+
command: 'plazbot workers secret remove STRIPE_API_KEY --yes' },
|
|
23
69
|
]);
|
|
24
70
|
// ── secret set ──────────────────────────────────────────────────────────────
|
|
25
71
|
function secretSetCommand() {
|
|
26
|
-
|
|
72
|
+
const cmd = new Command('set')
|
|
27
73
|
.description('Create or update a secret')
|
|
28
|
-
.argument('<key>', 'Secret name (e.g
|
|
29
|
-
.argument('
|
|
74
|
+
.argument('<key>', 'Secret name in UPPER_SNAKE_CASE (e.g. STRIPE_API_KEY)')
|
|
75
|
+
.argument('[value]', 'Secret value (omit it to be prompted, or pipe it through stdin)')
|
|
76
|
+
.addOption(jsonOption())
|
|
30
77
|
.option('--dev', 'Use development environment', false)
|
|
31
78
|
.action(async (key, value, options) => {
|
|
32
79
|
try {
|
|
80
|
+
validateKey(key);
|
|
81
|
+
const secretValue = await resolveSecretValue(key, value);
|
|
82
|
+
if (!secretValue.trim()) {
|
|
83
|
+
throw new CliError('The secret value cannot be empty.', 'missing_secret_value');
|
|
84
|
+
}
|
|
33
85
|
const credentials = await getStoredCredentials();
|
|
34
|
-
const api =
|
|
35
|
-
apiKey: credentials.apiKey,
|
|
36
|
-
workspace: credentials.workspace,
|
|
37
|
-
zone: credentials.zone,
|
|
38
|
-
dev: options.dev,
|
|
39
|
-
});
|
|
86
|
+
const api = apiFor(options, credentials);
|
|
40
87
|
const spinner = createSpinner(`Saving secret "${key}"...`);
|
|
41
88
|
spinner.start();
|
|
42
|
-
|
|
89
|
+
try {
|
|
90
|
+
const response = await api.put('/api/worker/secrets', { key, value: secretValue });
|
|
91
|
+
if (response.data?.success === false) {
|
|
92
|
+
throw new CliError(response.data.message || `Could not save secret "${key}".`, 'secret_save_failed');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
spinner.stop();
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
43
99
|
spinner.succeed(`Secret "${key}" saved`);
|
|
44
|
-
|
|
100
|
+
if (isJsonMode()) {
|
|
101
|
+
printJson({ key, saved: true });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
logger.dim(`Available in your workers as plz.env.${key}`);
|
|
105
|
+
if (value !== undefined) {
|
|
106
|
+
logger.dim('Tip: omit the value (or pipe it through stdin) to keep it out of your shell history.');
|
|
107
|
+
}
|
|
45
108
|
console.log();
|
|
46
109
|
if (options.dev) {
|
|
47
110
|
logger.warning('Environment: development');
|
|
48
111
|
}
|
|
49
112
|
}
|
|
50
113
|
catch (error) {
|
|
51
|
-
|
|
52
|
-
logger.error(message);
|
|
53
|
-
process.exit(1);
|
|
114
|
+
fail(error);
|
|
54
115
|
}
|
|
55
116
|
});
|
|
117
|
+
addExamples(cmd, [
|
|
118
|
+
{ description: 'Prompt for the value with hidden input (recommended)',
|
|
119
|
+
command: 'plazbot workers secret set STRIPE_API_KEY' },
|
|
120
|
+
{ description: 'Read the value from stdin',
|
|
121
|
+
command: 'echo "$STRIPE_KEY" | plazbot workers secret set STRIPE_API_KEY' },
|
|
122
|
+
{ description: 'Read the value from a file',
|
|
123
|
+
command: 'plazbot workers secret set GOOGLE_CREDENTIALS < ./service-account.json' },
|
|
124
|
+
{ description: 'Pass the value inline (stays in your shell history)',
|
|
125
|
+
command: 'plazbot workers secret set API_BASE_URL https://api.example.com' },
|
|
126
|
+
]);
|
|
127
|
+
addNotes(cmd, [
|
|
128
|
+
'Passing the value as an argument leaks it to your shell history and process list; prefer the prompt or stdin.',
|
|
129
|
+
'Setting an existing key overwrites its value. Values are encrypted and never shown again.',
|
|
130
|
+
]);
|
|
131
|
+
return cmd;
|
|
56
132
|
}
|
|
57
133
|
// ── secret list ─────────────────────────────────────────────────────────────
|
|
58
134
|
function secretListCommand() {
|
|
59
|
-
|
|
60
|
-
.description('List
|
|
135
|
+
const cmd = new Command('list')
|
|
136
|
+
.description('List the secrets in the workspace (values are never shown)')
|
|
137
|
+
.addOption(jsonOption())
|
|
61
138
|
.option('--dev', 'Use development environment', false)
|
|
62
139
|
.action(async (options) => {
|
|
63
140
|
try {
|
|
64
141
|
const credentials = await getStoredCredentials();
|
|
65
|
-
const api =
|
|
66
|
-
apiKey: credentials.apiKey,
|
|
67
|
-
workspace: credentials.workspace,
|
|
68
|
-
zone: credentials.zone,
|
|
69
|
-
dev: options.dev,
|
|
70
|
-
});
|
|
142
|
+
const api = apiFor(options, credentials);
|
|
71
143
|
const spinner = createSpinner('Fetching secrets...');
|
|
72
144
|
spinner.start();
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
145
|
+
let secrets;
|
|
146
|
+
try {
|
|
147
|
+
const response = await api.get('/api/worker/secrets');
|
|
148
|
+
secrets = Array.isArray(response.data) ? response.data : [];
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
spinner.stop();
|
|
152
|
+
}
|
|
153
|
+
if (isJsonMode()) {
|
|
154
|
+
printJson(secrets.map(s => ({ key: s.key, updatedAt: s.updatedAt ?? null })));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (secrets.length === 0) {
|
|
77
158
|
logger.info('\n No secrets configured');
|
|
78
|
-
logger.dim('
|
|
159
|
+
logger.dim('Add one: plazbot workers secret set MY_KEY');
|
|
79
160
|
console.log();
|
|
80
161
|
return;
|
|
81
162
|
}
|
|
82
163
|
logger.title(`Secrets (${secrets.length})`);
|
|
83
|
-
|
|
84
|
-
s.key,
|
|
85
|
-
theme.muted('••••••••'),
|
|
86
|
-
formatDate(s.updatedAt),
|
|
87
|
-
]);
|
|
88
|
-
console.log(createTable(['Key', 'Value', 'Updated'], rows));
|
|
164
|
+
console.log(createTable(['Key', 'Value', 'Updated'], secrets.map(s => [s.key, theme.muted('••••••••'), s.updatedAt ? formatDate(s.updatedAt) : '—'])));
|
|
89
165
|
console.log();
|
|
90
166
|
if (options.dev) {
|
|
91
167
|
logger.warning('Environment: development');
|
|
92
168
|
}
|
|
93
169
|
}
|
|
94
170
|
catch (error) {
|
|
95
|
-
|
|
96
|
-
logger.error(message);
|
|
97
|
-
process.exit(1);
|
|
171
|
+
fail(error);
|
|
98
172
|
}
|
|
99
173
|
});
|
|
174
|
+
addExamples(cmd, [
|
|
175
|
+
{ description: 'List every secret',
|
|
176
|
+
command: 'plazbot workers secret list' },
|
|
177
|
+
{ description: 'List secret names as JSON',
|
|
178
|
+
command: 'plazbot workers secret list --json' },
|
|
179
|
+
]);
|
|
180
|
+
return cmd;
|
|
100
181
|
}
|
|
101
182
|
// ── secret remove ───────────────────────────────────────────────────────────
|
|
102
183
|
function secretRemoveCommand() {
|
|
103
|
-
|
|
184
|
+
const cmd = new Command('remove')
|
|
104
185
|
.description('Delete a secret from the workspace')
|
|
105
186
|
.argument('<key>', 'Name of the secret to delete')
|
|
106
|
-
.
|
|
107
|
-
.option('--dev', 'Use development environment', false)
|
|
108
|
-
|
|
187
|
+
.addOption(jsonOption())
|
|
188
|
+
.option('--dev', 'Use development environment', false);
|
|
189
|
+
addYesOption(cmd);
|
|
190
|
+
cmd.action(async (key, options) => {
|
|
109
191
|
try {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
zone: credentials.zone,
|
|
115
|
-
dev: options.dev,
|
|
116
|
-
});
|
|
117
|
-
if (!options.force) {
|
|
118
|
-
const confirmed = await askConfirmation(` Delete secret "${key}"? (y/N): `);
|
|
119
|
-
if (!confirmed) {
|
|
120
|
-
logger.info('\n Operation cancelled');
|
|
121
|
-
process.exit(0);
|
|
122
|
-
}
|
|
192
|
+
const ok = await confirmAction(`Delete secret "${key}"? Workers using plz.env.${key} will stop working.`, options);
|
|
193
|
+
if (!ok) {
|
|
194
|
+
logger.dim('Aborted.');
|
|
195
|
+
process.exit(1);
|
|
123
196
|
}
|
|
197
|
+
const credentials = await getStoredCredentials();
|
|
198
|
+
const api = apiFor(options, credentials);
|
|
124
199
|
const spinner = createSpinner(`Deleting secret "${key}"...`);
|
|
125
200
|
spinner.start();
|
|
126
|
-
|
|
201
|
+
try {
|
|
202
|
+
await api.delete(`/api/worker/secrets/${encodeURIComponent(key)}`);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
spinner.stop();
|
|
206
|
+
if (error?.response?.status === 404) {
|
|
207
|
+
throw new CliError(`Secret "${key}" not found.`, 'not_found', 'List your secrets with: plazbot workers secret list');
|
|
208
|
+
}
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
127
211
|
spinner.succeed(`Secret "${key}" deleted`);
|
|
212
|
+
if (isJsonMode()) {
|
|
213
|
+
printJson({ key, removed: true });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
128
216
|
console.log();
|
|
129
217
|
if (options.dev) {
|
|
130
218
|
logger.warning('Environment: development');
|
|
131
219
|
}
|
|
132
220
|
}
|
|
133
221
|
catch (error) {
|
|
134
|
-
|
|
135
|
-
logger.error(`Secret "${key}" not found`);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
139
|
-
logger.error(message);
|
|
140
|
-
}
|
|
141
|
-
process.exit(1);
|
|
222
|
+
fail(error);
|
|
142
223
|
}
|
|
143
224
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
rl.question(question, (answer) => {
|
|
152
|
-
rl.close();
|
|
153
|
-
resolve(answer.toLowerCase() === 'y');
|
|
154
|
-
});
|
|
155
|
-
});
|
|
225
|
+
addExamples(cmd, [
|
|
226
|
+
{ description: 'Delete a secret (asks for confirmation)',
|
|
227
|
+
command: 'plazbot workers secret remove STRIPE_API_KEY' },
|
|
228
|
+
{ description: 'Delete without confirmation (scripts/CI)',
|
|
229
|
+
command: 'plazbot workers secret remove STRIPE_API_KEY --yes' },
|
|
230
|
+
]);
|
|
231
|
+
return cmd;
|
|
156
232
|
}
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
|
+
import fs from 'fs/promises';
|
|
2
3
|
import { getStoredCredentials } from '../../utils/credentials.js';
|
|
3
4
|
import { createApiClient } from '../../utils/api.js';
|
|
4
5
|
import { logger } from '../../utils/logger.js';
|
|
5
|
-
import { addExamples } from '../../utils/help.js';
|
|
6
|
+
import { addExamples, addNotes } from '../../utils/help.js';
|
|
6
7
|
import { createSpinner, theme } from '../../utils/ui.js';
|
|
8
|
+
import { jsonOption, isJsonMode, printJson, fail, CliError } from '../../utils/output.js';
|
|
7
9
|
export const testCommand = new Command('test')
|
|
8
|
-
.description('Run a worker manually to test it')
|
|
10
|
+
.description('Run a deployed worker manually to test it')
|
|
9
11
|
.argument('<name>', 'Name of the worker to run')
|
|
10
|
-
.option('-
|
|
11
|
-
.
|
|
12
|
-
.
|
|
13
|
-
.option('--
|
|
12
|
+
.option('-d, --data <json>', 'Input data as a JSON object, or @file.json to read it from a file')
|
|
13
|
+
.addOption(new Option('--payload <json>', 'Alias of --data').hideHelp())
|
|
14
|
+
.addOption(new Option('-t, --type <type>', 'Worker type').choices(['tool', 'worker', 'sync', 'schedule', 'webhook']))
|
|
15
|
+
.option('--contact <id>', 'Contact ID passed as context (tools)')
|
|
16
|
+
.option('--agent <id>', 'Agent ID passed as context (tools)')
|
|
17
|
+
.addOption(jsonOption('Print the raw execution result as JSON'))
|
|
14
18
|
.option('--dev', 'Use development environment', false)
|
|
15
19
|
.action(async (name, options) => {
|
|
16
20
|
try {
|
|
21
|
+
const payload = await parseData(options.data ?? options.payload);
|
|
17
22
|
const credentials = await getStoredCredentials();
|
|
18
23
|
const api = createApiClient({
|
|
19
24
|
apiKey: credentials.apiKey,
|
|
@@ -21,51 +26,53 @@ export const testCommand = new Command('test')
|
|
|
21
26
|
zone: credentials.zone,
|
|
22
27
|
dev: options.dev,
|
|
23
28
|
});
|
|
24
|
-
// Parsear payload
|
|
25
|
-
let payload = {};
|
|
26
|
-
if (options.payload) {
|
|
27
|
-
try {
|
|
28
|
-
payload = JSON.parse(options.payload);
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
logger.error('Payload is not valid JSON');
|
|
32
|
-
logger.dim('Example: --payload \'{"product":"iPhone 15"}\'');
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
29
|
logger.title(`Running worker "${name}"`);
|
|
37
|
-
console.log();
|
|
38
30
|
if (Object.keys(payload).length > 0) {
|
|
39
|
-
logger.label('
|
|
40
|
-
console.log();
|
|
31
|
+
logger.label('Data', JSON.stringify(payload));
|
|
41
32
|
}
|
|
42
33
|
const spinner = createSpinner('Running...');
|
|
43
34
|
spinner.start();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
35
|
+
let data;
|
|
36
|
+
try {
|
|
37
|
+
const response = await api.post('/api/worker/execute', {
|
|
38
|
+
workerName: name,
|
|
39
|
+
type: options.type,
|
|
40
|
+
payload,
|
|
41
|
+
triggerSource: 'cli-test',
|
|
42
|
+
agentId: options.agent,
|
|
43
|
+
contactId: options.contact,
|
|
44
|
+
});
|
|
45
|
+
data = response.data;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error?.response?.status === 404) {
|
|
49
|
+
throw new CliError(`Worker "${name}" not found or inactive.`, 'not_found', 'Check it is deployed with: plazbot workers list');
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
spinner.stop();
|
|
55
|
+
}
|
|
56
|
+
const ok = data?.success === true;
|
|
57
|
+
if (isJsonMode()) {
|
|
58
|
+
printJson(data);
|
|
59
|
+
if (!ok)
|
|
60
|
+
process.exit(1);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const duration = data?.duration ?? 0;
|
|
64
|
+
if (ok) {
|
|
56
65
|
logger.success(`Completed in ${duration}ms`);
|
|
57
66
|
}
|
|
58
67
|
else {
|
|
59
68
|
logger.error(`Failed in ${duration}ms`);
|
|
60
69
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (data.result !== undefined && data.result !== null) {
|
|
70
|
+
if (data?.result !== undefined && data?.result !== null) {
|
|
71
|
+
console.log();
|
|
64
72
|
logger.label('Result', '');
|
|
65
73
|
const result = data.result;
|
|
66
74
|
// Si es un ToolResponse, mostrar formateado
|
|
67
75
|
if (typeof result === 'object' && 'result' in result) {
|
|
68
|
-
console.log();
|
|
69
76
|
logger.label(' result', theme.success(String(result.result)));
|
|
70
77
|
if (result.data) {
|
|
71
78
|
logger.label(' data', JSON.stringify(result.data, null, 2));
|
|
@@ -74,44 +81,85 @@ export const testCommand = new Command('test')
|
|
|
74
81
|
logger.label(' actions', '');
|
|
75
82
|
for (const action of result.actions) {
|
|
76
83
|
const a = action;
|
|
77
|
-
logger.dim(`
|
|
84
|
+
logger.dim(` - ${a.type}: ${a.value}${a.code ? ` (${a.code})` : ''}`);
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
87
|
}
|
|
81
88
|
else {
|
|
82
|
-
// Resultado generico
|
|
83
89
|
console.log(JSON.stringify(result, null, 2));
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
|
-
|
|
87
|
-
if (
|
|
92
|
+
const errorMessage = data?.error || (!ok ? data?.message : undefined);
|
|
93
|
+
if (errorMessage) {
|
|
88
94
|
console.log();
|
|
89
|
-
logger.label('Error', theme.error(
|
|
95
|
+
logger.label('Error', theme.error(errorMessage));
|
|
96
|
+
}
|
|
97
|
+
if (Array.isArray(data?.errors) && data.errors.length > 0) {
|
|
98
|
+
data.errors.forEach(e => logger.dim(`- ${typeof e === 'string' ? e : JSON.stringify(e)}`));
|
|
90
99
|
}
|
|
91
100
|
console.log();
|
|
92
101
|
if (options.dev) {
|
|
93
102
|
logger.warning('Environment: development');
|
|
94
103
|
}
|
|
104
|
+
if (!ok)
|
|
105
|
+
process.exit(1);
|
|
95
106
|
}
|
|
96
107
|
catch (error) {
|
|
97
|
-
|
|
98
|
-
logger.error(`Worker "${name}" not found or inactive`);
|
|
99
|
-
logger.dim('Verify the worker is deployed with: plazbot workers list');
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
const message = error.response?.data?.message || error.message || 'Unknown error';
|
|
103
|
-
logger.error(message);
|
|
104
|
-
}
|
|
105
|
-
process.exit(1);
|
|
108
|
+
fail(error);
|
|
106
109
|
}
|
|
107
110
|
});
|
|
108
111
|
addExamples(testCommand, [
|
|
109
|
-
{ description: 'Run a worker without
|
|
110
|
-
command: 'plazbot workers test
|
|
111
|
-
{ description: 'Run a worker with
|
|
112
|
-
command: 'plazbot workers test get-stock -
|
|
113
|
-
{ description: '
|
|
114
|
-
command: 'plazbot workers test
|
|
115
|
-
{ description: 'Run
|
|
116
|
-
command: 'plazbot workers test
|
|
112
|
+
{ description: 'Run a worker without input data',
|
|
113
|
+
command: 'plazbot workers test get-stock' },
|
|
114
|
+
{ description: 'Run a worker with JSON data',
|
|
115
|
+
command: 'plazbot workers test get-stock -d \'{"product":"iPhone 15"}\'' },
|
|
116
|
+
{ description: 'Read the input data from a file',
|
|
117
|
+
command: 'plazbot workers test sync-orders -d @./payload.json' },
|
|
118
|
+
{ description: 'Run a tool with contact and agent context, raw JSON output',
|
|
119
|
+
command: 'plazbot workers test get-stock --contact ctc_AbcDef123 --agent age_AbcDef123 --json' },
|
|
117
120
|
]);
|
|
121
|
+
addNotes(testCommand, [
|
|
122
|
+
'The worker must be deployed first (plazbot workers deploy). Executions are logged with source "cli-test".',
|
|
123
|
+
'Exits with code 1 when the execution fails (success: false).',
|
|
124
|
+
]);
|
|
125
|
+
// Parses --data: inline JSON, @file.json, or @- for stdin. Must be a JSON object.
|
|
126
|
+
async function parseData(raw) {
|
|
127
|
+
if (raw === undefined || raw === '')
|
|
128
|
+
return {};
|
|
129
|
+
let text = raw;
|
|
130
|
+
let source = '--data';
|
|
131
|
+
if (raw.startsWith('@')) {
|
|
132
|
+
const file = raw.slice(1);
|
|
133
|
+
if (!file) {
|
|
134
|
+
throw new CliError('Missing file name after "@".', 'invalid_data', 'Example: -d @./payload.json');
|
|
135
|
+
}
|
|
136
|
+
if (file === '-') {
|
|
137
|
+
const chunks = [];
|
|
138
|
+
for await (const chunk of process.stdin)
|
|
139
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
140
|
+
text = Buffer.concat(chunks).toString('utf-8');
|
|
141
|
+
source = 'stdin';
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
try {
|
|
145
|
+
text = await fs.readFile(file, 'utf-8');
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
throw new CliError(`Could not read data file: ${file}.`, 'file_not_found', 'Check the path after "@".');
|
|
149
|
+
}
|
|
150
|
+
source = file;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
let parsed;
|
|
154
|
+
try {
|
|
155
|
+
parsed = JSON.parse(text);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const detail = error instanceof Error ? ` (${error.message})` : '';
|
|
159
|
+
throw new CliError(`Invalid JSON in ${source}${detail}.`, 'invalid_json', 'Example: -d \'{"product":"iPhone 15"}\' or -d @./payload.json');
|
|
160
|
+
}
|
|
161
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
162
|
+
throw new CliError(`The data in ${source} must be a JSON object.`, 'invalid_data', 'Example: -d \'{"product":"iPhone 15"}\'');
|
|
163
|
+
}
|
|
164
|
+
return parsed;
|
|
165
|
+
}
|