@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
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
27
|
+
spawn('node', [initScript, ...rawArgs.filter(a => a !== 'init')], {
|
|
25
28
|
stdio: 'inherit',
|
|
26
|
-
cwd: process.cwd(),
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/scripts/gen-and-run.mjs
CHANGED
|
@@ -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(
|
|
591
|
-
|
|
592
|
-
|
|
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
|
|
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:
|
|
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
|
|
package/src/configLoader.mjs
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|