@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.
- package/README.md +203 -6
- package/dist/agent/brain/diff-analyzer.js +140 -0
- package/dist/agent/brain/diff-analyzer.js.map +1 -0
- package/dist/agent/brain/llm-cache.js +47 -0
- package/dist/agent/brain/llm-cache.js.map +1 -0
- package/dist/agent/brain/llm-resilience.js +252 -0
- package/dist/agent/brain/llm-resilience.js.map +1 -0
- package/dist/agent/config/index.js +588 -0
- package/dist/agent/config/index.js.map +1 -0
- package/dist/agent/coverage/index.js +74 -0
- package/dist/agent/coverage/index.js.map +1 -0
- package/dist/agent/export/index.js +158 -0
- package/dist/agent/export/index.js.map +1 -0
- package/dist/agent/index-v2.js +2795 -0
- package/dist/agent/index-v2.js.map +1 -0
- package/dist/agent/index.js +369 -105
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/logger.js +41 -0
- package/dist/agent/logger.js.map +1 -0
- package/dist/agent/metrics.js +39 -0
- package/dist/agent/metrics.js.map +1 -0
- package/dist/agent/notifications/index.js +106 -0
- package/dist/agent/notifications/index.js.map +1 -0
- package/dist/agent/openapi/spec.js +338 -0
- package/dist/agent/openapi/spec.js.map +1 -0
- package/dist/agent/tools/project-runner.js +481 -0
- package/dist/agent/tools/project-runner.js.map +1 -0
- package/dist/cli/config.html.js +454 -0
- package/dist/cli/daemon.js +8810 -0
- package/dist/cli/dashboard.html.js +1622 -0
- package/dist/cli/env-config.js +391 -0
- package/dist/cli/env-routes.js +820 -0
- package/dist/cli/env.html.js +679 -0
- package/dist/cli/index.js +5980 -1896
- package/dist/cli/kanban.html.js +577 -0
- package/dist/cli/routes.js +895 -0
- package/dist/cli/routes.js.map +1 -0
- package/dist/cli/server.js +5855 -1860
- package/dist/database/index.js +485 -60
- package/dist/database/index.js.map +1 -1
- package/dist/database/sqlite.js +281 -0
- package/dist/database/sqlite.js.map +1 -0
- package/install.sh +19 -10
- package/package.json +19 -5
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
// cli/env-config.ts
|
|
2
|
+
var ENV_VARIABLES = [
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// LLM CONFIGURATION
|
|
5
|
+
// ============================================================================
|
|
6
|
+
{
|
|
7
|
+
key: "LLM_PROVIDER",
|
|
8
|
+
type: "select",
|
|
9
|
+
category: "llm",
|
|
10
|
+
required: true,
|
|
11
|
+
description: "LLM provider to use for AI operations",
|
|
12
|
+
options: ["openai", "anthropic", "ollama"],
|
|
13
|
+
placeholder: "openai",
|
|
14
|
+
restartRequired: true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: "OPENAI_API_KEY",
|
|
18
|
+
type: "password",
|
|
19
|
+
category: "llm",
|
|
20
|
+
required: false,
|
|
21
|
+
description: "OpenAI API key (required if LLM_PROVIDER=openai)",
|
|
22
|
+
placeholder: "sk-...",
|
|
23
|
+
sensitive: true,
|
|
24
|
+
testable: true,
|
|
25
|
+
validation: (value) => {
|
|
26
|
+
if (!value) return { valid: true };
|
|
27
|
+
if (!value.startsWith("sk-")) {
|
|
28
|
+
return { valid: false, error: 'OpenAI API key must start with "sk-"' };
|
|
29
|
+
}
|
|
30
|
+
if (value.length < 20) {
|
|
31
|
+
return { valid: false, error: "API key seems too short" };
|
|
32
|
+
}
|
|
33
|
+
return { valid: true };
|
|
34
|
+
},
|
|
35
|
+
restartRequired: true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: "ANTHROPIC_API_KEY",
|
|
39
|
+
type: "password",
|
|
40
|
+
category: "llm",
|
|
41
|
+
required: false,
|
|
42
|
+
description: "Anthropic API key (required if LLM_PROVIDER=anthropic)",
|
|
43
|
+
placeholder: "sk-ant-...",
|
|
44
|
+
sensitive: true,
|
|
45
|
+
testable: true,
|
|
46
|
+
validation: (value) => {
|
|
47
|
+
if (!value) return { valid: true };
|
|
48
|
+
if (!value.startsWith("sk-ant-")) {
|
|
49
|
+
return { valid: false, error: 'Anthropic API key must start with "sk-ant-"' };
|
|
50
|
+
}
|
|
51
|
+
return { valid: true };
|
|
52
|
+
},
|
|
53
|
+
restartRequired: true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: "OLLAMA_BASE_URL",
|
|
57
|
+
type: "url",
|
|
58
|
+
category: "llm",
|
|
59
|
+
required: false,
|
|
60
|
+
description: "Ollama server URL (required if LLM_PROVIDER=ollama)",
|
|
61
|
+
placeholder: "http://localhost:11434",
|
|
62
|
+
testable: true,
|
|
63
|
+
validation: (value) => {
|
|
64
|
+
if (!value) return { valid: true };
|
|
65
|
+
try {
|
|
66
|
+
new URL(value);
|
|
67
|
+
return { valid: true };
|
|
68
|
+
} catch {
|
|
69
|
+
return { valid: false, error: "Invalid URL format" };
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
restartRequired: true
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: "LLM_MODEL",
|
|
76
|
+
type: "text",
|
|
77
|
+
category: "llm",
|
|
78
|
+
required: false,
|
|
79
|
+
description: "LLM model to use (e.g., gpt-4, claude-3-opus, llama2)",
|
|
80
|
+
placeholder: "gpt-4",
|
|
81
|
+
restartRequired: true
|
|
82
|
+
},
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// SECURITY
|
|
85
|
+
// ============================================================================
|
|
86
|
+
{
|
|
87
|
+
key: "OPENQA_JWT_SECRET",
|
|
88
|
+
type: "password",
|
|
89
|
+
category: "security",
|
|
90
|
+
required: true,
|
|
91
|
+
description: "Secret key for JWT token signing (min 32 characters)",
|
|
92
|
+
placeholder: "Generate with: openssl rand -hex 32",
|
|
93
|
+
sensitive: true,
|
|
94
|
+
validation: (value) => {
|
|
95
|
+
if (!value) return { valid: false, error: "JWT secret is required" };
|
|
96
|
+
if (value.length < 32) {
|
|
97
|
+
return { valid: false, error: "JWT secret must be at least 32 characters" };
|
|
98
|
+
}
|
|
99
|
+
return { valid: true };
|
|
100
|
+
},
|
|
101
|
+
restartRequired: true
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: "OPENQA_AUTH_DISABLED",
|
|
105
|
+
type: "boolean",
|
|
106
|
+
category: "security",
|
|
107
|
+
required: false,
|
|
108
|
+
description: "\u26A0\uFE0F DANGER: Disable authentication (NEVER use in production!)",
|
|
109
|
+
placeholder: "false",
|
|
110
|
+
validation: (value) => {
|
|
111
|
+
if (value === "true" && process.env.NODE_ENV === "production") {
|
|
112
|
+
return { valid: false, error: "Cannot disable auth in production!" };
|
|
113
|
+
}
|
|
114
|
+
return { valid: true };
|
|
115
|
+
},
|
|
116
|
+
restartRequired: true
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
key: "NODE_ENV",
|
|
120
|
+
type: "select",
|
|
121
|
+
category: "security",
|
|
122
|
+
required: false,
|
|
123
|
+
description: "Node environment (production enables security features)",
|
|
124
|
+
options: ["development", "production", "test"],
|
|
125
|
+
placeholder: "production",
|
|
126
|
+
restartRequired: true
|
|
127
|
+
},
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// TARGET APPLICATION
|
|
130
|
+
// ============================================================================
|
|
131
|
+
{
|
|
132
|
+
key: "SAAS_URL",
|
|
133
|
+
type: "url",
|
|
134
|
+
category: "target",
|
|
135
|
+
required: true,
|
|
136
|
+
description: "URL of the application to test",
|
|
137
|
+
placeholder: "https://your-app.com",
|
|
138
|
+
testable: true,
|
|
139
|
+
validation: (value) => {
|
|
140
|
+
if (!value) return { valid: false, error: "Target URL is required" };
|
|
141
|
+
try {
|
|
142
|
+
const url = new URL(value);
|
|
143
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
144
|
+
return { valid: false, error: "URL must use http or https protocol" };
|
|
145
|
+
}
|
|
146
|
+
return { valid: true };
|
|
147
|
+
} catch {
|
|
148
|
+
return { valid: false, error: "Invalid URL format" };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
key: "SAAS_AUTH_TYPE",
|
|
154
|
+
type: "select",
|
|
155
|
+
category: "target",
|
|
156
|
+
required: false,
|
|
157
|
+
description: "Authentication type for target application",
|
|
158
|
+
options: ["none", "basic", "session"],
|
|
159
|
+
placeholder: "none"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: "SAAS_USERNAME",
|
|
163
|
+
type: "text",
|
|
164
|
+
category: "target",
|
|
165
|
+
required: false,
|
|
166
|
+
description: "Username for target application authentication",
|
|
167
|
+
placeholder: "test@example.com"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
key: "SAAS_PASSWORD",
|
|
171
|
+
type: "password",
|
|
172
|
+
category: "target",
|
|
173
|
+
required: false,
|
|
174
|
+
description: "Password for target application authentication",
|
|
175
|
+
placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
176
|
+
sensitive: true
|
|
177
|
+
},
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// GITHUB INTEGRATION
|
|
180
|
+
// ============================================================================
|
|
181
|
+
{
|
|
182
|
+
key: "GITHUB_TOKEN",
|
|
183
|
+
type: "password",
|
|
184
|
+
category: "github",
|
|
185
|
+
required: false,
|
|
186
|
+
description: "GitHub personal access token for issue creation",
|
|
187
|
+
placeholder: "ghp_...",
|
|
188
|
+
sensitive: true,
|
|
189
|
+
testable: true,
|
|
190
|
+
validation: (value) => {
|
|
191
|
+
if (!value) return { valid: true };
|
|
192
|
+
if (!value.startsWith("ghp_") && !value.startsWith("github_pat_")) {
|
|
193
|
+
return { valid: false, error: 'GitHub token must start with "ghp_" or "github_pat_"' };
|
|
194
|
+
}
|
|
195
|
+
return { valid: true };
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
key: "GITHUB_OWNER",
|
|
200
|
+
type: "text",
|
|
201
|
+
category: "github",
|
|
202
|
+
required: false,
|
|
203
|
+
description: "GitHub repository owner/organization",
|
|
204
|
+
placeholder: "your-username"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
key: "GITHUB_REPO",
|
|
208
|
+
type: "text",
|
|
209
|
+
category: "github",
|
|
210
|
+
required: false,
|
|
211
|
+
description: "GitHub repository name",
|
|
212
|
+
placeholder: "your-repo"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
key: "GITHUB_BRANCH",
|
|
216
|
+
type: "text",
|
|
217
|
+
category: "github",
|
|
218
|
+
required: false,
|
|
219
|
+
description: "GitHub branch to monitor",
|
|
220
|
+
placeholder: "main"
|
|
221
|
+
},
|
|
222
|
+
// ============================================================================
|
|
223
|
+
// WEB SERVER
|
|
224
|
+
// ============================================================================
|
|
225
|
+
{
|
|
226
|
+
key: "WEB_PORT",
|
|
227
|
+
type: "number",
|
|
228
|
+
category: "web",
|
|
229
|
+
required: false,
|
|
230
|
+
description: "Port for web server",
|
|
231
|
+
placeholder: "4242",
|
|
232
|
+
validation: (value) => {
|
|
233
|
+
if (!value) return { valid: true };
|
|
234
|
+
const port = parseInt(value, 10);
|
|
235
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
236
|
+
return { valid: false, error: "Port must be between 1 and 65535" };
|
|
237
|
+
}
|
|
238
|
+
return { valid: true };
|
|
239
|
+
},
|
|
240
|
+
restartRequired: true
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
key: "WEB_HOST",
|
|
244
|
+
type: "text",
|
|
245
|
+
category: "web",
|
|
246
|
+
required: false,
|
|
247
|
+
description: "Host to bind web server (0.0.0.0 for all interfaces)",
|
|
248
|
+
placeholder: "0.0.0.0",
|
|
249
|
+
restartRequired: true
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
key: "CORS_ORIGINS",
|
|
253
|
+
type: "text",
|
|
254
|
+
category: "web",
|
|
255
|
+
required: false,
|
|
256
|
+
description: "Allowed CORS origins (comma-separated)",
|
|
257
|
+
placeholder: "https://your-domain.com,https://app.example.com",
|
|
258
|
+
restartRequired: true
|
|
259
|
+
},
|
|
260
|
+
// ============================================================================
|
|
261
|
+
// AGENT CONFIGURATION
|
|
262
|
+
// ============================================================================
|
|
263
|
+
{
|
|
264
|
+
key: "AGENT_AUTO_START",
|
|
265
|
+
type: "boolean",
|
|
266
|
+
category: "agent",
|
|
267
|
+
required: false,
|
|
268
|
+
description: "Auto-start agent on server launch",
|
|
269
|
+
placeholder: "false"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
key: "AGENT_INTERVAL_MS",
|
|
273
|
+
type: "number",
|
|
274
|
+
category: "agent",
|
|
275
|
+
required: false,
|
|
276
|
+
description: "Agent run interval in milliseconds (1 hour = 3600000)",
|
|
277
|
+
placeholder: "3600000",
|
|
278
|
+
validation: (value) => {
|
|
279
|
+
if (!value) return { valid: true };
|
|
280
|
+
const interval = parseInt(value, 10);
|
|
281
|
+
if (isNaN(interval) || interval < 6e4) {
|
|
282
|
+
return { valid: false, error: "Interval must be at least 60000ms (1 minute)" };
|
|
283
|
+
}
|
|
284
|
+
return { valid: true };
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
key: "AGENT_MAX_ITERATIONS",
|
|
289
|
+
type: "number",
|
|
290
|
+
category: "agent",
|
|
291
|
+
required: false,
|
|
292
|
+
description: "Maximum iterations per agent session",
|
|
293
|
+
placeholder: "20",
|
|
294
|
+
validation: (value) => {
|
|
295
|
+
if (!value) return { valid: true };
|
|
296
|
+
const max = parseInt(value, 10);
|
|
297
|
+
if (isNaN(max) || max < 1 || max > 1e3) {
|
|
298
|
+
return { valid: false, error: "Max iterations must be between 1 and 1000" };
|
|
299
|
+
}
|
|
300
|
+
return { valid: true };
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
key: "GIT_LISTENER_ENABLED",
|
|
305
|
+
type: "boolean",
|
|
306
|
+
category: "agent",
|
|
307
|
+
required: false,
|
|
308
|
+
description: "Enable git merge/pipeline detection",
|
|
309
|
+
placeholder: "true"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
key: "GIT_POLL_INTERVAL_MS",
|
|
313
|
+
type: "number",
|
|
314
|
+
category: "agent",
|
|
315
|
+
required: false,
|
|
316
|
+
description: "Git polling interval in milliseconds",
|
|
317
|
+
placeholder: "60000"
|
|
318
|
+
},
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// DATABASE
|
|
321
|
+
// ============================================================================
|
|
322
|
+
{
|
|
323
|
+
key: "DB_PATH",
|
|
324
|
+
type: "text",
|
|
325
|
+
category: "database",
|
|
326
|
+
required: false,
|
|
327
|
+
description: "Path to SQLite database file",
|
|
328
|
+
placeholder: "./data/openqa.db",
|
|
329
|
+
restartRequired: true
|
|
330
|
+
},
|
|
331
|
+
// ============================================================================
|
|
332
|
+
// NOTIFICATIONS
|
|
333
|
+
// ============================================================================
|
|
334
|
+
{
|
|
335
|
+
key: "SLACK_WEBHOOK_URL",
|
|
336
|
+
type: "url",
|
|
337
|
+
category: "notifications",
|
|
338
|
+
required: false,
|
|
339
|
+
description: "Slack webhook URL for notifications",
|
|
340
|
+
placeholder: "https://hooks.slack.com/services/...",
|
|
341
|
+
sensitive: true,
|
|
342
|
+
testable: true,
|
|
343
|
+
validation: (value) => {
|
|
344
|
+
if (!value) return { valid: true };
|
|
345
|
+
if (!value.startsWith("https://hooks.slack.com/")) {
|
|
346
|
+
return { valid: false, error: "Invalid Slack webhook URL" };
|
|
347
|
+
}
|
|
348
|
+
return { valid: true };
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
key: "DISCORD_WEBHOOK_URL",
|
|
353
|
+
type: "url",
|
|
354
|
+
category: "notifications",
|
|
355
|
+
required: false,
|
|
356
|
+
description: "Discord webhook URL for notifications",
|
|
357
|
+
placeholder: "https://discord.com/api/webhooks/...",
|
|
358
|
+
sensitive: true,
|
|
359
|
+
testable: true,
|
|
360
|
+
validation: (value) => {
|
|
361
|
+
if (!value) return { valid: true };
|
|
362
|
+
if (!value.startsWith("https://discord.com/api/webhooks/")) {
|
|
363
|
+
return { valid: false, error: "Invalid Discord webhook URL" };
|
|
364
|
+
}
|
|
365
|
+
return { valid: true };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
function getEnvVariablesByCategory(category) {
|
|
370
|
+
return ENV_VARIABLES.filter((v) => v.category === category);
|
|
371
|
+
}
|
|
372
|
+
function getEnvVariable(key) {
|
|
373
|
+
return ENV_VARIABLES.find((v) => v.key === key);
|
|
374
|
+
}
|
|
375
|
+
function validateEnvValue(key, value) {
|
|
376
|
+
const envVar = getEnvVariable(key);
|
|
377
|
+
if (!envVar) return { valid: false, error: "Unknown environment variable" };
|
|
378
|
+
if (envVar.required && !value) {
|
|
379
|
+
return { valid: false, error: "This field is required" };
|
|
380
|
+
}
|
|
381
|
+
if (envVar.validation) {
|
|
382
|
+
return envVar.validation(value);
|
|
383
|
+
}
|
|
384
|
+
return { valid: true };
|
|
385
|
+
}
|
|
386
|
+
export {
|
|
387
|
+
ENV_VARIABLES,
|
|
388
|
+
getEnvVariable,
|
|
389
|
+
getEnvVariablesByCategory,
|
|
390
|
+
validateEnvValue
|
|
391
|
+
};
|