@arcadialdev/arcality 2.6.6 → 3.0.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.
package/bin/arcality.mjs CHANGED
@@ -15,6 +15,7 @@ let isRun = false;
15
15
  let isInit = false;
16
16
  let isSetup = false;
17
17
  let isLogs = false;
18
+ let isRunAll = false; // CI/CD mode: runs all missions from API headlessly
18
19
 
19
20
  // Buscamos los comandos en cualquier posición para evitar filtros de NPM en Windows
20
21
  rawArgs.forEach(arg => {
@@ -22,6 +23,7 @@ rawArgs.forEach(arg => {
22
23
  if (arg === 'init') isInit = true;
23
24
  if (arg === 'setup') isSetup = true;
24
25
  if (arg === '--logs') isLogs = true;
26
+ if (arg === '--run-all') isRunAll = true;
25
27
  });
26
28
 
27
29
  if (isLogs) {
@@ -38,6 +40,15 @@ if (isLogs) {
38
40
  cwd: process.cwd(),
39
41
  env: { ...process.env, ARCALITY_ROOT: PACKAGE_ROOT }
40
42
  }).on('exit', code => process.exit(code || 0));
43
+ } else if (isRunAll) {
44
+ // CI/CD HEADLESS MODE: Descarga y ejecuta todas las misiones del proyecto desde la API
45
+ // Uso: arcality --run-all [--workers=4] [--tags=smoke,regression] [--project-id=xxx]
46
+ const mainScript = path.join(PACKAGE_ROOT, 'scripts', 'gen-and-run.mjs');
47
+ spawn('node', [mainScript, '--run-all', ...rawArgs.filter(a => a !== '--run-all')], {
48
+ stdio: 'inherit',
49
+ cwd: process.cwd(),
50
+ env: { ...process.env, ARCALITY_ROOT: PACKAGE_ROOT, CI: process.env.CI || 'true' }
51
+ }).on('exit', code => process.exit(code ?? 1));
41
52
  } else if (isRun) {
42
53
  // FORZAMOS EL MODO AGENTE: Esto saltará el menú interactivo en gen-and-run.mjs
43
54
  const mainScript = path.join(PACKAGE_ROOT, 'scripts', 'gen-and-run.mjs');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadialdev/arcality",
3
- "version": "2.6.6",
3
+ "version": "3.0.0",
4
4
  "description": "El primer ingeniero QA Autónomo integrado al CI/CD. Creado por Arcadial.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -3,19 +3,37 @@
3
3
  const { defineConfig, devices } = require('@playwright/test');
4
4
  const path = require('path');
5
5
 
6
+ // ── Dynamic reporter: JUnit is added when running in CI (Azure DevOps, GitHub Actions) ──
7
+ // The JUnit XML file enables native test result publishing in your pipeline dashboard.
8
+ // In local/interactive mode, the JUnit reporter is skipped — only Arcality's HTML reporter runs.
9
+ const reportsDir = process.env.REPORTS_DIR || path.join(__dirname, 'tests-report');
10
+ const isCI = process.env.CI === 'true';
11
+
12
+ const reporters = [
13
+ ['line'],
14
+ [path.join(__dirname, 'tests', '_helpers', 'ArcalityReporter.js'), { outputDir: reportsDir }],
15
+ ];
16
+
17
+ if (isCI) {
18
+ reporters.push(['junit', { outputFile: path.join(reportsDir, 'results.xml') }]);
19
+ }
20
+
6
21
  module.exports = defineConfig({
7
22
  testDir: path.join(__dirname, 'tests', '_helpers'),
8
23
  testMatch: ['agentic-runner.bundle.spec.js'],
9
- reporter: [
10
- ['line'],
11
- [path.join(__dirname, 'tests', '_helpers', 'ArcalityReporter.js'),
12
- { outputDir: process.env.REPORTS_DIR || path.join(__dirname, 'tests-report') }]
13
- ],
24
+ reporter: reporters,
14
25
  use: {
26
+ // ── Dynamic BASE_URL ──
27
+ // Priority: CLI --base-url flag → arcality.config baseUrl → BASE_URL env → default
28
+ // This allows pipelines to inject the target environment dynamically:
29
+ // arcality --run-all --base-url=https://staging.myapp.com
30
+ baseURL: process.env.BASE_URL || 'http://localhost:3000',
15
31
  video: 'on',
16
32
  screenshot: 'on',
17
33
  trace: 'on',
18
34
  colorScheme: 'dark',
35
+ // In CI: headless is enforced by Playwright automatically when CI=true
36
+ headless: isCI ? true : undefined,
19
37
  contextOptions: {
20
38
  reducedMotion: 'reduce',
21
39
  },
@@ -32,3 +50,4 @@ module.exports = defineConfig({
32
50
  },
33
51
  ],
34
52
  });
53
+
@@ -162,7 +162,6 @@ function setupEnvironment() {
162
162
 
163
163
  function showBanner() {
164
164
  console.clear();
165
- const projectName = 'Arcality';
166
165
 
167
166
  let version = 'unknown';
168
167
  try {
@@ -170,19 +169,22 @@ function showBanner() {
170
169
  version = pkg.version || 'unknown';
171
170
  } catch { }
172
171
 
173
- const logo = figlet.textSync(projectName, {
174
- font: 'Standard',
175
- horizontalLayout: 'default',
176
- verticalLayout: 'default',
177
- });
172
+ const sentinelAscii = `
173
+ █████ ██████ ██████ █████ ██ ██ ████████ ██ ██
174
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
175
+ ███████ ██████ ██ ███████ ██ ██ ██ ████
176
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
177
+ ██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ ██ `;
178
178
 
179
- intro(chalk.cyanBright(logo));
179
+ intro(chalk.gray(sentinelAscii));
180
180
 
181
181
  note(
182
- chalk.magentaBright('Powered by Arcadial') + '\n' +
182
+ chalk.gray(' Motor de Observación: ') + chalk.bold.white('EN LÍNEA') + '\n' +
183
+ chalk.gray('◉ Módulo de Recuperación de UI: ') + chalk.bold.white('ACTIVO') + '\n' +
184
+ chalk.gray('◉ Lógica Forense: ') + chalk.bold.white('ACTIVA') + '\n' +
183
185
  chalk.gray('─'.repeat(50)) + '\n' +
184
- chalk.bold.white('📦 Version: ') + chalk.yellow(version),
185
- 'System Information'
186
+ chalk.gray('Versión del Sistema: ') + chalk.white(`v${version}`),
187
+ 'Estado Central de Arcality'
186
188
  );
187
189
 
188
190
  const { configName, baseUrl, techStack, configDir } = setupEnvironment();
@@ -190,30 +192,30 @@ function showBanner() {
190
192
  // Show arcality.config status instead of multi-config switching
191
193
  const hasConfig = !!projectConfig;
192
194
  const configStatus = hasConfig
193
- ? chalk.green(' Configured')
194
- : chalk.red(' Not configured run `arcality init`');
195
+ ? chalk.green(' Sincronización Establecida')
196
+ : chalk.red(' DesconectadoEjecuta `arcality init`');
195
197
 
196
198
  const { key: apiKeyVal, source: apiKeySource } = getApiKey();
197
199
  const apiKeyDisplay = apiKeyVal
198
- ? chalk.green(maskApiKey(apiKeyVal)) + chalk.gray(` (${apiKeySource})`)
199
- : chalk.red('Not configured');
200
+ ? chalk.green(maskApiKey(apiKeyVal)) + chalk.gray(` [${apiKeySource}]`)
201
+ : chalk.red('NO VERIFICADO');
200
202
 
201
203
  const infoLines = [
202
- chalk.bold.white('⚙️ Environment: ') + chalk.yellow(process.env.NODE_ENV ?? 'development'),
203
- chalk.bold.white('🔑 API Key: ') + apiKeyDisplay,
204
- chalk.bold.white('🛠️ Stack: ') + chalk.magenta(techStack),
204
+ chalk.gray('Nodo Objetivo: ') + chalk.white(process.env.NODE_ENV ?? 'development'),
205
+ chalk.gray('Token de Autenticación: ') + apiKeyDisplay,
206
+ chalk.gray('Stack: ') + chalk.white(techStack),
205
207
  ];
206
208
 
207
209
  if (hasConfig) {
208
210
  infoLines.splice(1, 0,
209
- chalk.bold.white('📋 Project: ') + chalk.cyan(configName) + chalk.gray(` (${baseUrl})`),
210
- chalk.bold.white('🆔 Project ID: ') + chalk.gray(projectConfig.projectId || 'N/A'),
211
+ chalk.gray('Proyecto: ') + chalk.white(configName) + chalk.gray(` [${baseUrl}]`),
212
+ chalk.gray('ID de Identidad: ') + chalk.white(projectConfig.projectId || 'N/A'),
211
213
  );
212
214
  } else {
213
- infoLines.push(chalk.bold.white('📋 Config: ') + configStatus);
215
+ infoLines.push(chalk.gray('Configuración: ') + configStatus);
214
216
  }
215
217
 
216
- note(infoLines.join('\n'), 'Active Configuration');
218
+ note(infoLines.join('\n'), 'Telemetría del Objetivo');
217
219
  }
218
220
 
219
221
  const ARCALITY_BRAIN_URL = "https://api.anthropic.com/v1/messages";
@@ -496,21 +498,21 @@ async function main() {
496
498
  const rootYamlFiles = fs.readdirSync(PROJECT_ROOT).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
497
499
 
498
500
  const options = [
499
- { label: '🤖 Run Autonomous Agent (Prompt)', value: 'agent' },
500
- ...(savedMissions.length ? [{ label: '🚀 Launch Saved Mission (.yaml)', value: 'launch_saved' }] : []),
501
- ...(yamlOutputFiles.length ? [{ label: '♻️ Reuse YAML + Auto-Guide (if prev. run exists)', value: 'reuse_yaml' }] : []),
502
- ...(rootYamlFiles.length ? [{ label: '📂 Choose Root YAML (Templates)', value: 'yaml_list' }] : []),
503
- { label: '🔄 Reconfigure (arcality init)', value: 'reconfigure' },
504
- { label: ' Exit', value: 'exit' }
501
+ { label: '◉ Desplegar Arcality (Prompt)', value: 'agent' },
502
+ ...(savedMissions.length ? [{ label: '⟳ Lanzar Misión Archivada (.yaml)', value: 'launch_saved' }] : []),
503
+ ...(yamlOutputFiles.length ? [{ label: ' Activar Modo GUÍA (Reusar YAML)', value: 'reuse_yaml' }] : []),
504
+ ...(rootYamlFiles.length ? [{ label: '📂 Cargar Matriz de Patrones (YAML Raíz)', value: 'yaml_list' }] : []),
505
+ { label: '⚙ Recalibrar Sistema (init)', value: 'reconfigure' },
506
+ { label: '✕ Terminar Proceso', value: 'exit' }
505
507
  ];
506
508
 
507
509
  const action = await select({
508
- message: 'What would you like to do?',
510
+ message: '¿Qué te gustaría hacer?',
509
511
  options
510
512
  });
511
513
 
512
514
  if (isCancel(action) || action === 'exit') {
513
- outro(chalk.cyan('See you later!'));
515
+ outro(chalk.cyan('¡Hasta luego!'));
514
516
  break;
515
517
  }
516
518
 
@@ -546,7 +548,7 @@ async function main() {
546
548
  continue;
547
549
  } else if (action === 'launch_saved') {
548
550
  const sel = await select({
549
- message: 'Choose saved mission:',
551
+ message: 'Elige la misión guardada:',
550
552
  options: savedMissions.map(f => ({ label: f, value: f }))
551
553
  });
552
554
  if (isCancel(sel)) continue;
@@ -558,7 +560,7 @@ async function main() {
558
560
  } else if (action === 'reuse_yaml') {
559
561
  const yamlDir = getYamlOutputDir(projectConfig, ORIGINAL_CWD);
560
562
  const sel = await select({
561
- message: 'Choose YAML to reuse:',
563
+ message: 'Elige el YAML a reusar:',
562
564
  options: yamlOutputFiles.map(f => ({ label: f, value: f }))
563
565
  });
564
566
  if (isCancel(sel)) continue;
@@ -569,7 +571,7 @@ async function main() {
569
571
  agentMode = true;
570
572
  } else if (action === 'yaml_list') {
571
573
  const sel = await select({
572
- message: 'Choose YAML file:',
574
+ message: 'Elige el archivo YAML:',
573
575
  options: rootYamlFiles.map(f => ({ label: f, value: f }))
574
576
  });
575
577
  if (isCancel(sel)) continue;
@@ -589,16 +591,16 @@ async function main() {
589
591
  if (agentMode) {
590
592
  if (!prompt || prompt.trim().length < 4) {
591
593
  const p = await text({
592
- message: chalk.cyan('🤖 Mission for the Agent (Required):'),
593
- placeholder: 'E.g., Fill the signup form with random data',
594
+ message: chalk.cyan('🤖 Misión para Arcality (Requerida):'),
595
+ placeholder: 'Ej., Llena el formulario de registro con datos aleatorios',
594
596
  validate: v => {
595
- if (!v || !v.trim()) return 'The mission is required to start.';
596
- if (v.trim().length < 4) return 'The mission must be more descriptive (minimum 4 characters).';
597
+ if (!v || !v.trim()) return 'La misión es requerida para iniciar.';
598
+ if (v.trim().length < 4) return 'La misión debe ser más descriptiva (mínimo 4 caracteres).';
597
599
  }
598
600
  });
599
601
 
600
602
  if (isCancel(p)) {
601
- cancel('Mission cancelled. Returning to main menu...');
603
+ cancel('Misión cancelada. Regresando al menú principal...');
602
604
  agentMode = false;
603
605
  prompt = "";
604
606
  skipMenu = false;
@@ -611,19 +613,19 @@ async function main() {
611
613
  const knownBaseUrl = projectConfig?.project?.baseUrl || process.env.BASE_URL;
612
614
  const promptMsg = knownBaseUrl
613
615
  ? `🌐 Initial navigation path (relative to ${knownBaseUrl}):`
614
- : '🌐 Full Navigation URL (e.g., http://localhost:3000/):';
616
+ : '🌐 URL Completa de Navegación (ej., http://localhost:3000/):';
615
617
 
616
618
  const d = await text({
617
619
  message: chalk.cyan(promptMsg),
618
620
  placeholder: knownBaseUrl ? '/' : 'http://localhost:3000/',
619
621
  validate: v => {
620
- if (!knownBaseUrl && !v.startsWith('http')) return 'Please provide a full URL with http:// or https://';
622
+ if (!knownBaseUrl && !v.startsWith('http')) return 'Por favor provee una URL completa con http:// o https://';
621
623
  return undefined;
622
624
  }
623
625
  });
624
626
 
625
627
  if (isCancel(d)) {
626
- cancel('Mission aborted. Returning to menu...');
628
+ cancel('Misión abortada. Regresando al menú...');
627
629
  agentMode = false;
628
630
  prompt = "";
629
631
  skipMenu = false;
@@ -653,12 +655,12 @@ async function main() {
653
655
  }
654
656
  if (!validation.valid) {
655
657
  const errorMessages = {
656
- 'no_api_key': '🔑 API Key not found.\n Configure it via `arcality init` or in .env (ARCALITY_API_KEY)',
657
- 'invalid_format': '🔑 Invalid API Key. It must start with "arc_"',
658
- 'invalid_api_key': '🔑 API Key not recognized by the server',
659
- 'plan_expired': '💳 Your plan has expired'
658
+ 'no_api_key': '🔑 API Key no encontrada.\n Configúrala vía `arcality init` o en .env (ARCALITY_API_KEY)',
659
+ 'invalid_format': '🔑 API Key inválida. Debe comenzar con "arc_"',
660
+ 'invalid_api_key': '🔑 API Key no reconocida por el servidor',
661
+ 'plan_expired': '💳 Tu plan ha expirado'
660
662
  };
661
- note(chalk.red(errorMessages[validation.error] || `Error: ${validation.error}`), '❌ Authentication');
663
+ note(chalk.red(errorMessages[validation.error] || `Error: ${validation.error}`), '❌ Autenticación');
662
664
  agentMode = false;
663
665
  prompt = "";
664
666
  skipMenu = false;
@@ -666,15 +668,15 @@ async function main() {
666
668
  }
667
669
 
668
670
  // Show plan info
669
- const modeLabel = validation.mode === 'mock' ? chalk.gray('(local)') : chalk.green('(live)');
671
+ const modeLabel = validation.mode === 'mock' ? chalk.gray('[sandbox]') : chalk.cyanBright('[live-net]');
670
672
  const dailyLimitDisplay = validation.daily_limit === -1 ? '∞' : (validation.daily_limit || 35);
671
673
  const remainingDisplay = validation.remaining >= 999999 ? '∞' : validation.remaining;
672
674
  note(
673
- chalk.bold.white(' Valid API Key ') + modeLabel + '\n' +
674
- chalk.bold.white('📋 Plan: ') + chalk.cyan(validation.plan || 'internal') + '\n' +
675
- chalk.bold.white('📊 Missions today: ') + chalk.yellow(`${validation.daily_used || 0}/${dailyLimitDisplay}`) + '\n' +
676
- chalk.bold.white('🎯 Remaining: ') + chalk.green(remainingDisplay),
677
- 'Arcality Account'
675
+ chalk.gray('Conexión: ') + chalk.cyanBright('SECURE ') + modeLabel + '\n' +
676
+ chalk.gray('Autorización: ') + chalk.white(validation.plan || 'internal') + '\n' +
677
+ chalk.gray('Misiones: ') + chalk.white(`${validation.daily_used || 0}/${dailyLimitDisplay}`) + '\n' +
678
+ chalk.gray('Reservas: ') + chalk.white(remainingDisplay),
679
+ 'Enlace Arcality'
678
680
  );
679
681
 
680
682
  // --- RESOLVE PROJECT ID ---
@@ -697,21 +699,21 @@ async function main() {
697
699
  const projects = data.projects || [];
698
700
 
699
701
  const projOptions = projects.map(p => ({ label: p.name || p.Name, value: p.id || p.Id }));
700
- projOptions.push({ label: '➕ Create new project', value: 'new' });
702
+ projOptions.push({ label: '➕ Crear nuevo proyecto', value: 'new' });
701
703
 
702
704
  const projSelection = await select({
703
- message: chalk.cyan('Choose a project for this mission:'),
705
+ message: chalk.cyan('Elige un proyecto para esta misión:'),
704
706
  options: projOptions
705
707
  });
706
708
 
707
709
  if (isCancel(projSelection)) {
708
- cancel('Mission aborted.');
710
+ cancel('Misión abortada.');
709
711
  agentMode = false; prompt = ""; skipMenu = false; continue;
710
712
  }
711
713
 
712
714
  if (projSelection === 'new') {
713
- const newName = await text({ message: 'Project name:', validate: v => !v ? 'Required' : undefined });
714
- if (isCancel(newName)) { cancel('Aborted'); agentMode = false; prompt = ""; skipMenu = false; continue; }
715
+ const newName = await text({ message: 'Nombre del proyecto:', validate: v => !v ? 'Requerido' : undefined });
716
+ if (isCancel(newName)) { cancel('Abortado'); agentMode = false; prompt = ""; skipMenu = false; continue; }
715
717
 
716
718
  const createRes = await fetch(`${apiBase}/api/v1/projects`, {
717
719
  method: 'POST',
@@ -727,9 +729,9 @@ async function main() {
727
729
  if (createRes.ok) {
728
730
  const created = await createRes.json();
729
731
  selectedProjectId = created.id || created.Id;
730
- note(chalk.green(`✅ Project created: ${newName}`));
732
+ note(chalk.green(`✅ Proyecto creado: ${newName}`));
731
733
  } else {
732
- note(chalk.red(`❌ Failed to create project.`));
734
+ note(chalk.red(`❌ Falló al crear proyecto.`));
733
735
  }
734
736
  } else {
735
737
  selectedProjectId = projSelection;
@@ -750,10 +752,10 @@ async function main() {
750
752
  const mission = await requestMission(prompt, discoverPath || '/');
751
753
  if (!mission.allowed) {
752
754
  note(
753
- chalk.red(`❌ ${mission.error === 'mission_limit_exceeded' ? 'Daily mission limit reached' : mission.error}`) + '\n' +
755
+ chalk.red(`❌ ${mission.error === 'mission_limit_exceeded' ? 'Límite diario de misiones alcanzado' : mission.error}`) + '\n' +
754
756
  chalk.yellow(`📊 Used: ${mission.daily_used}/${mission.daily_limit}`) + '\n' +
755
757
  chalk.gray(`🔄 Resets: ${mission.resets_at ? new Date(mission.resets_at).toLocaleString() : 'tomorrow'}`),
756
- '⚠️ Limit Reached'
758
+ '⚠️ Límite Alcanzado'
757
759
  );
758
760
  agentMode = false;
759
761
  prompt = "";
@@ -763,9 +765,9 @@ async function main() {
763
765
 
764
766
  const s = spinner();
765
767
  if (!process.argv.includes('--debug')) {
766
- s.start(`🤖 [AGENT MODE] Starting mission: "${prompt}" ${chalk.gray(`(${mission.daily_used}/${mission.daily_limit} today)`)}`);
768
+ s.start(`◉ [ARCALITY INICIADO] Ejecutando directiva: "${prompt}" ${chalk.gray(`(${mission.daily_used}/${mission.daily_limit})`)}`);
767
769
  } else {
768
- console.log(chalk.cyan(`\n🤖 [AGENT MODE] Starting mission: "${prompt}" ${chalk.gray(`(${mission.daily_used}/${mission.daily_limit} today)`)}`));
770
+ console.log(chalk.cyan(`\n [ARCALITY INICIADO] Ejecutando directiva: "${prompt}" ${chalk.gray(`(${mission.daily_used}/${mission.daily_limit})`)}`));
769
771
  }
770
772
 
771
773
  const finalProjectId = process.env.ARCALITY_PROJECT_ID || selectedProjectId;
@@ -821,9 +823,9 @@ async function main() {
821
823
  }
822
824
  userCanceledMission = true;
823
825
 
824
- console.log(chalk.yellow('\n\n⚠️ [ARCALITY] Misión cancelada por el usuario.'));
826
+ console.log(chalk.yellow('\n\n⚠️ [ARCALITY] Directiva abortada por el operador.'));
825
827
  if (!process.argv.includes('--debug')) {
826
- try { s.stop(chalk.yellow('⚠️ Misión cancelada.')); } catch { /* ignore */ }
828
+ try { s.stop(chalk.yellow('⚠️ Directiva abortada.')); } catch { /* ignore */ }
827
829
  }
828
830
 
829
831
  // Terminar el proceso hijo de Playwright directamente (Windows-safe)
@@ -842,7 +844,7 @@ async function main() {
842
844
  if (ctxDir && fs.existsSync(ctxDir)) {
843
845
  const cancelLog = {
844
846
  prompt,
845
- history: ['[CANCELADO POR USUARIO] La prueba fue detenida manualmente antes de completarse.'],
847
+ history: ['[ABORTO DEL OPERADOR] La directiva fue detenida manualmente antes de completarse.'],
846
848
  steps: 0,
847
849
  success: false,
848
850
  error: true,
@@ -850,7 +852,7 @@ async function main() {
850
852
  canceled_by_user: true,
851
853
  timestamp: new Date().toISOString()
852
854
  };
853
- fs.writeFileSync(path.join(ctxDir, `agent-log-${Date.now()}.json`), JSON.stringify(cancelLog, null, 2));
855
+ fs.writeFileSync(path.join(ctxDir, `arcality-log-${Date.now()}.json`), JSON.stringify(cancelLog, null, 2));
854
856
  }
855
857
  } catch { /* silencioso */ }
856
858
 
@@ -958,21 +960,34 @@ async function main() {
958
960
  console.log(chalk.gray(`[DEBUG] Using playwright CLI: ${playwrightCli}`));
959
961
  }
960
962
  } catch (e) {
961
- console.error(chalk.red('\n[FATAL] Playwright core not found. Make sure @playwright/test is installed.'));
963
+ console.error(chalk.red('\n[FATAL] Núcleo de Playwright no encontrado. Asegúrate de que @playwright/test esté instalado.'));
962
964
  console.error(chalk.red(e.message));
963
- console.error(chalk.yellow('\nTip: Run `npm install @playwright/test` in your project directory.'));
965
+ console.error(chalk.yellow('\nConsejo: Ejecuta `npm install @playwright/test` en tu directorio de proyecto.'));
964
966
  _missionActive = false;
965
967
  process.exit = _origProcessExit;
966
968
  _origProcessExit(1);
967
969
  }
968
970
 
969
- const postProcessSigint = () => console.log(chalk.yellow('\n⏳ Finalizando tareas de reporte, por favor espera...'));
971
+ const postProcessSigint = () => console.log(chalk.yellow('\n⏳ Finalizando reporte de telemetría, por favor espera...'));
970
972
  let missionResult = null;
971
973
  let finalFailReason = null;
972
974
  let finalUsageData = undefined;
973
975
  let finalReportUrl = null;
974
976
  let finalFailReasoning = null;
975
977
 
978
+ // ── Auto-compilar el runner antes de ejecutar Playwright para asegurar que los cambios de rama/merge estén aplicados ──
979
+ try {
980
+ if (!process.argv.includes('--debug')) {
981
+ s.message('⚙️ Compilando agente de pruebas (Arcality Runner)...');
982
+ } else {
983
+ console.log(chalk.cyan('⚙️ Compilando agente de pruebas (Arcality Runner)...'));
984
+ }
985
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
986
+ await run(npmCmd, ['run', 'build:runner'], { cwd: PROJECT_ROOT });
987
+ } catch (err) {
988
+ console.warn(chalk.yellow(`\n⚠️ Advertencia: No se pudo auto-compilar el runner: ${err.message}`));
989
+ }
990
+
976
991
  try {
977
992
  // No test file path is passed — playwright.config.js uses testMatch:'agentic-runner.spec.ts'
978
993
  // This eliminates the Windows regex path issue permanently on any user's machine.
@@ -1005,26 +1020,26 @@ async function main() {
1005
1020
  }
1006
1021
 
1007
1022
  missionResult = 'success';
1008
- if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Mission completed successfully.'));
1009
- else console.log(chalk.green('✅ Mission completed successfully.'));
1023
+ if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Misión completada exitosamente.'));
1024
+ else console.log(chalk.green('✅ Misión completada exitosamente.'));
1010
1025
 
1011
1026
  // ── Ping Project ──
1012
1027
  await pingProject(finalProjectId, process.env.ARCALITY_API_KEY);
1013
1028
 
1014
1029
  // ── Save Mission YAML ──
1015
1030
  const saveDecision = await select({
1016
- message: 'Do you want to save this mission to run it again later?',
1031
+ message: '¿Quieres guardar esta misión para ejecutarla de nuevo más tarde?',
1017
1032
  options: [
1018
- { label: '✅ Yes, save as YAML', value: 'yes' },
1019
- { label: '❌ No, thanks', value: 'no' }
1033
+ { label: '✅ Sí, guardar como YAML', value: 'yes' },
1034
+ { label: '❌ No, gracias', value: 'no' }
1020
1035
  ]
1021
1036
  });
1022
1037
 
1023
1038
  if (saveDecision === 'yes') {
1024
1039
  const name = await text({
1025
- message: 'Mission name (e.g., valid_login):',
1040
+ message: 'Nombre de la misión (ej., login_valido):',
1026
1041
  placeholder: 'my_mission',
1027
- validate: v => v.length < 3 ? 'Name too short' : undefined
1042
+ validate: v => v.length < 3 ? 'Nombre muy corto' : undefined
1028
1043
  });
1029
1044
 
1030
1045
  if (!isCancel(name)) {
@@ -1050,9 +1065,9 @@ async function main() {
1050
1065
  const yamlOutputDir = ensureYamlOutputDir(projectConfig, ORIGINAL_CWD);
1051
1066
  const yamlPathOutput = path.join(yamlOutputDir, `${safeName}.yaml`);
1052
1067
  fs.writeFileSync(yamlPathOutput, yamlData);
1053
- note(chalk.green(`✅ Mission saved at: ${yamlPathOutput}`), 'Arcality Persistence');
1068
+ note(chalk.green(`✅ Misión guardada en: ${yamlPathOutput}`), 'Persistencia Arcality');
1054
1069
  } else {
1055
- note(chalk.green(`✅ Mission saved at: ${yamlPathMissions}`), 'Arcality Persistence');
1070
+ note(chalk.green(`✅ Misión guardada en: ${yamlPathMissions}`), 'Persistencia Arcality');
1056
1071
  }
1057
1072
  }
1058
1073
  }
@@ -1061,12 +1076,12 @@ async function main() {
1061
1076
  if (userCanceledMission) {
1062
1077
  missionResult = 'canceled';
1063
1078
  finalFailReason = 'user_canceled';
1064
- console.log(chalk.yellow('\n⚠️ Misión cancelada — generando reporte de evidencia...'));
1079
+ console.log(chalk.yellow('\n⚠️ Directiva abortada — generando reporte de evidencia...'));
1065
1080
  } else {
1066
1081
  missionResult = 'failed';
1067
1082
  finalFailReason = 'timeout_or_crash';
1068
- s.stop(chalk.red(`❌ Mission could not be completed.`));
1069
- console.error(chalk.red(`\nError Details: ${e.message}`));
1083
+ s.stop(chalk.red(`❌ Arcality no pudo completar la directiva.`));
1084
+ console.error(chalk.red(`\nDetalles del Error: ${e.message}`));
1070
1085
  if (e.stack && process.argv.includes('--debug')) {
1071
1086
  console.error(chalk.gray(e.stack));
1072
1087
  }
@@ -1081,7 +1096,7 @@ async function main() {
1081
1096
  if (userCanceledMission) {
1082
1097
  missionResult = 'canceled';
1083
1098
  finalFailReason = 'user_canceled';
1084
- finalFailReasoning = 'La misión fue detenida manualmente por el usuario (Ctrl+C) antes de completarse. No se trata de un fallo técnico del sistema ni del portal.';
1099
+ finalFailReasoning = 'La misión fue detenida manualmente por el operador (Ctrl+C). Esto no es una falla técnica del sistema.';
1085
1100
  await new Promise(r => setTimeout(r, 800));
1086
1101
  }
1087
1102
 
@@ -1090,9 +1105,9 @@ async function main() {
1090
1105
 
1091
1106
  const sRep = spinner();
1092
1107
  if (!process.argv.includes('--debug')) {
1093
- sRep.start('📊 Generating report...');
1108
+ sRep.start('📊 Generando reporte...');
1094
1109
  } else {
1095
- console.log('📊 Generating report...');
1110
+ console.log('📊 Generando reporte...');
1096
1111
  }
1097
1112
 
1098
1113
  await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
@@ -1107,7 +1122,7 @@ async function main() {
1107
1122
  const ctxDir = process.env.CONTEXT_DIR;
1108
1123
  if (fs.existsSync(ctxDir)) {
1109
1124
  const files = fs.readdirSync(ctxDir);
1110
- const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
1125
+ const logFiles = files.filter(f => f.startsWith('arcality-log-') && f.endsWith('.json')).sort();
1111
1126
  if (logFiles.length > 0) {
1112
1127
  const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
1113
1128
  const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
@@ -1134,7 +1149,7 @@ async function main() {
1134
1149
 
1135
1150
  if (sasData && actualSasUrl && actualBasePath) {
1136
1151
  if (!process.argv.includes('--debug')) {
1137
- sRep.start('☁️ Uploading evidence to Azure...');
1152
+ sRep.start('☁️ Subiendo evidencia a Azure...');
1138
1153
  }
1139
1154
  process.removeAllListeners('SIGINT');
1140
1155
  process.removeAllListeners('SIGTERM');
@@ -1148,14 +1163,14 @@ async function main() {
1148
1163
  finalReportUrl = `${parsedSas.origin}${parsedSas.pathname}/${actualBasePath}/index.html`;
1149
1164
  } catch(e) {}
1150
1165
 
1151
- if (!process.argv.includes('--debug')) sRep.stop(chalk.green('✅ Evidence uploaded successfully.'));
1166
+ if (!process.argv.includes('--debug')) sRep.stop(chalk.green('✅ Evidencia subida exitosamente.'));
1152
1167
  }
1153
1168
  } catch (e) {
1154
1169
  if (process.argv.includes('--debug')) console.log(chalk.yellow(`⚠️ Could not upload evidence: ${e.message}`));
1155
- if (!process.argv.includes('--debug')) sRep.stop(chalk.yellow('⚠️ Evidence upload failed.'));
1170
+ if (!process.argv.includes('--debug')) sRep.stop(chalk.yellow('⚠️ Fallo al subir evidencia.'));
1156
1171
  }
1157
1172
  } else {
1158
- console.log(chalk.gray(' >> Skipping evidence upload (mission was canceled).'));
1173
+ console.log(chalk.gray(' >> Saltando subida de evidencia (la misión fue cancelada).'));
1159
1174
  }
1160
1175
 
1161
1176
  // ── End Mission — SIEMPRE se llama, independientemente de si la evidencia subió ──
@@ -1179,14 +1194,14 @@ async function main() {
1179
1194
  }
1180
1195
 
1181
1196
  if (fs.existsSync(arcalityPath)) {
1182
- console.log(chalk.blue(`🌐 Opening Arcality Report: ${arcalityPath}`));
1197
+ console.log(chalk.blue(`🌐 Abriendo Reporte de Arcality: ${arcalityPath}`));
1183
1198
  if (process.platform === "win32") {
1184
1199
  exec(`start "" "${arcalityPath}"`);
1185
1200
  } else {
1186
1201
  exec(`open "${arcalityPath}"`);
1187
1202
  }
1188
1203
  } else {
1189
- console.log(chalk.yellow(`⚠️ Report not generated (test may have crashed before completing).`));
1204
+ console.log(chalk.yellow(`⚠️ Reporte no generado (la prueba pudo haber fallado antes de completarse).`));
1190
1205
  }
1191
1206
 
1192
1207
  await new Promise(resolve => setTimeout(resolve, 2000));
@@ -1197,10 +1212,14 @@ async function main() {
1197
1212
  process.env.ARCALITY_API_KEY
1198
1213
  );
1199
1214
 
1215
+ // Limpiar handlers del post-proceso ANTES de esperar Enter, ya que toda la telemetría y reportes ya finalizaron
1216
+ process.removeListener('SIGINT', postProcessSigint);
1217
+ process.removeListener('SIGTERM', postProcessSigint);
1218
+
1200
1219
  // Esperar Enter para regresar al menú
1201
1220
  // Usamos escucha directa de stdin (raw mode) porque @clack/text
1202
1221
  // no procesa Enter correctamente cuando setRawMode(true) está activo.
1203
- console.log(chalk.gray('\n Press Enter to return to menu...'));
1222
+ console.log(chalk.gray('\n Presiona Enter para regresar al menú...'));
1204
1223
  await new Promise(resolve => {
1205
1224
  const onKey = (key) => {
1206
1225
  const keyStr = String(key);
@@ -1211,10 +1230,17 @@ async function main() {
1211
1230
  if (process.stdin.isTTY && process.stdin.setRawMode) {
1212
1231
  process.stdin.setRawMode(false);
1213
1232
  }
1214
- process.stdin.pause();
1233
+ // FIX: pausar stdin si vamos a salir para que el event loop no se quede colgado.
1234
+ // Si volvemos al menú, no lo pausamos (para evitar el bug de exit en clack).
1235
+ if (skipMenu) {
1236
+ process.stdin.pause();
1237
+ }
1215
1238
 
1216
- if (keyStr === '\u0003') process.emit('SIGINT'); // Ctrl+C = salir
1217
- else resolve();
1239
+ if (keyStr === '\u0003') {
1240
+ process.exit(0); // Ctrl+C = salir inmediatamente
1241
+ } else {
1242
+ resolve();
1243
+ }
1218
1244
  }
1219
1245
  };
1220
1246
 
@@ -1232,17 +1258,13 @@ async function main() {
1232
1258
  });
1233
1259
 
1234
1260
  if (skipMenu) {
1235
- outro(chalk.cyan('Mission finished! See you later.'));
1261
+ outro(chalk.cyan('¡Misión terminada! Hasta luego.'));
1236
1262
  return;
1237
1263
  }
1238
1264
 
1239
1265
  agentMode = false;
1240
1266
  prompt = "";
1241
1267
  firstRun = false;
1242
-
1243
- // Limpiar handlers del post-proceso
1244
- process.removeListener('SIGINT', postProcessSigint);
1245
- process.removeListener('SIGTERM', postProcessSigint);
1246
1268
  }
1247
1269
  }
1248
1270
  firstRun = false;