@openqa/cli 1.3.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +203 -6
  2. package/dist/agent/brain/diff-analyzer.js +140 -0
  3. package/dist/agent/brain/diff-analyzer.js.map +1 -0
  4. package/dist/agent/brain/llm-cache.js +47 -0
  5. package/dist/agent/brain/llm-cache.js.map +1 -0
  6. package/dist/agent/brain/llm-resilience.js +252 -0
  7. package/dist/agent/brain/llm-resilience.js.map +1 -0
  8. package/dist/agent/config/index.js +588 -0
  9. package/dist/agent/config/index.js.map +1 -0
  10. package/dist/agent/coverage/index.js +74 -0
  11. package/dist/agent/coverage/index.js.map +1 -0
  12. package/dist/agent/export/index.js +158 -0
  13. package/dist/agent/export/index.js.map +1 -0
  14. package/dist/agent/index-v2.js +2795 -0
  15. package/dist/agent/index-v2.js.map +1 -0
  16. package/dist/agent/index.js +369 -105
  17. package/dist/agent/index.js.map +1 -1
  18. package/dist/agent/logger.js +41 -0
  19. package/dist/agent/logger.js.map +1 -0
  20. package/dist/agent/metrics.js +39 -0
  21. package/dist/agent/metrics.js.map +1 -0
  22. package/dist/agent/notifications/index.js +106 -0
  23. package/dist/agent/notifications/index.js.map +1 -0
  24. package/dist/agent/openapi/spec.js +338 -0
  25. package/dist/agent/openapi/spec.js.map +1 -0
  26. package/dist/agent/tools/project-runner.js +481 -0
  27. package/dist/agent/tools/project-runner.js.map +1 -0
  28. package/dist/cli/config.html.js +454 -0
  29. package/dist/cli/daemon.js +8810 -0
  30. package/dist/cli/dashboard.html.js +1622 -0
  31. package/dist/cli/env-config.js +391 -0
  32. package/dist/cli/env-routes.js +820 -0
  33. package/dist/cli/env.html.js +679 -0
  34. package/dist/cli/index.js +5980 -1896
  35. package/dist/cli/kanban.html.js +577 -0
  36. package/dist/cli/routes.js +895 -0
  37. package/dist/cli/routes.js.map +1 -0
  38. package/dist/cli/server.js +5855 -1860
  39. package/dist/database/index.js +485 -60
  40. package/dist/database/index.js.map +1 -1
  41. package/dist/database/sqlite.js +281 -0
  42. package/dist/database/sqlite.js.map +1 -0
  43. package/install.sh +19 -10
  44. package/package.json +19 -5
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">OpenQA</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Autonomous QA Testing Agent - Thinks, codes, and executes tests like a senior QA engineer. Powered by Orka.js 🦦</strong>
8
+ <strong>Autonomous QA Testing Agent - Thinks, codes, and executes tests like a senior QA engineer. Powered by Orka Team</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -50,12 +50,30 @@ OpenQA is a **truly autonomous** QA testing agent that thinks, codes, and execut
50
50
 
51
51
  ## 🚀 Quick Start
52
52
 
53
- ### One-Line Installation
53
+ ### Development (Local)
54
54
 
55
55
  ```bash
56
+ # One-line installation
56
57
  curl -fsSL https://openqa.orkajs.com/install.sh | bash
58
+
59
+ # Or via npm
60
+ npx @openqa/cli start
61
+ ```
62
+
63
+ ### Production Deployment
64
+
65
+ ```bash
66
+ # Interactive production installer
67
+ curl -fsSL https://openqa.orkajs.com/install-production.sh | bash
57
68
  ```
58
69
 
70
+ **Supports:**
71
+ - 🐳 **Docker** (recommended)
72
+ - 🖥️ **VPS/Bare Metal** (Ubuntu/Debian with systemd)
73
+ - ☁️ **Cloud Platforms** (Railway, Render, Fly.io)
74
+
75
+ 📖 **[Full Deployment Guide](./DEPLOYMENT.md)** - Complete production setup instructions
76
+
59
77
  ### Configure Your SaaS (3 lines!)
60
78
 
61
79
  ```bash
@@ -105,9 +123,68 @@ openqa start --daemon
105
123
 
106
124
  Once started, open your browser:
107
125
 
108
- - **DevTools**: http://localhost:3000 - Monitor agent activity in real-time
109
- - **Kanban**: http://localhost:3000/kanban - View and manage QA tickets
110
- - **Config**: http://localhost:3000/config - Configure OpenQA settings
126
+ - **Dashboard**: http://localhost:4242 - Main dashboard with real-time monitoring
127
+ - **Kanban**: http://localhost:4242/kanban - View and manage QA tickets
128
+ - **Config**: http://localhost:4242/config - Configure OpenQA settings
129
+
130
+ ### 🔐 Dashboard Authentication
131
+
132
+ OpenQA includes a secure authentication system to protect your dashboard:
133
+
134
+ #### First-Time Setup
135
+
136
+ On first launch, you'll be redirected to `/setup` to create an admin account:
137
+
138
+ 1. Visit http://localhost:4242
139
+ 2. Create your admin username and password (min 8 characters)
140
+ 3. You'll be automatically logged in
141
+
142
+ #### Login
143
+
144
+ After setup, access the dashboard at http://localhost:4242/login with your credentials.
145
+
146
+ #### User Management (Admin Only)
147
+
148
+ Admins can manage users via the API:
149
+
150
+ ```bash
151
+ # List all users
152
+ curl http://localhost:4242/api/accounts \
153
+ -H "Authorization: Bearer YOUR_TOKEN"
154
+
155
+ # Create a viewer account
156
+ curl -X POST http://localhost:4242/api/accounts \
157
+ -H "Authorization: Bearer YOUR_TOKEN" \
158
+ -H "Content-Type: application/json" \
159
+ -d '{"username": "viewer1", "password": "securepass123", "role": "viewer"}'
160
+
161
+ # Change password
162
+ curl -X POST http://localhost:4242/api/auth/change-password \
163
+ -H "Authorization: Bearer YOUR_TOKEN" \
164
+ -H "Content-Type: application/json" \
165
+ -d '{"currentPassword": "old", "newPassword": "newsecure123"}'
166
+ ```
167
+
168
+ **Roles:**
169
+ - **admin** - Full access (manage users, configure, run tests)
170
+ - **viewer** - Read-only access (view tests, bugs, sessions)
171
+
172
+ **Security Features:**
173
+ - JWT-based authentication with httpOnly cookies
174
+ - Scrypt password hashing
175
+ - Rate limiting on auth endpoints
176
+ - CSRF protection via SameSite cookies
177
+
178
+ #### Disable Authentication (Development Only)
179
+
180
+ For local development, you can disable authentication:
181
+
182
+ ```bash
183
+ export OPENQA_AUTH_DISABLED=true
184
+ openqa start
185
+ ```
186
+
187
+ ⚠️ **Never disable authentication in production!**
111
188
 
112
189
  ### CLI Commands
113
190
 
@@ -330,7 +407,127 @@ curl -X POST http://localhost:3000/api/brain/analyze
330
407
  # }
331
408
  ```
332
409
 
333
- ### Docker Deployment
410
+ ## 🚀 Production Deployment
411
+
412
+ ### Quick Deploy (5 minutes)
413
+
414
+ ```bash
415
+ # Interactive installer - Choose Docker, VPS, or Cloud
416
+ curl -fsSL https://openqa.orkajs.com/install-production.sh | bash
417
+ ```
418
+
419
+ ### Deployment Options
420
+
421
+ | Method | Time | Difficulty | Best For |
422
+ |--------|------|------------|----------|
423
+ | 🐳 **Docker** | 5 min | Easy | VPS, Local servers |
424
+ | 🖥️ **VPS/Systemd** | 15 min | Medium | Full control |
425
+ | ☁️ **Railway** | 3 min | Easiest | Quick deploy |
426
+ | 🎨 **Render** | 2 min | Easiest | Free tier |
427
+ | 🪰 **Fly.io** | 5 min | Easy | Global edge |
428
+
429
+ ### Docker (Recommended)
430
+
431
+ ```bash
432
+ # 1. Clone and configure
433
+ git clone https://github.com/Orka-Community/OpenQA.git
434
+ cd OpenQA
435
+ cp .env.production .env
436
+
437
+ # 2. Edit .env - Add your API keys
438
+ nano .env
439
+ # Required: OPENAI_API_KEY, OPENQA_JWT_SECRET, SAAS_URL
440
+
441
+ # 3. Start with Docker Compose
442
+ docker-compose -f docker-compose.production.yml up -d
443
+
444
+ # 4. Access at http://localhost:4242
445
+ ```
446
+
447
+ **With HTTPS (Nginx):**
448
+ ```bash
449
+ # Update nginx.conf with your domain
450
+ nano nginx.conf
451
+
452
+ # Get SSL certificate
453
+ sudo certbot certonly --standalone -d your-domain.com
454
+
455
+ # Start with Nginx
456
+ docker-compose -f docker-compose.production.yml --profile with-nginx up -d
457
+ ```
458
+
459
+ ### Cloud Platforms
460
+
461
+ **Railway:**
462
+ ```bash
463
+ railway init && railway up
464
+ # Set env vars in dashboard: OPENAI_API_KEY, OPENQA_JWT_SECRET, SAAS_URL
465
+ ```
466
+
467
+ **Render:**
468
+ - Fork repo → Connect to Render → Auto-deploys with `render.yaml`
469
+
470
+ **Fly.io:**
471
+ ```bash
472
+ flyctl launch
473
+ flyctl secrets set OPENAI_API_KEY=sk-xxx OPENQA_JWT_SECRET=$(openssl rand -hex 32)
474
+ flyctl deploy
475
+ ```
476
+
477
+ ### VPS/Bare Metal
478
+
479
+ ```bash
480
+ # Automated installer
481
+ curl -fsSL https://openqa.orkajs.com/install-production.sh | bash
482
+ # Choose option 2 (VPS/Bare Metal)
483
+ ```
484
+
485
+ **Manual installation:**
486
+ ```bash
487
+ # Install Node.js 20
488
+ curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
489
+ sudo apt install -y nodejs build-essential git
490
+
491
+ # Install OpenQA
492
+ sudo useradd -r -m openqa
493
+ sudo -u openqa git clone https://github.com/Orka-Community/OpenQA.git /opt/openqa
494
+ cd /opt/openqa
495
+ sudo -u openqa npm ci --only=production
496
+ sudo -u openqa npm run build
497
+
498
+ # Configure
499
+ sudo -u openqa cp .env.production .env
500
+ sudo nano /opt/openqa/.env
501
+
502
+ # Install systemd service
503
+ sudo cp openqa.service /etc/systemd/system/
504
+ sudo systemctl enable openqa
505
+ sudo systemctl start openqa
506
+ ```
507
+
508
+ ### 🔒 Security Checklist
509
+
510
+ Before going live:
511
+
512
+ - [ ] Set strong `OPENQA_JWT_SECRET` (generate: `openssl rand -hex 32`)
513
+ - [ ] Use strong admin password (min 12 chars)
514
+ - [ ] Enable HTTPS (SSL certificate)
515
+ - [ ] Never set `OPENQA_AUTH_DISABLED=true` in production
516
+ - [ ] Set `NODE_ENV=production`
517
+ - [ ] Restrict CORS origins
518
+ - [ ] Enable firewall (ports 80, 443 only)
519
+ - [ ] Setup automated backups
520
+
521
+ ### 📚 Deployment Documentation
522
+
523
+ - **[DEPLOYMENT.md](./DEPLOYMENT.md)** - Complete deployment guide
524
+ - **[QUICKSTART-PRODUCTION.md](./QUICKSTART-PRODUCTION.md)** - 5-minute quick start
525
+ - **[Docker Compose](./docker-compose.production.yml)** - Production configuration
526
+ - **[Systemd Service](./openqa.service)** - Service configuration
527
+
528
+ ### Development Deployment
529
+
530
+ For local development only:
334
531
 
335
532
  ```bash
336
533
  docker-compose up -d
@@ -0,0 +1,140 @@
1
+ // agent/brain/diff-analyzer.ts
2
+ import { execSync } from "child_process";
3
+ import { existsSync } from "fs";
4
+ import { join, basename, dirname } from "path";
5
+ var DiffAnalyzer = class {
6
+ /**
7
+ * Get files changed between current branch and base branch
8
+ */
9
+ getChangedFiles(repoPath, baseBranch = "main") {
10
+ try {
11
+ const output = execSync(`git diff --name-only ${baseBranch}...HEAD`, {
12
+ cwd: repoPath,
13
+ stdio: "pipe"
14
+ }).toString().trim();
15
+ if (!output) return [];
16
+ return output.split("\n").filter(Boolean);
17
+ } catch {
18
+ try {
19
+ const output = execSync("git diff --name-only HEAD~1", {
20
+ cwd: repoPath,
21
+ stdio: "pipe"
22
+ }).toString().trim();
23
+ if (!output) return [];
24
+ return output.split("\n").filter(Boolean);
25
+ } catch {
26
+ return [];
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Map changed source files to their likely test files
32
+ */
33
+ mapFilesToTests(changedFiles, repoPath) {
34
+ const testFiles = /* @__PURE__ */ new Set();
35
+ for (const file of changedFiles) {
36
+ if (!this.isSourceFile(file)) continue;
37
+ const candidates = this.getTestCandidates(file);
38
+ for (const candidate of candidates) {
39
+ if (existsSync(join(repoPath, candidate))) {
40
+ testFiles.add(candidate);
41
+ }
42
+ }
43
+ }
44
+ for (const file of changedFiles) {
45
+ if (this.isTestFile(file)) {
46
+ testFiles.add(file);
47
+ }
48
+ }
49
+ return Array.from(testFiles);
50
+ }
51
+ /**
52
+ * Analyze a diff and return risk assessment + affected tests
53
+ */
54
+ analyze(repoPath, baseBranch = "main") {
55
+ const changedFiles = this.getChangedFiles(repoPath, baseBranch);
56
+ const affectedTests = this.mapFilesToTests(changedFiles, repoPath);
57
+ const riskLevel = this.assessRisk(changedFiles);
58
+ const summary = this.buildSummary(changedFiles, affectedTests, riskLevel);
59
+ return { changedFiles, affectedTests, riskLevel, summary };
60
+ }
61
+ getTestCandidates(filePath) {
62
+ const dir = dirname(filePath);
63
+ const base = basename(filePath);
64
+ const candidates = [];
65
+ const nameMatch = base.match(/^(.+)\.(tsx?|jsx?|vue|svelte|py|go|rs)$/);
66
+ if (!nameMatch) return candidates;
67
+ const name = nameMatch[1];
68
+ const ext = nameMatch[2];
69
+ const testExts = ext.startsWith("ts") ? ["test.ts", "test.tsx", "spec.ts", "spec.tsx"] : ext.startsWith("js") ? ["test.js", "test.jsx", "spec.js", "spec.jsx"] : ext === "py" ? ["test.py"] : ext === "go" ? ["_test.go"] : [];
70
+ for (const testExt of testExts) {
71
+ candidates.push(join(dir, `${name}.${testExt}`));
72
+ candidates.push(join(dir, "__tests__", `${name}.${testExt}`));
73
+ candidates.push(join(dir, "test", `${name}.${testExt}`));
74
+ candidates.push(join(dir, "tests", `${name}.${testExt}`));
75
+ candidates.push(join("__tests__", dir, `${name}.${testExt}`));
76
+ }
77
+ if (ext === "go") {
78
+ candidates.push(join(dir, `${name}_test.go`));
79
+ }
80
+ return candidates;
81
+ }
82
+ isSourceFile(file) {
83
+ return /\.(tsx?|jsx?|vue|svelte|py|go|rs)$/.test(file) && !this.isTestFile(file);
84
+ }
85
+ isTestFile(file) {
86
+ return /\.(test|spec)\.(tsx?|jsx?|py)$/.test(file) || /_test\.go$/.test(file) || file.includes("__tests__/");
87
+ }
88
+ assessRisk(changedFiles) {
89
+ const highRiskPatterns = [
90
+ /auth/i,
91
+ /security/i,
92
+ /middleware/i,
93
+ /database/i,
94
+ /migration/i,
95
+ /config/i,
96
+ /\.env/,
97
+ /package\.json$/,
98
+ /docker/i,
99
+ /ci\//i,
100
+ /payment/i,
101
+ /billing/i,
102
+ /permission/i
103
+ ];
104
+ const mediumRiskPatterns = [
105
+ /api/i,
106
+ /route/i,
107
+ /controller/i,
108
+ /service/i,
109
+ /model/i,
110
+ /hook/i,
111
+ /context/i,
112
+ /store/i,
113
+ /util/i
114
+ ];
115
+ let highCount = 0;
116
+ let mediumCount = 0;
117
+ for (const file of changedFiles) {
118
+ if (highRiskPatterns.some((p) => p.test(file))) highCount++;
119
+ else if (mediumRiskPatterns.some((p) => p.test(file))) mediumCount++;
120
+ }
121
+ if (highCount >= 2 || highCount >= 1 && changedFiles.length > 5) return "high";
122
+ if (highCount >= 1 || mediumCount >= 3) return "medium";
123
+ return "low";
124
+ }
125
+ buildSummary(changedFiles, affectedTests, riskLevel) {
126
+ const lines = [];
127
+ lines.push(`${changedFiles.length} file(s) changed, ${affectedTests.length} test(s) affected.`);
128
+ lines.push(`Risk level: ${riskLevel}.`);
129
+ if (affectedTests.length > 0) {
130
+ lines.push(`Run: ${affectedTests.slice(0, 5).join(", ")}${affectedTests.length > 5 ? ` (+${affectedTests.length - 5} more)` : ""}`);
131
+ } else if (changedFiles.length > 0) {
132
+ lines.push("No matching test files found \u2014 consider running full suite.");
133
+ }
134
+ return lines.join(" ");
135
+ }
136
+ };
137
+ export {
138
+ DiffAnalyzer
139
+ };
140
+ //# sourceMappingURL=diff-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../agent/brain/diff-analyzer.ts"],"sourcesContent":["import { execSync } from 'child_process';\nimport { existsSync } from 'fs';\nimport { join, basename, dirname } from 'path';\n\nexport interface DiffResult {\n changedFiles: string[];\n affectedTests: string[];\n riskLevel: 'low' | 'medium' | 'high';\n summary: string;\n}\n\nexport class DiffAnalyzer {\n /**\n * Get files changed between current branch and base branch\n */\n getChangedFiles(repoPath: string, baseBranch: string = 'main'): string[] {\n try {\n const output = execSync(`git diff --name-only ${baseBranch}...HEAD`, {\n cwd: repoPath,\n stdio: 'pipe',\n }).toString().trim();\n\n if (!output) return [];\n return output.split('\\n').filter(Boolean);\n } catch {\n // Fallback: diff against HEAD~1 if base branch doesn't exist\n try {\n const output = execSync('git diff --name-only HEAD~1', {\n cwd: repoPath,\n stdio: 'pipe',\n }).toString().trim();\n\n if (!output) return [];\n return output.split('\\n').filter(Boolean);\n } catch {\n return [];\n }\n }\n }\n\n /**\n * Map changed source files to their likely test files\n */\n mapFilesToTests(changedFiles: string[], repoPath: string): string[] {\n const testFiles = new Set<string>();\n\n for (const file of changedFiles) {\n // Skip non-source files\n if (!this.isSourceFile(file)) continue;\n\n const candidates = this.getTestCandidates(file);\n for (const candidate of candidates) {\n if (existsSync(join(repoPath, candidate))) {\n testFiles.add(candidate);\n }\n }\n }\n\n // Also include any changed test files directly\n for (const file of changedFiles) {\n if (this.isTestFile(file)) {\n testFiles.add(file);\n }\n }\n\n return Array.from(testFiles);\n }\n\n /**\n * Analyze a diff and return risk assessment + affected tests\n */\n analyze(repoPath: string, baseBranch: string = 'main'): DiffResult {\n const changedFiles = this.getChangedFiles(repoPath, baseBranch);\n const affectedTests = this.mapFilesToTests(changedFiles, repoPath);\n\n const riskLevel = this.assessRisk(changedFiles);\n\n const summary = this.buildSummary(changedFiles, affectedTests, riskLevel);\n\n return { changedFiles, affectedTests, riskLevel, summary };\n }\n\n private getTestCandidates(filePath: string): string[] {\n const dir = dirname(filePath);\n const base = basename(filePath);\n const candidates: string[] = [];\n\n // Remove extension\n const nameMatch = base.match(/^(.+)\\.(tsx?|jsx?|vue|svelte|py|go|rs)$/);\n if (!nameMatch) return candidates;\n const name = nameMatch[1];\n const ext = nameMatch[2];\n\n // Common test file patterns\n const testExts = ext.startsWith('ts') ? ['test.ts', 'test.tsx', 'spec.ts', 'spec.tsx']\n : ext.startsWith('js') ? ['test.js', 'test.jsx', 'spec.js', 'spec.jsx']\n : ext === 'py' ? ['test.py']\n : ext === 'go' ? ['_test.go']\n : [];\n\n for (const testExt of testExts) {\n // Same directory: Foo.test.ts\n candidates.push(join(dir, `${name}.${testExt}`));\n // __tests__ directory: __tests__/Foo.test.ts\n candidates.push(join(dir, '__tests__', `${name}.${testExt}`));\n // test/ directory: test/Foo.test.ts\n candidates.push(join(dir, 'test', `${name}.${testExt}`));\n // tests/ directory: tests/Foo.test.ts\n candidates.push(join(dir, 'tests', `${name}.${testExt}`));\n // Root __tests__: __tests__/dir/Foo.test.ts\n candidates.push(join('__tests__', dir, `${name}.${testExt}`));\n }\n\n // Go convention: same file with _test suffix\n if (ext === 'go') {\n candidates.push(join(dir, `${name}_test.go`));\n }\n\n return candidates;\n }\n\n private isSourceFile(file: string): boolean {\n return /\\.(tsx?|jsx?|vue|svelte|py|go|rs)$/.test(file) && !this.isTestFile(file);\n }\n\n private isTestFile(file: string): boolean {\n return /\\.(test|spec)\\.(tsx?|jsx?|py)$/.test(file) ||\n /_test\\.go$/.test(file) ||\n file.includes('__tests__/');\n }\n\n private assessRisk(changedFiles: string[]): 'low' | 'medium' | 'high' {\n const highRiskPatterns = [\n /auth/i, /security/i, /middleware/i, /database/i, /migration/i,\n /config/i, /\\.env/, /package\\.json$/, /docker/i, /ci\\//i,\n /payment/i, /billing/i, /permission/i,\n ];\n\n const mediumRiskPatterns = [\n /api/i, /route/i, /controller/i, /service/i, /model/i,\n /hook/i, /context/i, /store/i, /util/i,\n ];\n\n let highCount = 0;\n let mediumCount = 0;\n\n for (const file of changedFiles) {\n if (highRiskPatterns.some(p => p.test(file))) highCount++;\n else if (mediumRiskPatterns.some(p => p.test(file))) mediumCount++;\n }\n\n if (highCount >= 2 || (highCount >= 1 && changedFiles.length > 5)) return 'high';\n if (highCount >= 1 || mediumCount >= 3) return 'medium';\n return 'low';\n }\n\n private buildSummary(changedFiles: string[], affectedTests: string[], riskLevel: string): string {\n const lines: string[] = [];\n lines.push(`${changedFiles.length} file(s) changed, ${affectedTests.length} test(s) affected.`);\n lines.push(`Risk level: ${riskLevel}.`);\n\n if (affectedTests.length > 0) {\n lines.push(`Run: ${affectedTests.slice(0, 5).join(', ')}${affectedTests.length > 5 ? ` (+${affectedTests.length - 5} more)` : ''}`);\n } else if (changedFiles.length > 0) {\n lines.push('No matching test files found — consider running full suite.');\n }\n\n return lines.join(' ');\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,47 @@
1
+ // agent/brain/llm-cache.ts
2
+ import { createHash } from "crypto";
3
+ var LLMCache = class {
4
+ store = /* @__PURE__ */ new Map();
5
+ ttlMs;
6
+ maxSize;
7
+ constructor(options) {
8
+ this.ttlMs = options?.ttlMs ?? 36e5;
9
+ this.maxSize = options?.maxSize ?? 500;
10
+ }
11
+ key(prompt) {
12
+ return createHash("sha256").update(prompt).digest("hex");
13
+ }
14
+ get(prompt) {
15
+ const k = this.key(prompt);
16
+ const entry = this.store.get(k);
17
+ if (!entry) return null;
18
+ if (Date.now() > entry.expiresAt) {
19
+ this.store.delete(k);
20
+ return null;
21
+ }
22
+ return entry.response;
23
+ }
24
+ set(prompt, response) {
25
+ if (this.store.size >= this.maxSize) {
26
+ const oldest = this.store.keys().next().value;
27
+ if (oldest) this.store.delete(oldest);
28
+ }
29
+ this.store.set(this.key(prompt), {
30
+ response,
31
+ expiresAt: Date.now() + this.ttlMs
32
+ });
33
+ }
34
+ clear() {
35
+ this.store.clear();
36
+ }
37
+ get size() {
38
+ return this.store.size;
39
+ }
40
+ stats() {
41
+ return { size: this.store.size, ttlMs: this.ttlMs, maxSize: this.maxSize };
42
+ }
43
+ };
44
+ export {
45
+ LLMCache
46
+ };
47
+ //# sourceMappingURL=llm-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../agent/brain/llm-cache.ts"],"sourcesContent":["import { createHash } from 'crypto';\n\ninterface CacheEntry {\n response: string;\n expiresAt: number;\n}\n\nexport class LLMCache {\n private store = new Map<string, CacheEntry>();\n private ttlMs: number;\n private maxSize: number;\n\n constructor(options?: { ttlMs?: number; maxSize?: number }) {\n this.ttlMs = options?.ttlMs ?? 3600000; // 1 hour\n this.maxSize = options?.maxSize ?? 500;\n }\n\n private key(prompt: string): string {\n return createHash('sha256').update(prompt).digest('hex');\n }\n\n get(prompt: string): string | null {\n const k = this.key(prompt);\n const entry = this.store.get(k);\n if (!entry) return null;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(k);\n return null;\n }\n return entry.response;\n }\n\n set(prompt: string, response: string): void {\n if (this.store.size >= this.maxSize) {\n // Evict oldest entry\n const oldest = this.store.keys().next().value;\n if (oldest) this.store.delete(oldest);\n }\n this.store.set(this.key(prompt), {\n response,\n expiresAt: Date.now() + this.ttlMs,\n });\n }\n\n clear(): void {\n this.store.clear();\n }\n\n get size(): number {\n return this.store.size;\n }\n\n stats(): { size: number; ttlMs: number; maxSize: number } {\n return { size: this.store.size, ttlMs: this.ttlMs, maxSize: this.maxSize };\n }\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAOpB,IAAM,WAAN,MAAe;AAAA,EACZ,QAAQ,oBAAI,IAAwB;AAAA,EACpC;AAAA,EACA;AAAA,EAER,YAAY,SAAgD;AAC1D,SAAK,QAAQ,SAAS,SAAS;AAC/B,SAAK,UAAU,SAAS,WAAW;AAAA,EACrC;AAAA,EAEQ,IAAI,QAAwB;AAClC,WAAO,WAAW,QAAQ,EAAE,OAAO,MAAM,EAAE,OAAO,KAAK;AAAA,EACzD;AAAA,EAEA,IAAI,QAA+B;AACjC,UAAM,IAAI,KAAK,IAAI,MAAM;AACzB,UAAM,QAAQ,KAAK,MAAM,IAAI,CAAC;AAC9B,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,KAAK,IAAI,IAAI,MAAM,WAAW;AAChC,WAAK,MAAM,OAAO,CAAC;AACnB,aAAO;AAAA,IACT;AACA,WAAO,MAAM;AAAA,EACf;AAAA,EAEA,IAAI,QAAgB,UAAwB;AAC1C,QAAI,KAAK,MAAM,QAAQ,KAAK,SAAS;AAEnC,YAAM,SAAS,KAAK,MAAM,KAAK,EAAE,KAAK,EAAE;AACxC,UAAI,OAAQ,MAAK,MAAM,OAAO,MAAM;AAAA,IACtC;AACA,SAAK,MAAM,IAAI,KAAK,IAAI,MAAM,GAAG;AAAA,MAC/B;AAAA,MACA,WAAW,KAAK,IAAI,IAAI,KAAK;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,QAA0D;AACxD,WAAO,EAAE,MAAM,KAAK,MAAM,MAAM,OAAO,KAAK,OAAO,SAAS,KAAK,QAAQ;AAAA,EAC3E;AACF;","names":[]}