@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.
- package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
- package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
- package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
- package/.agents/skills/playwright-best-practices/README.md +147 -0
- package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
- package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
- package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
- package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
- package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
- package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
- package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
- package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
- package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
- package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
- package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
- package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
- package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
- package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
- package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
- package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
- package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
- package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
- package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
- package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
- package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
- package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
- package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
- package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
- package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
- package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
- package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
- package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
- package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
- package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
- package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
- package/.env.example +21 -0
- package/README.md +30 -0
- package/bin/arcality.mjs +86 -0
- package/package.json +66 -0
- package/playwright.config.ts +12 -0
- package/scripts/cleanup-qmsdev.mjs +63 -0
- package/scripts/discover-view.mjs +52 -0
- package/scripts/extract-view.mjs +64 -0
- package/scripts/gen-and-run.mjs +838 -0
- package/scripts/init.mjs +290 -0
- package/scripts/migrate-to-central-out.mjs +157 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/rebrand-report.mjs +241 -0
- package/scripts/setup.mjs +166 -0
- package/src/KnowledgeService.ts +239 -0
- package/src/arcalityClient.mjs +266 -0
- package/src/configLoader.mjs +179 -0
- package/src/configManager.mjs +172 -0
- package/src/consoleBanner.ts +32 -0
- package/src/envSetup.ts +205 -0
- package/src/index.ts +25 -0
- package/src/projectInspector.ts +42 -0
- package/src/services/collectiveMemoryService.ts +178 -0
- package/src/testRunner.ts +201 -0
- package/tests/_helpers/ArcalityReporter.ts +490 -0
- package/tests/_helpers/agentic-runner.spec.ts +741 -0
- package/tests/_helpers/ai-agent-helper.ts +1573 -0
- package/tests/_helpers/discover-view.spec.ts +238 -0
- package/tests/_helpers/extract-view.spec.ts +118 -0
- package/tests/_helpers/qa-tools.ts +333 -0
- 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
|
+
}
|