@openqa/cli 1.3.4 → 2.1.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 (44) hide show
  1. package/README.md +203 -6
  2. package/dist/agent/brain/diff-analyzer.js +140 -0
  3. package/dist/agent/brain/diff-analyzer.js.map +1 -0
  4. package/dist/agent/brain/llm-cache.js +47 -0
  5. package/dist/agent/brain/llm-cache.js.map +1 -0
  6. package/dist/agent/brain/llm-resilience.js +252 -0
  7. package/dist/agent/brain/llm-resilience.js.map +1 -0
  8. package/dist/agent/config/index.js +588 -0
  9. package/dist/agent/config/index.js.map +1 -0
  10. package/dist/agent/coverage/index.js +74 -0
  11. package/dist/agent/coverage/index.js.map +1 -0
  12. package/dist/agent/export/index.js +158 -0
  13. package/dist/agent/export/index.js.map +1 -0
  14. package/dist/agent/index-v2.js +2795 -0
  15. package/dist/agent/index-v2.js.map +1 -0
  16. package/dist/agent/index.js +369 -105
  17. package/dist/agent/index.js.map +1 -1
  18. package/dist/agent/logger.js +41 -0
  19. package/dist/agent/logger.js.map +1 -0
  20. package/dist/agent/metrics.js +39 -0
  21. package/dist/agent/metrics.js.map +1 -0
  22. package/dist/agent/notifications/index.js +106 -0
  23. package/dist/agent/notifications/index.js.map +1 -0
  24. package/dist/agent/openapi/spec.js +338 -0
  25. package/dist/agent/openapi/spec.js.map +1 -0
  26. package/dist/agent/tools/project-runner.js +481 -0
  27. package/dist/agent/tools/project-runner.js.map +1 -0
  28. package/dist/cli/config.html.js +454 -0
  29. package/dist/cli/daemon.js +8810 -0
  30. package/dist/cli/dashboard.html.js +1622 -0
  31. package/dist/cli/env-config.js +391 -0
  32. package/dist/cli/env-routes.js +820 -0
  33. package/dist/cli/env.html.js +679 -0
  34. package/dist/cli/index.js +5980 -1896
  35. package/dist/cli/kanban.html.js +577 -0
  36. package/dist/cli/routes.js +895 -0
  37. package/dist/cli/routes.js.map +1 -0
  38. package/dist/cli/server.js +5855 -1860
  39. package/dist/database/index.js +485 -60
  40. package/dist/database/index.js.map +1 -1
  41. package/dist/database/sqlite.js +281 -0
  42. package/dist/database/sqlite.js.map +1 -0
  43. package/install.sh +19 -10
  44. package/package.json +19 -5
@@ -0,0 +1,454 @@
1
+ // cli/config.html.ts
2
+ function getConfigHTML(cfg) {
3
+ return `<!DOCTYPE html>
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>OpenQA \u2014 Configuration</title>
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;600;700;800&display=swap" rel="stylesheet">
11
+ <style>
12
+ :root {
13
+ --bg: #080b10;
14
+ --surface: #0d1117;
15
+ --panel: #111720;
16
+ --border: rgba(255,255,255,0.06);
17
+ --border-hi: rgba(255,255,255,0.12);
18
+ --accent: #f97316;
19
+ --accent-lo: rgba(249,115,22,0.08);
20
+ --green: #22c55e;
21
+ --green-lo: rgba(34,197,94,0.08);
22
+ --red: #ef4444;
23
+ --red-lo: rgba(239,68,68,0.08);
24
+ --text-1: #f1f5f9;
25
+ --text-2: #8b98a8;
26
+ --text-3: #4b5563;
27
+ --mono: 'DM Mono', monospace;
28
+ --sans: 'Syne', sans-serif;
29
+ --radius: 10px;
30
+ --radius-lg: 16px;
31
+ }
32
+
33
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
34
+
35
+ body {
36
+ font-family: var(--sans);
37
+ background: var(--bg);
38
+ color: var(--text-1);
39
+ min-height: 100vh;
40
+ }
41
+
42
+ .shell {
43
+ display: grid;
44
+ grid-template-columns: 220px 1fr;
45
+ min-height: 100vh;
46
+ }
47
+
48
+ aside {
49
+ background: var(--surface);
50
+ border-right: 1px solid var(--border);
51
+ display: flex;
52
+ flex-direction: column;
53
+ padding: 28px 0;
54
+ position: sticky;
55
+ top: 0;
56
+ height: 100vh;
57
+ }
58
+
59
+ .logo {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 10px;
63
+ padding: 0 24px 32px;
64
+ border-bottom: 1px solid var(--border);
65
+ margin-bottom: 12px;
66
+ }
67
+
68
+ .logo-mark {
69
+ width: 34px;
70
+ height: 34px;
71
+ background: var(--accent);
72
+ border-radius: 8px;
73
+ display: grid;
74
+ place-items: center;
75
+ font-size: 16px;
76
+ }
77
+
78
+ .logo-name { font-weight: 800; font-size: 18px; letter-spacing: -0.5px; }
79
+ .logo-version { font-family: var(--mono); font-size: 10px; color: var(--text-3); }
80
+
81
+ .nav-section { padding: 8px 12px; flex: 1; }
82
+ .nav-label { font-family: var(--mono); font-size: 10px; color: var(--text-3); letter-spacing: 1.5px; text-transform: uppercase; padding: 0 12px; margin: 16px 0 6px; }
83
+
84
+ .nav-item {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 10px;
88
+ padding: 9px 12px;
89
+ border-radius: var(--radius);
90
+ color: var(--text-2);
91
+ text-decoration: none;
92
+ font-size: 14px;
93
+ font-weight: 600;
94
+ transition: all 0.15s ease;
95
+ }
96
+ .nav-item:hover { color: var(--text-1); background: var(--panel); }
97
+ .nav-item.active { color: var(--accent); background: var(--accent-lo); }
98
+ .nav-item .icon { font-size: 15px; width: 20px; text-align: center; }
99
+
100
+ .sidebar-footer { padding: 16px 24px; border-top: 1px solid var(--border); }
101
+ .status-pill { display: flex; align-items: center; gap: 8px; font-family: var(--mono); font-size: 11px; color: var(--text-2); }
102
+ .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px var(--green); }
103
+
104
+ main { display: flex; flex-direction: column; min-height: 100vh; overflow-y: auto; }
105
+
106
+ .topbar {
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: space-between;
110
+ padding: 20px 32px;
111
+ border-bottom: 1px solid var(--border);
112
+ background: var(--surface);
113
+ position: sticky;
114
+ top: 0;
115
+ z-index: 10;
116
+ }
117
+
118
+ .page-title { font-size: 15px; font-weight: 700; letter-spacing: -0.2px; }
119
+ .page-breadcrumb { font-family: var(--mono); font-size: 11px; color: var(--text-3); margin-top: 2px; }
120
+ .topbar-actions { display: flex; align-items: center; gap: 12px; }
121
+
122
+ .btn-sm {
123
+ font-family: var(--sans);
124
+ font-weight: 700;
125
+ font-size: 12px;
126
+ padding: 8px 16px;
127
+ border-radius: 8px;
128
+ border: none;
129
+ cursor: pointer;
130
+ transition: all 0.15s ease;
131
+ }
132
+ .btn-ghost { background: var(--panel); color: var(--text-2); border: 1px solid var(--border); }
133
+ .btn-ghost:hover { border-color: var(--border-hi); color: var(--text-1); }
134
+ .btn-primary { background: var(--accent); color: #fff; }
135
+ .btn-primary:hover { background: #ea580c; box-shadow: 0 0 20px rgba(249,115,22,0.35); }
136
+
137
+ .content { padding: 28px 32px; display: flex; flex-direction: column; gap: 24px; }
138
+
139
+ .panel { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
140
+ .panel-head { display: flex; align-items: center; justify-content: space-between; padding: 18px 24px; border-bottom: 1px solid var(--border); }
141
+ .panel-title { font-size: 13px; font-weight: 700; letter-spacing: -0.1px; }
142
+ .panel-body { padding: 24px; }
143
+
144
+ .form-grid { display: grid; gap: 20px; }
145
+ .form-section { display: flex; flex-direction: column; gap: 16px; }
146
+ .form-section-title { font-size: 12px; font-weight: 700; color: var(--text-2); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
147
+ .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
148
+ .form-field { display: flex; flex-direction: column; gap: 6px; }
149
+ .form-field.full { grid-column: 1 / -1; }
150
+
151
+ label { font-family: var(--mono); font-size: 11px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.5px; font-weight: 500; }
152
+
153
+ input, select {
154
+ background: var(--surface);
155
+ border: 1px solid var(--border);
156
+ color: var(--text-1);
157
+ padding: 10px 14px;
158
+ border-radius: var(--radius);
159
+ font-family: var(--mono);
160
+ font-size: 12px;
161
+ transition: all 0.15s ease;
162
+ }
163
+ input:focus, select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
164
+
165
+ input[type="checkbox"] { width: 16px; height: 16px; margin: 0; cursor: pointer; }
166
+ .checkbox-label { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 12px; color: var(--text-2); text-transform: none; letter-spacing: normal; }
167
+
168
+ .actions { display: flex; gap: 12px; padding: 20px 24px; background: var(--surface); border-top: 1px solid var(--border); }
169
+
170
+ .message { font-family: var(--mono); font-size: 11px; padding: 8px 12px; border-radius: var(--radius); margin-left: auto; }
171
+ .message.success { background: var(--green-lo); color: var(--green); border: 1px solid rgba(34,197,94,0.2); }
172
+ .message.error { background: var(--red-lo); color: var(--red); border: 1px solid rgba(239,68,68,0.2); }
173
+
174
+ .code-block { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; font-family: var(--mono); font-size: 11px; color: var(--text-2); overflow-x: auto; }
175
+ .code-block pre { margin: 0; line-height: 1.6; }
176
+
177
+ @media (max-width: 900px) {
178
+ .shell { grid-template-columns: 1fr; }
179
+ aside { display: none; }
180
+ .form-row { grid-template-columns: 1fr; }
181
+ }
182
+ </style>
183
+ </head>
184
+ <body>
185
+
186
+ <div class="shell">
187
+ <aside>
188
+ <div class="logo">
189
+ <div class="logo-mark">\u{1F52C}</div>
190
+ <div>
191
+ <div class="logo-name">OpenQA</div>
192
+ <div class="logo-version">v2.1.0 \xB7 OSS</div>
193
+ </div>
194
+ </div>
195
+
196
+ <div class="nav-section">
197
+ <div class="nav-label">Overview</div>
198
+ <a class="nav-item" href="/"><span class="icon">\u25A6</span> Dashboard</a>
199
+ <a class="nav-item" href="/kanban"><span class="icon">\u229E</span> Kanban</a>
200
+ <div class="nav-label">System</div>
201
+ <a class="nav-item active" href="/config"><span class="icon">\u2699</span> Configuration</a>
202
+ </div>
203
+
204
+ <div class="sidebar-footer">
205
+ <div class="status-pill"><div class="dot"></div><span>System Ready</span></div>
206
+ </div>
207
+ </aside>
208
+
209
+ <main>
210
+ <div class="topbar">
211
+ <div>
212
+ <div class="page-title">Configuration</div>
213
+ <div class="page-breadcrumb">openqa / system / settings</div>
214
+ </div>
215
+ <div class="topbar-actions">
216
+ <button class="btn-sm btn-ghost" onclick="exportConfig()">Export Config</button>
217
+ <button class="btn-sm btn-ghost" onclick="importConfig()">Import Config</button>
218
+ <button class="btn-sm btn-primary" onclick="saveAllConfig()">Save All</button>
219
+ </div>
220
+ </div>
221
+
222
+ <div class="content">
223
+ <!-- SaaS Configuration -->
224
+ <div class="panel">
225
+ <div class="panel-head"><span class="panel-title">\u{1F310} SaaS Target Configuration</span></div>
226
+ <div class="panel-body">
227
+ <form class="form-grid" id="saas-form">
228
+ <div class="form-section">
229
+ <div class="form-section-title">Target Application</div>
230
+ <div class="form-field full">
231
+ <label>Application URL</label>
232
+ <input type="url" id="saas_url" name="saas.url" value="${cfg.saas?.url || ""}" placeholder="https://your-app.com">
233
+ </div>
234
+ <div class="form-row">
235
+ <div class="form-field">
236
+ <label>Authentication Type</label>
237
+ <select id="saas_authType" name="saas.authType">
238
+ <option value="none" ${cfg.saas?.authType === "none" ? "selected" : ""}>None</option>
239
+ <option value="basic" ${cfg.saas?.authType === "basic" ? "selected" : ""}>Basic Auth</option>
240
+ <option value="bearer" ${cfg.saas?.authType === "bearer" ? "selected" : ""}>Bearer Token</option>
241
+ <option value="session" ${cfg.saas?.authType === "session" ? "selected" : ""}>Session</option>
242
+ </select>
243
+ </div>
244
+ <div class="form-field">
245
+ <label>Timeout (seconds)</label>
246
+ <input type="number" id="saas_timeout" name="saas.timeout" value="30" min="5" max="300">
247
+ </div>
248
+ </div>
249
+ <div class="form-row">
250
+ <div class="form-field">
251
+ <label>Username</label>
252
+ <input type="text" id="saas_username" name="saas.username" value="${cfg.saas?.username || ""}" placeholder="username">
253
+ </div>
254
+ <div class="form-field">
255
+ <label>Password</label>
256
+ <input type="password" id="saas_password" name="saas.password" value="${cfg.saas?.password || ""}" placeholder="password">
257
+ </div>
258
+ </div>
259
+ </div>
260
+ </form>
261
+ </div>
262
+ </div>
263
+
264
+ <!-- LLM Configuration -->
265
+ <div class="panel">
266
+ <div class="panel-head"><span class="panel-title">\u{1F916} LLM Configuration</span></div>
267
+ <div class="panel-body">
268
+ <form class="form-grid" id="llm-form">
269
+ <div class="form-section">
270
+ <div class="form-section-title">Language Model Provider</div>
271
+ <div class="form-row">
272
+ <div class="form-field">
273
+ <label>Provider</label>
274
+ <select id="llm_provider" name="llm.provider">
275
+ <option value="openai" ${cfg.llm?.provider === "openai" ? "selected" : ""}>OpenAI</option>
276
+ <option value="anthropic" ${cfg.llm?.provider === "anthropic" ? "selected" : ""}>Anthropic</option>
277
+ <option value="ollama" ${cfg.llm?.provider === "ollama" ? "selected" : ""}>Ollama</option>
278
+ </select>
279
+ </div>
280
+ <div class="form-field">
281
+ <label>Model</label>
282
+ <input type="text" id="llm_model" name="llm.model" value="${cfg.llm?.model || ""}" placeholder="gpt-4, claude-3-sonnet, etc.">
283
+ </div>
284
+ </div>
285
+ <div class="form-field full">
286
+ <label>API Key</label>
287
+ <input type="password" id="llm_apiKey" name="llm.apiKey" value="${cfg.llm?.apiKey || ""}" placeholder="Your API key">
288
+ </div>
289
+ <div class="form-field full">
290
+ <label>Base URL (for Ollama)</label>
291
+ <input type="url" id="llm_baseUrl" name="llm.baseUrl" value="${cfg.llm?.baseUrl || ""}" placeholder="http://localhost:11434">
292
+ </div>
293
+ </div>
294
+ </form>
295
+ </div>
296
+ </div>
297
+
298
+ <!-- Agent Configuration -->
299
+ <div class="panel">
300
+ <div class="panel-head"><span class="panel-title">\u{1F3AF} Agent Settings</span></div>
301
+ <div class="panel-body">
302
+ <form class="form-grid" id="agent-form">
303
+ <div class="form-section">
304
+ <div class="form-section-title">Agent Behavior</div>
305
+ <div class="form-field">
306
+ <label class="checkbox-label">
307
+ <input type="checkbox" id="agent_autoStart" name="agent.autoStart" ${cfg.agent?.autoStart ? "checked" : ""}>
308
+ Auto-start on launch
309
+ </label>
310
+ </div>
311
+ <div class="form-row">
312
+ <div class="form-field">
313
+ <label>Check Interval (ms)</label>
314
+ <input type="number" id="agent_intervalMs" name="agent.intervalMs" value="${cfg.agent?.intervalMs || 36e5}" min="60000" step="60000">
315
+ </div>
316
+ <div class="form-field">
317
+ <label>Max Iterations</label>
318
+ <input type="number" id="agent_maxIterations" name="agent.maxIterations" value="${cfg.agent?.maxIterations || 10}" min="1" max="100">
319
+ </div>
320
+ </div>
321
+ </div>
322
+ </form>
323
+ </div>
324
+ </div>
325
+
326
+ <div class="actions">
327
+ <button class="btn-sm btn-ghost" onclick="testConnection()">Test Connection</button>
328
+ <button class="btn-sm btn-ghost" onclick="resetConfig()">Reset to Defaults</button>
329
+ <div id="message"></div>
330
+ </div>
331
+ </div>
332
+ </main>
333
+ </div>
334
+
335
+ <script>
336
+ async function saveAllConfig() {
337
+ const forms = ['saas-form', 'llm-form', 'agent-form'];
338
+ const config = {};
339
+
340
+ for (const formId of forms) {
341
+ const form = document.getElementById(formId);
342
+ const formData = new FormData(form);
343
+
344
+ for (let [key, value] of formData.entries()) {
345
+ if (value === '') continue;
346
+ const keys = key.split('.');
347
+ let obj = config;
348
+ for (let i = 0; i < keys.length - 1; i++) {
349
+ if (!obj[keys[i]]) obj[keys[i]] = {};
350
+ obj = obj[keys[i]];
351
+ }
352
+ if (key.includes('autoStart')) {
353
+ obj[keys[keys.length - 1]] = value === 'on';
354
+ } else if (key.includes('intervalMs') || key.includes('maxIterations') || key.includes('timeout')) {
355
+ obj[keys[keys.length - 1]] = parseInt(value);
356
+ } else {
357
+ obj[keys[keys.length - 1]] = value;
358
+ }
359
+ }
360
+ }
361
+
362
+ try {
363
+ const response = await fetch('/api/config', {
364
+ method: 'POST',
365
+ headers: { 'Content-Type': 'application/json' },
366
+ credentials: 'include',
367
+ body: JSON.stringify(config)
368
+ });
369
+ showMessage(response.ok ? 'Configuration saved!' : 'Failed to save', response.ok ? 'success' : 'error');
370
+ } catch (error) {
371
+ showMessage('Error: ' + error.message, 'error');
372
+ }
373
+ }
374
+
375
+ async function testConnection() {
376
+ showMessage('Testing connection\u2026', 'success');
377
+ // Read the target URL from the saas.url field if present
378
+ const urlField = document.querySelector('[name="saas.url"]') || document.querySelector('[data-key="saas.url"]');
379
+ const url = urlField ? urlField.value : '';
380
+ if (!url) {
381
+ showMessage('Enter a target URL first', 'error');
382
+ return;
383
+ }
384
+ try {
385
+ const response = await fetch('/api/test-connection', {
386
+ method: 'POST',
387
+ headers: { 'Content-Type': 'application/json' },
388
+ credentials: 'include',
389
+ body: JSON.stringify({ url })
390
+ });
391
+ const result = await response.json();
392
+ showMessage(
393
+ result.success ? \`Connection successful (HTTP \${result.status})\` : \`Connection failed: \${result.error || 'unreachable'}\`,
394
+ result.success ? 'success' : 'error'
395
+ );
396
+ } catch (error) {
397
+ showMessage('Error: ' + error.message, 'error');
398
+ }
399
+ }
400
+
401
+ async function exportConfig() {
402
+ const response = await fetch('/api/config', { credentials: 'include' });
403
+ const config = await response.json();
404
+ const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
405
+ const a = document.createElement('a');
406
+ a.href = URL.createObjectURL(blob);
407
+ a.download = 'openqa-config.json';
408
+ a.click();
409
+ }
410
+
411
+ function importConfig() {
412
+ const input = document.createElement('input');
413
+ input.type = 'file';
414
+ input.accept = '.json';
415
+ input.onchange = async (e) => {
416
+ const file = e.target.files[0];
417
+ const text = await file.text();
418
+ try {
419
+ const config = JSON.parse(text);
420
+ await fetch('/api/config', {
421
+ method: 'POST',
422
+ headers: { 'Content-Type': 'application/json' },
423
+ credentials: 'include',
424
+ body: JSON.stringify(config)
425
+ });
426
+ location.reload();
427
+ } catch (err) {
428
+ showMessage('Invalid config file', 'error');
429
+ }
430
+ };
431
+ input.click();
432
+ }
433
+
434
+ async function resetConfig() {
435
+ if (confirm('Reset all configuration to defaults?')) {
436
+ await fetch('/api/config/reset', { method: 'POST', credentials: 'include' });
437
+ location.reload();
438
+ }
439
+ }
440
+
441
+ function showMessage(msg, type) {
442
+ const el = document.getElementById('message');
443
+ el.textContent = msg;
444
+ el.className = 'message ' + type;
445
+ setTimeout(() => { el.textContent = ''; el.className = ''; }, 5000);
446
+ }
447
+ </script>
448
+
449
+ </body>
450
+ </html>`;
451
+ }
452
+ export {
453
+ getConfigHTML
454
+ };