@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,588 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res) => function __init() {
3
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ };
5
+
6
+ // node_modules/tsup/assets/esm_shims.js
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
9
+ var init_esm_shims = __esm({
10
+ "node_modules/tsup/assets/esm_shims.js"() {
11
+ "use strict";
12
+ }
13
+ });
14
+
15
+ // database/sqlite.ts
16
+ import Database from "better-sqlite3";
17
+ var init_sqlite = __esm({
18
+ "database/sqlite.ts"() {
19
+ "use strict";
20
+ init_esm_shims();
21
+ }
22
+ });
23
+
24
+ // agent/config/index.ts
25
+ init_esm_shims();
26
+ import { config as dotenvConfig } from "dotenv";
27
+
28
+ // database/index.ts
29
+ init_esm_shims();
30
+ init_sqlite();
31
+ import { Low } from "lowdb";
32
+ import { JSONFile } from "lowdb/node";
33
+ import { dirname } from "path";
34
+ import { fileURLToPath as fileURLToPath2 } from "url";
35
+ import { mkdirSync } from "fs";
36
+ var __filename2 = fileURLToPath2(import.meta.url);
37
+ var __dirname2 = dirname(__filename2);
38
+ var OpenQADatabase = class {
39
+ constructor(dbPath = "./data/openqa.json") {
40
+ this.dbPath = dbPath;
41
+ this.initialize();
42
+ }
43
+ db = null;
44
+ initialize() {
45
+ const dir = dirname(this.dbPath);
46
+ mkdirSync(dir, { recursive: true });
47
+ const adapter = new JSONFile(this.dbPath);
48
+ this.db = new Low(adapter, {
49
+ config: {},
50
+ test_sessions: [],
51
+ actions: [],
52
+ bugs: [],
53
+ kanban_tickets: [],
54
+ users: []
55
+ });
56
+ this.db.read();
57
+ if (!this.db.data) {
58
+ this.db.data = {
59
+ config: {},
60
+ test_sessions: [],
61
+ actions: [],
62
+ bugs: [],
63
+ kanban_tickets: [],
64
+ users: []
65
+ };
66
+ this.db.write();
67
+ }
68
+ }
69
+ async ensureInitialized() {
70
+ if (!this.db) {
71
+ this.initialize();
72
+ }
73
+ await this.db.read();
74
+ let migrated = false;
75
+ if (!this.db.data.users) {
76
+ this.db.data.users = [];
77
+ migrated = true;
78
+ }
79
+ if (migrated) await this.db.write();
80
+ }
81
+ async getConfig(key) {
82
+ await this.ensureInitialized();
83
+ return this.db.data.config[key] || null;
84
+ }
85
+ async setConfig(key, value) {
86
+ await this.ensureInitialized();
87
+ this.db.data.config[key] = value;
88
+ await this.db.write();
89
+ }
90
+ async getAllConfig() {
91
+ await this.ensureInitialized();
92
+ return this.db.data.config;
93
+ }
94
+ async createSession(id, metadata) {
95
+ await this.ensureInitialized();
96
+ const session = {
97
+ id,
98
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
99
+ status: "running",
100
+ total_actions: 0,
101
+ bugs_found: 0,
102
+ metadata: metadata ? JSON.stringify(metadata) : void 0
103
+ };
104
+ this.db.data.test_sessions.push(session);
105
+ await this.db.write();
106
+ return session;
107
+ }
108
+ async getSession(id) {
109
+ await this.ensureInitialized();
110
+ return this.db.data.test_sessions.find((s) => s.id === id) || null;
111
+ }
112
+ async updateSession(id, updates) {
113
+ await this.ensureInitialized();
114
+ const index = this.db.data.test_sessions.findIndex((s) => s.id === id);
115
+ if (index !== -1) {
116
+ this.db.data.test_sessions[index] = { ...this.db.data.test_sessions[index], ...updates };
117
+ await this.db.write();
118
+ }
119
+ }
120
+ async getRecentSessions(limit = 10) {
121
+ await this.ensureInitialized();
122
+ return this.db.data.test_sessions.sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime()).slice(0, limit);
123
+ }
124
+ async createAction(action) {
125
+ await this.ensureInitialized();
126
+ const newAction = {
127
+ id: `action_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
128
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
129
+ ...action
130
+ };
131
+ this.db.data.actions.push(newAction);
132
+ await this.db.write();
133
+ return newAction;
134
+ }
135
+ async getSessionActions(sessionId) {
136
+ await this.ensureInitialized();
137
+ return this.db.data.actions.filter((a) => a.session_id === sessionId).sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
138
+ }
139
+ async createBug(bug) {
140
+ await this.ensureInitialized();
141
+ const newBug = {
142
+ id: `bug_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
143
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
144
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
145
+ ...bug
146
+ };
147
+ this.db.data.bugs.push(newBug);
148
+ await this.db.write();
149
+ return newBug;
150
+ }
151
+ async updateBug(id, updates) {
152
+ await this.ensureInitialized();
153
+ const index = this.db.data.bugs.findIndex((b) => b.id === id);
154
+ if (index !== -1) {
155
+ this.db.data.bugs[index] = {
156
+ ...this.db.data.bugs[index],
157
+ ...updates,
158
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
159
+ };
160
+ await this.db.write();
161
+ }
162
+ }
163
+ async getAllBugs() {
164
+ await this.ensureInitialized();
165
+ return this.db.data.bugs.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
166
+ }
167
+ async getBugsByStatus(status) {
168
+ await this.ensureInitialized();
169
+ return this.db.data.bugs.filter((b) => b.status === status).sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
170
+ }
171
+ async createKanbanTicket(ticket) {
172
+ await this.ensureInitialized();
173
+ const newTicket = {
174
+ id: `ticket_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
175
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
176
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
177
+ ...ticket
178
+ };
179
+ this.db.data.kanban_tickets.push(newTicket);
180
+ await this.db.write();
181
+ return newTicket;
182
+ }
183
+ async updateKanbanTicket(id, updates) {
184
+ await this.ensureInitialized();
185
+ const index = this.db.data.kanban_tickets.findIndex((t) => t.id === id);
186
+ if (index !== -1) {
187
+ this.db.data.kanban_tickets[index] = {
188
+ ...this.db.data.kanban_tickets[index],
189
+ ...updates,
190
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
191
+ };
192
+ await this.db.write();
193
+ }
194
+ }
195
+ async getKanbanTickets() {
196
+ await this.ensureInitialized();
197
+ return this.db.data.kanban_tickets.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
198
+ }
199
+ async getKanbanTicketsByColumn(column) {
200
+ await this.ensureInitialized();
201
+ return this.db.data.kanban_tickets.filter((t) => t.column === column).sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
202
+ }
203
+ async deleteKanbanTicket(id) {
204
+ await this.ensureInitialized();
205
+ const index = this.db.data.kanban_tickets.findIndex((t) => t.id === id);
206
+ if (index !== -1) {
207
+ this.db.data.kanban_tickets.splice(index, 1);
208
+ await this.db.write();
209
+ }
210
+ }
211
+ async clearAllConfig() {
212
+ await this.ensureInitialized();
213
+ this.db.data.config = {};
214
+ await this.db.write();
215
+ }
216
+ // Get real data methods - connected to actual database records
217
+ async getActiveAgents() {
218
+ await this.ensureInitialized();
219
+ const sessions = await this.getRecentSessions(1);
220
+ const currentSession = sessions[0];
221
+ const isRunning = currentSession?.status === "running";
222
+ const totalActions = currentSession?.total_actions || 0;
223
+ const agents = [
224
+ {
225
+ name: "Main Agent",
226
+ status: isRunning ? "running" : "idle",
227
+ purpose: "Autonomous QA orchestration",
228
+ performance: totalActions > 0 ? Math.min(100, Math.round(totalActions / 100 * 100)) : 0,
229
+ tasks: totalActions
230
+ }
231
+ ];
232
+ if (currentSession && totalActions > 0) {
233
+ const actions = await this.getSessionActions(currentSession.id);
234
+ const actionTypes = actions.reduce((acc, action) => {
235
+ const type = action.type || "unknown";
236
+ acc[type] = (acc[type] || 0) + 1;
237
+ return acc;
238
+ }, {});
239
+ if (actionTypes["navigate"] || actionTypes["click"] || actionTypes["screenshot"]) {
240
+ agents.push({
241
+ name: "Browser Specialist",
242
+ status: isRunning ? "running" : "idle",
243
+ purpose: "UI navigation and interaction",
244
+ performance: Math.round(((actionTypes["navigate"] || 0) + (actionTypes["click"] || 0)) / totalActions * 100),
245
+ tasks: (actionTypes["navigate"] || 0) + (actionTypes["click"] || 0)
246
+ });
247
+ }
248
+ if (actionTypes["api_call"] || actionTypes["request"]) {
249
+ agents.push({
250
+ name: "API Tester",
251
+ status: isRunning ? "running" : "idle",
252
+ purpose: "API endpoint testing",
253
+ performance: Math.round((actionTypes["api_call"] || actionTypes["request"] || 0) / totalActions * 100),
254
+ tasks: actionTypes["api_call"] || actionTypes["request"] || 0
255
+ });
256
+ }
257
+ if (actionTypes["auth"] || actionTypes["login"]) {
258
+ agents.push({
259
+ name: "Auth Specialist",
260
+ status: isRunning ? "running" : "idle",
261
+ purpose: "Authentication testing",
262
+ performance: Math.round((actionTypes["auth"] || actionTypes["login"] || 0) / totalActions * 100),
263
+ tasks: actionTypes["auth"] || actionTypes["login"] || 0
264
+ });
265
+ }
266
+ }
267
+ return agents;
268
+ }
269
+ async getCurrentTasks() {
270
+ await this.ensureInitialized();
271
+ const sessions = await this.getRecentSessions(1);
272
+ const currentSession = sessions[0];
273
+ if (!currentSession) {
274
+ return [];
275
+ }
276
+ const actions = await this.getSessionActions(currentSession.id);
277
+ const recentActions = actions.slice(-10).reverse();
278
+ return recentActions.map((action, index) => ({
279
+ id: action.id,
280
+ name: action.type || "Unknown Action",
281
+ status: index === 0 && currentSession.status === "running" ? "running" : "completed",
282
+ progress: index === 0 && currentSession.status === "running" ? "65%" : "100%",
283
+ agent: "Main Agent",
284
+ started_at: action.timestamp,
285
+ result: action.output || action.description || "Completed"
286
+ }));
287
+ }
288
+ async getCurrentIssues() {
289
+ await this.ensureInitialized();
290
+ const bugs = await this.getAllBugs();
291
+ return bugs.slice(0, 10).map((bug) => ({
292
+ id: bug.id,
293
+ title: bug.title,
294
+ description: bug.description,
295
+ severity: bug.severity || "medium",
296
+ status: bug.status || "open",
297
+ discovered_at: bug.created_at,
298
+ agent: "Main Agent"
299
+ }));
300
+ }
301
+ async pruneOldSessions(maxAgeDays) {
302
+ await this.ensureInitialized();
303
+ const cutoff = new Date(Date.now() - maxAgeDays * 864e5).toISOString();
304
+ const oldSessions = this.db.data.test_sessions.filter((s) => s.started_at < cutoff);
305
+ const oldSessionIds = new Set(oldSessions.map((s) => s.id));
306
+ const actionsBefore = this.db.data.actions.length;
307
+ this.db.data.actions = this.db.data.actions.filter((a) => !oldSessionIds.has(a.session_id));
308
+ const actionsRemoved = actionsBefore - this.db.data.actions.length;
309
+ this.db.data.test_sessions = this.db.data.test_sessions.filter((s) => s.started_at >= cutoff);
310
+ await this.db.write();
311
+ return { sessionsRemoved: oldSessions.length, actionsRemoved };
312
+ }
313
+ async getStorageStats() {
314
+ await this.ensureInitialized();
315
+ return {
316
+ sessions: this.db.data.test_sessions.length,
317
+ actions: this.db.data.actions.length,
318
+ bugs: this.db.data.bugs.length,
319
+ tickets: this.db.data.kanban_tickets.length
320
+ };
321
+ }
322
+ // ── User management ──────────────────────────────────────────────────────────
323
+ async countUsers() {
324
+ await this.ensureInitialized();
325
+ return this.db.data.users.length;
326
+ }
327
+ async findUserByUsername(username) {
328
+ await this.ensureInitialized();
329
+ return this.db.data.users.find((u) => u.username === username) ?? null;
330
+ }
331
+ async getUserById(id) {
332
+ await this.ensureInitialized();
333
+ return this.db.data.users.find((u) => u.id === id) ?? null;
334
+ }
335
+ async getAllUsers() {
336
+ await this.ensureInitialized();
337
+ return [...this.db.data.users];
338
+ }
339
+ async createUser(data) {
340
+ await this.ensureInitialized();
341
+ const now = (/* @__PURE__ */ new Date()).toISOString();
342
+ const user = {
343
+ id: `user_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
344
+ username: data.username,
345
+ passwordHash: data.passwordHash,
346
+ role: data.role,
347
+ createdAt: now,
348
+ updatedAt: now
349
+ };
350
+ this.db.data.users.push(user);
351
+ await this.db.write();
352
+ return user;
353
+ }
354
+ async updateUser(id, updates) {
355
+ await this.ensureInitialized();
356
+ const idx = this.db.data.users.findIndex((u) => u.id === id);
357
+ if (idx !== -1) {
358
+ this.db.data.users[idx] = {
359
+ ...this.db.data.users[idx],
360
+ ...updates,
361
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
362
+ };
363
+ await this.db.write();
364
+ }
365
+ }
366
+ async deleteUser(id) {
367
+ await this.ensureInitialized();
368
+ const idx = this.db.data.users.findIndex((u) => u.id === id);
369
+ if (idx !== -1) {
370
+ this.db.data.users.splice(idx, 1);
371
+ await this.db.write();
372
+ }
373
+ }
374
+ async close() {
375
+ }
376
+ };
377
+
378
+ // agent/config/schema.ts
379
+ init_esm_shims();
380
+ import { z } from "zod";
381
+ var llmConfigSchema = z.object({
382
+ provider: z.enum(["openai", "anthropic", "ollama"]).default("openai"),
383
+ apiKey: z.string().optional(),
384
+ model: z.string().optional(),
385
+ baseUrl: z.string().url().optional()
386
+ });
387
+ var saasConfigSchema = z.object({
388
+ url: z.string().default(""),
389
+ authType: z.enum(["none", "basic", "bearer", "session"]).default("none"),
390
+ username: z.string().optional(),
391
+ password: z.string().optional()
392
+ });
393
+ var githubConfigSchema = z.object({
394
+ token: z.string().min(1, "GITHUB_TOKEN is required when GitHub is configured"),
395
+ owner: z.string().default(""),
396
+ repo: z.string().default("")
397
+ });
398
+ var agentConfigSchema = z.object({
399
+ intervalMs: z.number().int().positive().default(36e5),
400
+ maxIterations: z.number().int().positive().default(20),
401
+ autoStart: z.boolean().default(false)
402
+ });
403
+ var webConfigSchema = z.object({
404
+ port: z.number().int().min(1).max(65535).default(4242),
405
+ host: z.string().default("0.0.0.0")
406
+ });
407
+ var databaseConfigSchema = z.object({
408
+ path: z.string().default("./data/openqa.db")
409
+ });
410
+ var notificationsConfigSchema = z.object({
411
+ slack: z.string().url().optional(),
412
+ discord: z.string().url().optional()
413
+ });
414
+ var openQAConfigSchema = z.object({
415
+ llm: llmConfigSchema,
416
+ saas: saasConfigSchema,
417
+ github: githubConfigSchema.optional(),
418
+ agent: agentConfigSchema,
419
+ web: webConfigSchema,
420
+ database: databaseConfigSchema,
421
+ notifications: notificationsConfigSchema.optional()
422
+ });
423
+ var saasAppConfigSchema = z.object({
424
+ name: z.string().min(1, "SaaS application name is required"),
425
+ description: z.string().min(1, "SaaS application description is required"),
426
+ url: z.string().url("SaaS application URL must be a valid URL"),
427
+ repoUrl: z.string().url().optional(),
428
+ localPath: z.string().optional(),
429
+ techStack: z.array(z.string()).optional(),
430
+ authInfo: z.object({
431
+ type: z.enum(["none", "basic", "oauth", "session"]),
432
+ testCredentials: z.object({
433
+ username: z.string(),
434
+ password: z.string()
435
+ }).optional()
436
+ }).optional(),
437
+ directives: z.array(z.string()).optional()
438
+ });
439
+ function validateConfigSafe(config) {
440
+ const result = openQAConfigSchema.safeParse(config);
441
+ if (result.success) {
442
+ return { success: true, data: result.data };
443
+ }
444
+ return {
445
+ success: false,
446
+ errors: result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
447
+ };
448
+ }
449
+
450
+ // agent/logger.ts
451
+ init_esm_shims();
452
+ var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
453
+ var MIN_LEVEL = process.env.LOG_LEVEL || "info";
454
+ function shouldLog(level) {
455
+ return LEVELS[level] >= LEVELS[MIN_LEVEL];
456
+ }
457
+ function format(level, message, context) {
458
+ const entry = {
459
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
460
+ level,
461
+ msg: message,
462
+ ...context
463
+ };
464
+ return JSON.stringify(entry);
465
+ }
466
+ var logger = {
467
+ debug(message, context) {
468
+ if (shouldLog("debug")) process.stdout.write(format("debug", message, context) + "\n");
469
+ },
470
+ info(message, context) {
471
+ if (shouldLog("info")) process.stdout.write(format("info", message, context) + "\n");
472
+ },
473
+ warn(message, context) {
474
+ if (shouldLog("warn")) process.stderr.write(format("warn", message, context) + "\n");
475
+ },
476
+ error(message, context) {
477
+ if (shouldLog("error")) process.stderr.write(format("error", message, context) + "\n");
478
+ },
479
+ child(defaults) {
480
+ return {
481
+ debug: (msg, ctx) => logger.debug(msg, { ...defaults, ...ctx }),
482
+ info: (msg, ctx) => logger.info(msg, { ...defaults, ...ctx }),
483
+ warn: (msg, ctx) => logger.warn(msg, { ...defaults, ...ctx }),
484
+ error: (msg, ctx) => logger.error(msg, { ...defaults, ...ctx })
485
+ };
486
+ }
487
+ };
488
+
489
+ // agent/config/index.ts
490
+ dotenvConfig();
491
+ var ConfigManager = class {
492
+ db = null;
493
+ envConfig;
494
+ constructor(dbPath) {
495
+ this.envConfig = this.loadFromEnv();
496
+ }
497
+ loadFromEnv() {
498
+ const raw = {
499
+ llm: {
500
+ provider: process.env.LLM_PROVIDER || "openai",
501
+ apiKey: process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY,
502
+ model: process.env.LLM_MODEL,
503
+ baseUrl: process.env.OLLAMA_BASE_URL
504
+ },
505
+ saas: {
506
+ url: process.env.SAAS_URL || "",
507
+ authType: process.env.SAAS_AUTH_TYPE || "none",
508
+ username: process.env.SAAS_USERNAME,
509
+ password: process.env.SAAS_PASSWORD
510
+ },
511
+ github: process.env.GITHUB_TOKEN ? {
512
+ token: process.env.GITHUB_TOKEN,
513
+ owner: process.env.GITHUB_OWNER || "",
514
+ repo: process.env.GITHUB_REPO || ""
515
+ } : void 0,
516
+ agent: {
517
+ intervalMs: parseInt(process.env.AGENT_INTERVAL_MS || "3600000"),
518
+ maxIterations: parseInt(process.env.AGENT_MAX_ITERATIONS || "20"),
519
+ autoStart: process.env.AGENT_AUTO_START === "true"
520
+ },
521
+ web: {
522
+ port: parseInt(process.env.WEB_PORT || "4242"),
523
+ host: process.env.WEB_HOST || "0.0.0.0"
524
+ },
525
+ database: {
526
+ path: process.env.DB_PATH || "./data/openqa.db"
527
+ },
528
+ notifications: {
529
+ slack: process.env.SLACK_WEBHOOK_URL,
530
+ discord: process.env.DISCORD_WEBHOOK_URL
531
+ }
532
+ };
533
+ const result = validateConfigSafe(raw);
534
+ if (!result.success) {
535
+ logger.warn("Config validation warnings", { errors: result.errors });
536
+ return raw;
537
+ }
538
+ return result.data;
539
+ }
540
+ getDB() {
541
+ if (!this.db) {
542
+ this.db = new OpenQADatabase("./data/openqa.json");
543
+ }
544
+ return this.db;
545
+ }
546
+ async get(key) {
547
+ const dbValue = await this.getDB().getConfig(key);
548
+ if (dbValue) return dbValue;
549
+ const keys = key.split(".");
550
+ let value = this.envConfig;
551
+ for (const k of keys) {
552
+ if (value && typeof value === "object") {
553
+ value = value[k];
554
+ } else {
555
+ return null;
556
+ }
557
+ }
558
+ return value != null ? String(value) : null;
559
+ }
560
+ async set(key, value) {
561
+ await this.getDB().setConfig(key, value);
562
+ }
563
+ async getAll() {
564
+ const dbConfig = await this.getDB().getAllConfig();
565
+ const merged = { ...this.envConfig };
566
+ for (const [key, value] of Object.entries(dbConfig)) {
567
+ const keys = key.split(".");
568
+ let obj = merged;
569
+ for (let i = 0; i < keys.length - 1; i++) {
570
+ if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") obj[keys[i]] = {};
571
+ obj = obj[keys[i]];
572
+ }
573
+ obj[keys[keys.length - 1]] = value;
574
+ }
575
+ return merged;
576
+ }
577
+ async getConfig() {
578
+ return await this.getAll();
579
+ }
580
+ // Synchronous version that only uses env vars (no DB)
581
+ getConfigSync() {
582
+ return this.envConfig;
583
+ }
584
+ };
585
+ export {
586
+ ConfigManager
587
+ };
588
+ //# sourceMappingURL=index.js.map