@arcadialdev/arcality 2.4.23 → 2.4.24

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
@@ -1,93 +1,55 @@
1
1
  #!/usr/bin/env node
2
2
  // bin/arcality.mjs — Entry point for the 'arcality' global command
3
- // Registered as "bin" in package.json → npm creates the global symlink
4
-
5
3
  import { fileURLToPath } from 'url';
6
4
  import path from 'node:path';
7
5
  import { spawn } from 'node:child_process';
6
+ import fs from 'node:fs';
8
7
 
9
8
  const __filename = fileURLToPath(import.meta.url);
10
9
  const __dirname = path.dirname(__filename);
11
10
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
12
11
 
13
- const args = process.argv.slice(2);
14
- const isInit = args.includes('init');
15
- const isRun = args.includes('run');
16
- const isSetup = args.includes('setup');
17
- const isVersion = args.includes('--version') || args.includes('-v');
18
- const isHelp = args.includes('--help') || args.includes('-h') || args.includes('help');
12
+ // REFUERZO DE ARGUMENTOS: Capturamos TODO lo que venga de la consola
13
+ const rawArgs = process.argv.slice(2);
14
+ let isRun = false;
15
+ let isInit = false;
16
+ let isSetup = false;
17
+
18
+ // Buscamos los comandos en cualquier posición para evitar filtros de NPM en Windows
19
+ rawArgs.forEach(arg => {
20
+ if (arg === 'run') isRun = true;
21
+ if (arg === 'init') isInit = true;
22
+ if (arg === 'setup') isSetup = true;
23
+ });
19
24
 
20
- // ── Subcommand: arcality init ──
21
25
  if (isInit) {
22
- const initArgs = args.filter(a => a !== 'init');
23
26
  const initScript = path.join(PACKAGE_ROOT, 'scripts', 'init.mjs');
24
- const child = spawn('node', [initScript, ...initArgs], {
27
+ spawn('node', [initScript, ...rawArgs.filter(a => a !== 'init')], {
25
28
  stdio: 'inherit',
26
- cwd: process.cwd(), // Run in the user's project directory
29
+ cwd: process.cwd(),
27
30
  env: { ...process.env, ARCALITY_ROOT: PACKAGE_ROOT }
28
- });
29
- child.on('exit', code => process.exit(code || 0));
30
-
31
- // ── Subcommand: arcality run ──
31
+ }).on('exit', code => process.exit(code || 0));
32
32
  } else if (isRun) {
33
- const runArgs = args.filter(a => a !== 'run');
33
+ // FORZAMOS EL MODO AGENTE: Esto saltará el menú interactivo en gen-and-run.mjs
34
34
  const mainScript = path.join(PACKAGE_ROOT, 'scripts', 'gen-and-run.mjs');
35
- const child = spawn('node', [mainScript, '--agent', ...runArgs], {
35
+ spawn('node', [mainScript, '--agent', ...rawArgs.filter(a => a !== 'run')], {
36
36
  stdio: 'inherit',
37
37
  cwd: process.cwd(),
38
38
  env: { ...process.env, ARCALITY_ROOT: PACKAGE_ROOT }
39
- });
40
- child.on('exit', code => process.exit(code || 0));
41
-
42
- // ── Subcommand: arcality setup (legacy) ──
39
+ }).on('exit', code => process.exit(code || 0));
43
40
  } else if (isSetup) {
44
- const setupArgs = args.filter(a => a !== 'setup');
45
41
  const setupScript = path.join(PACKAGE_ROOT, 'scripts', 'setup.mjs');
46
- const child = spawn('node', [setupScript, ...setupArgs], {
42
+ spawn('node', [setupScript, ...rawArgs.filter(a => a !== 'setup')], {
47
43
  stdio: 'inherit',
48
44
  cwd: PACKAGE_ROOT,
49
45
  env: { ...process.env, ARCALITY_ROOT: PACKAGE_ROOT }
50
- });
51
- child.on('exit', code => process.exit(code || 0));
52
-
53
- // ── Subcommand: arcality version ──
54
- } else if (isVersion) {
55
- const fs = await import('node:fs');
56
- try {
57
- const pkg = JSON.parse(fs.default.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
58
- console.log(`arcality v${pkg.version}`);
59
- } catch {
60
- console.log('arcality (version unknown)');
61
- }
62
-
63
- // ── Subcommand: arcality help ──
64
- } else if (isHelp) {
65
- const fs = await import('node:fs');
66
- let version = 'unknown';
67
- try {
68
- const pkg = JSON.parse(fs.default.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
69
- version = pkg.version || 'unknown';
70
- } catch { }
71
-
72
- console.log(`
73
- Arcality v${version} — AI-powered QA Testing Agent
74
-
75
- Usage:
76
- arcality init Configure project & create arcality.config
77
- arcality run [prompt] Run the autonomous agent
78
- arcality Open interactive menu
79
- arcality setup Legacy setup (API keys + Chromium)
80
- arcality --version Show version
81
- arcality --help Show this help
82
- `);
83
-
84
- // ── Default: interactive menu (gen-and-run.mjs) ──
46
+ }).on('exit', code => process.exit(code || 0));
85
47
  } else {
48
+ // Menú interactivo tradicional
86
49
  const mainScript = path.join(PACKAGE_ROOT, 'scripts', 'gen-and-run.mjs');
87
- const child = spawn('node', [mainScript, ...args], {
50
+ spawn('node', [mainScript, ...rawArgs], {
88
51
  stdio: 'inherit',
89
52
  cwd: process.cwd(),
90
53
  env: { ...process.env, ARCALITY_ROOT: PACKAGE_ROOT }
91
- });
92
- child.on('exit', code => process.exit(code || 0));
54
+ }).on('exit', code => process.exit(code || 0));
93
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadialdev/arcality",
3
- "version": "2.4.23",
3
+ "version": "2.4.24",
4
4
  "description": "AI-powered QA testing tool — Autonomous web testing agent by Arcadial",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -97,11 +97,6 @@ function updateDotEnvFile(updates) {
97
97
  }
98
98
 
99
99
  function setupEnvironment() {
100
- // Load API Key from global config
101
- const globalConfig = loadGlobalConfig();
102
- if (globalConfig?.api_key && !process.env.ARCALITY_API_KEY) {
103
- process.env.ARCALITY_API_KEY = globalConfig.api_key;
104
- }
105
100
 
106
101
  // Use config name from arcality.config or fallback
107
102
  const configName = projectConfig?.project?.name || 'Default';
@@ -439,7 +434,9 @@ async function main() {
439
434
  let smartMode = firstRun ? initialSmartMode : false;
440
435
  let discoverPath = firstRun ? initialDiscoverPath : null;
441
436
 
442
- let skipMenu = firstRun && (prompt || agentMode || smartMode);
437
+ let skipMenu = (firstRun && (prompt || agentMode || smartMode)) || agentMode;
438
+ if (agentMode) skipMenu = true; // Hard-lock: No menu in agent mode
439
+
443
440
  firstRun = false;
444
441
 
445
442
  if (!skipMenu) {
@@ -586,11 +583,20 @@ async function main() {
586
583
  }
587
584
 
588
585
  if (!discoverPath) {
586
+ const knownBaseUrl = projectConfig?.project?.baseUrl || process.env.BASE_URL;
587
+ const promptMsg = knownBaseUrl
588
+ ? `🌐 Initial navigation path (relative to ${knownBaseUrl}):`
589
+ : '🌐 Full Navigation URL (e.g., http://localhost:3000/):';
590
+
589
591
  const d = await text({
590
- message: chalk.cyan('🌐 Initial navigation path:'),
591
- initialValue: '/',
592
- placeholder: '/'
592
+ message: chalk.cyan(promptMsg),
593
+ placeholder: knownBaseUrl ? '/' : 'http://localhost:3000/',
594
+ validate: v => {
595
+ if (!knownBaseUrl && !v.startsWith('http')) return 'Please provide a full URL with http:// or https://';
596
+ return undefined;
597
+ }
593
598
  });
599
+
594
600
  if (isCancel(d)) {
595
601
  cancel('Mission aborted. Returning to menu...');
596
602
  agentMode = false;
@@ -606,7 +612,7 @@ async function main() {
606
612
  // Load environment from BOTH the user's project and the library's root
607
613
  const userDotEnv = parseDotEnvFile(path.join(ORIGINAL_CWD, '.env'));
608
614
  const libDotEnv = parseDotEnvFile(path.join(PROJECT_ROOT, '.env'));
609
- const dotEnv = { ...userDotEnv, ...libDotEnv };
615
+ const dotEnv = { ...userDotEnv, ...libDotEnv };
610
616
 
611
617
  // ── API Key and Quota Validation ──
612
618
  const { validateApiKey, startMission: requestMission } = await import('../src/arcalityClient.mjs');
@@ -730,12 +736,14 @@ async function main() {
730
736
  // Build env for the runner — merge arcality.config values.
731
737
  // ARCALITY_API_URL must be explicit — it comes from the library's internal config,
732
738
  // not from the user's project .env, so we inject it directly via getApiUrl().
739
+ const fallbackBaseUrl = (!projectConfig && discoverPath?.startsWith('http')) ? discoverPath : undefined;
740
+
733
741
  const mergedEnv = {
734
742
  ...dotEnv,
735
743
  ...process.env,
736
744
  ARCALITY_API_URL: getApiUrl(), // Internal URL — always injected explicitly
737
745
  ARCALITY_PROJECT_ID: finalProjectId,
738
- BASE_URL: projectConfig?.project?.baseUrl || dotEnv.BASE_URL || process.env.BASE_URL,
746
+ BASE_URL: projectConfig?.project?.baseUrl || dotEnv.BASE_URL || process.env.BASE_URL || fallbackBaseUrl || 'http://localhost:3000',
739
747
  LOGIN_USER: projectConfig?.auth?.username || dotEnv.LOGIN_USER || process.env.LOGIN_USER,
740
748
  LOGIN_PASSWORD: projectConfig?.auth?.password || dotEnv.LOGIN_PASSWORD || process.env.LOGIN_PASSWORD,
741
749
  DOMAIN_DIR: process.env.DOMAIN_DIR,
@@ -757,7 +765,7 @@ async function main() {
757
765
  //
758
766
  // KEY: tsx loader uses file:// URL (pathToFileURL) so spaces encode as %20 — no quoting needed.
759
767
 
760
- const testFile = path.join(PROJECT_ROOT, 'tests', '_helpers', 'agentic-runner.spec.ts');
768
+ const testFile = path.join(PROJECT_ROOT, 'tests', '_helpers', 'agentic-runner.spec.ts');
761
769
  const configFile = path.join(PROJECT_ROOT, 'playwright.config.js');
762
770
 
763
771
  // Resolve playwright CLI relative to PROJECT_ROOT exactly as Node handles module resolution.
package/scripts/init.mjs CHANGED
@@ -138,11 +138,12 @@ async function main() {
138
138
  });
139
139
 
140
140
  if (!res.ok) {
141
+ const errText = await res.text().catch(() => '');
141
142
  const errorMap = {
142
143
  401: '❌ Invalid API Key. Please check your key and try again.',
143
- 403: '❌ Your plan has expired. Contact support.',
144
+ 403: `❌ Authentication Failed (HTTP 403). Server says: ${errText.slice(0, 150)}`,
144
145
  };
145
- s.stop(chalk.red(errorMap[res.status] || `❌ Server error (HTTP ${res.status})`));
146
+ s.stop(chalk.red(errorMap[res.status] || `❌ Server error (HTTP ${res.status}): ${errText.slice(0, 150)}`));
146
147
  process.exit(1);
147
148
  }
148
149
 
@@ -52,10 +52,9 @@ function loadLocalArcalityConfig() {
52
52
  */
53
53
  export function loadConfig() {
54
54
  const localConfig = loadLocalArcalityConfig();
55
- const globalConfig = loadGlobalConfig();
56
55
 
57
56
  const config = {
58
- ARCALITY_API_KEY: localConfig?.apiKey || process.env.ARCALITY_API_KEY || globalConfig?.api_key,
57
+ ARCALITY_API_KEY: localConfig?.apiKey || process.env.ARCALITY_API_KEY,
59
58
  ARCALITY_PROJECT_ID: localConfig?.projectId || process.env.ARCALITY_PROJECT_ID,
60
59
  };
61
60
 
@@ -118,13 +117,8 @@ export function getApiKey() {
118
117
  return { key: process.env.ARCALITY_API_KEY, source: 'env' };
119
118
  }
120
119
 
121
- // 3. Global config ~/.arcality/config.json
122
- const globalCfg = loadGlobalConfig();
123
- if (globalCfg?.api_key) {
124
- process.env.ARCALITY_API_KEY = globalCfg.api_key;
125
- return { key: globalCfg.api_key, source: 'global' };
126
- }
127
-
120
+ // Global config fallback removed strictly per project requirements
121
+
128
122
  return { key: null, source: 'none' };
129
123
  }
130
124
 
@@ -2088,7 +2088,9 @@ function captureValidationRule(errorMessage, context) {
2088
2088
  const target = process.env.TARGET_PATH || "/";
2089
2089
  if (process.env.LOGIN_USER && process.env.LOGIN_PASSWORD) {
2090
2090
  console.log(">>ARCALITY_STATUS>> \u{1F511} Realizando login autom\xE1tico...");
2091
- await page.goto(base + "/login");
2091
+ const loginUrl = base + "/login";
2092
+ if (!loginUrl.startsWith("http")) throw new Error(`URL Inv\xE1lida: "${loginUrl}". Aseg\xFArate de configurar la Base URL con http://`);
2093
+ await page.goto(loginUrl);
2092
2094
  try {
2093
2095
  const userInp = page.locator('input[type="email"], input[name="email"], input[name="username"], [placeholder*="usuario" i], [placeholder*="correo" i], [placeholder*="email" i]').first();
2094
2096
  await userInp.waitFor({ state: "visible", timeout: 1e4 });
@@ -2108,11 +2110,15 @@ function captureValidationRule(errorMessage, context) {
2108
2110
  console.warn(` \u26A0\uFE0F Login autom\xE1tico omitido o ya autenticado.`);
2109
2111
  }
2110
2112
  if (target !== "/" && !page.url().includes(target)) {
2111
- await page.goto(base + target).catch(() => {
2113
+ const tgtUrl = target.startsWith("http") ? target : base + target;
2114
+ if (!tgtUrl.startsWith("http")) throw new Error(`URL Inv\xE1lida: "${tgtUrl}"`);
2115
+ await page.goto(tgtUrl).catch(() => {
2112
2116
  });
2113
2117
  }
2114
2118
  } else {
2115
- await page.goto(base + target);
2119
+ const tgtUrl = target.startsWith("http") ? target : base + target;
2120
+ if (!tgtUrl.startsWith("http")) throw new Error(`URL Inv\xE1lida: "${tgtUrl}". Por favor proporciona una URL v\xE1lida incluyendo http:// o https://`);
2121
+ await page.goto(tgtUrl);
2116
2122
  }
2117
2123
  await page.waitForLoadState("networkidle");
2118
2124
  page.on("download", async (download) => {
@@ -176,7 +176,9 @@ test('Arcality AI Runner', async ({ page }, testInfo) => {
176
176
 
177
177
  if (process.env.LOGIN_USER && process.env.LOGIN_PASSWORD) {
178
178
  console.log(">>ARCALITY_STATUS>> 🔑 Realizando login automático...");
179
- await page.goto(base + '/login');
179
+ const loginUrl = base + '/login';
180
+ if (!loginUrl.startsWith('http')) throw new Error(`URL Inválida: "${loginUrl}". Asegúrate de configurar la Base URL con http://`);
181
+ await page.goto(loginUrl);
180
182
  try {
181
183
  const userInp = page.locator('input[type="email"], input[name="email"], input[name="username"], [placeholder*="usuario" i], [placeholder*="correo" i], [placeholder*="email" i]').first();
182
184
  await userInp.waitFor({ state: 'visible', timeout: 10000 });
@@ -200,10 +202,14 @@ test('Arcality AI Runner', async ({ page }, testInfo) => {
200
202
  }
201
203
 
202
204
  if (target !== '/' && !page.url().includes(target)) {
203
- await page.goto(base + target).catch(() => { });
205
+ const tgtUrl = target.startsWith('http') ? target : (base + target);
206
+ if (!tgtUrl.startsWith('http')) throw new Error(`URL Inválida: "${tgtUrl}"`);
207
+ await page.goto(tgtUrl).catch(() => { });
204
208
  }
205
209
  } else {
206
- await page.goto(base + target);
210
+ const tgtUrl = target.startsWith('http') ? target : (base + target);
211
+ if (!tgtUrl.startsWith('http')) throw new Error(`URL Inválida: "${tgtUrl}". Por favor proporciona una URL válida incluyendo http:// o https://`);
212
+ await page.goto(tgtUrl);
207
213
  }
208
214
  await page.waitForLoadState('networkidle');
209
215