plazbot-cli 0.4.0 → 0.5.0

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.
Files changed (76) hide show
  1. package/README.md +67 -111
  2. package/dist/cli.js +26 -11
  3. package/dist/commands/agent/ai-config.js +264 -63
  4. package/dist/commands/agent/chat.js +16 -11
  5. package/dist/commands/agent/copy.js +49 -13
  6. package/dist/commands/agent/create.js +62 -17
  7. package/dist/commands/agent/delete.js +50 -39
  8. package/dist/commands/agent/enable-widget.js +47 -27
  9. package/dist/commands/agent/export.js +179 -88
  10. package/dist/commands/agent/files.js +155 -73
  11. package/dist/commands/agent/get.js +24 -16
  12. package/dist/commands/agent/index.js +12 -10
  13. package/dist/commands/agent/list.js +20 -11
  14. package/dist/commands/agent/monitor.js +101 -53
  15. package/dist/commands/agent/on-message.js +55 -32
  16. package/dist/commands/agent/set.js +230 -82
  17. package/dist/commands/agent/templates.js +259 -156
  18. package/dist/commands/agent/tools.js +356 -123
  19. package/dist/commands/agent/update.js +69 -28
  20. package/dist/commands/agent/validate.js +72 -40
  21. package/dist/commands/agent/wizard.js +240 -329
  22. package/dist/commands/auth/browser-login.js +37 -31
  23. package/dist/commands/auth/login.js +49 -45
  24. package/dist/commands/auth/logout.js +20 -11
  25. package/dist/commands/auth/status.js +36 -12
  26. package/dist/commands/auth/workspace.js +61 -27
  27. package/dist/commands/portal/add-agent.js +106 -45
  28. package/dist/commands/portal/add-link.js +25 -28
  29. package/dist/commands/portal/clear-links.js +39 -19
  30. package/dist/commands/portal/create.js +66 -37
  31. package/dist/commands/portal/delete.js +40 -45
  32. package/dist/commands/portal/get.js +21 -54
  33. package/dist/commands/portal/index.js +12 -12
  34. package/dist/commands/portal/list.js +34 -52
  35. package/dist/commands/portal/shared.js +92 -0
  36. package/dist/commands/portal/update.js +100 -70
  37. package/dist/commands/whatsapp/broadcast.js +169 -77
  38. package/dist/commands/whatsapp/channels.js +167 -65
  39. package/dist/commands/whatsapp/chat.js +45 -46
  40. package/dist/commands/whatsapp/connect-flow.js +16 -15
  41. package/dist/commands/whatsapp/connect.js +36 -21
  42. package/dist/commands/whatsapp/delete-webhook.js +42 -25
  43. package/dist/commands/whatsapp/index.js +15 -16
  44. package/dist/commands/whatsapp/register-webhook.js +43 -26
  45. package/dist/commands/whatsapp/send-template.js +62 -61
  46. package/dist/commands/whatsapp/send.js +46 -33
  47. package/dist/commands/whatsapp/setup.js +62 -52
  48. package/dist/commands/whatsapp/shared.js +75 -0
  49. package/dist/commands/whatsapp/widget.js +82 -45
  50. package/dist/commands/workers/deploy.js +80 -58
  51. package/dist/commands/workers/index.js +8 -8
  52. package/dist/commands/workers/list.js +42 -20
  53. package/dist/commands/workers/logs.js +139 -36
  54. package/dist/commands/workers/remove.js +45 -47
  55. package/dist/commands/workers/secret.js +159 -83
  56. package/dist/commands/workers/test.js +108 -60
  57. package/dist/schemas/agent.config.schema.json +183 -8
  58. package/dist/studio/api/sseClient.js +1 -1
  59. package/dist/studio/api/studioApi.js +8 -0
  60. package/dist/studio/components/App.js +12 -0
  61. package/dist/studio/components/Footer.js +1 -1
  62. package/dist/studio/index.js +77 -14
  63. package/dist/studio/runOneShot.js +62 -23
  64. package/dist/studio/runRepl.js +5 -5
  65. package/dist/studio/slash/handlers.js +11 -8
  66. package/dist/studio/state/store.js +1 -1
  67. package/dist/utils/agent-errors.js +14 -11
  68. package/dist/utils/agent-patch.js +60 -0
  69. package/dist/utils/banner.js +9 -5
  70. package/dist/utils/confirm.js +31 -0
  71. package/dist/utils/credentials.js +13 -3
  72. package/dist/utils/logger.js +13 -10
  73. package/dist/utils/output.js +78 -0
  74. package/dist/utils/phone.js +22 -0
  75. package/dist/utils/ui.js +14 -11
  76. package/package.json +2 -2
@@ -1,17 +1,26 @@
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
- import { addExamples } from '../../utils/help.js';
5
+ import { addExamples, addNotes } from '../../utils/help.js';
6
6
  import { createSpinner, createTable, theme, formatDate } from '../../utils/ui.js';
7
+ import { jsonOption, isJsonMode, printJson, fail, CliError, describeError } from '../../utils/output.js';
8
+ const FOLLOW_INTERVAL_MS = 3000;
9
+ const FOLLOW_BATCH = 50;
7
10
  export const logsCommand = new Command('logs')
8
- .description('View execution logs for a worker')
11
+ .description('View the execution logs of a worker')
9
12
  .argument('<name>', 'Worker name')
10
- .option('-l, --limit <number>', 'Number of logs to display', '20')
11
- .option('-s, --status <status>', 'Filter by status (success, error, timeout)')
13
+ .option('-l, --limit <number>', 'Number of logs to show, newest first (1-1000)', '20')
14
+ .addOption(new Option('-s, --status <status>', 'Filter by status').choices(['success', 'error', 'timeout']))
15
+ .option('-f, --follow', 'Keep polling every 3s and print new executions (Ctrl+C to stop)', false)
16
+ .addOption(jsonOption())
12
17
  .option('--dev', 'Use development environment', false)
13
18
  .action(async (name, options) => {
14
19
  try {
20
+ const limit = Number(options.limit ?? 20);
21
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {
22
+ throw new CliError('--limit must be a whole number between 1 and 1000.', 'invalid_limit', 'Example: plazbot workers logs my-tool -l 100');
23
+ }
15
24
  const credentials = await getStoredCredentials();
16
25
  const api = createApiClient({
17
26
  apiKey: credentials.apiKey,
@@ -21,62 +30,156 @@ export const logsCommand = new Command('logs')
21
30
  });
22
31
  const spinner = createSpinner(`Fetching logs for "${name}"...`);
23
32
  spinner.start();
24
- const params = {
25
- limit: String(options.limit || 20),
26
- };
27
- if (options.status)
28
- params.status = options.status;
29
- const response = await api.get(`/api/worker/${encodeURIComponent(name)}/logs`, { params });
30
- const logs = response.data;
31
- spinner.stop();
32
- if (!logs || logs.length === 0) {
33
+ let logs;
34
+ try {
35
+ logs = await fetchLogs(api, name, limit, options.status);
36
+ }
37
+ finally {
38
+ spinner.stop();
39
+ }
40
+ if (options.follow) {
41
+ await follow(api, name, logs, options.status);
42
+ return;
43
+ }
44
+ if (isJsonMode()) {
45
+ printJson(logs.map(toJson));
46
+ return;
47
+ }
48
+ if (logs.length === 0) {
33
49
  logger.info(`\n No logs found for "${name}"`);
50
+ logger.dim(`Check the worker name with: plazbot workers list`);
34
51
  console.log();
35
52
  return;
36
53
  }
37
54
  logger.title(`Logs for "${name}" (${logs.length})`);
38
- const rows = logs.map(log => [
55
+ console.log(createTable(['Date', 'Status', 'Duration', 'Source', 'Error'], logs.map(log => [
39
56
  formatDate(log.createdAt),
40
- log.status === 'success' ? theme.success('ok')
41
- : log.status === 'error' ? theme.error('error')
42
- : theme.warning('timeout'),
43
- `${log.durationMs}ms`,
57
+ statusLabel(log.status),
58
+ `${log.durationMs ?? 0}ms`,
44
59
  log.triggerSource || '—',
45
60
  log.error ? theme.error(truncate(log.error, 40)) : '—',
46
- ]);
47
- console.log(createTable(['Date', 'Status', 'Duration', 'Source', 'Error'], rows));
48
- // Resumen
61
+ ])));
49
62
  const successCount = logs.filter(l => l.status === 'success').length;
50
63
  const errorCount = logs.filter(l => l.status === 'error').length;
51
- const avgDuration = Math.round(logs.reduce((sum, l) => sum + l.durationMs, 0) / logs.length);
64
+ const timeoutCount = logs.filter(l => l.status === 'timeout').length;
65
+ const avgDuration = Math.round(logs.reduce((sum, l) => sum + (l.durationMs ?? 0), 0) / logs.length);
52
66
  console.log();
53
67
  logger.label('Successful', theme.success(`${successCount}`));
54
68
  logger.label('Errors', errorCount > 0 ? theme.error(`${errorCount}`) : theme.muted('0'));
69
+ logger.label('Timeouts', timeoutCount > 0 ? theme.warning(`${timeoutCount}`) : theme.muted('0'));
55
70
  logger.label('Average duration', `${avgDuration}ms`);
71
+ if (logs.some(l => l.error && l.error.length > 40)) {
72
+ logger.dim('Errors are truncated; use --json to see the full message.');
73
+ }
56
74
  console.log();
57
75
  if (options.dev) {
58
76
  logger.warning('Environment: development');
59
77
  }
60
78
  }
61
79
  catch (error) {
62
- if (error.response?.status === 404) {
63
- logger.error(`Worker "${name}" not found`);
64
- }
65
- else {
66
- const message = error instanceof Error ? error.message : 'Unknown error';
67
- logger.error(message);
68
- }
69
- process.exit(1);
80
+ fail(error);
70
81
  }
71
82
  });
72
83
  addExamples(logsCommand, [
73
84
  { description: 'Show the last 20 executions of a worker',
74
- command: 'plazbot workers logs my-tool' },
75
- { description: 'Get the last 100 executions',
76
- command: 'plazbot workers logs my-tool -l 100' },
77
- { description: 'Only show failed executions',
78
- command: 'plazbot workers logs my-tool -s error' },
85
+ command: 'plazbot workers logs get-stock' },
86
+ { description: 'Show the last 100 failed executions',
87
+ command: 'plazbot workers logs get-stock -l 100 -s error' },
88
+ { description: 'Follow new executions in real time',
89
+ command: 'plazbot workers logs get-stock --follow' },
90
+ { description: 'Get logs as JSON with full error messages',
91
+ command: 'plazbot workers logs get-stock --json' },
92
+ ]);
93
+ addNotes(logsCommand, [
94
+ 'The table truncates errors to 40 characters; --json includes the full message.',
95
+ 'With --follow and --json, each execution is printed as one JSON object per line (NDJSON).',
79
96
  ]);
97
+ async function fetchLogs(api, name, limit, status) {
98
+ const params = { limit: String(limit) };
99
+ if (status)
100
+ params.status = status;
101
+ const response = await api.get(`/api/worker/${encodeURIComponent(name)}/logs`, { params });
102
+ return Array.isArray(response.data) ? response.data : [];
103
+ }
104
+ // Polls the logs endpoint (ordered by created_at DESC) and prints entries not seen yet.
105
+ async function follow(api, name, initial, status) {
106
+ const seen = new Set();
107
+ const emit = (entries) => {
108
+ // API returns newest first; print oldest first
109
+ for (const log of [...entries].reverse()) {
110
+ const key = logKey(log);
111
+ if (seen.has(key))
112
+ continue;
113
+ seen.add(key);
114
+ if (isJsonMode()) {
115
+ process.stdout.write(JSON.stringify(toJson(log)) + '\n');
116
+ }
117
+ else {
118
+ console.log(formatLine(log));
119
+ }
120
+ }
121
+ };
122
+ if (!isJsonMode()) {
123
+ logger.title(`Following logs for "${name}"`);
124
+ logger.dim('Press Ctrl+C to stop.');
125
+ console.log();
126
+ }
127
+ emit(initial);
128
+ process.on('SIGINT', () => {
129
+ if (!isJsonMode())
130
+ console.log();
131
+ process.exit(0);
132
+ });
133
+ let failing = false;
134
+ for (;;) {
135
+ await new Promise(resolve => setTimeout(resolve, FOLLOW_INTERVAL_MS));
136
+ try {
137
+ emit(await fetchLogs(api, name, FOLLOW_BATCH, status));
138
+ if (failing)
139
+ logger.dim('Connection restored.');
140
+ failing = false;
141
+ }
142
+ catch (error) {
143
+ const info = describeError(error);
144
+ if (info.status === 401 || info.status === 403 || info.status === 404)
145
+ fail(error);
146
+ if (!failing)
147
+ logger.warning(`Could not fetch logs (${info.message}). Retrying...`);
148
+ failing = true;
149
+ }
150
+ }
151
+ }
152
+ function logKey(log) {
153
+ return log.id != null ? String(log.id) : `${log.createdAt}|${log.status}|${log.durationMs}`;
154
+ }
155
+ function toJson(log) {
156
+ return {
157
+ id: log.id,
158
+ workerName: log.workerName,
159
+ workerType: log.workerType,
160
+ status: log.status,
161
+ durationMs: log.durationMs ?? 0,
162
+ triggerSource: log.triggerSource ?? null,
163
+ error: log.error ?? null,
164
+ createdAt: log.createdAt,
165
+ };
166
+ }
167
+ function formatLine(log) {
168
+ const parts = [
169
+ theme.muted(formatDate(log.createdAt)),
170
+ statusLabel(log.status),
171
+ `${log.durationMs ?? 0}ms`,
172
+ theme.muted(log.triggerSource || '—'),
173
+ ];
174
+ if (log.error)
175
+ parts.push(theme.error(log.error));
176
+ return ' ' + parts.join(' ');
177
+ }
178
+ function statusLabel(status) {
179
+ return status === 'success' ? theme.success('ok')
180
+ : status === 'error' ? theme.error('error')
181
+ : theme.warning(status || 'timeout');
182
+ }
80
183
  function truncate(text, max) {
81
184
  if (text.length <= max)
82
185
  return text;
@@ -4,13 +4,15 @@ 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 } from '../../utils/ui.js';
7
- import readline from 'readline';
7
+ import { jsonOption, isJsonMode, printJson, fail, CliError } from '../../utils/output.js';
8
+ import { addYesOption, confirmAction } from '../../utils/confirm.js';
8
9
  export const removeCommand = new Command('remove')
9
10
  .description('Delete a worker from the workspace')
10
11
  .argument('<name>', 'Name of the worker to delete')
11
- .option('-f, --force', 'Delete without confirmation', false)
12
- .option('--dev', 'Use development environment', false)
13
- .action(async (name, options) => {
12
+ .addOption(jsonOption())
13
+ .option('--dev', 'Use development environment', false);
14
+ addYesOption(removeCommand);
15
+ removeCommand.action(async (name, options) => {
14
16
  try {
15
17
  const credentials = await getStoredCredentials();
16
18
  const api = createApiClient({
@@ -19,71 +21,67 @@ export const removeCommand = new Command('remove')
19
21
  zone: credentials.zone,
20
22
  dev: options.dev,
21
23
  });
22
- // Verificar que el worker existe
23
24
  const spinner = createSpinner(`Looking up worker "${name}"...`);
24
25
  spinner.start();
25
26
  let workerInfo;
26
27
  try {
27
28
  const response = await api.get(`/api/worker/${encodeURIComponent(name)}`);
28
29
  workerInfo = response.data;
29
- spinner.stop();
30
30
  }
31
31
  catch (error) {
32
- spinner.stop();
33
- if (error.response?.status === 404) {
34
- logger.error(`Worker "${name}" not found`);
35
- }
36
- else {
37
- logger.error(error.response?.data?.message || error.message);
32
+ if (error?.response?.status === 404) {
33
+ throw new CliError(`Worker "${name}" not found.`, 'not_found', 'List your workers with: plazbot workers list');
38
34
  }
39
- process.exit(1);
35
+ throw error;
40
36
  }
41
- // Mostrar info del worker
42
- logger.warning(`\n You are about to delete the following worker:`);
43
- logger.divider();
44
- logger.label('Name', workerInfo.name);
45
- logger.label('Type', workerInfo.type);
46
- logger.label('Status', workerInfo.status);
47
- logger.label('Executions', `${workerInfo.executionCount || 0}`);
48
- logger.divider();
49
- // Confirmacion
50
- if (!options.force) {
51
- const confirmed = await askConfirmation(`Delete worker "${name}"? This action cannot be undone (y/N): `);
52
- if (!confirmed) {
53
- logger.info('\n Operation cancelled');
54
- process.exit(0);
55
- }
37
+ finally {
38
+ spinner.stop();
39
+ }
40
+ if (!isJsonMode()) {
41
+ logger.warning('You are about to delete this worker:');
42
+ logger.divider();
43
+ logger.label('Name', workerInfo?.name ?? name);
44
+ logger.label('Type', workerInfo?.type ?? '—');
45
+ logger.label('Status', workerInfo?.status ?? '—');
46
+ logger.label('Executions', `${workerInfo?.executionCount ?? 0}`);
47
+ logger.divider();
48
+ }
49
+ const ok = await confirmAction(`Delete worker "${name}"? This cannot be undone.`, options);
50
+ if (!ok) {
51
+ logger.dim('Aborted.');
52
+ process.exit(1);
56
53
  }
57
54
  const deleteSpinner = createSpinner(`Deleting "${name}"...`);
58
55
  deleteSpinner.start();
59
- await api.delete(`/api/worker/${encodeURIComponent(name)}`);
56
+ try {
57
+ const response = await api.delete(`/api/worker/${encodeURIComponent(name)}`);
58
+ if (response.data?.success === false) {
59
+ throw new CliError(response.data.message || `Could not delete worker "${name}".`, 'delete_failed');
60
+ }
61
+ }
62
+ catch (error) {
63
+ deleteSpinner.stop();
64
+ throw error;
65
+ }
60
66
  deleteSpinner.succeed(`Worker "${name}" deleted`);
67
+ if (isJsonMode()) {
68
+ printJson({ name, removed: true });
69
+ return;
70
+ }
61
71
  console.log();
62
72
  if (options.dev) {
63
73
  logger.warning('Environment: development');
64
74
  }
65
75
  }
66
76
  catch (error) {
67
- const message = error instanceof Error ? error.message : 'Unknown error';
68
- logger.error(message);
69
- process.exit(1);
77
+ fail(error);
70
78
  }
71
79
  });
72
80
  addExamples(removeCommand, [
73
- { description: 'Delete a worker with confirmation prompt',
74
- command: 'plazbot workers remove my-tool' },
81
+ { description: 'Delete a worker (asks for confirmation)',
82
+ command: 'plazbot workers remove get-stock' },
75
83
  { description: 'Delete without confirmation (scripts/CI)',
76
- command: 'plazbot workers remove my-tool -f' },
84
+ command: 'plazbot workers remove get-stock --yes' },
85
+ { description: 'Delete and get the result as JSON',
86
+ command: 'plazbot workers remove get-stock --yes --json' },
77
87
  ]);
78
- function askConfirmation(question) {
79
- return new Promise((resolve) => {
80
- const rl = readline.createInterface({
81
- input: process.stdin,
82
- output: process.stdout,
83
- });
84
- rl.question(question, (answer) => {
85
- rl.close();
86
- resolve(answer.toLowerCase() === 'y');
87
- });
88
- });
89
- }