@arcadialdev/arcality 3.0.4 → 4.0.1
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/README.md +75 -6
- package/bin/arcality.mjs +26 -17
- package/package.json +2 -1
- package/playwright.config.js +22 -21
- package/scripts/gen-and-run.mjs +2610 -2608
- package/scripts/generate.mjs +236 -0
- package/scripts/init.mjs +278 -253
- package/scripts/rebrand-report.mjs +19 -18
- package/src/configManager.mjs +57 -51
- package/src/services/codebaseAnalyzer.mjs +59 -0
- package/src/services/generatedMissionSchema.mjs +76 -0
- package/src/services/generatedMissionStore.mjs +117 -0
- package/src/services/generationContext.mjs +242 -0
- package/src/services/missionGenerator.mjs +328 -0
- package/src/services/routeDiscovery.mjs +762 -0
- package/src/testRunner.ts +2 -1
- package/tests/_helpers/agentic-runner.bundle.spec.js +401 -60
package/scripts/init.mjs
CHANGED
|
@@ -2,39 +2,39 @@
|
|
|
2
2
|
// scripts/init.mjs — Command: arcality init
|
|
3
3
|
// Single-configuration setup flow:
|
|
4
4
|
// 1. Prompt API key → validate with backend
|
|
5
|
-
// 2. Prompt project name, baseUrl, username, password
|
|
5
|
+
// 2. Prompt project name, baseUrl, username, password
|
|
6
6
|
// 3. Detect framework (package.json only, NO .git)
|
|
7
7
|
// 4. Create project via POST /api/v1/projects
|
|
8
|
-
// 5. Write arcality.config
|
|
9
|
-
|
|
10
|
-
import 'dotenv/config';
|
|
11
|
-
import fs from 'node:fs';
|
|
12
|
-
import path from 'node:path';
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
|
-
import { intro, outro, text, password as passwordPrompt, spinner, note, isCancel, cancel, confirm } from '@clack/prompts';
|
|
15
|
-
import chalk from 'chalk';
|
|
16
|
-
import {
|
|
17
|
-
configExists,
|
|
18
|
-
loadProjectConfig,
|
|
19
|
-
saveProjectConfig,
|
|
20
|
-
createConfig,
|
|
21
|
-
isRealProjectId,
|
|
22
|
-
} from '../src/configManager.mjs';
|
|
23
|
-
import { getApiUrl } from '../src/configLoader.mjs';
|
|
24
|
-
import { installPlaywrightAssets, getPlaywrightInstallCommand } from '../src/playwrightAssets.mjs';
|
|
8
|
+
// 5. Write arcality.config
|
|
9
|
+
|
|
10
|
+
import 'dotenv/config';
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { intro, outro, text, password as passwordPrompt, spinner, note, isCancel, cancel, confirm } from '@clack/prompts';
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import {
|
|
17
|
+
configExists,
|
|
18
|
+
loadProjectConfig,
|
|
19
|
+
saveProjectConfig,
|
|
20
|
+
createConfig,
|
|
21
|
+
isRealProjectId,
|
|
22
|
+
} from '../src/configManager.mjs';
|
|
23
|
+
import { getApiUrl } from '../src/configLoader.mjs';
|
|
24
|
+
import { installPlaywrightAssets, getPlaywrightInstallCommand } from '../src/playwrightAssets.mjs';
|
|
25
25
|
|
|
26
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
27
|
const __dirname = path.dirname(__filename);
|
|
28
28
|
const PACKAGE_ROOT = process.env.ARCALITY_ROOT || path.resolve(__dirname, '..');
|
|
29
29
|
|
|
30
|
-
// ── Helpers ──
|
|
30
|
+
// ── Helpers ──
|
|
31
31
|
|
|
32
|
-
function getVersion() {
|
|
33
|
-
try {
|
|
34
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
35
|
-
return pkg.version || 'unknown';
|
|
36
|
-
} catch {
|
|
37
|
-
return 'unknown';
|
|
32
|
+
function getVersion() {
|
|
33
|
+
try {
|
|
34
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
35
|
+
return pkg.version || 'unknown';
|
|
36
|
+
} catch {
|
|
37
|
+
return 'unknown';
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -57,93 +57,93 @@ function detectFramework(projectRoot) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function isValidUrl(url) {
|
|
61
|
-
try {
|
|
62
|
-
const u = new URL(url);
|
|
63
|
-
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function showInitBanner(version, projectRoot) {
|
|
70
|
-
const sentinelAscii = `
|
|
71
|
-
█████ ██████ ██████ █████ ██ ██ ████████ ██ ██
|
|
72
|
-
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
73
|
-
███████ ██████ ██ ███████ ██ ██ ██ ████
|
|
74
|
-
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
75
|
-
██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ ██`;
|
|
76
|
-
|
|
77
|
-
intro(chalk.gray(sentinelAscii));
|
|
78
|
-
|
|
79
|
-
note(
|
|
80
|
-
chalk.gray('◉ Modo de Configuración: ') + chalk.bold.white('ACTIVO') + '\n' +
|
|
81
|
-
chalk.gray('◉ Enlace con Backend: ') + chalk.bold.white('PREPARANDO') + '\n' +
|
|
82
|
-
chalk.gray('◉ Generador de Contexto: ') + chalk.bold.white('DISPONIBLE') + '\n' +
|
|
83
|
-
chalk.gray('─'.repeat(50)) + '\n' +
|
|
84
|
-
chalk.gray('Versión del Sistema: ') + chalk.white(`v${version}`),
|
|
85
|
-
'Estado Central de Arcality'
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
note(
|
|
89
|
-
chalk.gray('Nodo Objetivo: ') + chalk.white(process.env.NODE_ENV ?? 'development') + '\n' +
|
|
90
|
-
chalk.gray('Proyecto Local: ') + chalk.white(projectRoot) + '\n' +
|
|
91
|
-
chalk.gray('Comando: ') + chalk.white('arcality init'),
|
|
92
|
-
'Inicialización del Proyecto'
|
|
93
|
-
);
|
|
94
|
-
}
|
|
60
|
+
function isValidUrl(url) {
|
|
61
|
+
try {
|
|
62
|
+
const u = new URL(url);
|
|
63
|
+
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function showInitBanner(version, projectRoot) {
|
|
70
|
+
const sentinelAscii = `
|
|
71
|
+
█████ ██████ ██████ █████ ██ ██ ████████ ██ ██
|
|
72
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
73
|
+
███████ ██████ ██ ███████ ██ ██ ██ ████
|
|
74
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
75
|
+
██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ ██`;
|
|
76
|
+
|
|
77
|
+
intro(chalk.gray(sentinelAscii));
|
|
78
|
+
|
|
79
|
+
note(
|
|
80
|
+
chalk.gray('◉ Modo de Configuración: ') + chalk.bold.white('ACTIVO') + '\n' +
|
|
81
|
+
chalk.gray('◉ Enlace con Backend: ') + chalk.bold.white('PREPARANDO') + '\n' +
|
|
82
|
+
chalk.gray('◉ Generador de Contexto: ') + chalk.bold.white('DISPONIBLE') + '\n' +
|
|
83
|
+
chalk.gray('─'.repeat(50)) + '\n' +
|
|
84
|
+
chalk.gray('Versión del Sistema: ') + chalk.white(`v${version}`),
|
|
85
|
+
'Estado Central de Arcality'
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
note(
|
|
89
|
+
chalk.gray('Nodo Objetivo: ') + chalk.white(process.env.NODE_ENV ?? 'development') + '\n' +
|
|
90
|
+
chalk.gray('Proyecto Local: ') + chalk.white(projectRoot) + '\n' +
|
|
91
|
+
chalk.gray('Comando: ') + chalk.white('arcality init'),
|
|
92
|
+
'Inicialización del Proyecto'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
95
|
|
|
96
96
|
// ── Main ──
|
|
97
97
|
|
|
98
|
-
async function main() {
|
|
99
|
-
const version = getVersion();
|
|
100
|
-
const projectRoot = process.cwd();
|
|
101
|
-
|
|
102
|
-
console.clear();
|
|
103
|
-
showInitBanner(version, projectRoot);
|
|
98
|
+
async function main() {
|
|
99
|
+
const version = getVersion();
|
|
100
|
+
const projectRoot = process.cwd();
|
|
101
|
+
|
|
102
|
+
console.clear();
|
|
103
|
+
showInitBanner(version, projectRoot);
|
|
104
104
|
|
|
105
105
|
// ── Check for existing config ──
|
|
106
|
-
if (configExists(projectRoot)) {
|
|
107
|
-
const existing = loadProjectConfig(projectRoot);
|
|
108
|
-
if (existing) {
|
|
109
|
-
note(
|
|
110
|
-
chalk.yellow('⚠️ Ya existe un arcality.config en este proyecto.') + '\n\n' +
|
|
111
|
-
chalk.white(' Proyecto: ') + chalk.cyan(existing.project?.name || 'unknown') + '\n' +
|
|
112
|
-
chalk.white(' Base URL: ') + chalk.cyan(existing.project?.baseUrl || 'unknown') + '\n' +
|
|
113
|
-
chalk.white(' ID del Proyecto: ') + chalk.gray(existing.projectId || 'desconocido'),
|
|
114
|
-
'Configuración Existente'
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const overwrite = await confirm({
|
|
118
|
-
message: '¿Deseas reconfigurar? Esto creará un NUEVO proyecto en backend.',
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
if (isCancel(overwrite) || !overwrite) {
|
|
122
|
-
outro(chalk.cyan('Configuración sin cambios. Usa `arcality run` para iniciar.'));
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
106
|
+
if (configExists(projectRoot)) {
|
|
107
|
+
const existing = loadProjectConfig(projectRoot);
|
|
108
|
+
if (existing) {
|
|
109
|
+
note(
|
|
110
|
+
chalk.yellow('⚠️ Ya existe un arcality.config en este proyecto.') + '\n\n' +
|
|
111
|
+
chalk.white(' Proyecto: ') + chalk.cyan(existing.project?.name || 'unknown') + '\n' +
|
|
112
|
+
chalk.white(' Base URL: ') + chalk.cyan(existing.project?.baseUrl || 'unknown') + '\n' +
|
|
113
|
+
chalk.white(' ID del Proyecto: ') + chalk.gray(existing.projectId || 'desconocido'),
|
|
114
|
+
'Configuración Existente'
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const overwrite = await confirm({
|
|
118
|
+
message: '¿Deseas reconfigurar? Esto creará un NUEVO proyecto en backend.',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (isCancel(overwrite) || !overwrite) {
|
|
122
|
+
outro(chalk.cyan('Configuración sin cambios. Usa `arcality run` para iniciar.'));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
127
|
|
|
128
128
|
// ── Step 1: API Key ──
|
|
129
129
|
const apiKey = await text({
|
|
130
|
-
message: chalk.cyan('🔑 Ingresa tu API Key de Arcality:'),
|
|
130
|
+
message: chalk.cyan('🔑 Ingresa tu API Key de Arcality:'),
|
|
131
131
|
placeholder: 'arc_live_xxxxx',
|
|
132
132
|
validate: (v) => {
|
|
133
|
-
if (!v || !v.trim()) return 'La API Key es obligatoria.';
|
|
134
|
-
if (!v.startsWith('arc_')) return 'La API Key debe iniciar con "arc_"';
|
|
133
|
+
if (!v || !v.trim()) return 'La API Key es obligatoria.';
|
|
134
|
+
if (!v.startsWith('arc_')) return 'La API Key debe iniciar con "arc_"';
|
|
135
135
|
},
|
|
136
136
|
});
|
|
137
137
|
|
|
138
138
|
if (isCancel(apiKey)) {
|
|
139
|
-
cancel('Configuración cancelada.');
|
|
139
|
+
cancel('Configuración cancelada.');
|
|
140
140
|
process.exit(0);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// ── Step 2: Validate API Key (uses internal API URL) ──
|
|
144
144
|
const apiUrl = getApiUrl();
|
|
145
145
|
const s = spinner();
|
|
146
|
-
s.start('Validando API Key...');
|
|
146
|
+
s.start('Validando API Key...');
|
|
147
147
|
|
|
148
148
|
let organizationId = null;
|
|
149
149
|
|
|
@@ -159,12 +159,12 @@ async function main() {
|
|
|
159
159
|
if (!res.ok) {
|
|
160
160
|
const errText = await res.text().catch(() => '');
|
|
161
161
|
const errorMap = {
|
|
162
|
-
401: '❌ API Key inválida. Revísala e inténtalo de nuevo.',
|
|
163
|
-
403: `❌ Autenticación fallida (HTTP 403). El servidor respondió: ${errText.slice(0, 150)}`,
|
|
164
|
-
};
|
|
165
|
-
const msg = errorMap[res.status] || `❌ Error del servidor (HTTP ${res.status}): ${errText.slice(0, 150)}`;
|
|
166
|
-
s.stop(chalk.red(msg));
|
|
167
|
-
console.log(chalk.yellow('\n Presiona Enter para volver al menú...'));
|
|
162
|
+
401: '❌ API Key inválida. Revísala e inténtalo de nuevo.',
|
|
163
|
+
403: `❌ Autenticación fallida (HTTP 403). El servidor respondió: ${errText.slice(0, 150)}`,
|
|
164
|
+
};
|
|
165
|
+
const msg = errorMap[res.status] || `❌ Error del servidor (HTTP ${res.status}): ${errText.slice(0, 150)}`;
|
|
166
|
+
s.stop(chalk.red(msg));
|
|
167
|
+
console.log(chalk.yellow('\n Presiona Enter para volver al menú...'));
|
|
168
168
|
// Salida limpia (código 0) para que el menú padre no interprete esto como crash
|
|
169
169
|
await new Promise(r => setTimeout(r, 3000));
|
|
170
170
|
process.exit(0);
|
|
@@ -173,77 +173,77 @@ async function main() {
|
|
|
173
173
|
const data = await res.json();
|
|
174
174
|
organizationId = data.organizationId || data.organization_id || null;
|
|
175
175
|
|
|
176
|
-
s.stop(chalk.green('✅ API Key validada correctamente'));
|
|
176
|
+
s.stop(chalk.green('✅ API Key validada correctamente'));
|
|
177
177
|
|
|
178
178
|
if (data.plan) {
|
|
179
179
|
note(
|
|
180
|
-
chalk.bold.white('📋 Plan: ') + chalk.cyan(data.plan) + '\n' +
|
|
181
|
-
chalk.bold.white('🏢 Organización: ') + chalk.cyan(organizationId || 'N/A'),
|
|
182
|
-
'Información de la Cuenta'
|
|
180
|
+
chalk.bold.white('📋 Plan: ') + chalk.cyan(data.plan) + '\n' +
|
|
181
|
+
chalk.bold.white('🏢 Organización: ') + chalk.cyan(organizationId || 'N/A'),
|
|
182
|
+
'Información de la Cuenta'
|
|
183
183
|
);
|
|
184
184
|
}
|
|
185
185
|
} catch (err) {
|
|
186
|
-
s.stop(chalk.red(`❌ No se pudo conectar al backend de Arcality: ${err.message}`));
|
|
187
|
-
console.log(chalk.yellow(' Asegúrate de que el backend esté activo y accesible.'));
|
|
186
|
+
s.stop(chalk.red(`❌ No se pudo conectar al backend de Arcality: ${err.message}`));
|
|
187
|
+
console.log(chalk.yellow(' Asegúrate de que el backend esté activo y accesible.'));
|
|
188
188
|
await new Promise(r => setTimeout(r, 3000));
|
|
189
189
|
process.exit(0);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// ── Step 3: Project details ──
|
|
193
193
|
const projectName = await text({
|
|
194
|
-
message: chalk.cyan('📋 Nombre del proyecto:'),
|
|
195
|
-
placeholder: 'Mi Portal QA',
|
|
196
|
-
validate: (v) => {
|
|
197
|
-
if (!v || v.trim().length < 2) return 'El nombre del proyecto es obligatorio (mínimo 2 caracteres).';
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
if (isCancel(projectName)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
201
|
-
|
|
202
|
-
const baseUrl = await text({
|
|
203
|
-
message: chalk.cyan('🌐 URL base de la aplicación a probar:'),
|
|
204
|
-
placeholder: 'https://staging.example.com',
|
|
205
|
-
validate: (v) => {
|
|
206
|
-
if (!v || !v.trim()) return 'La URL base es obligatoria.';
|
|
207
|
-
if (!isValidUrl(v)) return 'URL inválida. Usa http:// o https://';
|
|
208
|
-
},
|
|
209
|
-
});
|
|
210
|
-
if (isCancel(baseUrl)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
211
|
-
|
|
212
|
-
const hasLogin = await confirm({
|
|
213
|
-
message: chalk.cyan('🔐 ¿Tu aplicación requiere inicio de sesión/autenticación?'),
|
|
214
|
-
});
|
|
215
|
-
if (isCancel(hasLogin)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
194
|
+
message: chalk.cyan('📋 Nombre del proyecto:'),
|
|
195
|
+
placeholder: 'Mi Portal QA',
|
|
196
|
+
validate: (v) => {
|
|
197
|
+
if (!v || v.trim().length < 2) return 'El nombre del proyecto es obligatorio (mínimo 2 caracteres).';
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
if (isCancel(projectName)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
201
|
+
|
|
202
|
+
const baseUrl = await text({
|
|
203
|
+
message: chalk.cyan('🌐 URL base de la aplicación a probar:'),
|
|
204
|
+
placeholder: 'https://staging.example.com',
|
|
205
|
+
validate: (v) => {
|
|
206
|
+
if (!v || !v.trim()) return 'La URL base es obligatoria.';
|
|
207
|
+
if (!isValidUrl(v)) return 'URL inválida. Usa http:// o https://';
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
if (isCancel(baseUrl)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
211
|
+
|
|
212
|
+
const hasLogin = await confirm({
|
|
213
|
+
message: chalk.cyan('🔐 ¿Tu aplicación requiere inicio de sesión/autenticación?'),
|
|
214
|
+
});
|
|
215
|
+
if (isCancel(hasLogin)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
216
216
|
|
|
217
217
|
let username = '';
|
|
218
218
|
let password = '';
|
|
219
219
|
|
|
220
220
|
if (hasLogin) {
|
|
221
|
-
username = await text({
|
|
222
|
-
message: chalk.cyan('👤 Usuario o correo de acceso:'),
|
|
223
|
-
validate: (v) => {
|
|
224
|
-
if (!v || !v.trim()) return 'El usuario es obligatorio.';
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
if (isCancel(username)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
228
|
-
|
|
229
|
-
password = await passwordPrompt({
|
|
230
|
-
message: chalk.cyan('🔒 Contraseña de acceso:'),
|
|
231
|
-
validate: (v) => {
|
|
232
|
-
if (!v || !v.length) return 'La contraseña es obligatoria.';
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
if (isCancel(password)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
221
|
+
username = await text({
|
|
222
|
+
message: chalk.cyan('👤 Usuario o correo de acceso:'),
|
|
223
|
+
validate: (v) => {
|
|
224
|
+
if (!v || !v.trim()) return 'El usuario es obligatorio.';
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
if (isCancel(username)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
228
|
+
|
|
229
|
+
password = await passwordPrompt({
|
|
230
|
+
message: chalk.cyan('🔒 Contraseña de acceso:'),
|
|
231
|
+
validate: (v) => {
|
|
232
|
+
if (!v || !v.length) return 'La contraseña es obligatoria.';
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
if (isCancel(password)) { cancel('Configuración cancelada.'); process.exit(0); }
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
// ── Step 4: Detect framework ──
|
|
239
239
|
const frameworkDetected = detectFramework(projectRoot);
|
|
240
240
|
if (frameworkDetected) {
|
|
241
|
-
console.log(chalk.gray(` 🛠️ Framework detectado: ${chalk.magenta(frameworkDetected)}`));
|
|
241
|
+
console.log(chalk.gray(` 🛠️ Framework detectado: ${chalk.magenta(frameworkDetected)}`));
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
// ── Step 5: Create project in backend ──
|
|
245
245
|
const sCreate = spinner();
|
|
246
|
-
sCreate.start('Creando proyecto en el backend de Arcality...');
|
|
246
|
+
sCreate.start('Creando proyecto en el backend de Arcality...');
|
|
247
247
|
|
|
248
248
|
let projectId = null;
|
|
249
249
|
try {
|
|
@@ -273,152 +273,177 @@ async function main() {
|
|
|
273
273
|
// If response is HTML (e.g. Cloudflare 521), show a clean message
|
|
274
274
|
const isHtml = errText.trim().startsWith('<');
|
|
275
275
|
const displayError = isHtml
|
|
276
|
-
? `El servidor backend de Arcality no esta disponible temporalmente (HTTP ${res.status}).`
|
|
277
|
-
: `HTTP ${res.status}: ${errText.slice(0, 200)}`;
|
|
276
|
+
? `El servidor backend de Arcality no esta disponible temporalmente (HTTP ${res.status}).`
|
|
277
|
+
: `HTTP ${res.status}: ${errText.slice(0, 200)}`;
|
|
278
278
|
|
|
279
|
-
sCreate.stop(chalk.red(`No se pudo crear el proyecto en Arcality: ${displayError.replace(/El proyecto.*$/i, '').trim()}`));
|
|
279
|
+
sCreate.stop(chalk.red(`No se pudo crear el proyecto en Arcality: ${displayError.replace(/El proyecto.*$/i, '').trim()}`));
|
|
280
280
|
// Don't exit — write the config with a local-only project ID so user can still run
|
|
281
|
-
note(
|
|
282
|
-
chalk.white('No se guardo arcality.config porque Arcality requiere un proyecto real conectado al backend.'),
|
|
283
|
-
'Configuracion detenida'
|
|
284
|
-
);
|
|
285
|
-
process.exit(1);
|
|
281
|
+
note(
|
|
282
|
+
chalk.white('No se guardo arcality.config porque Arcality requiere un proyecto real conectado al backend.'),
|
|
283
|
+
'Configuracion detenida'
|
|
284
|
+
);
|
|
285
|
+
process.exit(1);
|
|
286
286
|
} else {
|
|
287
287
|
const created = await res.json();
|
|
288
|
-
projectId = created.id || created.Id;
|
|
289
|
-
if (!isRealProjectId(projectId)) {
|
|
290
|
-
sCreate.stop(chalk.red('El backend no devolvio un Project ID valido.'));
|
|
291
|
-
note(
|
|
292
|
-
chalk.white('No se guardo arcality.config. Toda instancia de Arcality debe estar ligada a un proyecto real.'),
|
|
293
|
-
'Configuracion detenida'
|
|
294
|
-
);
|
|
295
|
-
process.exit(1);
|
|
296
|
-
}
|
|
288
|
+
projectId = created.id || created.Id;
|
|
289
|
+
if (!isRealProjectId(projectId)) {
|
|
290
|
+
sCreate.stop(chalk.red('El backend no devolvio un Project ID valido.'));
|
|
291
|
+
note(
|
|
292
|
+
chalk.white('No se guardo arcality.config. Toda instancia de Arcality debe estar ligada a un proyecto real.'),
|
|
293
|
+
'Configuracion detenida'
|
|
294
|
+
);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
297
|
organizationId = organizationId || created.organizationId || created.organization_id || null;
|
|
298
|
-
sCreate.stop(chalk.green(`✅ Proyecto creado: "${projectName.trim()}" (${projectId})`));
|
|
299
|
-
}
|
|
300
|
-
} catch (err) {
|
|
301
|
-
sCreate.stop(chalk.red(`No se pudo conectar con el servidor de Arcality: ${err.message}`));
|
|
302
|
-
note(
|
|
303
|
-
chalk.white('No se guardo arcality.config porque la herramienta no funciona en modo offline/local.'),
|
|
304
|
-
'Configuracion detenida'
|
|
305
|
-
);
|
|
306
|
-
process.exit(1);
|
|
307
|
-
}
|
|
298
|
+
sCreate.stop(chalk.green(`✅ Proyecto creado: "${projectName.trim()}" (${projectId})`));
|
|
299
|
+
}
|
|
300
|
+
} catch (err) {
|
|
301
|
+
sCreate.stop(chalk.red(`No se pudo conectar con el servidor de Arcality: ${err.message}`));
|
|
302
|
+
note(
|
|
303
|
+
chalk.white('No se guardo arcality.config porque la herramienta no funciona en modo offline/local.'),
|
|
304
|
+
'Configuracion detenida'
|
|
305
|
+
);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
308
|
|
|
309
309
|
// ── Step 6: Write arcality.config ──
|
|
310
310
|
const config = createConfig({
|
|
311
311
|
apiKey: apiKey.trim(),
|
|
312
312
|
organizationId,
|
|
313
313
|
projectId,
|
|
314
|
-
projectName: projectName.trim(),
|
|
315
|
-
baseUrl: baseUrl.trim(),
|
|
316
|
-
frameworkDetected,
|
|
317
|
-
username: username.trim(),
|
|
318
|
-
password,
|
|
319
|
-
arcalityVersion: version,
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
saveProjectConfig(config, projectRoot);
|
|
323
|
-
|
|
324
|
-
const sPlaywright = spinner();
|
|
325
|
-
sPlaywright.start('Preparando navegador de pruebas (Chromium + ffmpeg)...');
|
|
326
|
-
let playwrightReady = true;
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
await installPlaywrightAssets({ cwd: PACKAGE_ROOT });
|
|
330
|
-
sPlaywright.stop(chalk.green('Playwright listo para ejecutar misiones'));
|
|
331
|
-
} catch (err) {
|
|
332
|
-
playwrightReady = false;
|
|
333
|
-
sPlaywright.stop(chalk.yellow(`No se pudieron instalar los assets de Playwright: ${err.message}`));
|
|
334
|
-
note(
|
|
335
|
-
chalk.white('La configuracion del proyecto ya fue guardada, pero antes de la primera mision ejecuta:') + '\n\n' +
|
|
336
|
-
chalk.cyan(getPlaywrightInstallCommand()),
|
|
337
|
-
'Accion Manual Requerida'
|
|
338
|
-
);
|
|
339
|
-
}
|
|
314
|
+
projectName: projectName.trim(),
|
|
315
|
+
baseUrl: baseUrl.trim(),
|
|
316
|
+
frameworkDetected,
|
|
317
|
+
username: username.trim(),
|
|
318
|
+
password,
|
|
319
|
+
arcalityVersion: version,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
saveProjectConfig(config, projectRoot);
|
|
340
323
|
|
|
341
|
-
|
|
324
|
+
const sPlaywright = spinner();
|
|
325
|
+
sPlaywright.start('Preparando navegador de pruebas (Chromium + ffmpeg)...');
|
|
326
|
+
let playwrightReady = true;
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
await installPlaywrightAssets({ cwd: PACKAGE_ROOT });
|
|
330
|
+
sPlaywright.stop(chalk.green('Playwright listo para ejecutar misiones'));
|
|
331
|
+
} catch (err) {
|
|
332
|
+
playwrightReady = false;
|
|
333
|
+
sPlaywright.stop(chalk.yellow(`No se pudieron instalar los assets de Playwright: ${err.message}`));
|
|
334
|
+
note(
|
|
335
|
+
chalk.white('La configuracion del proyecto ya fue guardada, pero antes de la primera mision ejecuta:') + '\n\n' +
|
|
336
|
+
chalk.cyan(getPlaywrightInstallCommand()),
|
|
337
|
+
'Accion Manual Requerida'
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ── Step 7: Custom QA Context ──
|
|
342
342
|
note(
|
|
343
343
|
chalk.white('El archivo ') + chalk.cyan('.arcality/qa-context.md') + chalk.white(' se inyecta al prompt del agente antes de cada misión.') + '\n' +
|
|
344
|
-
chalk.white('Úsalo para reglas del negocio, navegación especial y anti-patrones que Arcality deba respetar siempre.')
|
|
344
|
+
chalk.white('Úsalo para reglas del negocio, navegación especial y anti-patrones que Arcality deba respetar siempre.') + '\n' +
|
|
345
|
+
chalk.white('Complementa el archivo con ') + chalk.cyan('.arcality/qa-context.meta.json') + chalk.white(' para versionar responsables y cambios.'),
|
|
345
346
|
'QA Context Local'
|
|
346
347
|
);
|
|
347
|
-
|
|
348
|
-
const wantsContext = await confirm({
|
|
349
|
-
message: chalk.cyan('📄 ¿Deseas generar un archivo local QA Context (.arcality/qa-context.md) para enseñarle al agente reglas del negocio?'),
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
if (!isCancel(wantsContext) && wantsContext) {
|
|
353
|
-
const arcalityDir = path.join(projectRoot, '.arcality');
|
|
354
|
-
if (!fs.existsSync(arcalityDir)) {
|
|
348
|
+
|
|
349
|
+
const wantsContext = await confirm({
|
|
350
|
+
message: chalk.cyan('📄 ¿Deseas generar un archivo local QA Context (.arcality/qa-context.md) para enseñarle al agente reglas del negocio?'),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
if (!isCancel(wantsContext) && wantsContext) {
|
|
354
|
+
const arcalityDir = path.join(projectRoot, '.arcality');
|
|
355
|
+
if (!fs.existsSync(arcalityDir)) {
|
|
355
356
|
fs.mkdirSync(arcalityDir, { recursive: true });
|
|
356
357
|
}
|
|
357
358
|
|
|
358
359
|
const qaContextPath = path.join(arcalityDir, 'qa-context.md');
|
|
360
|
+
const qaContextMetaPath = path.join(arcalityDir, 'qa-context.meta.json');
|
|
359
361
|
const qaTemplate = `# Arcality QA Context
|
|
360
362
|
<!--
|
|
361
363
|
Este archivo se inyecta antes de cada mision.
|
|
362
364
|
Escribe reglas cortas, especificas y accionables.
|
|
363
|
-
Usa un bullet por regla real.
|
|
364
|
-
Los bullets que empiecen con "Ejemplo:" se ignoran hasta que los reemplaces.
|
|
365
|
-
-->
|
|
366
|
-
|
|
367
|
-
## Reglas del Dominio
|
|
368
|
-
<!-- Restricciones de negocio, validaciones duras, formatos, limites -->
|
|
369
|
-
- Ejemplo: Un usuario no puede registrar mas de 24 horas en un mismo dia.
|
|
370
|
-
- Ejemplo: El campo "Numero de empleado" debe conservar ceros a la izquierda.
|
|
371
|
-
|
|
372
|
-
## Navegacion y UI Especial
|
|
373
|
-
<!-- Pasos raros de la interfaz, cargas lentas, modales, menus no obvios -->
|
|
374
|
-
- Ejemplo: Despues de elegir un proyecto, espera a que cargue la tabla antes de registrar horas.
|
|
375
|
-
- Ejemplo: El menu de acciones siempre se abre desde el icono ":" de la primera fila.
|
|
376
|
-
|
|
377
|
-
## Casos Prohibidos y Anti-patrones
|
|
378
|
-
<!-- Cosas que Arcality nunca debe intentar -->
|
|
379
|
-
- Ejemplo: No uses "Continuar con Microsoft".
|
|
380
|
-
- Ejemplo: No cierres el modal con Escape; usa el boton "Cancelar".
|
|
381
|
-
|
|
382
|
-
## Datos y Credenciales de Prueba
|
|
383
|
-
<!-- Usuarios de prueba, ambientes, datos obligatorios o datos que deben evitarse -->
|
|
384
|
-
- Ejemplo: Usa el usuario QA_TIMESHEET_01 para pruebas de captura diaria.
|
|
385
|
-
- Ejemplo: No reutilices folios ya aprobados.
|
|
386
|
-
|
|
365
|
+
Usa un bullet por regla real.
|
|
366
|
+
Los bullets que empiecen con "Ejemplo:" se ignoran hasta que los reemplaces.
|
|
367
|
+
-->
|
|
368
|
+
|
|
369
|
+
## Reglas del Dominio
|
|
370
|
+
<!-- Restricciones de negocio, validaciones duras, formatos, limites -->
|
|
371
|
+
- Ejemplo: Un usuario no puede registrar mas de 24 horas en un mismo dia.
|
|
372
|
+
- Ejemplo: El campo "Numero de empleado" debe conservar ceros a la izquierda.
|
|
373
|
+
|
|
374
|
+
## Navegacion y UI Especial
|
|
375
|
+
<!-- Pasos raros de la interfaz, cargas lentas, modales, menus no obvios -->
|
|
376
|
+
- Ejemplo: Despues de elegir un proyecto, espera a que cargue la tabla antes de registrar horas.
|
|
377
|
+
- Ejemplo: El menu de acciones siempre se abre desde el icono ":" de la primera fila.
|
|
378
|
+
|
|
379
|
+
## Casos Prohibidos y Anti-patrones
|
|
380
|
+
<!-- Cosas que Arcality nunca debe intentar -->
|
|
381
|
+
- Ejemplo: No uses "Continuar con Microsoft".
|
|
382
|
+
- Ejemplo: No cierres el modal con Escape; usa el boton "Cancelar".
|
|
383
|
+
|
|
384
|
+
## Datos y Credenciales de Prueba
|
|
385
|
+
<!-- Usuarios de prueba, ambientes, datos obligatorios o datos que deben evitarse -->
|
|
386
|
+
- Ejemplo: Usa el usuario QA_TIMESHEET_01 para pruebas de captura diaria.
|
|
387
|
+
- Ejemplo: No reutilices folios ya aprobados.
|
|
388
|
+
|
|
387
389
|
## Resultado Esperado y Validaciones Clave
|
|
388
390
|
<!-- Como saber que la mision realmente termino bien -->
|
|
389
391
|
- Ejemplo: La prueba termina cuando el registro aparece en la tabla "Timesheet - Hoy" de /welcome.
|
|
390
392
|
- Ejemplo: Si aparece la leyenda "Expirado" en el proyecto, la prueba sigue siendo valida.
|
|
391
393
|
`;
|
|
394
|
+
const qaMetaTemplate = {
|
|
395
|
+
version: '1.0.0',
|
|
396
|
+
updated_by: username.trim() || 'qa.owner@empresa.com',
|
|
397
|
+
approved_by: '',
|
|
398
|
+
owner_team: 'QA',
|
|
399
|
+
effective_from: new Date().toISOString().slice(0, 10),
|
|
400
|
+
change_summary: 'Creacion inicial del QA Context para este proyecto.',
|
|
401
|
+
tags: ['business-rules', 'ui-navigation']
|
|
402
|
+
};
|
|
392
403
|
if (!fs.existsSync(qaContextPath)) {
|
|
393
404
|
fs.writeFileSync(qaContextPath, qaTemplate);
|
|
394
405
|
note(chalk.green(`✅ QA Context creado en: ${qaContextPath}`), 'Contexto Guardado');
|
|
395
406
|
} else {
|
|
396
407
|
note(chalk.cyan(`ℹ️ QA Context ya existente: ${qaContextPath}`), 'Contexto Detectado');
|
|
397
408
|
}
|
|
409
|
+
if (!fs.existsSync(qaContextMetaPath)) {
|
|
410
|
+
fs.writeFileSync(qaContextMetaPath, JSON.stringify(qaMetaTemplate, null, 2), 'utf8');
|
|
411
|
+
note(chalk.green(`✅ QA Context metadata creada en: ${qaContextMetaPath}`), 'Metadata Guardada');
|
|
412
|
+
} else {
|
|
413
|
+
note(chalk.cyan(`ℹ️ QA Context metadata ya existente: ${qaContextMetaPath}`), 'Metadata Detectada');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
note(
|
|
417
|
+
chalk.white('Si ejecutas Arcality en CI, la gobernanza del contexto se aplica en modo ') +
|
|
418
|
+
chalk.cyan('strict') + chalk.white(' por defecto.') + '\n' +
|
|
419
|
+
chalk.white('Puedes controlarlo con la variable ') + chalk.cyan('ARCALITY_QA_CONTEXT_GOVERNANCE') +
|
|
420
|
+
chalk.white(' usando ') + chalk.cyan('off') + chalk.white(', ') + chalk.cyan('warn') + chalk.white(' o ') + chalk.cyan('strict') + chalk.white('.'),
|
|
421
|
+
'Gobernanza del Contexto'
|
|
422
|
+
);
|
|
398
423
|
}
|
|
399
424
|
|
|
400
425
|
// ── Summary ──
|
|
401
426
|
note(
|
|
402
|
-
chalk.green('✅ arcality.config creado correctamente') + '\n\n' +
|
|
403
|
-
chalk.bold.white(' Proyecto: ') + chalk.cyan(config.project.name) + '\n' +
|
|
404
|
-
chalk.bold.white(' URL Base: ') + chalk.cyan(config.project.baseUrl) + '\n' +
|
|
405
|
-
chalk.bold.white(' Framework: ') + chalk.magenta(config.project.frameworkDetected || 'N/A') + '\n' +
|
|
406
|
-
chalk.bold.white(' ID del Proyecto: ') + chalk.gray(config.projectId) + '\n' +
|
|
407
|
-
chalk.bold.white(' Carpeta de Misiones: ') + chalk.yellow(config.runtime.yamlOutputDir) + '\n' +
|
|
408
|
-
chalk.bold.white(' Playwright: ') + (playwrightReady ? chalk.green('listo') : chalk.yellow('pendiente de instalacion manual')),
|
|
409
|
-
'Configuración Guardada'
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
outro(
|
|
413
|
-
chalk.cyanBright('🚀 Listo. Ejecuta ') +
|
|
414
|
-
chalk.bold.white('arcality run') +
|
|
415
|
-
chalk.cyanBright(' o ') +
|
|
416
|
-
chalk.bold.white('arcality') +
|
|
417
|
-
chalk.cyanBright(' para comenzar a probar.')
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
main().catch(err => {
|
|
422
|
-
console.error(chalk.red(' ❌ Error durante init:'), err.message);
|
|
423
|
-
process.exit(1);
|
|
424
|
-
});
|
|
427
|
+
chalk.green('✅ arcality.config creado correctamente') + '\n\n' +
|
|
428
|
+
chalk.bold.white(' Proyecto: ') + chalk.cyan(config.project.name) + '\n' +
|
|
429
|
+
chalk.bold.white(' URL Base: ') + chalk.cyan(config.project.baseUrl) + '\n' +
|
|
430
|
+
chalk.bold.white(' Framework: ') + chalk.magenta(config.project.frameworkDetected || 'N/A') + '\n' +
|
|
431
|
+
chalk.bold.white(' ID del Proyecto: ') + chalk.gray(config.projectId) + '\n' +
|
|
432
|
+
chalk.bold.white(' Carpeta de Misiones: ') + chalk.yellow(config.runtime.yamlOutputDir) + '\n' +
|
|
433
|
+
chalk.bold.white(' Playwright: ') + (playwrightReady ? chalk.green('listo') : chalk.yellow('pendiente de instalacion manual')),
|
|
434
|
+
'Configuración Guardada'
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
outro(
|
|
438
|
+
chalk.cyanBright('🚀 Listo. Ejecuta ') +
|
|
439
|
+
chalk.bold.white('arcality run') +
|
|
440
|
+
chalk.cyanBright(' o ') +
|
|
441
|
+
chalk.bold.white('arcality') +
|
|
442
|
+
chalk.cyanBright(' para comenzar a probar.')
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
main().catch(err => {
|
|
447
|
+
console.error(chalk.red(' ❌ Error durante init:'), err.message);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
});
|