plazbot-cli 0.4.1 → 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 +159 -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
@@ -3,10 +3,10 @@ import os from 'node:os';
3
3
  import axios from 'axios';
4
4
  import chalk from 'chalk';
5
5
  import inquirer from 'inquirer';
6
- import { apiBaseUrl, appBaseUrl, saveCredentials } from '../../utils/credentials.js';
6
+ import { apiBaseUrl, appBaseUrl, parseZone, saveCredentials } from '../../utils/credentials.js';
7
+ import { fail, CliError } from '../../utils/output.js';
7
8
  import { openBrowser } from '../../utils/open-browser.js';
8
9
  import { createSpinner, theme } from '../../utils/ui.js';
9
- import { logger } from '../../utils/logger.js';
10
10
  import { addExamples, addNotes } from '../../utils/help.js';
11
11
  const sleep = (ms) => new Promise(r => setTimeout(r, ms));
12
12
  /**
@@ -16,16 +16,23 @@ const sleep = (ms) => new Promise(r => setTimeout(r, ms));
16
16
  export async function runBrowserLogin(options) {
17
17
  let zone;
18
18
  if (options.zone) {
19
- zone = options.zone.toUpperCase() === 'EU' ? 'EU' : 'LA';
19
+ const parsed = parseZone(options.zone);
20
+ if (!parsed) {
21
+ throw new CliError(`Invalid zone "${options.zone}".`, 'invalid_zone', 'Use --zone LA (Americas) or --zone EU (Europe).');
22
+ }
23
+ zone = parsed;
20
24
  }
21
25
  else {
26
+ if (!process.stdin.isTTY) {
27
+ throw new CliError('The zone is required without an interactive terminal.', 'missing_zone', 'Pass --zone LA or --zone EU.');
28
+ }
22
29
  const answer = await inquirer.prompt([{
23
30
  type: 'list',
24
31
  name: 'zone',
25
- message: '¿Dónde creaste tu cuenta de Plazbot?',
32
+ message: 'Where did you create your Plazbot account?',
26
33
  choices: [
27
- { name: 'América (appla.plazbot.com)', value: 'LA' },
28
- { name: 'Europa (appeu.plazbot.com)', value: 'EU' },
34
+ { name: 'Americas (appla.plazbot.com)', value: 'LA' },
35
+ { name: 'Europe (appeu.plazbot.com)', value: 'EU' },
29
36
  ],
30
37
  }]);
31
38
  zone = answer.zone;
@@ -38,22 +45,22 @@ export async function runBrowserLogin(options) {
38
45
  data = start.data?.success ? start.data?.data : undefined;
39
46
  }
40
47
  catch (error) {
41
- throw new Error(`No se pudo conectar con Plazbot (${axios.isAxiosError(error) ? error.message : 'error de red'}).`);
48
+ throw new CliError(`Could not reach Plazbot (${axios.isAxiosError(error) ? error.message : 'network error'}).`, 'network_error', 'Check your internet connection and try again.');
42
49
  }
43
50
  if (!data?.deviceCode) {
44
- throw new Error('No se pudo iniciar el login. Inténtalo de nuevo en unos segundos.');
51
+ throw new CliError('Could not start the login.', 'login_start_failed', 'Try again in a few seconds.');
45
52
  }
46
53
  const url = `${appBaseUrl(zone, dev)}/cli/authorize?code=${encodeURIComponent(data.userCode)}&r=${encodeURIComponent(data.requestToken)}`;
47
54
  console.log();
48
- console.log(` Tu código de verificación: ${chalk.bold.hex('#22d3ee')(data.userCode)}`);
55
+ console.log(` Your verification code: ${chalk.bold.hex('#22d3ee')(data.userCode)}`);
49
56
  console.log();
50
57
  const opened = options.browser !== false && openBrowser(url);
51
58
  console.log(theme.muted(opened
52
- ? ' Abrimos tu navegador. Si no se abrió, entra a:'
53
- : ' Abre este link en tu navegador:'));
59
+ ? ' We opened your browser. If it did not open, go to:'
60
+ : ' Open this link in your browser:'));
54
61
  console.log(` ${chalk.underline(url)}`);
55
62
  console.log();
56
- const spinner = createSpinner('Esperando la autorización en el navegador... (Ctrl+C para cancelar)');
63
+ const spinner = createSpinner('Waiting for authorization in the browser... (Ctrl+C to cancel)');
57
64
  spinner.start();
58
65
  const deadline = Date.now() + data.expiresIn * 1000;
59
66
  const intervalMs = Math.max(2, data.interval || 3) * 1000;
@@ -72,8 +79,8 @@ export async function runBrowserLogin(options) {
72
79
  }
73
80
  }
74
81
  if (!approved) {
75
- spinner.fail('El código venció sin autorización.');
76
- throw new Error('Ejecuta plazbot login otra vez.');
82
+ spinner.fail('The code expired before it was authorized.');
83
+ throw new CliError('Login timed out.', 'login_expired', "Run 'plazbot login' again.");
77
84
  }
78
85
  const credentials = {
79
86
  apiKey: approved.apiKey,
@@ -85,37 +92,36 @@ export async function runBrowserLogin(options) {
85
92
  source: 'login',
86
93
  };
87
94
  await saveCredentials(credentials);
88
- spinner.succeed(`Sesión iniciada en ${chalk.bold(approved.workspaceName || approved.workspaceId)} (${credentials.zone})`);
95
+ spinner.succeed(`Signed in to ${chalk.bold(approved.workspaceName || approved.workspaceId)} (${credentials.zone})`);
89
96
  if (approved.email)
90
- console.log(theme.muted(` Cuenta: ${approved.email}`));
97
+ console.log(theme.muted(` Account: ${approved.email}`));
91
98
  console.log();
92
99
  return credentials;
93
100
  }
94
101
  export const browserLoginCommand = new Command('login')
95
- .description('Inicia sesión desde el navegador (sin copiar API key)')
96
- .option('-z, --zone <zone>', 'Zona donde creaste tu cuenta: LA (América) o EU (Europa)')
97
- .option('--no-browser', 'No abrir el navegador automáticamente (solo mostrar el link)')
98
- .option('--dev', 'Usar el entorno de desarrollo local', false)
102
+ .description('Sign in with your browser (no API key to copy)')
103
+ .option('-z, --zone <zone>', 'Zone where you created your account: LA (Americas) or EU (Europe)')
104
+ .option('--no-browser', 'Do not open the browser, only print the link')
105
+ .option('--dev', 'Use development environment', false)
99
106
  .action(async (options) => {
100
107
  try {
101
108
  await runBrowserLogin(options);
102
- console.log(theme.muted(' Siguiente paso:'));
103
- console.log(` ${chalk.cyan('plazbot whatsapp setup')} ${theme.muted('Conecta WhatsApp y envía tu primer mensaje')}`);
104
- console.log(` ${chalk.cyan('plazbot whoami')} ${theme.muted('Ver la sesión activa')}`);
105
- console.log(` ${chalk.cyan('plazbot workspace list')} ${theme.muted('Cambiar de workspace')}`);
109
+ console.log(theme.muted(' Next steps:'));
110
+ console.log(` ${chalk.cyan('plazbot whatsapp setup')} ${theme.muted('Connect WhatsApp and send your first message')}`);
111
+ console.log(` ${chalk.cyan('plazbot whoami')} ${theme.muted('Show the active session')}`);
112
+ console.log(` ${chalk.cyan('plazbot workspace list')} ${theme.muted('Switch workspace')}`);
106
113
  console.log();
107
114
  }
108
115
  catch (error) {
109
- logger.error(error instanceof Error ? error.message : 'Error desconocido');
110
- process.exit(1);
116
+ fail(error);
111
117
  }
112
118
  });
113
119
  addExamples(browserLoginCommand, [
114
- { description: 'Iniciar sesión (pregunta la zona y abre el navegador)', command: 'plazbot login' },
115
- { description: 'Cuenta en Europa', command: 'plazbot login --zone EU' },
116
- { description: 'Servidor sin navegador: solo muestra el link', command: 'plazbot login --no-browser' },
120
+ { description: 'Sign in (asks for the zone and opens the browser)', command: 'plazbot login' },
121
+ { description: 'Account in Europe', command: 'plazbot login --zone EU' },
122
+ { description: 'Server without a browser: only print the link', command: 'plazbot login --no-browser' },
117
123
  ]);
118
124
  addNotes(browserLoginCommand, [
119
- 'En CI o servidores usa variables de entorno: PLAZBOT_API_KEY, PLAZBOT_WORKSPACE_ID y PLAZBOT_ZONE.',
120
- 'Puedes revocar el acceso en el panel: Configuración → Developer.',
125
+ 'In CI or servers use environment variables: PLAZBOT_API_KEY, PLAZBOT_WORKSPACE_ID and PLAZBOT_ZONE (or plazbot init).',
126
+ 'You can revoke access in the app: Settings → Developer.',
121
127
  ]);
@@ -1,64 +1,68 @@
1
1
  import { Command } from 'commander';
2
- import { saveCredentials } from '../../utils/credentials.js';
2
+ import chalk from 'chalk';
3
+ import { parseZone, saveCredentials } from '../../utils/credentials.js';
3
4
  import { logger } from '../../utils/logger.js';
4
- import { addExamples } from '../../utils/help.js';
5
+ import { addExamples, addNotes } from '../../utils/help.js';
6
+ import { jsonOption, isJsonMode, printJson, fail, CliError } from '../../utils/output.js';
5
7
  export const loginCommand = new Command('init')
6
- .description('Guarda una API key manualmente (para CI; lo normal es plazbot login)')
7
- .option('-e, --email <email>', 'User email (optional)')
8
- .requiredOption('-k, --api-key <apiKey>', 'User API key')
9
- .requiredOption('-w, --workspace <workspace>', 'Workspace ID')
10
- .requiredOption('-z, --zone <zone>', 'Zone (LA or EU)')
11
- .option('-u, --user-id <userId>', 'User ID for x-user-id (optional, used by studio)')
8
+ .description('Save an API key manually (for CI; use plazbot login on your machine)')
9
+ .requiredOption('-k, --api-key <apiKey>', 'API key (Settings → Developer)')
10
+ .requiredOption('-w, --workspace <workspaceId>', 'Workspace ID')
11
+ .requiredOption('-z, --zone <zone>', 'Zone where the account lives: LA (Americas) or EU (Europe)')
12
+ .option('-e, --email <email>', 'Account email (optional, shown by whoami)')
13
+ .option('-u, --user-id <userId>', 'User ID (optional, needed by studio and workspace list)')
14
+ .addOption(jsonOption())
12
15
  .option('--dev', 'Use development environment', false)
13
16
  .action(async (options) => {
14
17
  try {
15
- // Banner de bienvenida
16
- logger.info('┌' + '─'.repeat(70) + '┐');
17
- logger.info('│' + ' Welcome to Plazbot CLI'.padEnd(69) + '│');
18
- logger.info('│' + ''.padEnd(69) + '│');
19
- logger.info('│' + ' With this tool you can:'.padEnd(69) + '│');
20
- logger.info('│' + ' Create and update agents'.padEnd(69) + '│');
21
- logger.info('│' + ' Manage configurations'.padEnd(69) + '│');
22
- logger.info('│' + ' Interact with your agents'.padEnd(69) + '│');
23
- logger.info('│' + ' Activate all their capabilities'.padEnd(69) + '│');
24
- logger.info('└' + '─'.repeat(70) + '┘\n');
18
+ const zone = parseZone(options.zone);
19
+ if (!zone) {
20
+ throw new CliError(`Invalid zone "${options.zone}".`, 'invalid_zone', 'Use -z LA (Americas) or -z EU (Europe).');
21
+ }
22
+ const apiKey = options.apiKey.trim();
23
+ const workspace = options.workspace.trim();
24
+ if (!apiKey)
25
+ throw new CliError('API key cannot be empty.', 'missing_api_key', 'Pass it with -k, --api-key <apiKey>.');
26
+ if (!workspace)
27
+ throw new CliError('Workspace ID cannot be empty.', 'missing_workspace', 'Pass it with -w, --workspace <workspaceId>.');
25
28
  await saveCredentials({
26
29
  email: options.email || '',
27
- apiKey: options.apiKey,
28
- workspace: options.workspace,
29
- zone: options.zone,
30
- userId: options.userId
30
+ apiKey,
31
+ workspace,
32
+ zone,
33
+ userId: options.userId,
34
+ source: 'init',
31
35
  });
32
- logger.success('Configuration saved successfully:');
36
+ if (isJsonMode()) {
37
+ printJson({ workspace, zone });
38
+ return;
39
+ }
40
+ logger.success('Credentials saved to ~/.plazbot/config.json');
41
+ logger.label('Workspace', workspace);
42
+ logger.label('Zone', zone === 'EU' ? 'Europe (EU)' : 'Americas (LA)');
33
43
  if (options.email)
34
44
  logger.label('Email', options.email);
35
- logger.label('Workspace', options.workspace);
36
- logger.label('Zone', options.zone);
37
45
  if (options.userId)
38
- logger.label('UserId', options.userId);
39
- logger.title('You can start by using the following commands');
40
- logger.dim('plazbot agent list - View your agents');
41
- logger.dim('plazbot agent templates - Create an agent from a template');
42
- logger.dim('plazbot agent chat - Chat with an agent');
43
- logger.dim('plazbot --help - View all commands');
46
+ logger.label('User ID', options.userId);
47
+ console.log();
48
+ logger.dim('The key is not verified yet. Check it with:');
49
+ console.log(` ${chalk.cyan('plazbot agent list')} ${chalk.gray('List your agents (fails if the key or zone is wrong)')}`);
50
+ console.log(` ${chalk.cyan('plazbot whoami')} ${chalk.gray('Show the active session')}`);
44
51
  console.log();
45
- if (options.dev) {
46
- logger.warning('Environment: development');
47
- }
48
52
  }
49
53
  catch (error) {
50
- const message = error instanceof Error ? error.message : 'Unknown error';
51
- logger.error(message);
52
- process.exit(1);
54
+ fail(error);
53
55
  }
54
56
  });
55
57
  addExamples(loginCommand, [
56
- { description: 'Save credentials for a LA workspace',
57
- command: 'plazbot init -e you@company.com -k <jwt> -w wok_AbcDef123 -z LA' },
58
- { description: 'Save credentials for an EU workspace',
59
- command: 'plazbot init -k <jwt> -w wok_AbcDef123 -z EU' },
60
- { description: 'Include userId (used by studio support mode)',
61
- command: 'plazbot init -k <jwt> -w wok_AbcDef123 -z LA -u usr_AbcDef123' },
62
- { description: 'Target the local backend (dev mode)',
63
- command: 'plazbot init -k <jwt> -w wok_AbcDef123 -z LA --dev' },
58
+ { description: 'Save credentials for a workspace in the Americas',
59
+ command: 'plazbot init -k <apiKey> -w wok_AbcDef123 -z LA' },
60
+ { description: 'Save credentials for a workspace in Europe',
61
+ command: 'plazbot init -k <apiKey> -w wok_AbcDef123 -z EU -e you@company.com' },
62
+ { description: 'Include the user ID (needed by studio and workspace list)',
63
+ command: 'plazbot init -k <apiKey> -w wok_AbcDef123 -z LA -u usr_AbcDef123' },
64
+ ]);
65
+ addNotes(loginCommand, [
66
+ 'On your own machine prefer plazbot login (browser, no key to copy).',
67
+ 'In CI you can skip the file entirely: set PLAZBOT_API_KEY, PLAZBOT_WORKSPACE_ID and PLAZBOT_ZONE.',
64
68
  ]);
@@ -1,26 +1,35 @@
1
1
  import { Command } from 'commander';
2
2
  import { removeCredentials } from '../../utils/credentials.js';
3
3
  import { logger } from '../../utils/logger.js';
4
- import { addExamples } from '../../utils/help.js';
4
+ import { addExamples, addNotes } from '../../utils/help.js';
5
+ import { jsonOption, isJsonMode, printJson, fail } from '../../utils/output.js';
5
6
  export const logoutCommand = new Command('logout')
6
7
  .alias('finish')
7
- .description('Cierra la sesión local (borra ~/.plazbot/config.json)')
8
+ .description('Sign out on this machine (removes ~/.plazbot/config.json)')
9
+ .addOption(jsonOption())
8
10
  .option('--dev', 'Use development environment', false)
9
- .action(async (options) => {
11
+ .action(async (_options) => {
10
12
  try {
11
13
  await removeCredentials();
12
- logger.success('You have been signed out successfully');
13
- if (options.dev) {
14
- logger.warning('Environment: development');
14
+ if (isJsonMode()) {
15
+ printJson({ loggedOut: true });
16
+ return;
15
17
  }
18
+ logger.success('Signed out');
19
+ logger.dim('The token stays valid until you revoke it in Settings → Developer.');
20
+ if (process.env.PLAZBOT_API_KEY) {
21
+ logger.warning('PLAZBOT_API_KEY is still set in your environment, so commands keep working with it.');
22
+ }
23
+ console.log();
16
24
  }
17
25
  catch (error) {
18
- const message = error instanceof Error ? error.message : 'Unknown error while signing out';
19
- logger.error(message);
20
- process.exit(1);
26
+ fail(error);
21
27
  }
22
28
  });
23
29
  addExamples(logoutCommand, [
24
- { description: 'Remove the local configuration (~/.plazbot/config.json)',
25
- command: 'plazbot logout' },
30
+ { description: 'Sign out on this machine', command: 'plazbot logout' },
31
+ { description: 'Sign out from a script', command: 'plazbot logout --json' },
32
+ ]);
33
+ addNotes(logoutCommand, [
34
+ 'Logging out only deletes the local session. Revoke the token in Settings → Developer to invalidate it.',
26
35
  ]);
@@ -1,30 +1,54 @@
1
1
  import { Command } from 'commander';
2
- import { getStoredCredentials } from '../../utils/credentials.js';
2
+ import { apiBaseUrl, getStoredCredentials } from '../../utils/credentials.js';
3
3
  import { logger } from '../../utils/logger.js';
4
4
  import { addExamples } from '../../utils/help.js';
5
+ import { jsonOption, isJsonMode, printJson, fail, CliError } from '../../utils/output.js';
5
6
  import { section } from '../../utils/ui.js';
7
+ const SOURCE_LABELS = {
8
+ login: 'Browser login (plazbot login)',
9
+ init: 'API key (plazbot init)',
10
+ env: 'Environment variables (PLAZBOT_API_KEY)',
11
+ };
6
12
  export const statusCommand = new Command('whoami')
7
13
  .alias('status')
8
- .description('Muestra la sesión activa (cuenta, workspace, zona)')
14
+ .description('Show the active session (account, workspace and zone)')
15
+ .addOption(jsonOption())
9
16
  .action(async () => {
10
17
  try {
11
- const credentials = await getStoredCredentials();
18
+ let credentials;
19
+ try {
20
+ credentials = await getStoredCredentials();
21
+ }
22
+ catch {
23
+ throw new CliError('You are not signed in.', 'not_signed_in', "Run 'plazbot login' to sign in.");
24
+ }
25
+ const api = apiBaseUrl(credentials.zone);
26
+ if (isJsonMode()) {
27
+ printJson({
28
+ email: credentials.email || null,
29
+ workspace: credentials.workspace,
30
+ workspaceName: credentials.workspaceName || null,
31
+ zone: credentials.zone,
32
+ api,
33
+ source: credentials.source || 'init',
34
+ });
35
+ return;
36
+ }
12
37
  console.log(section('Active session'));
13
38
  if (credentials.email)
14
39
  logger.label('Email', credentials.email);
15
40
  logger.label('Workspace', credentials.workspaceName ? `${credentials.workspaceName} (${credentials.workspace})` : credentials.workspace);
16
- logger.label('Zone', credentials.zone === 'LA' ? 'Latin America (LA)' : 'Europe (EU)');
17
- logger.label('API', credentials.zone === 'EU' ? 'https://apieu.plazbot.com' : 'https://api.plazbot.com');
18
- if (credentials.source === 'env')
19
- logger.label('Origen', 'variables de entorno (PLAZBOT_API_KEY)');
41
+ logger.label('Zone', credentials.zone === 'EU' ? 'Europe (EU)' : 'Americas (LA)');
42
+ logger.label('API', api);
43
+ logger.label('Source', SOURCE_LABELS[credentials.source || 'init'] || credentials.source || 'init');
20
44
  console.log();
21
45
  }
22
- catch {
23
- logger.error("No hay una sesión activa. Ejecuta 'plazbot login' para iniciar sesión.");
24
- process.exit(1);
46
+ catch (error) {
47
+ fail(error);
25
48
  }
26
49
  });
27
50
  addExamples(statusCommand, [
28
- { description: 'Show the active workspace, email and zone',
29
- command: 'plazbot whoami' },
51
+ { description: 'Show the active workspace, email and zone', command: 'plazbot whoami' },
52
+ { description: 'Get the session as JSON', command: 'plazbot whoami --json' },
53
+ { description: 'Get only the workspace ID', command: "plazbot whoami --json | jq -r '.workspace'" },
30
54
  ]);
@@ -4,11 +4,12 @@ import chalk from 'chalk';
4
4
  import { apiBaseUrl, getStoredCredentials, saveCredentials } from '../../utils/credentials.js';
5
5
  import { createSpinner, createTable, theme } from '../../utils/ui.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
+ import { jsonOption, isJsonMode, printJson, fail, CliError } from '../../utils/output.js';
8
9
  async function fetchWorkspaces(dev) {
9
10
  const creds = await getStoredCredentials();
10
11
  if (!creds.userId) {
11
- throw new Error("Esta sesión no tiene usuario asociado. Vuelve a iniciar sesión con 'plazbot login'.");
12
+ throw new CliError('This session has no user attached, so workspaces cannot be listed.', 'missing_user_id', "Sign in again with 'plazbot login'.");
12
13
  }
13
14
  const res = await axios.get(`${apiBaseUrl(creds.zone, dev)}/api/workspace/workspaceByUserId/${encodeURIComponent(creds.userId)}`, {
14
15
  headers: { Authorization: `Bearer ${creds.apiKey}`, 'x-workspace-id': creds.workspace },
@@ -18,56 +19,89 @@ async function fetchWorkspaces(dev) {
18
19
  .map((w) => ({ id: w.data.id, name: w.data.name || w.data.id }));
19
20
  }
20
21
  const listCommand = new Command('list')
21
- .description('Lista tus workspaces')
22
- .option('--json', 'Salida en JSON')
23
- .option('--dev', 'Usar el entorno de desarrollo local', false)
22
+ .description('List the workspaces you belong to (same zone as the session)')
23
+ .addOption(jsonOption())
24
+ .option('--dev', 'Use development environment', false)
24
25
  .action(async (options) => {
25
26
  try {
26
27
  const creds = await getStoredCredentials();
27
- const spinner = options.json ? null : createSpinner('Cargando workspaces...').start();
28
- const items = await fetchWorkspaces(options.dev);
29
- spinner?.stop();
30
- if (options.json) {
31
- console.log(JSON.stringify(items.map(w => ({ ...w, active: w.id === creds.workspace })), null, 2));
28
+ const spinner = createSpinner('Loading workspaces...').start();
29
+ let items;
30
+ try {
31
+ items = await fetchWorkspaces(options.dev);
32
+ }
33
+ finally {
34
+ spinner.stop();
35
+ }
36
+ if (isJsonMode()) {
37
+ printJson(items.map(w => ({ ...w, active: w.id === creds.workspace })));
38
+ return;
39
+ }
40
+ if (items.length === 0) {
41
+ logger.dim('No workspaces found for this account.');
32
42
  return;
33
43
  }
34
44
  console.log(createTable(['', 'Workspace', 'ID'], items.map(w => [
35
45
  w.id === creds.workspace ? chalk.green('●') : ' ', w.name, theme.muted(w.id),
36
46
  ])));
37
- console.log(theme.muted(` Cambia con: plazbot workspace use <id>`));
47
+ console.log(theme.muted(' Switch with: plazbot workspace use <workspaceId>'));
38
48
  }
39
49
  catch (error) {
40
- logger.error(error instanceof Error ? error.message : 'Error desconocido');
41
- process.exit(1);
50
+ fail(error);
42
51
  }
43
52
  });
44
53
  const useCommand = new Command('use')
45
- .description('Cambia el workspace activo')
46
- .argument('<workspaceId>', 'ID del workspace (ver plazbot workspace list)')
47
- .option('--dev', 'Usar el entorno de desarrollo local', false)
54
+ .description('Switch the active workspace')
55
+ .argument('<workspaceId>', 'Workspace ID (see plazbot workspace list)')
56
+ .addOption(jsonOption())
57
+ .option('--dev', 'Use development environment', false)
48
58
  .action(async (workspaceId, options) => {
49
59
  try {
50
60
  const creds = await getStoredCredentials();
51
61
  if (creds.source === 'env') {
52
- throw new Error('La sesión viene de variables de entorno; cambia PLAZBOT_WORKSPACE_ID.');
62
+ throw new CliError('The session comes from environment variables.', 'env_session', 'Change PLAZBOT_WORKSPACE_ID instead.');
63
+ }
64
+ const spinner = createSpinner('Checking access...').start();
65
+ let items;
66
+ try {
67
+ items = await fetchWorkspaces(options.dev);
68
+ }
69
+ finally {
70
+ spinner.stop();
71
+ }
72
+ const target = items.find(w => w.id === workspaceId.trim());
73
+ if (!target) {
74
+ throw new CliError(`You do not have access to workspace ${workspaceId}.`, 'workspace_not_found', "See your workspaces with 'plazbot workspace list'.");
53
75
  }
54
- const items = await fetchWorkspaces(options.dev);
55
- const target = items.find(w => w.id === workspaceId);
56
- if (!target)
57
- throw new Error(`No tienes acceso al workspace ${workspaceId}.`);
58
76
  await saveCredentials({ ...creds, workspace: target.id, workspaceName: target.name });
59
- logger.success(`Workspace activo: ${target.name}`);
77
+ if (isJsonMode()) {
78
+ printJson({ id: target.id, name: target.name, active: true });
79
+ return;
80
+ }
81
+ logger.success(`Active workspace: ${target.name} ${theme.muted(`(${target.id})`)}`);
82
+ console.log();
60
83
  }
61
84
  catch (error) {
62
- logger.error(error instanceof Error ? error.message : 'Error desconocido');
63
- process.exit(1);
85
+ fail(error);
64
86
  }
65
87
  });
66
88
  export const workspaceCommand = new Command('workspace')
67
- .description('Ver y cambiar el workspace activo')
89
+ .description('List and switch workspaces')
68
90
  .addCommand(listCommand)
69
91
  .addCommand(useCommand);
70
92
  addExamples(workspaceCommand, [
71
- { description: 'Listar tus workspaces', command: 'plazbot workspace list' },
72
- { description: 'Cambiar el workspace activo', command: 'plazbot workspace use wok_AbcDef123' },
93
+ { description: 'List your workspaces', command: 'plazbot workspace list' },
94
+ { description: 'Switch the active workspace', command: 'plazbot workspace use wok_AbcDef123' },
95
+ { description: 'List workspaces as JSON', command: 'plazbot workspace list --json' },
96
+ ]);
97
+ addExamples(listCommand, [
98
+ { description: 'List your workspaces (● marks the active one)', command: 'plazbot workspace list' },
99
+ { description: 'Get them as JSON', command: 'plazbot workspace list --json' },
100
+ ]);
101
+ addExamples(useCommand, [
102
+ { description: 'Switch the active workspace', command: 'plazbot workspace use wok_AbcDef123' },
103
+ { description: 'Switch from a script', command: 'plazbot workspace use wok_AbcDef123 --json' },
104
+ ]);
105
+ addNotes(workspaceCommand, [
106
+ 'Only workspaces in the zone of the current session are listed. For another zone, run plazbot login --zone EU (or LA).',
73
107
  ]);