@arcadialdev/arcality 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
  2. package/.agents/skills/frontend-design/LICENSE.txt +177 -0
  3. package/.agents/skills/frontend-design/SKILL.md +42 -0
  4. package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
  5. package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
  6. package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
  7. package/.agents/skills/playwright-best-practices/README.md +147 -0
  8. package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
  9. package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
  10. package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
  11. package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
  12. package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
  13. package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
  14. package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
  15. package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
  16. package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
  17. package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
  18. package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
  19. package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
  20. package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
  21. package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
  22. package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
  23. package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
  24. package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
  25. package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
  26. package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
  27. package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
  28. package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
  29. package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
  30. package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
  31. package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
  32. package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
  33. package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
  34. package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
  35. package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
  36. package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
  37. package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
  38. package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
  39. package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
  40. package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
  41. package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
  42. package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
  43. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
  44. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
  45. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
  46. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
  47. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
  48. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
  49. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
  50. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
  51. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
  52. package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
  53. package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
  54. package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
  55. package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
  56. package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
  57. package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
  58. package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
  59. package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
  60. package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
  61. package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
  62. package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
  63. package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
  64. package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
  65. package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
  66. package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
  67. package/.env.example +21 -0
  68. package/README.md +30 -0
  69. package/bin/arcality.mjs +86 -0
  70. package/package.json +66 -0
  71. package/playwright.config.ts +12 -0
  72. package/scripts/cleanup-qmsdev.mjs +63 -0
  73. package/scripts/discover-view.mjs +52 -0
  74. package/scripts/extract-view.mjs +64 -0
  75. package/scripts/gen-and-run.mjs +838 -0
  76. package/scripts/init.mjs +290 -0
  77. package/scripts/migrate-to-central-out.mjs +157 -0
  78. package/scripts/postinstall.mjs +63 -0
  79. package/scripts/rebrand-report.mjs +241 -0
  80. package/scripts/setup.mjs +166 -0
  81. package/src/KnowledgeService.ts +239 -0
  82. package/src/arcalityClient.mjs +266 -0
  83. package/src/configLoader.mjs +179 -0
  84. package/src/configManager.mjs +172 -0
  85. package/src/consoleBanner.ts +32 -0
  86. package/src/envSetup.ts +205 -0
  87. package/src/index.ts +25 -0
  88. package/src/projectInspector.ts +42 -0
  89. package/src/services/collectiveMemoryService.ts +178 -0
  90. package/src/testRunner.ts +201 -0
  91. package/tests/_helpers/ArcalityReporter.ts +490 -0
  92. package/tests/_helpers/agentic-runner.spec.ts +741 -0
  93. package/tests/_helpers/ai-agent-helper.ts +1573 -0
  94. package/tests/_helpers/discover-view.spec.ts +238 -0
  95. package/tests/_helpers/extract-view.spec.ts +118 -0
  96. package/tests/_helpers/qa-tools.ts +333 -0
  97. package/tests/_helpers/smart-action.spec.ts +1458 -0
@@ -0,0 +1,241 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ async function rebrandReport() {
5
+ // Definir directorios objetivo: El reporte personalizado, el nativo anidado y el reporte interno
6
+ const reportDir = process.env.REPORTS_DIR || 'tests-report';
7
+ const targetDirs = [
8
+ reportDir,
9
+ path.join(reportDir, 'native'),
10
+ 'playwright-internal-report'
11
+ ];
12
+
13
+ const logoPngPath = path.join('public', 'logo.png');
14
+ let logoBase64 = '';
15
+
16
+ // Preparar Logo Arcality
17
+ if (fs.existsSync(logoPngPath)) {
18
+ try {
19
+ const logoBuffer = fs.readFileSync(logoPngPath);
20
+ logoBase64 = `data:image/png;base64,${logoBuffer.toString('base64')}`;
21
+ console.log("✅ Logo Arcality cargado desde public/logo.png");
22
+ } catch (e) {
23
+ console.error("⚠️ Error leyendo logo:", e.message);
24
+ }
25
+ } else {
26
+ console.warn("⚠️ No se encontró public/logo.png");
27
+ }
28
+
29
+ for (const dir of targetDirs) {
30
+ if (fs.existsSync(dir)) {
31
+ console.log(`Processing report directory: ${dir}`);
32
+ processDirectory(dir, logoBase64);
33
+ }
34
+ }
35
+
36
+ console.log("🏁 Branding process finished.");
37
+ }
38
+
39
+ function processDirectory(reportDir, logoBase64) {
40
+ const indexPath = path.join(reportDir, 'index.html');
41
+
42
+ // Procesar index.html principal
43
+ if (fs.existsSync(indexPath)) {
44
+ try {
45
+ let html = fs.readFileSync(indexPath, 'utf8');
46
+
47
+ // 1. Reemplazo de Títulos Estáticos
48
+ html = html.replace(/<title>.*?<\/title>/gi, '<title>Arcality Mission Report</title>');
49
+ html = html.replace(/Playwright Test Report/g, 'Arcality Mission Report');
50
+ html = html.replace(/Playwright Report/g, 'Arcality Report');
51
+
52
+ // 2. Limpieza de Stack Traces Sucios (Node Modules)
53
+ html = html.replace(/at\s+.*?node_modules.*?(\n|\r)/g, '');
54
+ html = html.replace(/at\s+.*?agentic-runner\.spec\.ts:\d+/g, '');
55
+
56
+ // 3. Inyección de Estilos y Scripts (MODO AGRESIVO)
57
+ const customStyles = `
58
+ <script id="arcality-branding-script">
59
+ /* ARCALITY_BRANDING_SCRIPT */
60
+ // 1. Limpieza de Caché y Service Worker
61
+ if ('serviceWorker' in navigator) {
62
+ navigator.serviceWorker.getRegistrations().then(r => r.forEach(reg => reg.unregister()));
63
+ }
64
+
65
+ // 2. Reemplazo Dinámico (Loop para vencer al JS de Playwright)
66
+ function enforceBranding() {
67
+ // Forzar Título
68
+ if (!document.title.includes('Arcality')) {
69
+ document.title = 'Arcality Mission Report';
70
+ }
71
+
72
+ // Forzar Favicon
73
+ let icon = document.querySelector("link[rel*='icon']");
74
+ if (!icon) {
75
+ icon = document.createElement('link');
76
+ icon.rel = 'shortcut icon';
77
+ document.head.appendChild(icon);
78
+ }
79
+ if (icon && icon.href !== '${logoBase64}') icon.href = '${logoBase64}';
80
+
81
+ // Insertar Logo Arcality en el Header
82
+ if ('${logoBase64}' && !document.querySelector('.arcality-logo-injected')) {
83
+ const logoImg = document.createElement('img');
84
+ logoImg.src = '${logoBase64}';
85
+ logoImg.className = 'arcality-logo-injected';
86
+ logoImg.style.height = '32px';
87
+ logoImg.style.marginRight = '12px';
88
+ logoImg.style.verticalAlign = 'middle';
89
+
90
+ // Intentar encontrar un lugar en el header
91
+ const headerElements = ['header h1', '.Box-header h1', '.test-details-header h1', 'h1', 'header div'];
92
+ for (const selector of headerElements) {
93
+ const el = document.querySelector(selector);
94
+ if (el && !el.querySelector('.arcality-logo-injected')) {
95
+ el.prepend(logoImg);
96
+ break;
97
+ }
98
+ }
99
+ }
100
+
101
+ // Reemplazar textos visibles
102
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
103
+ let node;
104
+ while(node = walker.nextNode()) {
105
+ if (node.nodeValue && node.nodeValue.includes('Playwright')) {
106
+ node.nodeValue = node.nodeValue.replace(/Playwright/g, 'Arcality');
107
+ }
108
+ }
109
+
110
+ // Ocultar elementos de Playwright/Github
111
+ document.querySelectorAll('svg.octicon-mark-github, header svg, [aria-label*="Playwright"]').forEach(el => {
112
+ el.style.display = 'none';
113
+ });
114
+ }
115
+
116
+ setInterval(enforceBranding, 300);
117
+ window.addEventListener('load', enforceBranding);
118
+ </script>
119
+ <style id="arcality-branding-style">
120
+ /* ARCALITY_BRANDING_STYLE */
121
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
122
+ body, .Box-header, .Box-body, .test-details-header {
123
+ font-family: 'Inter', -apple-system, system-ui, sans-serif !important;
124
+ }
125
+ /* Pequeños retoques visuales premium */
126
+ div[role="tablist"] { border-radius: 8px; overflow: hidden; }
127
+ .label, span[class*="Label"] { border-radius: 999px !important; }
128
+ .arcality-logo-injected {
129
+ filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
130
+ transition: transform 0.2s ease;
131
+ }
132
+ .arcality-logo-injected:hover {
133
+ transform: scale(1.05);
134
+ }
135
+ </style>
136
+ `;
137
+
138
+ // Eliminar inyecciones previas si existen (limpieza profunda)
139
+ html = html.replace(/<script id="arcality-branding-script">[\s\S]*?<\/script>/g, '');
140
+ html = html.replace(/<style id="arcality-branding-style">[\s\S]*?<\/style>/g, '');
141
+
142
+ // Compatibilidad con versiones sin ID (Modo agresivo)
143
+ html = html.replace(/<script>\s*\/\* ARCALITY_BRANDING \*\/[\s\S]*?<\/script>/g, '');
144
+ html = html.replace(/<style>\s*\/\* ARCALITY_BRANDING \*\/[\s\S]*?<\/style>/g, '');
145
+
146
+ // Eliminar cualquier script que contenga lógica de "Limpieza de Caché" o "document.createTreeWalker" inyectado previamente
147
+ html = html.replace(/<script>\s*\/\/ 1\. Limpieza de Caché[\s\S]*?<\/script>/g, '');
148
+ html = html.replace(/<script>\s*\/[\s\S]*?enforceBranding[\s\S]*?<\/script>/g, '');
149
+
150
+ // Inyectar justo antes del cierre de head
151
+ if (html.includes('</head>')) {
152
+ html = html.replace('</head>', `${customStyles}\n</head>`);
153
+ } else if (html.includes('<head>')) {
154
+ html = html.replace('<head>', `<head>\n${customStyles}`);
155
+ }
156
+
157
+ // 4. Cambiar icono estático si existe
158
+ if (logoBase64) {
159
+ html = html.replace(/data:image\/svg\+xml,%3csvg.*?%3c\/svg%3e/g, logoBase64);
160
+ html = html.replace(/rel="shortcut icon" href=".*?"/g, `rel="shortcut icon" href="${logoBase64}"`);
161
+ html = html.replace(/rel="icon" href=".*?"/g, `rel="icon" href="${logoBase64}"`);
162
+ }
163
+
164
+ fs.writeFileSync(indexPath, html, 'utf8');
165
+ console.log(`✨ [REBRAND] Report transformado en: ${indexPath}`);
166
+ } catch (err) {
167
+ console.error(`❌ Error al procesar ${indexPath}: ${err.message}`);
168
+ }
169
+ }
170
+
171
+ // Procesar Trace Viewer anidado si existe
172
+ processTraceViewer(reportDir, logoBase64);
173
+ }
174
+
175
+ function processTraceViewer(reportDir, logoBase64) {
176
+ const tracePath = path.join(reportDir, 'trace', 'index.html');
177
+
178
+ if (fs.existsSync(tracePath)) {
179
+ try {
180
+ let traceHtml = fs.readFileSync(tracePath, 'utf8');
181
+
182
+ // Reemplazos básicos
183
+ traceHtml = traceHtml.replace(/<title>.*?<\/title>/gi, '<title>Arcality Trace Viewer</title>');
184
+ traceHtml = traceHtml.replace(/Playwright Trace Viewer/g, 'Arcality Trace Viewer');
185
+ traceHtml = traceHtml.replace(/For more information, please see the <a href=".*?">docs<\/a>\./, 'Powered by Arcality Engine.');
186
+
187
+ const tvScript = `
188
+ <script id="arcality-trace-branding">
189
+ /* ARCALITY_TRACE_BRANDING */
190
+ function fixTraceBranding() {
191
+ if (!document.title.includes('Arcality')) document.title = 'Arcality Trace Viewer';
192
+ let icon = document.querySelector("link[rel*='icon']");
193
+ if (!icon) {
194
+ icon = document.createElement('link');
195
+ icon.rel = 'icon';
196
+ document.head.appendChild(icon);
197
+ }
198
+ if (icon && icon.href !== '${logoBase64}') icon.href = '${logoBase64}';
199
+
200
+ if ('${logoBase64}' && !document.querySelector('.arcality-logo-injected')) {
201
+ const logoImg = document.createElement('img');
202
+ logoImg.src = '${logoBase64}';
203
+ logoImg.className = 'arcality-logo-injected';
204
+ logoImg.style.height = '24px';
205
+ logoImg.style.margin = '0 10px';
206
+ logoImg.style.verticalAlign = 'middle';
207
+
208
+ const toolbar = document.querySelector('.split-view-vertical > div:first-child, header, .toolbar');
209
+ if (toolbar) toolbar.prepend(logoImg);
210
+ }
211
+ }
212
+ setInterval(fixTraceBranding, 500);
213
+ </script>
214
+ `;
215
+
216
+ // Limpieza e inyección
217
+ traceHtml = traceHtml.replace(/<script id="arcality-trace-branding">[\s\S]*?<\/script>/g, '');
218
+ traceHtml = traceHtml.replace(/<script>\s*\/\* ARCALITY_BRANDING \*\/[\s\S]*?<\/script>/g, '');
219
+
220
+ // Fix for potential spaces in HTML tags like '< head >', '< link >', etc.
221
+ traceHtml = traceHtml.replace(/<\s*(\w+)\s*>/g, '<$1>');
222
+ traceHtml = traceHtml.replace(/<\s*\/\s*(\w+)\s*>/g, '</$1>');
223
+
224
+ if (traceHtml.includes('</head>')) {
225
+ traceHtml = traceHtml.replace('</head>', `${tvScript}\n</head>`);
226
+ }
227
+
228
+ // Favicon
229
+ if (logoBase64) {
230
+ traceHtml = traceHtml.replace(/rel="icon" href=".*?"/g, `rel="icon" href="${logoBase64}"`);
231
+ }
232
+
233
+ fs.writeFileSync(tracePath, traceHtml, 'utf8');
234
+ console.log(`✨ [REBRAND] Trace Viewer transformado en: ${tracePath}`);
235
+ } catch (e) {
236
+ console.error(`⚠️ Error en Trace Viewer ${tracePath}: ${e.message}`);
237
+ }
238
+ }
239
+ }
240
+
241
+ rebrandReport().catch(console.error);
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ // scripts/setup.mjs — Comando: arcality setup
3
+ // Configura API Key + instala navegador Playwright (Chromium)
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import { spawn } from 'node:child_process';
9
+ import { createInterface } from 'node:readline';
10
+
11
+ const CONFIG_DIR = path.join(os.homedir(), '.arcality');
12
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
13
+ const PACKAGE_ROOT = process.env.ARCALITY_ROOT || path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
14
+
15
+ function question(prompt) {
16
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
17
+ return new Promise(resolve => {
18
+ rl.question(prompt, answer => { rl.close(); resolve(answer.trim()); });
19
+ });
20
+ }
21
+
22
+ function loadConfig() {
23
+ try {
24
+ if (fs.existsSync(CONFIG_FILE)) {
25
+ let raw = fs.readFileSync(CONFIG_FILE, 'utf8');
26
+ if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1);
27
+ return JSON.parse(raw);
28
+ }
29
+ } catch { }
30
+ return {};
31
+ }
32
+
33
+ function saveConfig(config) {
34
+ if (!fs.existsSync(CONFIG_DIR)) {
35
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
36
+ }
37
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
38
+ }
39
+
40
+ async function main() {
41
+ // Leer versión
42
+ let version = 'unknown';
43
+ try {
44
+ const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
45
+ version = pkg.version || 'unknown';
46
+ } catch { }
47
+
48
+ console.log('');
49
+ console.log(' ╔══════════════════════════════════════════╗');
50
+ console.log(` ║ ARCALITY — Setup v${version.padEnd(24)}║`);
51
+ console.log(' ╚══════════════════════════════════════════╝');
52
+ console.log('');
53
+
54
+ const config = loadConfig();
55
+
56
+ // ──────────────── Paso 1: API Key de Arcality ────────────────
57
+ const currentKey = config.api_key || '';
58
+ const currentAnthropic = config.anthropic_api_key || '';
59
+
60
+ const maskedArc = currentKey
61
+ ? `✅ ${currentKey.slice(0, 6)}****${currentKey.slice(-4)}`
62
+ : '❌ (No configurada)';
63
+
64
+ const maskedAnt = currentAnthropic
65
+ ? `✅ ${currentAnthropic.slice(0, 6)}****${currentAnthropic.slice(-4)}`
66
+ : '❌ (No configurada)';
67
+
68
+ console.log(` 🔑 Arcality API Key (dummy/real): ${maskedArc}`);
69
+ console.log(` 🧠 Anthropic API Key (corporativa): ${maskedAnt}`);
70
+ console.log('');
71
+
72
+ const newKey = await question(' 👉 Ingresa Arcality API Key (Enter p. mantener): ');
73
+ if (newKey) {
74
+ if (!newKey.startsWith('arc_k_')) {
75
+ console.log(' ❌ La API Key debe iniciar con "arc_k_"');
76
+ process.exit(1);
77
+ }
78
+ config.api_key = newKey;
79
+ }
80
+
81
+ // API URL is internal-only (loaded via dotenv), always available
82
+ const hasSaaSBackend = !!process.env.ARCALITY_API_URL;
83
+
84
+ if (hasSaaSBackend) {
85
+ console.log(' 🧠 Anthropic API Key (omitida — se usará el Cerebro SaaS)');
86
+ // Podemos limpiar la llave local por seguridad si queremos
87
+ if (config.anthropic_api_key) delete config.anthropic_api_key;
88
+ } else {
89
+ const newAnt = await question(' 👉 Ingresa Anthropic API Key (Enter p. mantener): ');
90
+ if (newAnt) {
91
+ if (!newAnt.startsWith('sk-ant-')) {
92
+ console.log(' ⚠️ Formato de llave Anthropic inusual. Verifica que inicie con "sk-ant-"');
93
+ }
94
+ config.anthropic_api_key = newAnt;
95
+ }
96
+ }
97
+
98
+ // Configurar modelo por defecto
99
+ config.model = config.model || 'claude-sonnet-4-5';
100
+
101
+ // Guardar config
102
+ config.version = version;
103
+ config.setup_at = new Date().toISOString();
104
+ saveConfig(config);
105
+ console.log('');
106
+ console.log(' ✅ Configuración guardada correctamente.');
107
+
108
+ // ─── Paso 2: Instalar Chromium ───
109
+ console.log('');
110
+ console.log(' 🌐 Instalando navegador (Chromium para Playwright)...');
111
+ console.log('');
112
+
113
+ try {
114
+ await new Promise((resolve, reject) => {
115
+ const child = spawn('npx', ['playwright', 'install', 'chromium'], {
116
+ stdio: 'inherit',
117
+ shell: true,
118
+ cwd: PACKAGE_ROOT
119
+ });
120
+ child.on('exit', code => code === 0 ? resolve() : reject(new Error(`Playwright install falló (exit code ${code})`)));
121
+ child.on('error', reject);
122
+ });
123
+ console.log('');
124
+ console.log(' ✅ Chromium instalado correctamente');
125
+ } catch (err) {
126
+ console.log('');
127
+ console.log(` ⚠️ No se pudo instalar Chromium automáticamente: ${err.message}`);
128
+ console.log(' Puedes instalarlo manualmente ejecutando:');
129
+ console.log(' npx playwright install chromium');
130
+ }
131
+
132
+ // ─── Paso 3: Crear .env si no existe ───
133
+ const envFile = path.join(PACKAGE_ROOT, '.env');
134
+ const envExample = path.join(PACKAGE_ROOT, '.env.example');
135
+
136
+ if (!fs.existsSync(envFile) && fs.existsSync(envExample)) {
137
+ let content = fs.readFileSync(envExample, 'utf8');
138
+
139
+ // Inyectar la API Key
140
+ if (config.api_key) {
141
+ content = content.replace('ARCALITY_API_KEY=', `ARCALITY_API_KEY=${config.api_key}`);
142
+ }
143
+
144
+ fs.writeFileSync(envFile, content, 'utf8');
145
+ console.log(' ✅ .env creado desde .env.example');
146
+ } else if (fs.existsSync(envFile)) {
147
+ console.log(' ✅ .env existente (sin modificar)');
148
+ }
149
+
150
+ // ─── Resumen final ───
151
+ console.log('');
152
+ console.log(' ╔══════════════════════════════════════════╗');
153
+ console.log(' ║ ✅ SETUP COMPLETADO ║');
154
+ console.log(' ╚══════════════════════════════════════════╝');
155
+ console.log('');
156
+ console.log(' Para empezar, ejecuta:');
157
+ console.log('');
158
+ console.log(' arcality → Menú interactivo');
159
+ console.log(' arcality --agent "tu misión" → Ejecución directa');
160
+ console.log('');
161
+ }
162
+
163
+ main().catch(err => {
164
+ console.error(' ❌ Error durante setup:', err.message);
165
+ process.exit(1);
166
+ });
@@ -0,0 +1,239 @@
1
+
2
+ import * as crypto from 'crypto';
3
+ import chalk from 'chalk';
4
+
5
+ /**
6
+ * KnowledgeService (SaaS v2)
7
+ * Servicio centralizado para alimentar y consumir la base de conocimiento persistente de Arcality.
8
+ * Configurado para usar SNAKE_CASE según la especificación del servidor.
9
+ */
10
+
11
+ export interface PortalIngestRequest {
12
+ project_id: string;
13
+ target_url: string;
14
+ page_title?: string;
15
+ dom_hash: string;
16
+ viewport?: { width: number; height: number };
17
+ components: Array<{
18
+ tag: string;
19
+ component_type: "ACTION" | "FIELD" | "INFO" | "NAVIGATION" | "DATA";
20
+ semantic_label: string;
21
+ text_content?: string;
22
+ is_interactive: boolean;
23
+ attributes?: any;
24
+ bounding_rect?: { x: number; y: number; width: number; height: number };
25
+ }>;
26
+ }
27
+
28
+ export interface PortalContextResponse {
29
+ rules: Array<{ title: string; description: string; severity: string }>;
30
+ fields: Array<{ field_identifier: string; field_type: string; is_required: boolean }>;
31
+ }
32
+
33
+ export interface ProjectDto {
34
+ id: string;
35
+ name: string;
36
+ base_url?: string;
37
+ }
38
+
39
+ export class KnowledgeService {
40
+ private static instance: KnowledgeService;
41
+ private apiBase: string;
42
+ private apiKey: string;
43
+ private projectId: string | null = null;
44
+
45
+ private constructor() {
46
+ // API URL is internal-only, loaded from the tool's .env via dotenv
47
+ this.apiBase = process.env.ARCALITY_API_URL || 'http://localhost:5164';
48
+ this.apiKey = process.env.ARCALITY_API_KEY || '';
49
+ this.projectId = process.env.ARCALITY_PROJECT_ID || null;
50
+ }
51
+
52
+ public static getInstance(): KnowledgeService {
53
+ if (!KnowledgeService.instance) {
54
+ KnowledgeService.instance = new KnowledgeService();
55
+ }
56
+ return KnowledgeService.instance;
57
+ }
58
+
59
+ public setProjectId(id: string) {
60
+ this.projectId = id;
61
+ }
62
+
63
+ public getProjectId(): string | null {
64
+ if (!this.projectId || this.projectId === 'undefined' || this.projectId === 'null') {
65
+ this.projectId = process.env.ARCALITY_PROJECT_ID || null;
66
+ }
67
+ return this.projectId;
68
+ }
69
+
70
+ /**
71
+ * 1. Gestión de Proyectos
72
+ */
73
+ public async getProjects(): Promise<ProjectDto[]> {
74
+ try {
75
+ const res = await fetch(`${this.apiBase}/api/v1/projects`, {
76
+ headers: { 'x-api-key': this.apiKey }
77
+ });
78
+ if (!res.ok) return [];
79
+ const data = await res.json();
80
+ return data.projects || [];
81
+ } catch (e: any) {
82
+ console.warn(chalk.yellow(`[KnowledgeService] Error al obtener proyectos: ${e.message}`));
83
+ return [];
84
+ }
85
+ }
86
+
87
+ public async createProject(name: string, baseUrl: string): Promise<ProjectDto | null> {
88
+ try {
89
+ const res = await fetch(`${this.apiBase}/api/v1/projects`, {
90
+ method: 'POST',
91
+ headers: {
92
+ 'Content-Type': 'application/json',
93
+ 'x-api-key': this.apiKey
94
+ },
95
+ body: JSON.stringify({ name, base_url: baseUrl })
96
+ });
97
+ if (!res.ok) return null;
98
+ return await res.json();
99
+ } catch (e: any) {
100
+ console.error(chalk.red(`[KnowledgeService] Falló creación de proyecto: ${e.message}`));
101
+ return null;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * 2. Ingesta (Post-Percept)
107
+ */
108
+ public async ingest(payload: PortalIngestRequest): Promise<void> {
109
+ if (!payload.project_id || payload.project_id === 'undefined') {
110
+ console.warn(chalk.yellow(`[Knowledge] Ingesta cancelada: project_id es inválido.`));
111
+ return;
112
+ }
113
+
114
+ try {
115
+ const res = await fetch(`${this.apiBase}/api/v1/portal/ingest`, {
116
+ method: 'POST',
117
+ headers: {
118
+ 'Content-Type': 'application/json',
119
+ 'x-api-key': this.apiKey
120
+ },
121
+ body: JSON.stringify(payload)
122
+ });
123
+ if (res.status === 200) {
124
+ const data = await res.json();
125
+ if (data.status === 'skipped_duplicate_dom') {
126
+ // console.log(chalk.gray(`[Knowledge] Ingesta omitida (Duplicado).`));
127
+ } else {
128
+ console.log(chalk.cyan(`[Knowledge] Ingesta exitosa (Project: ${payload.project_id})`));
129
+ }
130
+ }
131
+ } catch (e: any) {
132
+ console.warn(chalk.yellow(`[Knowledge] Error en ingesta: ${e.message}`));
133
+ }
134
+ }
135
+
136
+ /**
137
+ * 3. Contexto (Pre-Reasoning)
138
+ */
139
+ public async getContext(url: string): Promise<PortalContextResponse | null> {
140
+ const pid = this.getProjectId();
141
+ if (!pid || pid === 'undefined') return null;
142
+ try {
143
+ const pathUrl = new URL(url).pathname;
144
+ // Para queries de GET solemos usar snake_case si el server es estricto
145
+ const res = await fetch(`${this.apiBase}/api/v1/portal/context?project_id=${pid}&path=${encodeURIComponent(pathUrl)}`, {
146
+ headers: { 'x-api-key': this.apiKey }
147
+ });
148
+ if (!res.ok) return null;
149
+ return await res.json();
150
+ } catch (e: any) {
151
+ console.warn(chalk.yellow(`[Knowledge] Error al obtener contexto: ${e.message}`));
152
+ return null;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * 4. Aprendizaje (portal_domain_rules)
158
+ */
159
+ public async saveRule(title: string, description: string, severity: string = 'important', pageUrl?: string): Promise<void> {
160
+ const pid = this.getProjectId();
161
+ if (!pid || pid === 'undefined') return;
162
+ try {
163
+ const pathUrl = pageUrl ? new URL(pageUrl).pathname : null;
164
+ await fetch(`${this.apiBase}/api/v1/portal/rules`, {
165
+ method: 'POST',
166
+ headers: {
167
+ 'Content-Type': 'application/json',
168
+ 'x-api-key': this.apiKey
169
+ },
170
+ body: JSON.stringify({
171
+ project_id: pid,
172
+ rule_type: "UI_UX", // Default según especificación
173
+ title,
174
+ description,
175
+ severity
176
+ })
177
+ });
178
+ console.log(chalk.green(`[Knowledge] Nueva regla registrada: ${title}`));
179
+ } catch (e) { }
180
+ }
181
+
182
+ /**
183
+ * 5. Catálogo de Campos (portal_field_catalog)
184
+ */
185
+ public async saveField(identifier: string, type: string, isRequired: boolean): Promise<void> {
186
+ const pid = this.getProjectId();
187
+ if (!pid || pid === 'undefined') return;
188
+ try {
189
+ await fetch(`${this.apiBase}/api/v1/portal/fields`, {
190
+ method: 'POST',
191
+ headers: {
192
+ 'Content-Type': 'application/json',
193
+ 'x-api-key': this.apiKey
194
+ },
195
+ body: JSON.stringify({
196
+ project_id: pid,
197
+ field_identifier: identifier,
198
+ field_type: type,
199
+ is_required: isRequired,
200
+ source: "agent_auto"
201
+ })
202
+ });
203
+ console.log(chalk.green(`[Knowledge] Nuevo campo catalogado: ${identifier}`));
204
+ } catch (e) { }
205
+ }
206
+
207
+ /**
208
+ * 6. Conocimiento General (Documentación)
209
+ */
210
+ public async saveKnowledge(title: string, content: string, docType: string = 'PROCESS'): Promise<void> {
211
+ const pid = this.getProjectId();
212
+ if (!pid || pid === 'undefined') return;
213
+ try {
214
+ await fetch(`${this.apiBase}/api/v1/portal/knowledge`, {
215
+ method: 'POST',
216
+ headers: {
217
+ 'Content-Type': 'application/json',
218
+ 'x-api-key': this.apiKey
219
+ },
220
+ body: JSON.stringify({
221
+ project_id: pid,
222
+ doc_type: docType,
223
+ title,
224
+ content,
225
+ source: "agent_auto"
226
+ })
227
+ });
228
+ console.log(chalk.green(`[Knowledge] Nuevo conocimiento guardado: ${title}`));
229
+ } catch (e) { }
230
+ }
231
+
232
+ /**
233
+ * Utilidad: Calcular Hash para Ingesta
234
+ */
235
+ public calculateDomHash(components: any[]): string {
236
+ const fingerprint = components.map(c => `${c.tag}-${c.semanticLabel}`).join('|');
237
+ return crypto.createHash('sha256').update(fingerprint).digest('hex');
238
+ }
239
+ }