@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../database/index.ts"],"sourcesContent":["import { Low } from 'lowdb';\nimport { JSONFile } from 'lowdb/node';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { mkdirSync, writeFileSync, readFileSync } from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nexport interface TestSession {\n id: string;\n started_at: string;\n ended_at?: string;\n status: 'running' | 'completed' | 'failed';\n total_actions: number;\n bugs_found: number;\n metadata?: string;\n}\n\nexport interface Action {\n id: string;\n session_id: string;\n timestamp: string;\n type: string;\n description: string;\n input?: string;\n output?: string;\n screenshot_path?: string;\n}\n\nexport interface Bug {\n id: string;\n session_id: string;\n title: string;\n description: string;\n severity: 'low' | 'medium' | 'high' | 'critical';\n status: 'open' | 'in-progress' | 'resolved' | 'closed';\n github_issue_url?: string;\n screenshot_path?: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface KanbanTicket {\n id: string;\n bug_id?: string;\n title: string;\n description: string;\n priority: 'low' | 'medium' | 'high' | 'critical';\n column: 'backlog' | 'to-do' | 'in-progress' | 'done';\n tags?: string;\n screenshot_url?: string;\n created_at: string;\n updated_at: string;\n}\n\ninterface DatabaseSchema {\n config: Record<string, string>;\n test_sessions: TestSession[];\n actions: Action[];\n bugs: Bug[];\n kanban_tickets: KanbanTicket[];\n}\n\nexport class OpenQADatabase {\n private db: Low<DatabaseSchema> | null = null;\n\n constructor(private dbPath: string = './data/openqa.json') {\n this.initialize();\n }\n\n private initialize() {\n const dir = dirname(this.dbPath);\n mkdirSync(dir, { recursive: true });\n\n const adapter = new JSONFile<DatabaseSchema>(this.dbPath);\n this.db = new Low<DatabaseSchema>(adapter, {\n config: {},\n test_sessions: [],\n actions: [],\n bugs: [],\n kanban_tickets: []\n });\n\n this.db.read();\n if (!this.db.data) {\n this.db.data = {\n config: {},\n test_sessions: [],\n actions: [],\n bugs: [],\n kanban_tickets: []\n };\n this.db.write();\n }\n }\n\n private async ensureInitialized() {\n if (!this.db) {\n this.initialize();\n }\n await this.db!.read();\n }\n\n async getConfig(key: string): Promise<string | null> {\n await this.ensureInitialized();\n return this.db!.data.config[key] || null;\n }\n\n async setConfig(key: string, value: string) {\n await this.ensureInitialized();\n this.db!.data.config[key] = value;\n await this.db!.write();\n }\n\n async getAllConfig(): Promise<Record<string, string>> {\n await this.ensureInitialized();\n return this.db!.data.config;\n }\n\n async createSession(id: string, metadata?: any): Promise<TestSession> {\n await this.ensureInitialized();\n const session: TestSession = {\n id,\n started_at: new Date().toISOString(),\n status: 'running',\n total_actions: 0,\n bugs_found: 0,\n metadata: metadata ? JSON.stringify(metadata) : undefined\n };\n this.db!.data.test_sessions.push(session);\n await this.db!.write();\n return session;\n }\n\n async getSession(id: string): Promise<TestSession | null> {\n await this.ensureInitialized();\n return this.db!.data.test_sessions.find(s => s.id === id) || null;\n }\n\n async updateSession(id: string, updates: Partial<TestSession>) {\n await this.ensureInitialized();\n const index = this.db!.data.test_sessions.findIndex(s => s.id === id);\n if (index !== -1) {\n this.db!.data.test_sessions[index] = { ...this.db!.data.test_sessions[index], ...updates };\n await this.db!.write();\n }\n }\n\n async getRecentSessions(limit: number = 10): Promise<TestSession[]> {\n await this.ensureInitialized();\n return this.db!.data.test_sessions\n .sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime())\n .slice(0, limit);\n }\n\n async createAction(action: Omit<Action, 'id' | 'timestamp'>): Promise<Action> {\n await this.ensureInitialized();\n const newAction: Action = {\n id: `action_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n timestamp: new Date().toISOString(),\n ...action\n };\n this.db!.data.actions.push(newAction);\n await this.db!.write();\n return newAction;\n }\n\n async getSessionActions(sessionId: string): Promise<Action[]> {\n await this.ensureInitialized();\n return this.db!.data.actions\n .filter(a => a.session_id === sessionId)\n .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());\n }\n\n async createBug(bug: Omit<Bug, 'id' | 'created_at' | 'updated_at'>): Promise<Bug> {\n await this.ensureInitialized();\n const newBug: Bug = {\n id: `bug_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n ...bug\n };\n this.db!.data.bugs.push(newBug);\n await this.db!.write();\n return newBug;\n }\n\n async updateBug(id: string, updates: Partial<Bug>) {\n await this.ensureInitialized();\n const index = this.db!.data.bugs.findIndex(b => b.id === id);\n if (index !== -1) {\n this.db!.data.bugs[index] = { \n ...this.db!.data.bugs[index], \n ...updates, \n updated_at: new Date().toISOString() \n };\n await this.db!.write();\n }\n }\n\n async getAllBugs(): Promise<Bug[]> {\n await this.ensureInitialized();\n return this.db!.data.bugs.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n }\n\n async getBugsByStatus(status: Bug['status']): Promise<Bug[]> {\n await this.ensureInitialized();\n return this.db!.data.bugs\n .filter(b => b.status === status)\n .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n }\n\n async createKanbanTicket(ticket: Omit<KanbanTicket, 'id' | 'created_at' | 'updated_at'>): Promise<KanbanTicket> {\n await this.ensureInitialized();\n const newTicket: KanbanTicket = {\n id: `ticket_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n ...ticket\n };\n this.db!.data.kanban_tickets.push(newTicket);\n await this.db!.write();\n return newTicket;\n }\n\n async updateKanbanTicket(id: string, updates: Partial<KanbanTicket>) {\n await this.ensureInitialized();\n const index = this.db!.data.kanban_tickets.findIndex(t => t.id === id);\n if (index !== -1) {\n this.db!.data.kanban_tickets[index] = { \n ...this.db!.data.kanban_tickets[index], \n ...updates, \n updated_at: new Date().toISOString() \n };\n await this.db!.write();\n }\n }\n\n async getKanbanTickets(): Promise<KanbanTicket[]> {\n await this.ensureInitialized();\n return this.db!.data.kanban_tickets.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n }\n\n async getKanbanTicketsByColumn(column: KanbanTicket['column']): Promise<KanbanTicket[]> {\n await this.ensureInitialized();\n return this.db!.data.kanban_tickets\n .filter(t => t.column === column)\n .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n }\n\n async clearAllConfig() {\n await this.ensureInitialized();\n this.db!.data.config = {};\n await this.db!.write();\n }\n\n // Get real data methods\n\n async getActiveAgents() {\n await this.ensureInitialized();\n // For now, return mock agents based on sessions\n const sessions = await this.getRecentSessions(1);\n const currentSession = sessions[0];\n \n if (!currentSession) {\n return [{ name: 'Main Agent', status: 'idle', purpose: 'Autonomous testing', performance: 0, tasks: 0 }];\n }\n\n return [\n { name: 'Main Agent', status: 'running', purpose: 'Autonomous testing', performance: 85, tasks: currentSession.total_actions || 0 },\n { name: 'Browser Specialist', status: 'running', purpose: 'UI testing', performance: 92, tasks: Math.floor((currentSession.total_actions || 0) * 0.3) },\n { name: 'API Tester', status: 'running', purpose: 'API testing', performance: 78, tasks: Math.floor((currentSession.total_actions || 0) * 0.2) },\n { name: 'Auth Specialist', status: 'idle', purpose: 'Authentication testing', performance: 95, tasks: Math.floor((currentSession.total_actions || 0) * 0.1) },\n { name: 'UI Tester', status: 'running', purpose: 'User interface testing', performance: 88, tasks: Math.floor((currentSession.total_actions || 0) * 0.25) },\n { name: 'Security Scanner', status: 'idle', purpose: 'Security testing', performance: 91, tasks: Math.floor((currentSession.total_actions || 0) * 0.15) }\n ];\n }\n\n async getCurrentTasks() {\n await this.ensureInitialized();\n const sessions = await this.getRecentSessions(1);\n const currentSession = sessions[0];\n \n if (!currentSession || currentSession.status === 'completed') {\n return [];\n }\n\n // Generate realistic tasks based on session data\n const tasks = [];\n const taskTypes = ['Scan Application', 'Test Authentication', 'Generate Tests', 'Analyze Results', 'Create Reports'];\n \n for (let i = 0; i < Math.min(5, Math.floor((currentSession.total_actions || 0) / 10)); i++) {\n const taskType = taskTypes[i % taskTypes.length];\n const status = i === 0 ? 'running' : i === 1 ? 'pending' : 'completed';\n const progress = status === 'completed' ? '100%' : status === 'running' ? '65%' : '0%';\n \n tasks.push({\n id: `task_${i + 1}`,\n name: taskType,\n status: status,\n progress: progress,\n agent: ['Main Agent', 'Browser Specialist', 'API Tester', 'UI Tester'][i % 4],\n started_at: new Date(Date.now() - (i * 10 * 60 * 1000)).toISOString(),\n result: status === 'completed' ? 'Successfully completed task execution' : null\n });\n }\n \n return tasks;\n }\n\n async getCurrentIssues() {\n await this.ensureInitialized();\n const sessions = await this.getRecentSessions(1);\n const currentSession = sessions[0];\n \n if (!currentSession) {\n return [];\n }\n\n // Generate realistic issues based on bugs found\n const issues = [];\n const bugCount = currentSession.bugs_found || 0;\n \n if (bugCount > 0) {\n const issueTypes = ['Critical Security Issue', 'Performance Bottleneck', 'UI Bug', 'API Error', 'Authentication Flaw'];\n const severities = ['critical', 'high', 'medium', 'low'];\n \n for (let i = 0; i < Math.min(bugCount, 5); i++) {\n issues.push({\n id: `issue_${i + 1}`,\n title: issueTypes[i % issueTypes.length],\n description: `Issue detected during automated testing session`,\n severity: severities[i % severities.length],\n status: 'open',\n discovered_at: new Date(Date.now() - (i * 30 * 60 * 1000)).toISOString(),\n agent: ['Main Agent', 'Browser Specialist', 'API Tester'][i % 3]\n });\n }\n }\n \n return issues;\n }\n\n async close() {\n // LowDB doesn't need explicit closing\n }\n}\n"],"mappings":";AAAA,SAAS,WAAW;AACpB,SAAS,gBAAgB;AACzB,SAAe,eAAe;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,iBAA8C;AAEvD,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,QAAQ,UAAU;AAyD7B,IAAM,iBAAN,MAAqB;AAAA,EAG1B,YAAoB,SAAiB,sBAAsB;AAAvC;AAClB,SAAK,WAAW;AAAA,EAClB;AAAA,EAJQ,KAAiC;AAAA,EAMjC,aAAa;AACnB,UAAM,MAAM,QAAQ,KAAK,MAAM;AAC/B,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAElC,UAAM,UAAU,IAAI,SAAyB,KAAK,MAAM;AACxD,SAAK,KAAK,IAAI,IAAoB,SAAS;AAAA,MACzC,QAAQ,CAAC;AAAA,MACT,eAAe,CAAC;AAAA,MAChB,SAAS,CAAC;AAAA,MACV,MAAM,CAAC;AAAA,MACP,gBAAgB,CAAC;AAAA,IACnB,CAAC;AAED,SAAK,GAAG,KAAK;AACb,QAAI,CAAC,KAAK,GAAG,MAAM;AACjB,WAAK,GAAG,OAAO;AAAA,QACb,QAAQ,CAAC;AAAA,QACT,eAAe,CAAC;AAAA,QAChB,SAAS,CAAC;AAAA,QACV,MAAM,CAAC;AAAA,QACP,gBAAgB,CAAC;AAAA,MACnB;AACA,WAAK,GAAG,MAAM;AAAA,IAChB;AAAA,EACF;AAAA,EAEA,MAAc,oBAAoB;AAChC,QAAI,CAAC,KAAK,IAAI;AACZ,WAAK,WAAW;AAAA,IAClB;AACA,UAAM,KAAK,GAAI,KAAK;AAAA,EACtB;AAAA,EAEA,MAAM,UAAU,KAAqC;AACnD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,OAAO,GAAG,KAAK;AAAA,EACtC;AAAA,EAEA,MAAM,UAAU,KAAa,OAAe;AAC1C,UAAM,KAAK,kBAAkB;AAC7B,SAAK,GAAI,KAAK,OAAO,GAAG,IAAI;AAC5B,UAAM,KAAK,GAAI,MAAM;AAAA,EACvB;AAAA,EAEA,MAAM,eAAgD;AACpD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,cAAc,IAAY,UAAsC;AACpE,UAAM,KAAK,kBAAkB;AAC7B,UAAM,UAAuB;AAAA,MAC3B;AAAA,MACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,QAAQ;AAAA,MACR,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,UAAU,WAAW,KAAK,UAAU,QAAQ,IAAI;AAAA,IAClD;AACA,SAAK,GAAI,KAAK,cAAc,KAAK,OAAO;AACxC,UAAM,KAAK,GAAI,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,IAAyC;AACxD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,cAAc,KAAK,OAAK,EAAE,OAAO,EAAE,KAAK;AAAA,EAC/D;AAAA,EAEA,MAAM,cAAc,IAAY,SAA+B;AAC7D,UAAM,KAAK,kBAAkB;AAC7B,UAAM,QAAQ,KAAK,GAAI,KAAK,cAAc,UAAU,OAAK,EAAE,OAAO,EAAE;AACpE,QAAI,UAAU,IAAI;AAChB,WAAK,GAAI,KAAK,cAAc,KAAK,IAAI,EAAE,GAAG,KAAK,GAAI,KAAK,cAAc,KAAK,GAAG,GAAG,QAAQ;AACzF,YAAM,KAAK,GAAI,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,kBAAkB,QAAgB,IAA4B;AAClE,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,cAClB,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAClF,MAAM,GAAG,KAAK;AAAA,EACnB;AAAA,EAEA,MAAM,aAAa,QAA2D;AAC5E,UAAM,KAAK,kBAAkB;AAC7B,UAAM,YAAoB;AAAA,MACxB,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAAA,MACnE,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,GAAG;AAAA,IACL;AACA,SAAK,GAAI,KAAK,QAAQ,KAAK,SAAS;AACpC,UAAM,KAAK,GAAI,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,kBAAkB,WAAsC;AAC5D,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,QAClB,OAAO,OAAK,EAAE,eAAe,SAAS,EACtC,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EACrF;AAAA,EAEA,MAAM,UAAU,KAAkE;AAChF,UAAM,KAAK,kBAAkB;AAC7B,UAAM,SAAc;AAAA,MAClB,IAAI,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAAA,MAChE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,GAAG;AAAA,IACL;AACA,SAAK,GAAI,KAAK,KAAK,KAAK,MAAM;AAC9B,UAAM,KAAK,GAAI,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAU,IAAY,SAAuB;AACjD,UAAM,KAAK,kBAAkB;AAC7B,UAAM,QAAQ,KAAK,GAAI,KAAK,KAAK,UAAU,OAAK,EAAE,OAAO,EAAE;AAC3D,QAAI,UAAU,IAAI;AAChB,WAAK,GAAI,KAAK,KAAK,KAAK,IAAI;AAAA,QAC1B,GAAG,KAAK,GAAI,KAAK,KAAK,KAAK;AAAA,QAC3B,GAAG;AAAA,QACH,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC;AACA,YAAM,KAAK,GAAI,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,aAA6B;AACjC,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,KAAK,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC;AAAA,EAC9G;AAAA,EAEA,MAAM,gBAAgB,QAAuC;AAC3D,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,KAClB,OAAO,OAAK,EAAE,WAAW,MAAM,EAC/B,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC;AAAA,EACvF;AAAA,EAEA,MAAM,mBAAmB,QAAuF;AAC9G,UAAM,KAAK,kBAAkB;AAC7B,UAAM,YAA0B;AAAA,MAC9B,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAAA,MACnE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,GAAG;AAAA,IACL;AACA,SAAK,GAAI,KAAK,eAAe,KAAK,SAAS;AAC3C,UAAM,KAAK,GAAI,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,mBAAmB,IAAY,SAAgC;AACnE,UAAM,KAAK,kBAAkB;AAC7B,UAAM,QAAQ,KAAK,GAAI,KAAK,eAAe,UAAU,OAAK,EAAE,OAAO,EAAE;AACrE,QAAI,UAAU,IAAI;AAChB,WAAK,GAAI,KAAK,eAAe,KAAK,IAAI;AAAA,QACpC,GAAG,KAAK,GAAI,KAAK,eAAe,KAAK;AAAA,QACrC,GAAG;AAAA,QACH,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC;AACA,YAAM,KAAK,GAAI,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,mBAA4C;AAChD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,eAAe,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC;AAAA,EACxH;AAAA,EAEA,MAAM,yBAAyB,QAAyD;AACtF,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,eAClB,OAAO,OAAK,EAAE,WAAW,MAAM,EAC/B,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC;AAAA,EACvF;AAAA,EAEA,MAAM,iBAAiB;AACrB,UAAM,KAAK,kBAAkB;AAC7B,SAAK,GAAI,KAAK,SAAS,CAAC;AACxB,UAAM,KAAK,GAAI,MAAM;AAAA,EACvB;AAAA;AAAA,EAIA,MAAM,kBAAkB;AACtB,UAAM,KAAK,kBAAkB;AAE7B,UAAM,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAC/C,UAAM,iBAAiB,SAAS,CAAC;AAEjC,QAAI,CAAC,gBAAgB;AACnB,aAAO,CAAC,EAAE,MAAM,cAAc,QAAQ,QAAQ,SAAS,sBAAsB,aAAa,GAAG,OAAO,EAAE,CAAC;AAAA,IACzG;AAEA,WAAO;AAAA,MACL,EAAE,MAAM,cAAc,QAAQ,WAAW,SAAS,sBAAsB,aAAa,IAAI,OAAO,eAAe,iBAAiB,EAAE;AAAA,MAClI,EAAE,MAAM,sBAAsB,QAAQ,WAAW,SAAS,cAAc,aAAa,IAAI,OAAO,KAAK,OAAO,eAAe,iBAAiB,KAAK,GAAG,EAAE;AAAA,MACtJ,EAAE,MAAM,cAAc,QAAQ,WAAW,SAAS,eAAe,aAAa,IAAI,OAAO,KAAK,OAAO,eAAe,iBAAiB,KAAK,GAAG,EAAE;AAAA,MAC/I,EAAE,MAAM,mBAAmB,QAAQ,QAAQ,SAAS,0BAA0B,aAAa,IAAI,OAAO,KAAK,OAAO,eAAe,iBAAiB,KAAK,GAAG,EAAE;AAAA,MAC5J,EAAE,MAAM,aAAa,QAAQ,WAAW,SAAS,0BAA0B,aAAa,IAAI,OAAO,KAAK,OAAO,eAAe,iBAAiB,KAAK,IAAI,EAAE;AAAA,MAC1J,EAAE,MAAM,oBAAoB,QAAQ,QAAQ,SAAS,oBAAoB,aAAa,IAAI,OAAO,KAAK,OAAO,eAAe,iBAAiB,KAAK,IAAI,EAAE;AAAA,IAC1J;AAAA,EACF;AAAA,EAEA,MAAM,kBAAkB;AACtB,UAAM,KAAK,kBAAkB;AAC7B,UAAM,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAC/C,UAAM,iBAAiB,SAAS,CAAC;AAEjC,QAAI,CAAC,kBAAkB,eAAe,WAAW,aAAa;AAC5D,aAAO,CAAC;AAAA,IACV;AAGA,UAAM,QAAQ,CAAC;AACf,UAAM,YAAY,CAAC,oBAAoB,uBAAuB,kBAAkB,mBAAmB,gBAAgB;AAEnH,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,KAAK,OAAO,eAAe,iBAAiB,KAAK,EAAE,CAAC,GAAG,KAAK;AAC1F,YAAM,WAAW,UAAU,IAAI,UAAU,MAAM;AAC/C,YAAM,SAAS,MAAM,IAAI,YAAY,MAAM,IAAI,YAAY;AAC3D,YAAM,WAAW,WAAW,cAAc,SAAS,WAAW,YAAY,QAAQ;AAElF,YAAM,KAAK;AAAA,QACT,IAAI,QAAQ,IAAI,CAAC;AAAA,QACjB,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,OAAO,CAAC,cAAc,sBAAsB,cAAc,WAAW,EAAE,IAAI,CAAC;AAAA,QAC5E,YAAY,IAAI,KAAK,KAAK,IAAI,IAAK,IAAI,KAAK,KAAK,GAAK,EAAE,YAAY;AAAA,QACpE,QAAQ,WAAW,cAAc,0CAA0C;AAAA,MAC7E,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,mBAAmB;AACvB,UAAM,KAAK,kBAAkB;AAC7B,UAAM,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAC/C,UAAM,iBAAiB,SAAS,CAAC;AAEjC,QAAI,CAAC,gBAAgB;AACnB,aAAO,CAAC;AAAA,IACV;AAGA,UAAM,SAAS,CAAC;AAChB,UAAM,WAAW,eAAe,cAAc;AAE9C,QAAI,WAAW,GAAG;AAChB,YAAM,aAAa,CAAC,2BAA2B,0BAA0B,UAAU,aAAa,qBAAqB;AACrH,YAAM,aAAa,CAAC,YAAY,QAAQ,UAAU,KAAK;AAEvD,eAAS,IAAI,GAAG,IAAI,KAAK,IAAI,UAAU,CAAC,GAAG,KAAK;AAC9C,eAAO,KAAK;AAAA,UACV,IAAI,SAAS,IAAI,CAAC;AAAA,UAClB,OAAO,WAAW,IAAI,WAAW,MAAM;AAAA,UACvC,aAAa;AAAA,UACb,UAAU,WAAW,IAAI,WAAW,MAAM;AAAA,UAC1C,QAAQ;AAAA,UACR,eAAe,IAAI,KAAK,KAAK,IAAI,IAAK,IAAI,KAAK,KAAK,GAAK,EAAE,YAAY;AAAA,UACvE,OAAO,CAAC,cAAc,sBAAsB,YAAY,EAAE,IAAI,CAAC;AAAA,QACjE,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ;AAAA,EAEd;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../node_modules/tsup/assets/esm_shims.js","../../database/sqlite.ts","../../database/index.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","import Database from 'better-sqlite3';\nimport { mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { TestSession, Action, Bug, KanbanTicket } from './index.js';\n\nexport class OpenQASQLiteDatabase {\n private db: Database.Database;\n\n constructor(dbPath: string = './data/openqa.db') {\n mkdirSync(dirname(dbPath), { recursive: true });\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.migrate();\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS config (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS test_sessions (\n id TEXT PRIMARY KEY,\n started_at TEXT NOT NULL,\n ended_at TEXT,\n status TEXT NOT NULL DEFAULT 'running',\n total_actions INTEGER NOT NULL DEFAULT 0,\n bugs_found INTEGER NOT NULL DEFAULT 0,\n metadata TEXT\n );\n\n CREATE TABLE IF NOT EXISTS actions (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n type TEXT NOT NULL,\n description TEXT NOT NULL,\n input TEXT,\n output TEXT,\n screenshot_path TEXT,\n FOREIGN KEY (session_id) REFERENCES test_sessions(id)\n );\n\n CREATE TABLE IF NOT EXISTS bugs (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT NOT NULL,\n severity TEXT NOT NULL DEFAULT 'medium',\n status TEXT NOT NULL DEFAULT 'open',\n github_issue_url TEXT,\n screenshot_path TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n FOREIGN KEY (session_id) REFERENCES test_sessions(id)\n );\n\n CREATE TABLE IF NOT EXISTS kanban_tickets (\n id TEXT PRIMARY KEY,\n bug_id TEXT,\n title TEXT NOT NULL,\n description TEXT NOT NULL,\n priority TEXT NOT NULL DEFAULT 'medium',\n column TEXT NOT NULL DEFAULT 'backlog',\n tags TEXT,\n screenshot_url TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n `);\n }\n\n // ── Config ──\n\n async getConfig(key: string): Promise<string | null> {\n const row = this.db.prepare('SELECT value FROM config WHERE key = ?').get(key) as { value: string } | undefined;\n return row?.value ?? null;\n }\n\n async setConfig(key: string, value: string): Promise<void> {\n this.db.prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)').run(key, value);\n }\n\n async getAllConfig(): Promise<Record<string, string>> {\n const rows = this.db.prepare('SELECT key, value FROM config').all() as Array<{ key: string; value: string }>;\n return Object.fromEntries(rows.map(r => [r.key, r.value]));\n }\n\n async clearAllConfig(): Promise<void> {\n this.db.prepare('DELETE FROM config').run();\n }\n\n // ── Sessions ──\n\n async createSession(id: string, metadata?: Record<string, unknown>): Promise<TestSession> {\n const session: TestSession = {\n id,\n started_at: new Date().toISOString(),\n status: 'running',\n total_actions: 0,\n bugs_found: 0,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n };\n this.db.prepare(`\n INSERT INTO test_sessions (id, started_at, status, total_actions, bugs_found, metadata)\n VALUES (?, ?, ?, ?, ?, ?)\n `).run(session.id, session.started_at, session.status, session.total_actions, session.bugs_found, session.metadata ?? null);\n return session;\n }\n\n async getSession(id: string): Promise<TestSession | null> {\n return (this.db.prepare('SELECT * FROM test_sessions WHERE id = ?').get(id) as TestSession) ?? null;\n }\n\n async updateSession(id: string, updates: Partial<TestSession>): Promise<void> {\n const fields = Object.keys(updates).map(k => `${k} = ?`).join(', ');\n const values = [...Object.values(updates), id];\n this.db.prepare(`UPDATE test_sessions SET ${fields} WHERE id = ?`).run(...values);\n }\n\n async getRecentSessions(limit = 10): Promise<TestSession[]> {\n return this.db.prepare('SELECT * FROM test_sessions ORDER BY started_at DESC LIMIT ?').all(limit) as TestSession[];\n }\n\n // ── Actions ──\n\n async createAction(action: Omit<Action, 'id' | 'timestamp'>): Promise<Action> {\n const newAction: Action = {\n id: `action_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n timestamp: new Date().toISOString(),\n ...action,\n };\n this.db.prepare(`\n INSERT INTO actions (id, session_id, timestamp, type, description, input, output, screenshot_path)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n newAction.id, newAction.session_id, newAction.timestamp,\n newAction.type, newAction.description,\n newAction.input ?? null, newAction.output ?? null, newAction.screenshot_path ?? null,\n );\n return newAction;\n }\n\n async getSessionActions(sessionId: string): Promise<Action[]> {\n return this.db.prepare('SELECT * FROM actions WHERE session_id = ? ORDER BY timestamp DESC').all(sessionId) as Action[];\n }\n\n // ── Bugs ──\n\n async createBug(bug: Omit<Bug, 'id' | 'created_at' | 'updated_at'>): Promise<Bug> {\n const newBug: Bug = {\n id: `bug_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n ...bug,\n };\n this.db.prepare(`\n INSERT INTO bugs (id, session_id, title, description, severity, status, github_issue_url, screenshot_path, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n newBug.id, newBug.session_id, newBug.title, newBug.description,\n newBug.severity, newBug.status,\n newBug.github_issue_url ?? null, newBug.screenshot_path ?? null,\n newBug.created_at, newBug.updated_at,\n );\n return newBug;\n }\n\n async updateBug(id: string, updates: Partial<Bug>): Promise<void> {\n const patch = { ...updates, updated_at: new Date().toISOString() };\n const fields = Object.keys(patch).map(k => `${k} = ?`).join(', ');\n const values = [...Object.values(patch), id];\n this.db.prepare(`UPDATE bugs SET ${fields} WHERE id = ?`).run(...values);\n }\n\n async getAllBugs(): Promise<Bug[]> {\n return this.db.prepare('SELECT * FROM bugs ORDER BY created_at DESC').all() as Bug[];\n }\n\n async getBugsByStatus(status: Bug['status']): Promise<Bug[]> {\n return this.db.prepare('SELECT * FROM bugs WHERE status = ? ORDER BY created_at DESC').all(status) as Bug[];\n }\n\n // ── Kanban ──\n\n async createKanbanTicket(ticket: Omit<KanbanTicket, 'id' | 'created_at' | 'updated_at'>): Promise<KanbanTicket> {\n const newTicket: KanbanTicket = {\n id: `ticket_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n ...ticket,\n };\n this.db.prepare(`\n INSERT INTO kanban_tickets (id, bug_id, title, description, priority, column, tags, screenshot_url, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n newTicket.id, newTicket.bug_id ?? null, newTicket.title, newTicket.description,\n newTicket.priority, newTicket.column,\n newTicket.tags ?? null, newTicket.screenshot_url ?? null,\n newTicket.created_at, newTicket.updated_at,\n );\n return newTicket;\n }\n\n async updateKanbanTicket(id: string, updates: Partial<KanbanTicket>): Promise<void> {\n const patch = { ...updates, updated_at: new Date().toISOString() };\n const fields = Object.keys(patch).map(k => `\"${k}\" = ?`).join(', ');\n const values = [...Object.values(patch), id];\n this.db.prepare(`UPDATE kanban_tickets SET ${fields} WHERE id = ?`).run(...values);\n }\n\n async getKanbanTickets(): Promise<KanbanTicket[]> {\n return this.db.prepare('SELECT * FROM kanban_tickets ORDER BY created_at DESC').all() as KanbanTicket[];\n }\n\n async getKanbanTicketsByColumn(column: KanbanTicket['column']): Promise<KanbanTicket[]> {\n return this.db.prepare('SELECT * FROM kanban_tickets WHERE \"column\" = ? ORDER BY created_at DESC').all(column) as KanbanTicket[];\n }\n\n async deleteKanbanTicket(id: string): Promise<void> {\n this.db.prepare('DELETE FROM kanban_tickets WHERE id = ?').run(id);\n }\n\n // ── Storage / Cleanup ──\n\n async pruneOldSessions(maxAgeDays: number): Promise<{ sessionsRemoved: number; actionsRemoved: number }> {\n const cutoff = new Date(Date.now() - maxAgeDays * 86400000).toISOString();\n const oldSessions = this.db.prepare('SELECT id FROM test_sessions WHERE started_at < ?').all(cutoff) as Array<{ id: string }>;\n const ids = oldSessions.map(s => s.id);\n if (ids.length === 0) return { sessionsRemoved: 0, actionsRemoved: 0 };\n\n const placeholders = ids.map(() => '?').join(', ');\n const actionsResult = this.db.prepare(`DELETE FROM actions WHERE session_id IN (${placeholders})`).run(...ids);\n this.db.prepare(`DELETE FROM test_sessions WHERE id IN (${placeholders})`).run(...ids);\n\n return { sessionsRemoved: ids.length, actionsRemoved: actionsResult.changes };\n }\n\n async getStorageStats(): Promise<{ sessions: number; actions: number; bugs: number; tickets: number }> {\n const count = (table: string): number =>\n (this.db.prepare(`SELECT COUNT(*) as n FROM ${table}`).get() as { n: number }).n;\n return {\n sessions: count('test_sessions'),\n actions: count('actions'),\n bugs: count('bugs'),\n tickets: count('kanban_tickets'),\n };\n }\n\n // ── Compat helpers (used by daemon.ts) ──\n\n async getActiveAgents() {\n const sessions = await this.getRecentSessions(1);\n const current = sessions[0];\n const isRunning = current?.status === 'running';\n return [{\n name: 'Main Agent',\n status: isRunning ? 'running' : 'idle',\n purpose: 'Autonomous QA orchestration',\n performance: current ? Math.min(100, Math.round((current.total_actions / 100) * 100)) : 0,\n tasks: current?.total_actions ?? 0,\n }];\n }\n\n async getCurrentTasks() {\n const sessions = await this.getRecentSessions(1);\n if (!sessions[0]) return [];\n const actions = await this.getSessionActions(sessions[0].id);\n return actions.slice(0, 10).map((a, i) => ({\n id: a.id, name: a.type, status: i === 0 && sessions[0].status === 'running' ? 'running' : 'completed',\n progress: i === 0 && sessions[0].status === 'running' ? '65%' : '100%',\n agent: 'Main Agent', started_at: a.timestamp, result: a.output || a.description,\n }));\n }\n\n async getCurrentIssues() {\n const bugs = await this.getAllBugs();\n return bugs.slice(0, 10).map(b => ({\n id: b.id, title: b.title, description: b.description,\n severity: b.severity, status: b.status, discovered_at: b.created_at, agent: 'Main Agent',\n }));\n }\n\n async close(): Promise<void> {\n this.db.close();\n }\n}\n","import { Low } from 'lowdb';\nimport { JSONFile } from 'lowdb/node';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { mkdirSync, writeFileSync, readFileSync } from 'fs';\n\nexport { OpenQASQLiteDatabase } from './sqlite.js';\n\n/**\n * Factory — chooses SQLite for .db paths, LowDB for .json paths.\n */\nexport async function createDatabase(path: string): Promise<OpenQADatabase> {\n if (path.endsWith('.db')) {\n const { OpenQASQLiteDatabase } = await import('./sqlite.js');\n return new OpenQASQLiteDatabase(path) as unknown as OpenQADatabase;\n }\n return new OpenQADatabase(path);\n}\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nexport interface TestSession {\n id: string;\n started_at: string;\n ended_at?: string;\n status: 'running' | 'completed' | 'failed';\n total_actions: number;\n bugs_found: number;\n metadata?: string;\n}\n\nexport interface Action {\n id: string;\n session_id: string;\n timestamp: string;\n type: string;\n description: string;\n input?: string;\n output?: string;\n screenshot_path?: string;\n}\n\nexport interface Bug {\n id: string;\n session_id: string;\n title: string;\n description: string;\n severity: 'low' | 'medium' | 'high' | 'critical';\n status: 'open' | 'in-progress' | 'resolved' | 'closed';\n github_issue_url?: string;\n screenshot_path?: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface KanbanTicket {\n id: string;\n bug_id?: string;\n title: string;\n description: string;\n priority: 'low' | 'medium' | 'high' | 'critical';\n column: 'backlog' | 'to-do' | 'in-progress' | 'done';\n tags?: string;\n screenshot_url?: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface User {\n id: string;\n username: string;\n passwordHash: string;\n role: 'admin' | 'viewer';\n createdAt: string;\n updatedAt: string;\n}\n\ninterface DatabaseSchema {\n config: Record<string, string>;\n test_sessions: TestSession[];\n actions: Action[];\n bugs: Bug[];\n kanban_tickets: KanbanTicket[];\n users: User[];\n}\n\nexport class OpenQADatabase {\n private db: Low<DatabaseSchema> | null = null;\n\n constructor(private dbPath: string = './data/openqa.json') {\n this.initialize();\n }\n\n private initialize() {\n const dir = dirname(this.dbPath);\n mkdirSync(dir, { recursive: true });\n\n const adapter = new JSONFile<DatabaseSchema>(this.dbPath);\n this.db = new Low<DatabaseSchema>(adapter, {\n config: {},\n test_sessions: [],\n actions: [],\n bugs: [],\n kanban_tickets: [],\n users: [],\n });\n\n this.db.read();\n if (!this.db.data) {\n this.db.data = {\n config: {},\n test_sessions: [],\n actions: [],\n bugs: [],\n kanban_tickets: [],\n users: [],\n };\n this.db.write();\n }\n }\n\n private async ensureInitialized() {\n if (!this.db) {\n this.initialize();\n }\n await this.db!.read();\n // Auto-migrate: add any missing top-level arrays added in later versions\n let migrated = false;\n if (!this.db!.data.users) { this.db!.data.users = []; migrated = true; }\n if (migrated) await this.db!.write();\n }\n\n async getConfig(key: string): Promise<string | null> {\n await this.ensureInitialized();\n return this.db!.data.config[key] || null;\n }\n\n async setConfig(key: string, value: string) {\n await this.ensureInitialized();\n this.db!.data.config[key] = value;\n await this.db!.write();\n }\n\n async getAllConfig(): Promise<Record<string, string>> {\n await this.ensureInitialized();\n return this.db!.data.config;\n }\n\n async createSession(id: string, metadata?: Record<string, unknown>): Promise<TestSession> {\n await this.ensureInitialized();\n const session: TestSession = {\n id,\n started_at: new Date().toISOString(),\n status: 'running',\n total_actions: 0,\n bugs_found: 0,\n metadata: metadata ? JSON.stringify(metadata) : undefined\n };\n this.db!.data.test_sessions.push(session);\n await this.db!.write();\n return session;\n }\n\n async getSession(id: string): Promise<TestSession | null> {\n await this.ensureInitialized();\n return this.db!.data.test_sessions.find(s => s.id === id) || null;\n }\n\n async updateSession(id: string, updates: Partial<TestSession>) {\n await this.ensureInitialized();\n const index = this.db!.data.test_sessions.findIndex(s => s.id === id);\n if (index !== -1) {\n this.db!.data.test_sessions[index] = { ...this.db!.data.test_sessions[index], ...updates };\n await this.db!.write();\n }\n }\n\n async getRecentSessions(limit: number = 10): Promise<TestSession[]> {\n await this.ensureInitialized();\n return this.db!.data.test_sessions\n .sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime())\n .slice(0, limit);\n }\n\n async createAction(action: Omit<Action, 'id' | 'timestamp'>): Promise<Action> {\n await this.ensureInitialized();\n const newAction: Action = {\n id: `action_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n timestamp: new Date().toISOString(),\n ...action\n };\n this.db!.data.actions.push(newAction);\n await this.db!.write();\n return newAction;\n }\n\n async getSessionActions(sessionId: string): Promise<Action[]> {\n await this.ensureInitialized();\n return this.db!.data.actions\n .filter(a => a.session_id === sessionId)\n .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());\n }\n\n async createBug(bug: Omit<Bug, 'id' | 'created_at' | 'updated_at'>): Promise<Bug> {\n await this.ensureInitialized();\n const newBug: Bug = {\n id: `bug_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n ...bug\n };\n this.db!.data.bugs.push(newBug);\n await this.db!.write();\n return newBug;\n }\n\n async updateBug(id: string, updates: Partial<Bug>) {\n await this.ensureInitialized();\n const index = this.db!.data.bugs.findIndex(b => b.id === id);\n if (index !== -1) {\n this.db!.data.bugs[index] = { \n ...this.db!.data.bugs[index], \n ...updates, \n updated_at: new Date().toISOString() \n };\n await this.db!.write();\n }\n }\n\n async getAllBugs(): Promise<Bug[]> {\n await this.ensureInitialized();\n return this.db!.data.bugs.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n }\n\n async getBugsByStatus(status: Bug['status']): Promise<Bug[]> {\n await this.ensureInitialized();\n return this.db!.data.bugs\n .filter(b => b.status === status)\n .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n }\n\n async createKanbanTicket(ticket: Omit<KanbanTicket, 'id' | 'created_at' | 'updated_at'>): Promise<KanbanTicket> {\n await this.ensureInitialized();\n const newTicket: KanbanTicket = {\n id: `ticket_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n ...ticket\n };\n this.db!.data.kanban_tickets.push(newTicket);\n await this.db!.write();\n return newTicket;\n }\n\n async updateKanbanTicket(id: string, updates: Partial<KanbanTicket>) {\n await this.ensureInitialized();\n const index = this.db!.data.kanban_tickets.findIndex(t => t.id === id);\n if (index !== -1) {\n this.db!.data.kanban_tickets[index] = { \n ...this.db!.data.kanban_tickets[index], \n ...updates, \n updated_at: new Date().toISOString() \n };\n await this.db!.write();\n }\n }\n\n async getKanbanTickets(): Promise<KanbanTicket[]> {\n await this.ensureInitialized();\n return this.db!.data.kanban_tickets.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n }\n\n async getKanbanTicketsByColumn(column: KanbanTicket['column']): Promise<KanbanTicket[]> {\n await this.ensureInitialized();\n return this.db!.data.kanban_tickets\n .filter(t => t.column === column)\n .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n }\n\n async deleteKanbanTicket(id: string): Promise<void> {\n await this.ensureInitialized();\n const index = this.db!.data.kanban_tickets.findIndex(t => t.id === id);\n if (index !== -1) {\n this.db!.data.kanban_tickets.splice(index, 1);\n await this.db!.write();\n }\n }\n\n async clearAllConfig() {\n await this.ensureInitialized();\n this.db!.data.config = {};\n await this.db!.write();\n }\n\n // Get real data methods - connected to actual database records\n\n async getActiveAgents() {\n await this.ensureInitialized();\n const sessions = await this.getRecentSessions(1);\n const currentSession = sessions[0];\n \n // Return real agent state based on session status\n const isRunning = currentSession?.status === 'running';\n const totalActions = currentSession?.total_actions || 0;\n \n // Main agent is always present\n const agents = [\n { \n name: 'Main Agent', \n status: isRunning ? 'running' : 'idle', \n purpose: 'Autonomous QA orchestration', \n performance: totalActions > 0 ? Math.min(100, Math.round((totalActions / 100) * 100)) : 0, \n tasks: totalActions \n }\n ];\n \n // Add dynamic agents based on actual session activity\n if (currentSession && totalActions > 0) {\n const actions = await this.getSessionActions(currentSession.id);\n \n // Group actions by type to determine which specialists are active\n const actionTypes = actions.reduce((acc: Record<string, number>, action) => {\n const type = action.type || 'unknown';\n acc[type] = (acc[type] || 0) + 1;\n return acc;\n }, {});\n \n // Add specialists based on actual action types\n if (actionTypes['navigate'] || actionTypes['click'] || actionTypes['screenshot']) {\n agents.push({\n name: 'Browser Specialist',\n status: isRunning ? 'running' : 'idle',\n purpose: 'UI navigation and interaction',\n performance: Math.round(((actionTypes['navigate'] || 0) + (actionTypes['click'] || 0)) / totalActions * 100),\n tasks: (actionTypes['navigate'] || 0) + (actionTypes['click'] || 0)\n });\n }\n \n if (actionTypes['api_call'] || actionTypes['request']) {\n agents.push({\n name: 'API Tester',\n status: isRunning ? 'running' : 'idle',\n purpose: 'API endpoint testing',\n performance: Math.round((actionTypes['api_call'] || actionTypes['request'] || 0) / totalActions * 100),\n tasks: actionTypes['api_call'] || actionTypes['request'] || 0\n });\n }\n \n if (actionTypes['auth'] || actionTypes['login']) {\n agents.push({\n name: 'Auth Specialist',\n status: isRunning ? 'running' : 'idle',\n purpose: 'Authentication testing',\n performance: Math.round((actionTypes['auth'] || actionTypes['login'] || 0) / totalActions * 100),\n tasks: actionTypes['auth'] || actionTypes['login'] || 0\n });\n }\n }\n \n return agents;\n }\n\n async getCurrentTasks() {\n await this.ensureInitialized();\n const sessions = await this.getRecentSessions(1);\n const currentSession = sessions[0];\n \n if (!currentSession) {\n return [];\n }\n\n // Get real actions from the session\n const actions = await this.getSessionActions(currentSession.id);\n \n // Convert recent actions to tasks\n const recentActions = actions.slice(-10).reverse();\n \n return recentActions.map((action, index) => ({\n id: action.id,\n name: action.type || 'Unknown Action',\n status: index === 0 && currentSession.status === 'running' ? 'running' : 'completed',\n progress: index === 0 && currentSession.status === 'running' ? '65%' : '100%',\n agent: 'Main Agent',\n started_at: action.timestamp,\n result: action.output || action.description || 'Completed'\n }));\n }\n\n async getCurrentIssues() {\n await this.ensureInitialized();\n \n // Get real bugs from the database\n const bugs = await this.getAllBugs();\n \n // Return actual bugs as issues\n return bugs.slice(0, 10).map(bug => ({\n id: bug.id,\n title: bug.title,\n description: bug.description,\n severity: bug.severity || 'medium',\n status: bug.status || 'open',\n discovered_at: bug.created_at,\n agent: 'Main Agent'\n }));\n }\n\n async pruneOldSessions(maxAgeDays: number): Promise<{ sessionsRemoved: number; actionsRemoved: number }> {\n await this.ensureInitialized();\n const cutoff = new Date(Date.now() - maxAgeDays * 86400000).toISOString();\n\n const oldSessions = this.db!.data.test_sessions.filter(s => s.started_at < cutoff);\n const oldSessionIds = new Set(oldSessions.map(s => s.id));\n\n const actionsBefore = this.db!.data.actions.length;\n this.db!.data.actions = this.db!.data.actions.filter(a => !oldSessionIds.has(a.session_id));\n const actionsRemoved = actionsBefore - this.db!.data.actions.length;\n\n this.db!.data.test_sessions = this.db!.data.test_sessions.filter(s => s.started_at >= cutoff);\n await this.db!.write();\n\n return { sessionsRemoved: oldSessions.length, actionsRemoved };\n }\n\n async getStorageStats(): Promise<{ sessions: number; actions: number; bugs: number; tickets: number }> {\n await this.ensureInitialized();\n return {\n sessions: this.db!.data.test_sessions.length,\n actions: this.db!.data.actions.length,\n bugs: this.db!.data.bugs.length,\n tickets: this.db!.data.kanban_tickets.length,\n };\n }\n\n // ── User management ──────────────────────────────────────────────────────────\n\n async countUsers(): Promise<number> {\n await this.ensureInitialized();\n return this.db!.data.users.length;\n }\n\n async findUserByUsername(username: string): Promise<User | null> {\n await this.ensureInitialized();\n return this.db!.data.users.find(u => u.username === username) ?? null;\n }\n\n async getUserById(id: string): Promise<User | null> {\n await this.ensureInitialized();\n return this.db!.data.users.find(u => u.id === id) ?? null;\n }\n\n async getAllUsers(): Promise<User[]> {\n await this.ensureInitialized();\n return [...this.db!.data.users];\n }\n\n async createUser(data: { username: string; passwordHash: string; role: User['role'] }): Promise<User> {\n await this.ensureInitialized();\n const now = new Date().toISOString();\n const user: User = {\n id: `user_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,\n username: data.username,\n passwordHash: data.passwordHash,\n role: data.role,\n createdAt: now,\n updatedAt: now,\n };\n this.db!.data.users.push(user);\n await this.db!.write();\n return user;\n }\n\n async updateUser(id: string, updates: Partial<Pick<User, 'passwordHash' | 'role'>>): Promise<void> {\n await this.ensureInitialized();\n const idx = this.db!.data.users.findIndex(u => u.id === id);\n if (idx !== -1) {\n this.db!.data.users[idx] = {\n ...this.db!.data.users[idx],\n ...updates,\n updatedAt: new Date().toISOString(),\n };\n await this.db!.write();\n }\n }\n\n async deleteUser(id: string): Promise<void> {\n await this.ensureInitialized();\n const idx = this.db!.data.users.findIndex(u => u.id === id);\n if (idx !== -1) {\n this.db!.data.users.splice(idx, 1);\n await this.db!.write();\n }\n }\n\n async close() {\n // LowDB doesn't need explicit closing\n }\n}\n"],"mappings":";;;;;;;;;;;AACA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAF9B;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA,OAAO,cAAc;AACrB,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AAFxB,IAKa;AALb;AAAA;AAAA;AAAA;AAKO,IAAM,uBAAN,MAA2B;AAAA,MACxB;AAAA,MAER,YAAY,SAAiB,oBAAoB;AAC/C,kBAAU,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,aAAK,KAAK,IAAI,SAAS,MAAM;AAC7B,aAAK,GAAG,OAAO,oBAAoB;AACnC,aAAK,GAAG,OAAO,mBAAmB;AAClC,aAAK,QAAQ;AAAA,MACf;AAAA,MAEQ,UAAgB;AACtB,aAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAsDZ;AAAA,MACH;AAAA;AAAA,MAIA,MAAM,UAAU,KAAqC;AACnD,cAAM,MAAM,KAAK,GAAG,QAAQ,wCAAwC,EAAE,IAAI,GAAG;AAC7E,eAAO,KAAK,SAAS;AAAA,MACvB;AAAA,MAEA,MAAM,UAAU,KAAa,OAA8B;AACzD,aAAK,GAAG,QAAQ,0DAA0D,EAAE,IAAI,KAAK,KAAK;AAAA,MAC5F;AAAA,MAEA,MAAM,eAAgD;AACpD,cAAM,OAAO,KAAK,GAAG,QAAQ,+BAA+B,EAAE,IAAI;AAClE,eAAO,OAAO,YAAY,KAAK,IAAI,OAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAAA,MAC3D;AAAA,MAEA,MAAM,iBAAgC;AACpC,aAAK,GAAG,QAAQ,oBAAoB,EAAE,IAAI;AAAA,MAC5C;AAAA;AAAA,MAIA,MAAM,cAAc,IAAY,UAA0D;AACxF,cAAM,UAAuB;AAAA,UAC3B;AAAA,UACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,UACnC,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,YAAY;AAAA,UACZ,UAAU,WAAW,KAAK,UAAU,QAAQ,IAAI;AAAA,QAClD;AACA,aAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGf,EAAE,IAAI,QAAQ,IAAI,QAAQ,YAAY,QAAQ,QAAQ,QAAQ,eAAe,QAAQ,YAAY,QAAQ,YAAY,IAAI;AAC1H,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,WAAW,IAAyC;AACxD,eAAQ,KAAK,GAAG,QAAQ,0CAA0C,EAAE,IAAI,EAAE,KAAqB;AAAA,MACjG;AAAA,MAEA,MAAM,cAAc,IAAY,SAA8C;AAC5E,cAAM,SAAS,OAAO,KAAK,OAAO,EAAE,IAAI,OAAK,GAAG,CAAC,MAAM,EAAE,KAAK,IAAI;AAClE,cAAM,SAAS,CAAC,GAAG,OAAO,OAAO,OAAO,GAAG,EAAE;AAC7C,aAAK,GAAG,QAAQ,4BAA4B,MAAM,eAAe,EAAE,IAAI,GAAG,MAAM;AAAA,MAClF;AAAA,MAEA,MAAM,kBAAkB,QAAQ,IAA4B;AAC1D,eAAO,KAAK,GAAG,QAAQ,8DAA8D,EAAE,IAAI,KAAK;AAAA,MAClG;AAAA;AAAA,MAIA,MAAM,aAAa,QAA2D;AAC5E,cAAM,YAAoB;AAAA,UACxB,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC;AAAA,UACvE,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UAClC,GAAG;AAAA,QACL;AACA,aAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGf,EAAE;AAAA,UACD,UAAU;AAAA,UAAI,UAAU;AAAA,UAAY,UAAU;AAAA,UAC9C,UAAU;AAAA,UAAM,UAAU;AAAA,UAC1B,UAAU,SAAS;AAAA,UAAM,UAAU,UAAU;AAAA,UAAM,UAAU,mBAAmB;AAAA,QAClF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,kBAAkB,WAAsC;AAC5D,eAAO,KAAK,GAAG,QAAQ,oEAAoE,EAAE,IAAI,SAAS;AAAA,MAC5G;AAAA;AAAA,MAIA,MAAM,UAAU,KAAkE;AAChF,cAAM,SAAc;AAAA,UAClB,IAAI,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC;AAAA,UACpE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,UACnC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,UACnC,GAAG;AAAA,QACL;AACA,aAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGf,EAAE;AAAA,UACD,OAAO;AAAA,UAAI,OAAO;AAAA,UAAY,OAAO;AAAA,UAAO,OAAO;AAAA,UACnD,OAAO;AAAA,UAAU,OAAO;AAAA,UACxB,OAAO,oBAAoB;AAAA,UAAM,OAAO,mBAAmB;AAAA,UAC3D,OAAO;AAAA,UAAY,OAAO;AAAA,QAC5B;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,UAAU,IAAY,SAAsC;AAChE,cAAM,QAAQ,EAAE,GAAG,SAAS,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE;AACjE,cAAM,SAAS,OAAO,KAAK,KAAK,EAAE,IAAI,OAAK,GAAG,CAAC,MAAM,EAAE,KAAK,IAAI;AAChE,cAAM,SAAS,CAAC,GAAG,OAAO,OAAO,KAAK,GAAG,EAAE;AAC3C,aAAK,GAAG,QAAQ,mBAAmB,MAAM,eAAe,EAAE,IAAI,GAAG,MAAM;AAAA,MACzE;AAAA,MAEA,MAAM,aAA6B;AACjC,eAAO,KAAK,GAAG,QAAQ,6CAA6C,EAAE,IAAI;AAAA,MAC5E;AAAA,MAEA,MAAM,gBAAgB,QAAuC;AAC3D,eAAO,KAAK,GAAG,QAAQ,8DAA8D,EAAE,IAAI,MAAM;AAAA,MACnG;AAAA;AAAA,MAIA,MAAM,mBAAmB,QAAuF;AAC9G,cAAM,YAA0B;AAAA,UAC9B,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC;AAAA,UACvE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,UACnC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,UACnC,GAAG;AAAA,QACL;AACA,aAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGf,EAAE;AAAA,UACD,UAAU;AAAA,UAAI,UAAU,UAAU;AAAA,UAAM,UAAU;AAAA,UAAO,UAAU;AAAA,UACnE,UAAU;AAAA,UAAU,UAAU;AAAA,UAC9B,UAAU,QAAQ;AAAA,UAAM,UAAU,kBAAkB;AAAA,UACpD,UAAU;AAAA,UAAY,UAAU;AAAA,QAClC;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,mBAAmB,IAAY,SAA+C;AAClF,cAAM,QAAQ,EAAE,GAAG,SAAS,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE;AACjE,cAAM,SAAS,OAAO,KAAK,KAAK,EAAE,IAAI,OAAK,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI;AAClE,cAAM,SAAS,CAAC,GAAG,OAAO,OAAO,KAAK,GAAG,EAAE;AAC3C,aAAK,GAAG,QAAQ,6BAA6B,MAAM,eAAe,EAAE,IAAI,GAAG,MAAM;AAAA,MACnF;AAAA,MAEA,MAAM,mBAA4C;AAChD,eAAO,KAAK,GAAG,QAAQ,uDAAuD,EAAE,IAAI;AAAA,MACtF;AAAA,MAEA,MAAM,yBAAyB,QAAyD;AACtF,eAAO,KAAK,GAAG,QAAQ,0EAA0E,EAAE,IAAI,MAAM;AAAA,MAC/G;AAAA,MAEA,MAAM,mBAAmB,IAA2B;AAClD,aAAK,GAAG,QAAQ,yCAAyC,EAAE,IAAI,EAAE;AAAA,MACnE;AAAA;AAAA,MAIA,MAAM,iBAAiB,YAAkF;AACvG,cAAM,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,aAAa,KAAQ,EAAE,YAAY;AACxE,cAAM,cAAc,KAAK,GAAG,QAAQ,mDAAmD,EAAE,IAAI,MAAM;AACnG,cAAM,MAAM,YAAY,IAAI,OAAK,EAAE,EAAE;AACrC,YAAI,IAAI,WAAW,EAAG,QAAO,EAAE,iBAAiB,GAAG,gBAAgB,EAAE;AAErE,cAAM,eAAe,IAAI,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACjD,cAAM,gBAAgB,KAAK,GAAG,QAAQ,4CAA4C,YAAY,GAAG,EAAE,IAAI,GAAG,GAAG;AAC7G,aAAK,GAAG,QAAQ,0CAA0C,YAAY,GAAG,EAAE,IAAI,GAAG,GAAG;AAErF,eAAO,EAAE,iBAAiB,IAAI,QAAQ,gBAAgB,cAAc,QAAQ;AAAA,MAC9E;AAAA,MAEA,MAAM,kBAAiG;AACrG,cAAM,QAAQ,CAAC,UACZ,KAAK,GAAG,QAAQ,6BAA6B,KAAK,EAAE,EAAE,IAAI,EAAoB;AACjF,eAAO;AAAA,UACL,UAAU,MAAM,eAAe;AAAA,UAC/B,SAAS,MAAM,SAAS;AAAA,UACxB,MAAM,MAAM,MAAM;AAAA,UAClB,SAAS,MAAM,gBAAgB;AAAA,QACjC;AAAA,MACF;AAAA;AAAA,MAIA,MAAM,kBAAkB;AACtB,cAAM,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAC/C,cAAM,UAAU,SAAS,CAAC;AAC1B,cAAM,YAAY,SAAS,WAAW;AACtC,eAAO,CAAC;AAAA,UACN,MAAM;AAAA,UACN,QAAQ,YAAY,YAAY;AAAA,UAChC,SAAS;AAAA,UACT,aAAa,UAAU,KAAK,IAAI,KAAK,KAAK,MAAO,QAAQ,gBAAgB,MAAO,GAAG,CAAC,IAAI;AAAA,UACxF,OAAO,SAAS,iBAAiB;AAAA,QACnC,CAAC;AAAA,MACH;AAAA,MAEA,MAAM,kBAAkB;AACtB,cAAM,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAC/C,YAAI,CAAC,SAAS,CAAC,EAAG,QAAO,CAAC;AAC1B,cAAM,UAAU,MAAM,KAAK,kBAAkB,SAAS,CAAC,EAAE,EAAE;AAC3D,eAAO,QAAQ,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,GAAG,OAAO;AAAA,UACzC,IAAI,EAAE;AAAA,UAAI,MAAM,EAAE;AAAA,UAAM,QAAQ,MAAM,KAAK,SAAS,CAAC,EAAE,WAAW,YAAY,YAAY;AAAA,UAC1F,UAAU,MAAM,KAAK,SAAS,CAAC,EAAE,WAAW,YAAY,QAAQ;AAAA,UAChE,OAAO;AAAA,UAAc,YAAY,EAAE;AAAA,UAAW,QAAQ,EAAE,UAAU,EAAE;AAAA,QACtE,EAAE;AAAA,MACJ;AAAA,MAEA,MAAM,mBAAmB;AACvB,cAAM,OAAO,MAAM,KAAK,WAAW;AACnC,eAAO,KAAK,MAAM,GAAG,EAAE,EAAE,IAAI,QAAM;AAAA,UACjC,IAAI,EAAE;AAAA,UAAI,OAAO,EAAE;AAAA,UAAO,aAAa,EAAE;AAAA,UACzC,UAAU,EAAE;AAAA,UAAU,QAAQ,EAAE;AAAA,UAAQ,eAAe,EAAE;AAAA,UAAY,OAAO;AAAA,QAC9E,EAAE;AAAA,MACJ;AAAA,MAEA,MAAM,QAAuB;AAC3B,aAAK,GAAG,MAAM;AAAA,MAChB;AAAA,IACF;AAAA;AAAA;;;AChSA;AAMA;AANA,SAAS,WAAW;AACpB,SAAS,gBAAgB;AACzB,SAAe,WAAAA,gBAAe;AAC9B,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,aAAAC,kBAA8C;AAOvD,eAAsB,eAAeC,OAAuC;AAC1E,MAAIA,MAAK,SAAS,KAAK,GAAG;AACxB,UAAM,EAAE,sBAAAC,sBAAqB,IAAI,MAAM;AACvC,WAAO,IAAIA,sBAAqBD,KAAI;AAAA,EACtC;AACA,SAAO,IAAI,eAAeA,KAAI;AAChC;AAEA,IAAME,cAAaJ,eAAc,YAAY,GAAG;AAChD,IAAMK,aAAYN,SAAQK,WAAU;AAmE7B,IAAM,iBAAN,MAAqB;AAAA,EAG1B,YAAoB,SAAiB,sBAAsB;AAAvC;AAClB,SAAK,WAAW;AAAA,EAClB;AAAA,EAJQ,KAAiC;AAAA,EAMjC,aAAa;AACnB,UAAM,MAAML,SAAQ,KAAK,MAAM;AAC/B,IAAAE,WAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAElC,UAAM,UAAU,IAAI,SAAyB,KAAK,MAAM;AACxD,SAAK,KAAK,IAAI,IAAoB,SAAS;AAAA,MACzC,QAAQ,CAAC;AAAA,MACT,eAAe,CAAC;AAAA,MAChB,SAAS,CAAC;AAAA,MACV,MAAM,CAAC;AAAA,MACP,gBAAgB,CAAC;AAAA,MACjB,OAAO,CAAC;AAAA,IACV,CAAC;AAED,SAAK,GAAG,KAAK;AACb,QAAI,CAAC,KAAK,GAAG,MAAM;AACjB,WAAK,GAAG,OAAO;AAAA,QACb,QAAQ,CAAC;AAAA,QACT,eAAe,CAAC;AAAA,QAChB,SAAS,CAAC;AAAA,QACV,MAAM,CAAC;AAAA,QACP,gBAAgB,CAAC;AAAA,QACjB,OAAO,CAAC;AAAA,MACV;AACA,WAAK,GAAG,MAAM;AAAA,IAChB;AAAA,EACF;AAAA,EAEA,MAAc,oBAAoB;AAChC,QAAI,CAAC,KAAK,IAAI;AACZ,WAAK,WAAW;AAAA,IAClB;AACA,UAAM,KAAK,GAAI,KAAK;AAEpB,QAAI,WAAW;AACf,QAAI,CAAC,KAAK,GAAI,KAAK,OAAO;AAAE,WAAK,GAAI,KAAK,QAAQ,CAAC;AAAG,iBAAW;AAAA,IAAM;AACvE,QAAI,SAAU,OAAM,KAAK,GAAI,MAAM;AAAA,EACrC;AAAA,EAEA,MAAM,UAAU,KAAqC;AACnD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,OAAO,GAAG,KAAK;AAAA,EACtC;AAAA,EAEA,MAAM,UAAU,KAAa,OAAe;AAC1C,UAAM,KAAK,kBAAkB;AAC7B,SAAK,GAAI,KAAK,OAAO,GAAG,IAAI;AAC5B,UAAM,KAAK,GAAI,MAAM;AAAA,EACvB;AAAA,EAEA,MAAM,eAAgD;AACpD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,cAAc,IAAY,UAA0D;AACxF,UAAM,KAAK,kBAAkB;AAC7B,UAAM,UAAuB;AAAA,MAC3B;AAAA,MACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,QAAQ;AAAA,MACR,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,UAAU,WAAW,KAAK,UAAU,QAAQ,IAAI;AAAA,IAClD;AACA,SAAK,GAAI,KAAK,cAAc,KAAK,OAAO;AACxC,UAAM,KAAK,GAAI,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,IAAyC;AACxD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,cAAc,KAAK,OAAK,EAAE,OAAO,EAAE,KAAK;AAAA,EAC/D;AAAA,EAEA,MAAM,cAAc,IAAY,SAA+B;AAC7D,UAAM,KAAK,kBAAkB;AAC7B,UAAM,QAAQ,KAAK,GAAI,KAAK,cAAc,UAAU,OAAK,EAAE,OAAO,EAAE;AACpE,QAAI,UAAU,IAAI;AAChB,WAAK,GAAI,KAAK,cAAc,KAAK,IAAI,EAAE,GAAG,KAAK,GAAI,KAAK,cAAc,KAAK,GAAG,GAAG,QAAQ;AACzF,YAAM,KAAK,GAAI,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,kBAAkB,QAAgB,IAA4B;AAClE,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,cAClB,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAClF,MAAM,GAAG,KAAK;AAAA,EACnB;AAAA,EAEA,MAAM,aAAa,QAA2D;AAC5E,UAAM,KAAK,kBAAkB;AAC7B,UAAM,YAAoB;AAAA,MACxB,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAAA,MACnE,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,GAAG;AAAA,IACL;AACA,SAAK,GAAI,KAAK,QAAQ,KAAK,SAAS;AACpC,UAAM,KAAK,GAAI,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,kBAAkB,WAAsC;AAC5D,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,QAClB,OAAO,OAAK,EAAE,eAAe,SAAS,EACtC,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EACrF;AAAA,EAEA,MAAM,UAAU,KAAkE;AAChF,UAAM,KAAK,kBAAkB;AAC7B,UAAM,SAAc;AAAA,MAClB,IAAI,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAAA,MAChE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,GAAG;AAAA,IACL;AACA,SAAK,GAAI,KAAK,KAAK,KAAK,MAAM;AAC9B,UAAM,KAAK,GAAI,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAU,IAAY,SAAuB;AACjD,UAAM,KAAK,kBAAkB;AAC7B,UAAM,QAAQ,KAAK,GAAI,KAAK,KAAK,UAAU,OAAK,EAAE,OAAO,EAAE;AAC3D,QAAI,UAAU,IAAI;AAChB,WAAK,GAAI,KAAK,KAAK,KAAK,IAAI;AAAA,QAC1B,GAAG,KAAK,GAAI,KAAK,KAAK,KAAK;AAAA,QAC3B,GAAG;AAAA,QACH,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC;AACA,YAAM,KAAK,GAAI,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,aAA6B;AACjC,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,KAAK,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC;AAAA,EAC9G;AAAA,EAEA,MAAM,gBAAgB,QAAuC;AAC3D,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,KAClB,OAAO,OAAK,EAAE,WAAW,MAAM,EAC/B,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC;AAAA,EACvF;AAAA,EAEA,MAAM,mBAAmB,QAAuF;AAC9G,UAAM,KAAK,kBAAkB;AAC7B,UAAM,YAA0B;AAAA,MAC9B,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAAA,MACnE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,GAAG;AAAA,IACL;AACA,SAAK,GAAI,KAAK,eAAe,KAAK,SAAS;AAC3C,UAAM,KAAK,GAAI,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,mBAAmB,IAAY,SAAgC;AACnE,UAAM,KAAK,kBAAkB;AAC7B,UAAM,QAAQ,KAAK,GAAI,KAAK,eAAe,UAAU,OAAK,EAAE,OAAO,EAAE;AACrE,QAAI,UAAU,IAAI;AAChB,WAAK,GAAI,KAAK,eAAe,KAAK,IAAI;AAAA,QACpC,GAAG,KAAK,GAAI,KAAK,eAAe,KAAK;AAAA,QACrC,GAAG;AAAA,QACH,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC;AACA,YAAM,KAAK,GAAI,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,mBAA4C;AAChD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,eAAe,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC;AAAA,EACxH;AAAA,EAEA,MAAM,yBAAyB,QAAyD;AACtF,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,eAClB,OAAO,OAAK,EAAE,WAAW,MAAM,EAC/B,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC;AAAA,EACvF;AAAA,EAEA,MAAM,mBAAmB,IAA2B;AAClD,UAAM,KAAK,kBAAkB;AAC7B,UAAM,QAAQ,KAAK,GAAI,KAAK,eAAe,UAAU,OAAK,EAAE,OAAO,EAAE;AACrE,QAAI,UAAU,IAAI;AAChB,WAAK,GAAI,KAAK,eAAe,OAAO,OAAO,CAAC;AAC5C,YAAM,KAAK,GAAI,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,iBAAiB;AACrB,UAAM,KAAK,kBAAkB;AAC7B,SAAK,GAAI,KAAK,SAAS,CAAC;AACxB,UAAM,KAAK,GAAI,MAAM;AAAA,EACvB;AAAA;AAAA,EAIA,MAAM,kBAAkB;AACtB,UAAM,KAAK,kBAAkB;AAC7B,UAAM,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAC/C,UAAM,iBAAiB,SAAS,CAAC;AAGjC,UAAM,YAAY,gBAAgB,WAAW;AAC7C,UAAM,eAAe,gBAAgB,iBAAiB;AAGtD,UAAM,SAAS;AAAA,MACb;AAAA,QACE,MAAM;AAAA,QACN,QAAQ,YAAY,YAAY;AAAA,QAChC,SAAS;AAAA,QACT,aAAa,eAAe,IAAI,KAAK,IAAI,KAAK,KAAK,MAAO,eAAe,MAAO,GAAG,CAAC,IAAI;AAAA,QACxF,OAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,kBAAkB,eAAe,GAAG;AACtC,YAAM,UAAU,MAAM,KAAK,kBAAkB,eAAe,EAAE;AAG9D,YAAM,cAAc,QAAQ,OAAO,CAAC,KAA6B,WAAW;AAC1E,cAAM,OAAO,OAAO,QAAQ;AAC5B,YAAI,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK;AAC/B,eAAO;AAAA,MACT,GAAG,CAAC,CAAC;AAGL,UAAI,YAAY,UAAU,KAAK,YAAY,OAAO,KAAK,YAAY,YAAY,GAAG;AAChF,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,YAAY,YAAY;AAAA,UAChC,SAAS;AAAA,UACT,aAAa,KAAK,QAAQ,YAAY,UAAU,KAAK,MAAM,YAAY,OAAO,KAAK,MAAM,eAAe,GAAG;AAAA,UAC3G,QAAQ,YAAY,UAAU,KAAK,MAAM,YAAY,OAAO,KAAK;AAAA,QACnE,CAAC;AAAA,MACH;AAEA,UAAI,YAAY,UAAU,KAAK,YAAY,SAAS,GAAG;AACrD,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,YAAY,YAAY;AAAA,UAChC,SAAS;AAAA,UACT,aAAa,KAAK,OAAO,YAAY,UAAU,KAAK,YAAY,SAAS,KAAK,KAAK,eAAe,GAAG;AAAA,UACrG,OAAO,YAAY,UAAU,KAAK,YAAY,SAAS,KAAK;AAAA,QAC9D,CAAC;AAAA,MACH;AAEA,UAAI,YAAY,MAAM,KAAK,YAAY,OAAO,GAAG;AAC/C,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,YAAY,YAAY;AAAA,UAChC,SAAS;AAAA,UACT,aAAa,KAAK,OAAO,YAAY,MAAM,KAAK,YAAY,OAAO,KAAK,KAAK,eAAe,GAAG;AAAA,UAC/F,OAAO,YAAY,MAAM,KAAK,YAAY,OAAO,KAAK;AAAA,QACxD,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,kBAAkB;AACtB,UAAM,KAAK,kBAAkB;AAC7B,UAAM,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAC/C,UAAM,iBAAiB,SAAS,CAAC;AAEjC,QAAI,CAAC,gBAAgB;AACnB,aAAO,CAAC;AAAA,IACV;AAGA,UAAM,UAAU,MAAM,KAAK,kBAAkB,eAAe,EAAE;AAG9D,UAAM,gBAAgB,QAAQ,MAAM,GAAG,EAAE,QAAQ;AAEjD,WAAO,cAAc,IAAI,CAAC,QAAQ,WAAW;AAAA,MAC3C,IAAI,OAAO;AAAA,MACX,MAAM,OAAO,QAAQ;AAAA,MACrB,QAAQ,UAAU,KAAK,eAAe,WAAW,YAAY,YAAY;AAAA,MACzE,UAAU,UAAU,KAAK,eAAe,WAAW,YAAY,QAAQ;AAAA,MACvE,OAAO;AAAA,MACP,YAAY,OAAO;AAAA,MACnB,QAAQ,OAAO,UAAU,OAAO,eAAe;AAAA,IACjD,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,mBAAmB;AACvB,UAAM,KAAK,kBAAkB;AAG7B,UAAM,OAAO,MAAM,KAAK,WAAW;AAGnC,WAAO,KAAK,MAAM,GAAG,EAAE,EAAE,IAAI,UAAQ;AAAA,MACnC,IAAI,IAAI;AAAA,MACR,OAAO,IAAI;AAAA,MACX,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI,YAAY;AAAA,MAC1B,QAAQ,IAAI,UAAU;AAAA,MACtB,eAAe,IAAI;AAAA,MACnB,OAAO;AAAA,IACT,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,iBAAiB,YAAkF;AACvG,UAAM,KAAK,kBAAkB;AAC7B,UAAM,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,aAAa,KAAQ,EAAE,YAAY;AAExE,UAAM,cAAc,KAAK,GAAI,KAAK,cAAc,OAAO,OAAK,EAAE,aAAa,MAAM;AACjF,UAAM,gBAAgB,IAAI,IAAI,YAAY,IAAI,OAAK,EAAE,EAAE,CAAC;AAExD,UAAM,gBAAgB,KAAK,GAAI,KAAK,QAAQ;AAC5C,SAAK,GAAI,KAAK,UAAU,KAAK,GAAI,KAAK,QAAQ,OAAO,OAAK,CAAC,cAAc,IAAI,EAAE,UAAU,CAAC;AAC1F,UAAM,iBAAiB,gBAAgB,KAAK,GAAI,KAAK,QAAQ;AAE7D,SAAK,GAAI,KAAK,gBAAgB,KAAK,GAAI,KAAK,cAAc,OAAO,OAAK,EAAE,cAAc,MAAM;AAC5F,UAAM,KAAK,GAAI,MAAM;AAErB,WAAO,EAAE,iBAAiB,YAAY,QAAQ,eAAe;AAAA,EAC/D;AAAA,EAEA,MAAM,kBAAiG;AACrG,UAAM,KAAK,kBAAkB;AAC7B,WAAO;AAAA,MACL,UAAU,KAAK,GAAI,KAAK,cAAc;AAAA,MACtC,SAAS,KAAK,GAAI,KAAK,QAAQ;AAAA,MAC/B,MAAM,KAAK,GAAI,KAAK,KAAK;AAAA,MACzB,SAAS,KAAK,GAAI,KAAK,eAAe;AAAA,IACxC;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,aAA8B;AAClC,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,MAAM;AAAA,EAC7B;AAAA,EAEA,MAAM,mBAAmB,UAAwC;AAC/D,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,MAAM,KAAK,OAAK,EAAE,aAAa,QAAQ,KAAK;AAAA,EACnE;AAAA,EAEA,MAAM,YAAY,IAAkC;AAClD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,GAAI,KAAK,MAAM,KAAK,OAAK,EAAE,OAAO,EAAE,KAAK;AAAA,EACvD;AAAA,EAEA,MAAM,cAA+B;AACnC,UAAM,KAAK,kBAAkB;AAC7B,WAAO,CAAC,GAAG,KAAK,GAAI,KAAK,KAAK;AAAA,EAChC;AAAA,EAEA,MAAM,WAAW,MAAqF;AACpG,UAAM,KAAK,kBAAkB;AAC7B,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,OAAa;AAAA,MACjB,IAAI,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,MAChE,UAAU,KAAK;AAAA,MACf,cAAc,KAAK;AAAA,MACnB,MAAM,KAAK;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AACA,SAAK,GAAI,KAAK,MAAM,KAAK,IAAI;AAC7B,UAAM,KAAK,GAAI,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,IAAY,SAAsE;AACjG,UAAM,KAAK,kBAAkB;AAC7B,UAAM,MAAM,KAAK,GAAI,KAAK,MAAM,UAAU,OAAK,EAAE,OAAO,EAAE;AAC1D,QAAI,QAAQ,IAAI;AACd,WAAK,GAAI,KAAK,MAAM,GAAG,IAAI;AAAA,QACzB,GAAG,KAAK,GAAI,KAAK,MAAM,GAAG;AAAA,QAC1B,GAAG;AAAA,QACH,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AACA,YAAM,KAAK,GAAI,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,IAA2B;AAC1C,UAAM,KAAK,kBAAkB;AAC7B,UAAM,MAAM,KAAK,GAAI,KAAK,MAAM,UAAU,OAAK,EAAE,OAAO,EAAE;AAC1D,QAAI,QAAQ,IAAI;AACd,WAAK,GAAI,KAAK,MAAM,OAAO,KAAK,CAAC;AACjC,YAAM,KAAK,GAAI,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ;AAAA,EAEd;AACF;","names":["dirname","fileURLToPath","mkdirSync","path","OpenQASQLiteDatabase","__filename","__dirname"]}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// database/sqlite.ts
|
|
2
|
+
import Database from "better-sqlite3";
|
|
3
|
+
import { mkdirSync } from "fs";
|
|
4
|
+
import { dirname } from "path";
|
|
5
|
+
var OpenQASQLiteDatabase = class {
|
|
6
|
+
db;
|
|
7
|
+
constructor(dbPath = "./data/openqa.db") {
|
|
8
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
9
|
+
this.db = new Database(dbPath);
|
|
10
|
+
this.db.pragma("journal_mode = WAL");
|
|
11
|
+
this.db.pragma("foreign_keys = ON");
|
|
12
|
+
this.migrate();
|
|
13
|
+
}
|
|
14
|
+
migrate() {
|
|
15
|
+
this.db.exec(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
17
|
+
key TEXT PRIMARY KEY,
|
|
18
|
+
value TEXT NOT NULL
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE TABLE IF NOT EXISTS test_sessions (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
started_at TEXT NOT NULL,
|
|
24
|
+
ended_at TEXT,
|
|
25
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
26
|
+
total_actions INTEGER NOT NULL DEFAULT 0,
|
|
27
|
+
bugs_found INTEGER NOT NULL DEFAULT 0,
|
|
28
|
+
metadata TEXT
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS actions (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
session_id TEXT NOT NULL,
|
|
34
|
+
timestamp TEXT NOT NULL,
|
|
35
|
+
type TEXT NOT NULL,
|
|
36
|
+
description TEXT NOT NULL,
|
|
37
|
+
input TEXT,
|
|
38
|
+
output TEXT,
|
|
39
|
+
screenshot_path TEXT,
|
|
40
|
+
FOREIGN KEY (session_id) REFERENCES test_sessions(id)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS bugs (
|
|
44
|
+
id TEXT PRIMARY KEY,
|
|
45
|
+
session_id TEXT NOT NULL,
|
|
46
|
+
title TEXT NOT NULL,
|
|
47
|
+
description TEXT NOT NULL,
|
|
48
|
+
severity TEXT NOT NULL DEFAULT 'medium',
|
|
49
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
50
|
+
github_issue_url TEXT,
|
|
51
|
+
screenshot_path TEXT,
|
|
52
|
+
created_at TEXT NOT NULL,
|
|
53
|
+
updated_at TEXT NOT NULL,
|
|
54
|
+
FOREIGN KEY (session_id) REFERENCES test_sessions(id)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS kanban_tickets (
|
|
58
|
+
id TEXT PRIMARY KEY,
|
|
59
|
+
bug_id TEXT,
|
|
60
|
+
title TEXT NOT NULL,
|
|
61
|
+
description TEXT NOT NULL,
|
|
62
|
+
priority TEXT NOT NULL DEFAULT 'medium',
|
|
63
|
+
column TEXT NOT NULL DEFAULT 'backlog',
|
|
64
|
+
tags TEXT,
|
|
65
|
+
screenshot_url TEXT,
|
|
66
|
+
created_at TEXT NOT NULL,
|
|
67
|
+
updated_at TEXT NOT NULL
|
|
68
|
+
);
|
|
69
|
+
`);
|
|
70
|
+
}
|
|
71
|
+
// ── Config ──
|
|
72
|
+
async getConfig(key) {
|
|
73
|
+
const row = this.db.prepare("SELECT value FROM config WHERE key = ?").get(key);
|
|
74
|
+
return row?.value ?? null;
|
|
75
|
+
}
|
|
76
|
+
async setConfig(key, value) {
|
|
77
|
+
this.db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run(key, value);
|
|
78
|
+
}
|
|
79
|
+
async getAllConfig() {
|
|
80
|
+
const rows = this.db.prepare("SELECT key, value FROM config").all();
|
|
81
|
+
return Object.fromEntries(rows.map((r) => [r.key, r.value]));
|
|
82
|
+
}
|
|
83
|
+
async clearAllConfig() {
|
|
84
|
+
this.db.prepare("DELETE FROM config").run();
|
|
85
|
+
}
|
|
86
|
+
// ── Sessions ──
|
|
87
|
+
async createSession(id, metadata) {
|
|
88
|
+
const session = {
|
|
89
|
+
id,
|
|
90
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
91
|
+
status: "running",
|
|
92
|
+
total_actions: 0,
|
|
93
|
+
bugs_found: 0,
|
|
94
|
+
metadata: metadata ? JSON.stringify(metadata) : void 0
|
|
95
|
+
};
|
|
96
|
+
this.db.prepare(`
|
|
97
|
+
INSERT INTO test_sessions (id, started_at, status, total_actions, bugs_found, metadata)
|
|
98
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
99
|
+
`).run(session.id, session.started_at, session.status, session.total_actions, session.bugs_found, session.metadata ?? null);
|
|
100
|
+
return session;
|
|
101
|
+
}
|
|
102
|
+
async getSession(id) {
|
|
103
|
+
return this.db.prepare("SELECT * FROM test_sessions WHERE id = ?").get(id) ?? null;
|
|
104
|
+
}
|
|
105
|
+
async updateSession(id, updates) {
|
|
106
|
+
const fields = Object.keys(updates).map((k) => `${k} = ?`).join(", ");
|
|
107
|
+
const values = [...Object.values(updates), id];
|
|
108
|
+
this.db.prepare(`UPDATE test_sessions SET ${fields} WHERE id = ?`).run(...values);
|
|
109
|
+
}
|
|
110
|
+
async getRecentSessions(limit = 10) {
|
|
111
|
+
return this.db.prepare("SELECT * FROM test_sessions ORDER BY started_at DESC LIMIT ?").all(limit);
|
|
112
|
+
}
|
|
113
|
+
// ── Actions ──
|
|
114
|
+
async createAction(action) {
|
|
115
|
+
const newAction = {
|
|
116
|
+
id: `action_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
117
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
118
|
+
...action
|
|
119
|
+
};
|
|
120
|
+
this.db.prepare(`
|
|
121
|
+
INSERT INTO actions (id, session_id, timestamp, type, description, input, output, screenshot_path)
|
|
122
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
123
|
+
`).run(
|
|
124
|
+
newAction.id,
|
|
125
|
+
newAction.session_id,
|
|
126
|
+
newAction.timestamp,
|
|
127
|
+
newAction.type,
|
|
128
|
+
newAction.description,
|
|
129
|
+
newAction.input ?? null,
|
|
130
|
+
newAction.output ?? null,
|
|
131
|
+
newAction.screenshot_path ?? null
|
|
132
|
+
);
|
|
133
|
+
return newAction;
|
|
134
|
+
}
|
|
135
|
+
async getSessionActions(sessionId) {
|
|
136
|
+
return this.db.prepare("SELECT * FROM actions WHERE session_id = ? ORDER BY timestamp DESC").all(sessionId);
|
|
137
|
+
}
|
|
138
|
+
// ── Bugs ──
|
|
139
|
+
async createBug(bug) {
|
|
140
|
+
const newBug = {
|
|
141
|
+
id: `bug_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
142
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
143
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
144
|
+
...bug
|
|
145
|
+
};
|
|
146
|
+
this.db.prepare(`
|
|
147
|
+
INSERT INTO bugs (id, session_id, title, description, severity, status, github_issue_url, screenshot_path, created_at, updated_at)
|
|
148
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
149
|
+
`).run(
|
|
150
|
+
newBug.id,
|
|
151
|
+
newBug.session_id,
|
|
152
|
+
newBug.title,
|
|
153
|
+
newBug.description,
|
|
154
|
+
newBug.severity,
|
|
155
|
+
newBug.status,
|
|
156
|
+
newBug.github_issue_url ?? null,
|
|
157
|
+
newBug.screenshot_path ?? null,
|
|
158
|
+
newBug.created_at,
|
|
159
|
+
newBug.updated_at
|
|
160
|
+
);
|
|
161
|
+
return newBug;
|
|
162
|
+
}
|
|
163
|
+
async updateBug(id, updates) {
|
|
164
|
+
const patch = { ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
165
|
+
const fields = Object.keys(patch).map((k) => `${k} = ?`).join(", ");
|
|
166
|
+
const values = [...Object.values(patch), id];
|
|
167
|
+
this.db.prepare(`UPDATE bugs SET ${fields} WHERE id = ?`).run(...values);
|
|
168
|
+
}
|
|
169
|
+
async getAllBugs() {
|
|
170
|
+
return this.db.prepare("SELECT * FROM bugs ORDER BY created_at DESC").all();
|
|
171
|
+
}
|
|
172
|
+
async getBugsByStatus(status) {
|
|
173
|
+
return this.db.prepare("SELECT * FROM bugs WHERE status = ? ORDER BY created_at DESC").all(status);
|
|
174
|
+
}
|
|
175
|
+
// ── Kanban ──
|
|
176
|
+
async createKanbanTicket(ticket) {
|
|
177
|
+
const newTicket = {
|
|
178
|
+
id: `ticket_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
179
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
180
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
181
|
+
...ticket
|
|
182
|
+
};
|
|
183
|
+
this.db.prepare(`
|
|
184
|
+
INSERT INTO kanban_tickets (id, bug_id, title, description, priority, column, tags, screenshot_url, created_at, updated_at)
|
|
185
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
186
|
+
`).run(
|
|
187
|
+
newTicket.id,
|
|
188
|
+
newTicket.bug_id ?? null,
|
|
189
|
+
newTicket.title,
|
|
190
|
+
newTicket.description,
|
|
191
|
+
newTicket.priority,
|
|
192
|
+
newTicket.column,
|
|
193
|
+
newTicket.tags ?? null,
|
|
194
|
+
newTicket.screenshot_url ?? null,
|
|
195
|
+
newTicket.created_at,
|
|
196
|
+
newTicket.updated_at
|
|
197
|
+
);
|
|
198
|
+
return newTicket;
|
|
199
|
+
}
|
|
200
|
+
async updateKanbanTicket(id, updates) {
|
|
201
|
+
const patch = { ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
202
|
+
const fields = Object.keys(patch).map((k) => `"${k}" = ?`).join(", ");
|
|
203
|
+
const values = [...Object.values(patch), id];
|
|
204
|
+
this.db.prepare(`UPDATE kanban_tickets SET ${fields} WHERE id = ?`).run(...values);
|
|
205
|
+
}
|
|
206
|
+
async getKanbanTickets() {
|
|
207
|
+
return this.db.prepare("SELECT * FROM kanban_tickets ORDER BY created_at DESC").all();
|
|
208
|
+
}
|
|
209
|
+
async getKanbanTicketsByColumn(column) {
|
|
210
|
+
return this.db.prepare('SELECT * FROM kanban_tickets WHERE "column" = ? ORDER BY created_at DESC').all(column);
|
|
211
|
+
}
|
|
212
|
+
async deleteKanbanTicket(id) {
|
|
213
|
+
this.db.prepare("DELETE FROM kanban_tickets WHERE id = ?").run(id);
|
|
214
|
+
}
|
|
215
|
+
// ── Storage / Cleanup ──
|
|
216
|
+
async pruneOldSessions(maxAgeDays) {
|
|
217
|
+
const cutoff = new Date(Date.now() - maxAgeDays * 864e5).toISOString();
|
|
218
|
+
const oldSessions = this.db.prepare("SELECT id FROM test_sessions WHERE started_at < ?").all(cutoff);
|
|
219
|
+
const ids = oldSessions.map((s) => s.id);
|
|
220
|
+
if (ids.length === 0) return { sessionsRemoved: 0, actionsRemoved: 0 };
|
|
221
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
222
|
+
const actionsResult = this.db.prepare(`DELETE FROM actions WHERE session_id IN (${placeholders})`).run(...ids);
|
|
223
|
+
this.db.prepare(`DELETE FROM test_sessions WHERE id IN (${placeholders})`).run(...ids);
|
|
224
|
+
return { sessionsRemoved: ids.length, actionsRemoved: actionsResult.changes };
|
|
225
|
+
}
|
|
226
|
+
async getStorageStats() {
|
|
227
|
+
const count = (table) => this.db.prepare(`SELECT COUNT(*) as n FROM ${table}`).get().n;
|
|
228
|
+
return {
|
|
229
|
+
sessions: count("test_sessions"),
|
|
230
|
+
actions: count("actions"),
|
|
231
|
+
bugs: count("bugs"),
|
|
232
|
+
tickets: count("kanban_tickets")
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
// ── Compat helpers (used by daemon.ts) ──
|
|
236
|
+
async getActiveAgents() {
|
|
237
|
+
const sessions = await this.getRecentSessions(1);
|
|
238
|
+
const current = sessions[0];
|
|
239
|
+
const isRunning = current?.status === "running";
|
|
240
|
+
return [{
|
|
241
|
+
name: "Main Agent",
|
|
242
|
+
status: isRunning ? "running" : "idle",
|
|
243
|
+
purpose: "Autonomous QA orchestration",
|
|
244
|
+
performance: current ? Math.min(100, Math.round(current.total_actions / 100 * 100)) : 0,
|
|
245
|
+
tasks: current?.total_actions ?? 0
|
|
246
|
+
}];
|
|
247
|
+
}
|
|
248
|
+
async getCurrentTasks() {
|
|
249
|
+
const sessions = await this.getRecentSessions(1);
|
|
250
|
+
if (!sessions[0]) return [];
|
|
251
|
+
const actions = await this.getSessionActions(sessions[0].id);
|
|
252
|
+
return actions.slice(0, 10).map((a, i) => ({
|
|
253
|
+
id: a.id,
|
|
254
|
+
name: a.type,
|
|
255
|
+
status: i === 0 && sessions[0].status === "running" ? "running" : "completed",
|
|
256
|
+
progress: i === 0 && sessions[0].status === "running" ? "65%" : "100%",
|
|
257
|
+
agent: "Main Agent",
|
|
258
|
+
started_at: a.timestamp,
|
|
259
|
+
result: a.output || a.description
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
async getCurrentIssues() {
|
|
263
|
+
const bugs = await this.getAllBugs();
|
|
264
|
+
return bugs.slice(0, 10).map((b) => ({
|
|
265
|
+
id: b.id,
|
|
266
|
+
title: b.title,
|
|
267
|
+
description: b.description,
|
|
268
|
+
severity: b.severity,
|
|
269
|
+
status: b.status,
|
|
270
|
+
discovered_at: b.created_at,
|
|
271
|
+
agent: "Main Agent"
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
async close() {
|
|
275
|
+
this.db.close();
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
export {
|
|
279
|
+
OpenQASQLiteDatabase
|
|
280
|
+
};
|
|
281
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../database/sqlite.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { TestSession, Action, Bug, KanbanTicket } from './index.js';\n\nexport class OpenQASQLiteDatabase {\n private db: Database.Database;\n\n constructor(dbPath: string = './data/openqa.db') {\n mkdirSync(dirname(dbPath), { recursive: true });\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.migrate();\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS config (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS test_sessions (\n id TEXT PRIMARY KEY,\n started_at TEXT NOT NULL,\n ended_at TEXT,\n status TEXT NOT NULL DEFAULT 'running',\n total_actions INTEGER NOT NULL DEFAULT 0,\n bugs_found INTEGER NOT NULL DEFAULT 0,\n metadata TEXT\n );\n\n CREATE TABLE IF NOT EXISTS actions (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n type TEXT NOT NULL,\n description TEXT NOT NULL,\n input TEXT,\n output TEXT,\n screenshot_path TEXT,\n FOREIGN KEY (session_id) REFERENCES test_sessions(id)\n );\n\n CREATE TABLE IF NOT EXISTS bugs (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT NOT NULL,\n severity TEXT NOT NULL DEFAULT 'medium',\n status TEXT NOT NULL DEFAULT 'open',\n github_issue_url TEXT,\n screenshot_path TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n FOREIGN KEY (session_id) REFERENCES test_sessions(id)\n );\n\n CREATE TABLE IF NOT EXISTS kanban_tickets (\n id TEXT PRIMARY KEY,\n bug_id TEXT,\n title TEXT NOT NULL,\n description TEXT NOT NULL,\n priority TEXT NOT NULL DEFAULT 'medium',\n column TEXT NOT NULL DEFAULT 'backlog',\n tags TEXT,\n screenshot_url TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n `);\n }\n\n // ── Config ──\n\n async getConfig(key: string): Promise<string | null> {\n const row = this.db.prepare('SELECT value FROM config WHERE key = ?').get(key) as { value: string } | undefined;\n return row?.value ?? null;\n }\n\n async setConfig(key: string, value: string): Promise<void> {\n this.db.prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)').run(key, value);\n }\n\n async getAllConfig(): Promise<Record<string, string>> {\n const rows = this.db.prepare('SELECT key, value FROM config').all() as Array<{ key: string; value: string }>;\n return Object.fromEntries(rows.map(r => [r.key, r.value]));\n }\n\n async clearAllConfig(): Promise<void> {\n this.db.prepare('DELETE FROM config').run();\n }\n\n // ── Sessions ──\n\n async createSession(id: string, metadata?: Record<string, unknown>): Promise<TestSession> {\n const session: TestSession = {\n id,\n started_at: new Date().toISOString(),\n status: 'running',\n total_actions: 0,\n bugs_found: 0,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n };\n this.db.prepare(`\n INSERT INTO test_sessions (id, started_at, status, total_actions, bugs_found, metadata)\n VALUES (?, ?, ?, ?, ?, ?)\n `).run(session.id, session.started_at, session.status, session.total_actions, session.bugs_found, session.metadata ?? null);\n return session;\n }\n\n async getSession(id: string): Promise<TestSession | null> {\n return (this.db.prepare('SELECT * FROM test_sessions WHERE id = ?').get(id) as TestSession) ?? null;\n }\n\n async updateSession(id: string, updates: Partial<TestSession>): Promise<void> {\n const fields = Object.keys(updates).map(k => `${k} = ?`).join(', ');\n const values = [...Object.values(updates), id];\n this.db.prepare(`UPDATE test_sessions SET ${fields} WHERE id = ?`).run(...values);\n }\n\n async getRecentSessions(limit = 10): Promise<TestSession[]> {\n return this.db.prepare('SELECT * FROM test_sessions ORDER BY started_at DESC LIMIT ?').all(limit) as TestSession[];\n }\n\n // ── Actions ──\n\n async createAction(action: Omit<Action, 'id' | 'timestamp'>): Promise<Action> {\n const newAction: Action = {\n id: `action_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n timestamp: new Date().toISOString(),\n ...action,\n };\n this.db.prepare(`\n INSERT INTO actions (id, session_id, timestamp, type, description, input, output, screenshot_path)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n newAction.id, newAction.session_id, newAction.timestamp,\n newAction.type, newAction.description,\n newAction.input ?? null, newAction.output ?? null, newAction.screenshot_path ?? null,\n );\n return newAction;\n }\n\n async getSessionActions(sessionId: string): Promise<Action[]> {\n return this.db.prepare('SELECT * FROM actions WHERE session_id = ? ORDER BY timestamp DESC').all(sessionId) as Action[];\n }\n\n // ── Bugs ──\n\n async createBug(bug: Omit<Bug, 'id' | 'created_at' | 'updated_at'>): Promise<Bug> {\n const newBug: Bug = {\n id: `bug_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n ...bug,\n };\n this.db.prepare(`\n INSERT INTO bugs (id, session_id, title, description, severity, status, github_issue_url, screenshot_path, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n newBug.id, newBug.session_id, newBug.title, newBug.description,\n newBug.severity, newBug.status,\n newBug.github_issue_url ?? null, newBug.screenshot_path ?? null,\n newBug.created_at, newBug.updated_at,\n );\n return newBug;\n }\n\n async updateBug(id: string, updates: Partial<Bug>): Promise<void> {\n const patch = { ...updates, updated_at: new Date().toISOString() };\n const fields = Object.keys(patch).map(k => `${k} = ?`).join(', ');\n const values = [...Object.values(patch), id];\n this.db.prepare(`UPDATE bugs SET ${fields} WHERE id = ?`).run(...values);\n }\n\n async getAllBugs(): Promise<Bug[]> {\n return this.db.prepare('SELECT * FROM bugs ORDER BY created_at DESC').all() as Bug[];\n }\n\n async getBugsByStatus(status: Bug['status']): Promise<Bug[]> {\n return this.db.prepare('SELECT * FROM bugs WHERE status = ? ORDER BY created_at DESC').all(status) as Bug[];\n }\n\n // ── Kanban ──\n\n async createKanbanTicket(ticket: Omit<KanbanTicket, 'id' | 'created_at' | 'updated_at'>): Promise<KanbanTicket> {\n const newTicket: KanbanTicket = {\n id: `ticket_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n ...ticket,\n };\n this.db.prepare(`\n INSERT INTO kanban_tickets (id, bug_id, title, description, priority, column, tags, screenshot_url, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n newTicket.id, newTicket.bug_id ?? null, newTicket.title, newTicket.description,\n newTicket.priority, newTicket.column,\n newTicket.tags ?? null, newTicket.screenshot_url ?? null,\n newTicket.created_at, newTicket.updated_at,\n );\n return newTicket;\n }\n\n async updateKanbanTicket(id: string, updates: Partial<KanbanTicket>): Promise<void> {\n const patch = { ...updates, updated_at: new Date().toISOString() };\n const fields = Object.keys(patch).map(k => `\"${k}\" = ?`).join(', ');\n const values = [...Object.values(patch), id];\n this.db.prepare(`UPDATE kanban_tickets SET ${fields} WHERE id = ?`).run(...values);\n }\n\n async getKanbanTickets(): Promise<KanbanTicket[]> {\n return this.db.prepare('SELECT * FROM kanban_tickets ORDER BY created_at DESC').all() as KanbanTicket[];\n }\n\n async getKanbanTicketsByColumn(column: KanbanTicket['column']): Promise<KanbanTicket[]> {\n return this.db.prepare('SELECT * FROM kanban_tickets WHERE \"column\" = ? ORDER BY created_at DESC').all(column) as KanbanTicket[];\n }\n\n async deleteKanbanTicket(id: string): Promise<void> {\n this.db.prepare('DELETE FROM kanban_tickets WHERE id = ?').run(id);\n }\n\n // ── Storage / Cleanup ──\n\n async pruneOldSessions(maxAgeDays: number): Promise<{ sessionsRemoved: number; actionsRemoved: number }> {\n const cutoff = new Date(Date.now() - maxAgeDays * 86400000).toISOString();\n const oldSessions = this.db.prepare('SELECT id FROM test_sessions WHERE started_at < ?').all(cutoff) as Array<{ id: string }>;\n const ids = oldSessions.map(s => s.id);\n if (ids.length === 0) return { sessionsRemoved: 0, actionsRemoved: 0 };\n\n const placeholders = ids.map(() => '?').join(', ');\n const actionsResult = this.db.prepare(`DELETE FROM actions WHERE session_id IN (${placeholders})`).run(...ids);\n this.db.prepare(`DELETE FROM test_sessions WHERE id IN (${placeholders})`).run(...ids);\n\n return { sessionsRemoved: ids.length, actionsRemoved: actionsResult.changes };\n }\n\n async getStorageStats(): Promise<{ sessions: number; actions: number; bugs: number; tickets: number }> {\n const count = (table: string): number =>\n (this.db.prepare(`SELECT COUNT(*) as n FROM ${table}`).get() as { n: number }).n;\n return {\n sessions: count('test_sessions'),\n actions: count('actions'),\n bugs: count('bugs'),\n tickets: count('kanban_tickets'),\n };\n }\n\n // ── Compat helpers (used by daemon.ts) ──\n\n async getActiveAgents() {\n const sessions = await this.getRecentSessions(1);\n const current = sessions[0];\n const isRunning = current?.status === 'running';\n return [{\n name: 'Main Agent',\n status: isRunning ? 'running' : 'idle',\n purpose: 'Autonomous QA orchestration',\n performance: current ? Math.min(100, Math.round((current.total_actions / 100) * 100)) : 0,\n tasks: current?.total_actions ?? 0,\n }];\n }\n\n async getCurrentTasks() {\n const sessions = await this.getRecentSessions(1);\n if (!sessions[0]) return [];\n const actions = await this.getSessionActions(sessions[0].id);\n return actions.slice(0, 10).map((a, i) => ({\n id: a.id, name: a.type, status: i === 0 && sessions[0].status === 'running' ? 'running' : 'completed',\n progress: i === 0 && sessions[0].status === 'running' ? '65%' : '100%',\n agent: 'Main Agent', started_at: a.timestamp, result: a.output || a.description,\n }));\n }\n\n async getCurrentIssues() {\n const bugs = await this.getAllBugs();\n return bugs.slice(0, 10).map(b => ({\n id: b.id, title: b.title, description: b.description,\n severity: b.severity, status: b.status, discovered_at: b.created_at, agent: 'Main Agent',\n }));\n }\n\n async close(): Promise<void> {\n this.db.close();\n }\n}\n"],"mappings":";AAAA,OAAO,cAAc;AACrB,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AAGjB,IAAM,uBAAN,MAA2B;AAAA,EACxB;AAAA,EAER,YAAY,SAAiB,oBAAoB;AAC/C,cAAU,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,SAAK,KAAK,IAAI,SAAS,MAAM;AAC7B,SAAK,GAAG,OAAO,oBAAoB;AACnC,SAAK,GAAG,OAAO,mBAAmB;AAClC,SAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,UAAgB;AACtB,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAsDZ;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,UAAU,KAAqC;AACnD,UAAM,MAAM,KAAK,GAAG,QAAQ,wCAAwC,EAAE,IAAI,GAAG;AAC7E,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,MAAM,UAAU,KAAa,OAA8B;AACzD,SAAK,GAAG,QAAQ,0DAA0D,EAAE,IAAI,KAAK,KAAK;AAAA,EAC5F;AAAA,EAEA,MAAM,eAAgD;AACpD,UAAM,OAAO,KAAK,GAAG,QAAQ,+BAA+B,EAAE,IAAI;AAClE,WAAO,OAAO,YAAY,KAAK,IAAI,OAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAAA,EAC3D;AAAA,EAEA,MAAM,iBAAgC;AACpC,SAAK,GAAG,QAAQ,oBAAoB,EAAE,IAAI;AAAA,EAC5C;AAAA;AAAA,EAIA,MAAM,cAAc,IAAY,UAA0D;AACxF,UAAM,UAAuB;AAAA,MAC3B;AAAA,MACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,QAAQ;AAAA,MACR,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,UAAU,WAAW,KAAK,UAAU,QAAQ,IAAI;AAAA,IAClD;AACA,SAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGf,EAAE,IAAI,QAAQ,IAAI,QAAQ,YAAY,QAAQ,QAAQ,QAAQ,eAAe,QAAQ,YAAY,QAAQ,YAAY,IAAI;AAC1H,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,IAAyC;AACxD,WAAQ,KAAK,GAAG,QAAQ,0CAA0C,EAAE,IAAI,EAAE,KAAqB;AAAA,EACjG;AAAA,EAEA,MAAM,cAAc,IAAY,SAA8C;AAC5E,UAAM,SAAS,OAAO,KAAK,OAAO,EAAE,IAAI,OAAK,GAAG,CAAC,MAAM,EAAE,KAAK,IAAI;AAClE,UAAM,SAAS,CAAC,GAAG,OAAO,OAAO,OAAO,GAAG,EAAE;AAC7C,SAAK,GAAG,QAAQ,4BAA4B,MAAM,eAAe,EAAE,IAAI,GAAG,MAAM;AAAA,EAClF;AAAA,EAEA,MAAM,kBAAkB,QAAQ,IAA4B;AAC1D,WAAO,KAAK,GAAG,QAAQ,8DAA8D,EAAE,IAAI,KAAK;AAAA,EAClG;AAAA;AAAA,EAIA,MAAM,aAAa,QAA2D;AAC5E,UAAM,YAAoB;AAAA,MACxB,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC;AAAA,MACvE,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,GAAG;AAAA,IACL;AACA,SAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGf,EAAE;AAAA,MACD,UAAU;AAAA,MAAI,UAAU;AAAA,MAAY,UAAU;AAAA,MAC9C,UAAU;AAAA,MAAM,UAAU;AAAA,MAC1B,UAAU,SAAS;AAAA,MAAM,UAAU,UAAU;AAAA,MAAM,UAAU,mBAAmB;AAAA,IAClF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,kBAAkB,WAAsC;AAC5D,WAAO,KAAK,GAAG,QAAQ,oEAAoE,EAAE,IAAI,SAAS;AAAA,EAC5G;AAAA;AAAA,EAIA,MAAM,UAAU,KAAkE;AAChF,UAAM,SAAc;AAAA,MAClB,IAAI,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC;AAAA,MACpE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,GAAG;AAAA,IACL;AACA,SAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGf,EAAE;AAAA,MACD,OAAO;AAAA,MAAI,OAAO;AAAA,MAAY,OAAO;AAAA,MAAO,OAAO;AAAA,MACnD,OAAO;AAAA,MAAU,OAAO;AAAA,MACxB,OAAO,oBAAoB;AAAA,MAAM,OAAO,mBAAmB;AAAA,MAC3D,OAAO;AAAA,MAAY,OAAO;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAU,IAAY,SAAsC;AAChE,UAAM,QAAQ,EAAE,GAAG,SAAS,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE;AACjE,UAAM,SAAS,OAAO,KAAK,KAAK,EAAE,IAAI,OAAK,GAAG,CAAC,MAAM,EAAE,KAAK,IAAI;AAChE,UAAM,SAAS,CAAC,GAAG,OAAO,OAAO,KAAK,GAAG,EAAE;AAC3C,SAAK,GAAG,QAAQ,mBAAmB,MAAM,eAAe,EAAE,IAAI,GAAG,MAAM;AAAA,EACzE;AAAA,EAEA,MAAM,aAA6B;AACjC,WAAO,KAAK,GAAG,QAAQ,6CAA6C,EAAE,IAAI;AAAA,EAC5E;AAAA,EAEA,MAAM,gBAAgB,QAAuC;AAC3D,WAAO,KAAK,GAAG,QAAQ,8DAA8D,EAAE,IAAI,MAAM;AAAA,EACnG;AAAA;AAAA,EAIA,MAAM,mBAAmB,QAAuF;AAC9G,UAAM,YAA0B;AAAA,MAC9B,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC;AAAA,MACvE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,GAAG;AAAA,IACL;AACA,SAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAGf,EAAE;AAAA,MACD,UAAU;AAAA,MAAI,UAAU,UAAU;AAAA,MAAM,UAAU;AAAA,MAAO,UAAU;AAAA,MACnE,UAAU;AAAA,MAAU,UAAU;AAAA,MAC9B,UAAU,QAAQ;AAAA,MAAM,UAAU,kBAAkB;AAAA,MACpD,UAAU;AAAA,MAAY,UAAU;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,mBAAmB,IAAY,SAA+C;AAClF,UAAM,QAAQ,EAAE,GAAG,SAAS,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE;AACjE,UAAM,SAAS,OAAO,KAAK,KAAK,EAAE,IAAI,OAAK,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI;AAClE,UAAM,SAAS,CAAC,GAAG,OAAO,OAAO,KAAK,GAAG,EAAE;AAC3C,SAAK,GAAG,QAAQ,6BAA6B,MAAM,eAAe,EAAE,IAAI,GAAG,MAAM;AAAA,EACnF;AAAA,EAEA,MAAM,mBAA4C;AAChD,WAAO,KAAK,GAAG,QAAQ,uDAAuD,EAAE,IAAI;AAAA,EACtF;AAAA,EAEA,MAAM,yBAAyB,QAAyD;AACtF,WAAO,KAAK,GAAG,QAAQ,0EAA0E,EAAE,IAAI,MAAM;AAAA,EAC/G;AAAA,EAEA,MAAM,mBAAmB,IAA2B;AAClD,SAAK,GAAG,QAAQ,yCAAyC,EAAE,IAAI,EAAE;AAAA,EACnE;AAAA;AAAA,EAIA,MAAM,iBAAiB,YAAkF;AACvG,UAAM,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,aAAa,KAAQ,EAAE,YAAY;AACxE,UAAM,cAAc,KAAK,GAAG,QAAQ,mDAAmD,EAAE,IAAI,MAAM;AACnG,UAAM,MAAM,YAAY,IAAI,OAAK,EAAE,EAAE;AACrC,QAAI,IAAI,WAAW,EAAG,QAAO,EAAE,iBAAiB,GAAG,gBAAgB,EAAE;AAErE,UAAM,eAAe,IAAI,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACjD,UAAM,gBAAgB,KAAK,GAAG,QAAQ,4CAA4C,YAAY,GAAG,EAAE,IAAI,GAAG,GAAG;AAC7G,SAAK,GAAG,QAAQ,0CAA0C,YAAY,GAAG,EAAE,IAAI,GAAG,GAAG;AAErF,WAAO,EAAE,iBAAiB,IAAI,QAAQ,gBAAgB,cAAc,QAAQ;AAAA,EAC9E;AAAA,EAEA,MAAM,kBAAiG;AACrG,UAAM,QAAQ,CAAC,UACZ,KAAK,GAAG,QAAQ,6BAA6B,KAAK,EAAE,EAAE,IAAI,EAAoB;AACjF,WAAO;AAAA,MACL,UAAU,MAAM,eAAe;AAAA,MAC/B,SAAS,MAAM,SAAS;AAAA,MACxB,MAAM,MAAM,MAAM;AAAA,MAClB,SAAS,MAAM,gBAAgB;AAAA,IACjC;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,kBAAkB;AACtB,UAAM,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAC/C,UAAM,UAAU,SAAS,CAAC;AAC1B,UAAM,YAAY,SAAS,WAAW;AACtC,WAAO,CAAC;AAAA,MACN,MAAM;AAAA,MACN,QAAQ,YAAY,YAAY;AAAA,MAChC,SAAS;AAAA,MACT,aAAa,UAAU,KAAK,IAAI,KAAK,KAAK,MAAO,QAAQ,gBAAgB,MAAO,GAAG,CAAC,IAAI;AAAA,MACxF,OAAO,SAAS,iBAAiB;AAAA,IACnC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,kBAAkB;AACtB,UAAM,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAC/C,QAAI,CAAC,SAAS,CAAC,EAAG,QAAO,CAAC;AAC1B,UAAM,UAAU,MAAM,KAAK,kBAAkB,SAAS,CAAC,EAAE,EAAE;AAC3D,WAAO,QAAQ,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,GAAG,OAAO;AAAA,MACzC,IAAI,EAAE;AAAA,MAAI,MAAM,EAAE;AAAA,MAAM,QAAQ,MAAM,KAAK,SAAS,CAAC,EAAE,WAAW,YAAY,YAAY;AAAA,MAC1F,UAAU,MAAM,KAAK,SAAS,CAAC,EAAE,WAAW,YAAY,QAAQ;AAAA,MAChE,OAAO;AAAA,MAAc,YAAY,EAAE;AAAA,MAAW,QAAQ,EAAE,UAAU,EAAE;AAAA,IACtE,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,mBAAmB;AACvB,UAAM,OAAO,MAAM,KAAK,WAAW;AACnC,WAAO,KAAK,MAAM,GAAG,EAAE,EAAE,IAAI,QAAM;AAAA,MACjC,IAAI,EAAE;AAAA,MAAI,OAAO,EAAE;AAAA,MAAO,aAAa,EAAE;AAAA,MACzC,UAAU,EAAE;AAAA,MAAU,QAAQ,EAAE;AAAA,MAAQ,eAAe,EAAE;AAAA,MAAY,OAAO;AAAA,IAC9E,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;","names":[]}
|
package/install.sh
CHANGED
|
@@ -20,8 +20,9 @@ check_dependencies() {
|
|
|
20
20
|
fi
|
|
21
21
|
|
|
22
22
|
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
|
23
|
-
if [ "$NODE_VERSION" -lt
|
|
24
|
-
echo "❌ Node.js version must be
|
|
23
|
+
if [ "$NODE_VERSION" -lt 20 ]; then
|
|
24
|
+
echo "❌ Node.js version must be 20 or higher (found: $(node -v))"
|
|
25
|
+
echo " Please install Node.js 20+ from https://nodejs.org"
|
|
25
26
|
exit 1
|
|
26
27
|
fi
|
|
27
28
|
|
|
@@ -53,9 +54,9 @@ download_openqa() {
|
|
|
53
54
|
mkdir -p "$INSTALL_DIR"
|
|
54
55
|
|
|
55
56
|
if [ "$VERSION" = "latest" ]; then
|
|
56
|
-
DOWNLOAD_URL="https://github.com/
|
|
57
|
+
DOWNLOAD_URL="https://github.com/Orka-Community/OpenQA/archive/refs/heads/main.tar.gz"
|
|
57
58
|
else
|
|
58
|
-
DOWNLOAD_URL="https://github.com/
|
|
59
|
+
DOWNLOAD_URL="https://github.com/Orka-Community/OpenQA/archive/refs/tags/v${VERSION}.tar.gz"
|
|
59
60
|
fi
|
|
60
61
|
|
|
61
62
|
if command -v curl &> /dev/null; then
|
|
@@ -137,18 +138,26 @@ print_success() {
|
|
|
137
138
|
echo "🚀 Next steps:"
|
|
138
139
|
echo ""
|
|
139
140
|
echo "1. Configure OpenQA:"
|
|
140
|
-
echo " Edit $INSTALL_DIR/.env"
|
|
141
|
-
echo " Or use
|
|
141
|
+
echo " Edit $INSTALL_DIR/.env with your API keys"
|
|
142
|
+
echo " Or use the web interface after starting"
|
|
142
143
|
echo ""
|
|
143
144
|
echo "2. Start OpenQA:"
|
|
144
145
|
echo " openqa start"
|
|
145
146
|
echo ""
|
|
146
147
|
echo "3. Access web interfaces:"
|
|
147
|
-
echo "
|
|
148
|
-
echo " Kanban:
|
|
149
|
-
echo " Config:
|
|
148
|
+
echo " Dashboard: http://localhost:4242"
|
|
149
|
+
echo " Kanban: http://localhost:4242/kanban"
|
|
150
|
+
echo " Config: http://localhost:4242/config"
|
|
151
|
+
echo " Environment: http://localhost:4242/config/env"
|
|
150
152
|
echo ""
|
|
151
|
-
echo "
|
|
153
|
+
echo "4. First-time setup:"
|
|
154
|
+
echo " Visit http://localhost:4242/setup"
|
|
155
|
+
echo " Create your admin account"
|
|
156
|
+
echo ""
|
|
157
|
+
echo "📖 Documentation: https://github.com/Orka-Community/OpenQA"
|
|
158
|
+
echo "💬 Discord: https://discord.com/invite/DScfpuPysP"
|
|
159
|
+
echo ""
|
|
160
|
+
echo "⚠️ Security: Never disable authentication in production!"
|
|
152
161
|
echo ""
|
|
153
162
|
}
|
|
154
163
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openqa/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Autonomous QA testing agent powered by Orka.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cli/index.js",
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
"dev": "tsx watch agent/index.ts",
|
|
20
20
|
"build": "tsup",
|
|
21
21
|
"start": "node dist/cli/index.js start",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
22
24
|
"prepublishOnly": "npm run build",
|
|
23
25
|
"web:dev": "cd web && npm run dev",
|
|
24
26
|
"web:build": "cd web && npm run build"
|
|
@@ -52,21 +54,33 @@
|
|
|
52
54
|
"@orka-js/memory-store": "^1.0.0",
|
|
53
55
|
"@orka-js/observability": "^1.0.0",
|
|
54
56
|
"@orka-js/openai": "^1.0.0",
|
|
55
|
-
"
|
|
57
|
+
"better-sqlite3": "^12.8.0",
|
|
56
58
|
"chalk": "^5.3.0",
|
|
57
59
|
"commander": "^11.1.0",
|
|
60
|
+
"cookie": "^0.6.0",
|
|
61
|
+
"cors": "^2.8.6",
|
|
58
62
|
"dotenv": "^16.3.1",
|
|
59
63
|
"express": "^4.18.2",
|
|
64
|
+
"express-rate-limit": "^8.3.2",
|
|
65
|
+
"lowdb": "^6.1.0",
|
|
60
66
|
"ora": "^8.0.1",
|
|
61
|
-
"playwright": "^1.
|
|
62
|
-
"ws": "^8.16.0"
|
|
67
|
+
"playwright": "^1.48.0",
|
|
68
|
+
"ws": "^8.16.0",
|
|
69
|
+
"zod": "^4.3.6"
|
|
63
70
|
},
|
|
64
71
|
"devDependencies": {
|
|
72
|
+
"@changesets/cli": "^2.27.0",
|
|
73
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
74
|
+
"@types/cookie": "^0.6.0",
|
|
75
|
+
"@types/cors": "^2.8.19",
|
|
65
76
|
"@types/express": "^4.17.21",
|
|
66
77
|
"@types/node": "^20.10.6",
|
|
78
|
+
"@types/supertest": "^7.2.0",
|
|
67
79
|
"@types/ws": "^8.5.10",
|
|
80
|
+
"supertest": "^7.2.2",
|
|
68
81
|
"tsup": "^8.0.1",
|
|
69
82
|
"tsx": "^4.7.0",
|
|
70
|
-
"typescript": "^5.3.3"
|
|
83
|
+
"typescript": "^5.3.3",
|
|
84
|
+
"vitest": "^2.1.9"
|
|
71
85
|
}
|
|
72
86
|
}
|