@openqa/cli 1.3.3 → 2.0.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 (40) hide show
  1. package/README.md +1 -1
  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 +387 -55
  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 +7572 -0
  30. package/dist/cli/dashboard.html.js +1619 -0
  31. package/dist/cli/index.js +3624 -1675
  32. package/dist/cli/kanban.html.js +577 -0
  33. package/dist/cli/routes.js +895 -0
  34. package/dist/cli/routes.js.map +1 -0
  35. package/dist/cli/server.js +3564 -1646
  36. package/dist/database/index.js +503 -10
  37. package/dist/database/index.js.map +1 -1
  38. package/dist/database/sqlite.js +281 -0
  39. package/dist/database/sqlite.js.map +1 -0
  40. package/package.json +18 -5
@@ -1,11 +1,328 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // node_modules/tsup/assets/esm_shims.js
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+ var init_esm_shims = __esm({
15
+ "node_modules/tsup/assets/esm_shims.js"() {
16
+ "use strict";
17
+ }
18
+ });
19
+
20
+ // database/sqlite.ts
21
+ var sqlite_exports = {};
22
+ __export(sqlite_exports, {
23
+ OpenQASQLiteDatabase: () => OpenQASQLiteDatabase
24
+ });
25
+ import Database from "better-sqlite3";
26
+ import { mkdirSync } from "fs";
27
+ import { dirname } from "path";
28
+ var OpenQASQLiteDatabase;
29
+ var init_sqlite = __esm({
30
+ "database/sqlite.ts"() {
31
+ "use strict";
32
+ init_esm_shims();
33
+ OpenQASQLiteDatabase = class {
34
+ db;
35
+ constructor(dbPath = "./data/openqa.db") {
36
+ mkdirSync(dirname(dbPath), { recursive: true });
37
+ this.db = new Database(dbPath);
38
+ this.db.pragma("journal_mode = WAL");
39
+ this.db.pragma("foreign_keys = ON");
40
+ this.migrate();
41
+ }
42
+ migrate() {
43
+ this.db.exec(`
44
+ CREATE TABLE IF NOT EXISTS config (
45
+ key TEXT PRIMARY KEY,
46
+ value TEXT NOT NULL
47
+ );
48
+
49
+ CREATE TABLE IF NOT EXISTS test_sessions (
50
+ id TEXT PRIMARY KEY,
51
+ started_at TEXT NOT NULL,
52
+ ended_at TEXT,
53
+ status TEXT NOT NULL DEFAULT 'running',
54
+ total_actions INTEGER NOT NULL DEFAULT 0,
55
+ bugs_found INTEGER NOT NULL DEFAULT 0,
56
+ metadata TEXT
57
+ );
58
+
59
+ CREATE TABLE IF NOT EXISTS actions (
60
+ id TEXT PRIMARY KEY,
61
+ session_id TEXT NOT NULL,
62
+ timestamp TEXT NOT NULL,
63
+ type TEXT NOT NULL,
64
+ description TEXT NOT NULL,
65
+ input TEXT,
66
+ output TEXT,
67
+ screenshot_path TEXT,
68
+ FOREIGN KEY (session_id) REFERENCES test_sessions(id)
69
+ );
70
+
71
+ CREATE TABLE IF NOT EXISTS bugs (
72
+ id TEXT PRIMARY KEY,
73
+ session_id TEXT NOT NULL,
74
+ title TEXT NOT NULL,
75
+ description TEXT NOT NULL,
76
+ severity TEXT NOT NULL DEFAULT 'medium',
77
+ status TEXT NOT NULL DEFAULT 'open',
78
+ github_issue_url TEXT,
79
+ screenshot_path TEXT,
80
+ created_at TEXT NOT NULL,
81
+ updated_at TEXT NOT NULL,
82
+ FOREIGN KEY (session_id) REFERENCES test_sessions(id)
83
+ );
84
+
85
+ CREATE TABLE IF NOT EXISTS kanban_tickets (
86
+ id TEXT PRIMARY KEY,
87
+ bug_id TEXT,
88
+ title TEXT NOT NULL,
89
+ description TEXT NOT NULL,
90
+ priority TEXT NOT NULL DEFAULT 'medium',
91
+ column TEXT NOT NULL DEFAULT 'backlog',
92
+ tags TEXT,
93
+ screenshot_url TEXT,
94
+ created_at TEXT NOT NULL,
95
+ updated_at TEXT NOT NULL
96
+ );
97
+ `);
98
+ }
99
+ // ── Config ──
100
+ async getConfig(key) {
101
+ const row = this.db.prepare("SELECT value FROM config WHERE key = ?").get(key);
102
+ return row?.value ?? null;
103
+ }
104
+ async setConfig(key, value) {
105
+ this.db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run(key, value);
106
+ }
107
+ async getAllConfig() {
108
+ const rows = this.db.prepare("SELECT key, value FROM config").all();
109
+ return Object.fromEntries(rows.map((r) => [r.key, r.value]));
110
+ }
111
+ async clearAllConfig() {
112
+ this.db.prepare("DELETE FROM config").run();
113
+ }
114
+ // ── Sessions ──
115
+ async createSession(id, metadata) {
116
+ const session = {
117
+ id,
118
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
119
+ status: "running",
120
+ total_actions: 0,
121
+ bugs_found: 0,
122
+ metadata: metadata ? JSON.stringify(metadata) : void 0
123
+ };
124
+ this.db.prepare(`
125
+ INSERT INTO test_sessions (id, started_at, status, total_actions, bugs_found, metadata)
126
+ VALUES (?, ?, ?, ?, ?, ?)
127
+ `).run(session.id, session.started_at, session.status, session.total_actions, session.bugs_found, session.metadata ?? null);
128
+ return session;
129
+ }
130
+ async getSession(id) {
131
+ return this.db.prepare("SELECT * FROM test_sessions WHERE id = ?").get(id) ?? null;
132
+ }
133
+ async updateSession(id, updates) {
134
+ const fields = Object.keys(updates).map((k) => `${k} = ?`).join(", ");
135
+ const values = [...Object.values(updates), id];
136
+ this.db.prepare(`UPDATE test_sessions SET ${fields} WHERE id = ?`).run(...values);
137
+ }
138
+ async getRecentSessions(limit = 10) {
139
+ return this.db.prepare("SELECT * FROM test_sessions ORDER BY started_at DESC LIMIT ?").all(limit);
140
+ }
141
+ // ── Actions ──
142
+ async createAction(action) {
143
+ const newAction = {
144
+ id: `action_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
145
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
146
+ ...action
147
+ };
148
+ this.db.prepare(`
149
+ INSERT INTO actions (id, session_id, timestamp, type, description, input, output, screenshot_path)
150
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
151
+ `).run(
152
+ newAction.id,
153
+ newAction.session_id,
154
+ newAction.timestamp,
155
+ newAction.type,
156
+ newAction.description,
157
+ newAction.input ?? null,
158
+ newAction.output ?? null,
159
+ newAction.screenshot_path ?? null
160
+ );
161
+ return newAction;
162
+ }
163
+ async getSessionActions(sessionId) {
164
+ return this.db.prepare("SELECT * FROM actions WHERE session_id = ? ORDER BY timestamp DESC").all(sessionId);
165
+ }
166
+ // ── Bugs ──
167
+ async createBug(bug) {
168
+ const newBug = {
169
+ id: `bug_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
170
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
171
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
172
+ ...bug
173
+ };
174
+ this.db.prepare(`
175
+ INSERT INTO bugs (id, session_id, title, description, severity, status, github_issue_url, screenshot_path, created_at, updated_at)
176
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
177
+ `).run(
178
+ newBug.id,
179
+ newBug.session_id,
180
+ newBug.title,
181
+ newBug.description,
182
+ newBug.severity,
183
+ newBug.status,
184
+ newBug.github_issue_url ?? null,
185
+ newBug.screenshot_path ?? null,
186
+ newBug.created_at,
187
+ newBug.updated_at
188
+ );
189
+ return newBug;
190
+ }
191
+ async updateBug(id, updates) {
192
+ const patch = { ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
193
+ const fields = Object.keys(patch).map((k) => `${k} = ?`).join(", ");
194
+ const values = [...Object.values(patch), id];
195
+ this.db.prepare(`UPDATE bugs SET ${fields} WHERE id = ?`).run(...values);
196
+ }
197
+ async getAllBugs() {
198
+ return this.db.prepare("SELECT * FROM bugs ORDER BY created_at DESC").all();
199
+ }
200
+ async getBugsByStatus(status) {
201
+ return this.db.prepare("SELECT * FROM bugs WHERE status = ? ORDER BY created_at DESC").all(status);
202
+ }
203
+ // ── Kanban ──
204
+ async createKanbanTicket(ticket) {
205
+ const newTicket = {
206
+ id: `ticket_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
207
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
208
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
209
+ ...ticket
210
+ };
211
+ this.db.prepare(`
212
+ INSERT INTO kanban_tickets (id, bug_id, title, description, priority, column, tags, screenshot_url, created_at, updated_at)
213
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
214
+ `).run(
215
+ newTicket.id,
216
+ newTicket.bug_id ?? null,
217
+ newTicket.title,
218
+ newTicket.description,
219
+ newTicket.priority,
220
+ newTicket.column,
221
+ newTicket.tags ?? null,
222
+ newTicket.screenshot_url ?? null,
223
+ newTicket.created_at,
224
+ newTicket.updated_at
225
+ );
226
+ return newTicket;
227
+ }
228
+ async updateKanbanTicket(id, updates) {
229
+ const patch = { ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
230
+ const fields = Object.keys(patch).map((k) => `"${k}" = ?`).join(", ");
231
+ const values = [...Object.values(patch), id];
232
+ this.db.prepare(`UPDATE kanban_tickets SET ${fields} WHERE id = ?`).run(...values);
233
+ }
234
+ async getKanbanTickets() {
235
+ return this.db.prepare("SELECT * FROM kanban_tickets ORDER BY created_at DESC").all();
236
+ }
237
+ async getKanbanTicketsByColumn(column) {
238
+ return this.db.prepare('SELECT * FROM kanban_tickets WHERE "column" = ? ORDER BY created_at DESC').all(column);
239
+ }
240
+ async deleteKanbanTicket(id) {
241
+ this.db.prepare("DELETE FROM kanban_tickets WHERE id = ?").run(id);
242
+ }
243
+ // ── Storage / Cleanup ──
244
+ async pruneOldSessions(maxAgeDays) {
245
+ const cutoff = new Date(Date.now() - maxAgeDays * 864e5).toISOString();
246
+ const oldSessions = this.db.prepare("SELECT id FROM test_sessions WHERE started_at < ?").all(cutoff);
247
+ const ids = oldSessions.map((s) => s.id);
248
+ if (ids.length === 0) return { sessionsRemoved: 0, actionsRemoved: 0 };
249
+ const placeholders = ids.map(() => "?").join(", ");
250
+ const actionsResult = this.db.prepare(`DELETE FROM actions WHERE session_id IN (${placeholders})`).run(...ids);
251
+ this.db.prepare(`DELETE FROM test_sessions WHERE id IN (${placeholders})`).run(...ids);
252
+ return { sessionsRemoved: ids.length, actionsRemoved: actionsResult.changes };
253
+ }
254
+ async getStorageStats() {
255
+ const count = (table) => this.db.prepare(`SELECT COUNT(*) as n FROM ${table}`).get().n;
256
+ return {
257
+ sessions: count("test_sessions"),
258
+ actions: count("actions"),
259
+ bugs: count("bugs"),
260
+ tickets: count("kanban_tickets")
261
+ };
262
+ }
263
+ // ── Compat helpers (used by daemon.ts) ──
264
+ async getActiveAgents() {
265
+ const sessions = await this.getRecentSessions(1);
266
+ const current = sessions[0];
267
+ const isRunning = current?.status === "running";
268
+ return [{
269
+ name: "Main Agent",
270
+ status: isRunning ? "running" : "idle",
271
+ purpose: "Autonomous QA orchestration",
272
+ performance: current ? Math.min(100, Math.round(current.total_actions / 100 * 100)) : 0,
273
+ tasks: current?.total_actions ?? 0
274
+ }];
275
+ }
276
+ async getCurrentTasks() {
277
+ const sessions = await this.getRecentSessions(1);
278
+ if (!sessions[0]) return [];
279
+ const actions = await this.getSessionActions(sessions[0].id);
280
+ return actions.slice(0, 10).map((a, i) => ({
281
+ id: a.id,
282
+ name: a.type,
283
+ status: i === 0 && sessions[0].status === "running" ? "running" : "completed",
284
+ progress: i === 0 && sessions[0].status === "running" ? "65%" : "100%",
285
+ agent: "Main Agent",
286
+ started_at: a.timestamp,
287
+ result: a.output || a.description
288
+ }));
289
+ }
290
+ async getCurrentIssues() {
291
+ const bugs = await this.getAllBugs();
292
+ return bugs.slice(0, 10).map((b) => ({
293
+ id: b.id,
294
+ title: b.title,
295
+ description: b.description,
296
+ severity: b.severity,
297
+ status: b.status,
298
+ discovered_at: b.created_at,
299
+ agent: "Main Agent"
300
+ }));
301
+ }
302
+ async close() {
303
+ this.db.close();
304
+ }
305
+ };
306
+ }
307
+ });
308
+
1
309
  // database/index.ts
310
+ init_esm_shims();
311
+ init_sqlite();
2
312
  import { Low } from "lowdb";
3
313
  import { JSONFile } from "lowdb/node";
4
- import { dirname } from "path";
5
- import { fileURLToPath } from "url";
6
- import { mkdirSync } from "fs";
7
- var __filename = fileURLToPath(import.meta.url);
8
- var __dirname = dirname(__filename);
314
+ import { dirname as dirname2 } from "path";
315
+ import { fileURLToPath as fileURLToPath2 } from "url";
316
+ import { mkdirSync as mkdirSync2 } from "fs";
317
+ async function createDatabase(path2) {
318
+ if (path2.endsWith(".db")) {
319
+ const { OpenQASQLiteDatabase: OpenQASQLiteDatabase2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
320
+ return new OpenQASQLiteDatabase2(path2);
321
+ }
322
+ return new OpenQADatabase(path2);
323
+ }
324
+ var __filename2 = fileURLToPath2(import.meta.url);
325
+ var __dirname2 = dirname2(__filename2);
9
326
  var OpenQADatabase = class {
10
327
  constructor(dbPath = "./data/openqa.json") {
11
328
  this.dbPath = dbPath;
@@ -13,15 +330,16 @@ var OpenQADatabase = class {
13
330
  }
14
331
  db = null;
15
332
  initialize() {
16
- const dir = dirname(this.dbPath);
17
- mkdirSync(dir, { recursive: true });
333
+ const dir = dirname2(this.dbPath);
334
+ mkdirSync2(dir, { recursive: true });
18
335
  const adapter = new JSONFile(this.dbPath);
19
336
  this.db = new Low(adapter, {
20
337
  config: {},
21
338
  test_sessions: [],
22
339
  actions: [],
23
340
  bugs: [],
24
- kanban_tickets: []
341
+ kanban_tickets: [],
342
+ users: []
25
343
  });
26
344
  this.db.read();
27
345
  if (!this.db.data) {
@@ -30,7 +348,8 @@ var OpenQADatabase = class {
30
348
  test_sessions: [],
31
349
  actions: [],
32
350
  bugs: [],
33
- kanban_tickets: []
351
+ kanban_tickets: [],
352
+ users: []
34
353
  };
35
354
  this.db.write();
36
355
  }
@@ -40,6 +359,12 @@ var OpenQADatabase = class {
40
359
  this.initialize();
41
360
  }
42
361
  await this.db.read();
362
+ let migrated = false;
363
+ if (!this.db.data.users) {
364
+ this.db.data.users = [];
365
+ migrated = true;
366
+ }
367
+ if (migrated) await this.db.write();
43
368
  }
44
369
  async getConfig(key) {
45
370
  await this.ensureInitialized();
@@ -163,15 +488,183 @@ var OpenQADatabase = class {
163
488
  await this.ensureInitialized();
164
489
  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());
165
490
  }
491
+ async deleteKanbanTicket(id) {
492
+ await this.ensureInitialized();
493
+ const index = this.db.data.kanban_tickets.findIndex((t) => t.id === id);
494
+ if (index !== -1) {
495
+ this.db.data.kanban_tickets.splice(index, 1);
496
+ await this.db.write();
497
+ }
498
+ }
166
499
  async clearAllConfig() {
167
500
  await this.ensureInitialized();
168
501
  this.db.data.config = {};
169
502
  await this.db.write();
170
503
  }
504
+ // Get real data methods - connected to actual database records
505
+ async getActiveAgents() {
506
+ await this.ensureInitialized();
507
+ const sessions = await this.getRecentSessions(1);
508
+ const currentSession = sessions[0];
509
+ const isRunning = currentSession?.status === "running";
510
+ const totalActions = currentSession?.total_actions || 0;
511
+ const agents = [
512
+ {
513
+ name: "Main Agent",
514
+ status: isRunning ? "running" : "idle",
515
+ purpose: "Autonomous QA orchestration",
516
+ performance: totalActions > 0 ? Math.min(100, Math.round(totalActions / 100 * 100)) : 0,
517
+ tasks: totalActions
518
+ }
519
+ ];
520
+ if (currentSession && totalActions > 0) {
521
+ const actions = await this.getSessionActions(currentSession.id);
522
+ const actionTypes = actions.reduce((acc, action) => {
523
+ const type = action.type || "unknown";
524
+ acc[type] = (acc[type] || 0) + 1;
525
+ return acc;
526
+ }, {});
527
+ if (actionTypes["navigate"] || actionTypes["click"] || actionTypes["screenshot"]) {
528
+ agents.push({
529
+ name: "Browser Specialist",
530
+ status: isRunning ? "running" : "idle",
531
+ purpose: "UI navigation and interaction",
532
+ performance: Math.round(((actionTypes["navigate"] || 0) + (actionTypes["click"] || 0)) / totalActions * 100),
533
+ tasks: (actionTypes["navigate"] || 0) + (actionTypes["click"] || 0)
534
+ });
535
+ }
536
+ if (actionTypes["api_call"] || actionTypes["request"]) {
537
+ agents.push({
538
+ name: "API Tester",
539
+ status: isRunning ? "running" : "idle",
540
+ purpose: "API endpoint testing",
541
+ performance: Math.round((actionTypes["api_call"] || actionTypes["request"] || 0) / totalActions * 100),
542
+ tasks: actionTypes["api_call"] || actionTypes["request"] || 0
543
+ });
544
+ }
545
+ if (actionTypes["auth"] || actionTypes["login"]) {
546
+ agents.push({
547
+ name: "Auth Specialist",
548
+ status: isRunning ? "running" : "idle",
549
+ purpose: "Authentication testing",
550
+ performance: Math.round((actionTypes["auth"] || actionTypes["login"] || 0) / totalActions * 100),
551
+ tasks: actionTypes["auth"] || actionTypes["login"] || 0
552
+ });
553
+ }
554
+ }
555
+ return agents;
556
+ }
557
+ async getCurrentTasks() {
558
+ await this.ensureInitialized();
559
+ const sessions = await this.getRecentSessions(1);
560
+ const currentSession = sessions[0];
561
+ if (!currentSession) {
562
+ return [];
563
+ }
564
+ const actions = await this.getSessionActions(currentSession.id);
565
+ const recentActions = actions.slice(-10).reverse();
566
+ return recentActions.map((action, index) => ({
567
+ id: action.id,
568
+ name: action.type || "Unknown Action",
569
+ status: index === 0 && currentSession.status === "running" ? "running" : "completed",
570
+ progress: index === 0 && currentSession.status === "running" ? "65%" : "100%",
571
+ agent: "Main Agent",
572
+ started_at: action.timestamp,
573
+ result: action.output || action.description || "Completed"
574
+ }));
575
+ }
576
+ async getCurrentIssues() {
577
+ await this.ensureInitialized();
578
+ const bugs = await this.getAllBugs();
579
+ return bugs.slice(0, 10).map((bug) => ({
580
+ id: bug.id,
581
+ title: bug.title,
582
+ description: bug.description,
583
+ severity: bug.severity || "medium",
584
+ status: bug.status || "open",
585
+ discovered_at: bug.created_at,
586
+ agent: "Main Agent"
587
+ }));
588
+ }
589
+ async pruneOldSessions(maxAgeDays) {
590
+ await this.ensureInitialized();
591
+ const cutoff = new Date(Date.now() - maxAgeDays * 864e5).toISOString();
592
+ const oldSessions = this.db.data.test_sessions.filter((s) => s.started_at < cutoff);
593
+ const oldSessionIds = new Set(oldSessions.map((s) => s.id));
594
+ const actionsBefore = this.db.data.actions.length;
595
+ this.db.data.actions = this.db.data.actions.filter((a) => !oldSessionIds.has(a.session_id));
596
+ const actionsRemoved = actionsBefore - this.db.data.actions.length;
597
+ this.db.data.test_sessions = this.db.data.test_sessions.filter((s) => s.started_at >= cutoff);
598
+ await this.db.write();
599
+ return { sessionsRemoved: oldSessions.length, actionsRemoved };
600
+ }
601
+ async getStorageStats() {
602
+ await this.ensureInitialized();
603
+ return {
604
+ sessions: this.db.data.test_sessions.length,
605
+ actions: this.db.data.actions.length,
606
+ bugs: this.db.data.bugs.length,
607
+ tickets: this.db.data.kanban_tickets.length
608
+ };
609
+ }
610
+ // ── User management ──────────────────────────────────────────────────────────
611
+ async countUsers() {
612
+ await this.ensureInitialized();
613
+ return this.db.data.users.length;
614
+ }
615
+ async findUserByUsername(username) {
616
+ await this.ensureInitialized();
617
+ return this.db.data.users.find((u) => u.username === username) ?? null;
618
+ }
619
+ async getUserById(id) {
620
+ await this.ensureInitialized();
621
+ return this.db.data.users.find((u) => u.id === id) ?? null;
622
+ }
623
+ async getAllUsers() {
624
+ await this.ensureInitialized();
625
+ return [...this.db.data.users];
626
+ }
627
+ async createUser(data) {
628
+ await this.ensureInitialized();
629
+ const now = (/* @__PURE__ */ new Date()).toISOString();
630
+ const user = {
631
+ id: `user_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
632
+ username: data.username,
633
+ passwordHash: data.passwordHash,
634
+ role: data.role,
635
+ createdAt: now,
636
+ updatedAt: now
637
+ };
638
+ this.db.data.users.push(user);
639
+ await this.db.write();
640
+ return user;
641
+ }
642
+ async updateUser(id, updates) {
643
+ await this.ensureInitialized();
644
+ const idx = this.db.data.users.findIndex((u) => u.id === id);
645
+ if (idx !== -1) {
646
+ this.db.data.users[idx] = {
647
+ ...this.db.data.users[idx],
648
+ ...updates,
649
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
650
+ };
651
+ await this.db.write();
652
+ }
653
+ }
654
+ async deleteUser(id) {
655
+ await this.ensureInitialized();
656
+ const idx = this.db.data.users.findIndex((u) => u.id === id);
657
+ if (idx !== -1) {
658
+ this.db.data.users.splice(idx, 1);
659
+ await this.db.write();
660
+ }
661
+ }
171
662
  async close() {
172
663
  }
173
664
  };
174
665
  export {
175
- OpenQADatabase
666
+ OpenQADatabase,
667
+ OpenQASQLiteDatabase,
668
+ createDatabase
176
669
  };
177
670
  //# sourceMappingURL=index.js.map