@openqa/cli 2.0.0 → 2.1.1

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.
@@ -75,7 +75,7 @@ function getDashboardHTML() {
75
75
  .logo-mark {
76
76
  width: 34px;
77
77
  height: 34px;
78
- background: var(--accent);
78
+ background: transparent;
79
79
  border-radius: 8px;
80
80
  display: grid;
81
81
  place-items: center;
@@ -697,7 +697,9 @@ function getDashboardHTML() {
697
697
  <!-- Sidebar -->
698
698
  <aside>
699
699
  <div class="logo">
700
- <div class="logo-mark">\u{1F52C}</div>
700
+ <div class="logo-mark">
701
+ <img src="https://openqa.orkajs.com/_next/image?url=https%3A%2F%2Forkajs.com%2Floutre-orka-qa.png&w=256&q=75" alt="OpenQA Logo" style="width: 40px; height: 40px;">
702
+ </div>
701
703
  <div>
702
704
  <div class="logo-name">OpenQA</div>
703
705
  <div class="logo-version">v2.1.0 \xB7 OSS</div>
@@ -707,39 +709,69 @@ function getDashboardHTML() {
707
709
  <div class="nav-section">
708
710
  <div class="nav-label">Overview</div>
709
711
  <a class="nav-item active" href="/">
710
- <span class="icon">\u25A6</span> Dashboard
712
+ <span class="icon">
713
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-gauge-icon lucide-gauge"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>
714
+ </span> Dashboard
711
715
  </a>
712
716
  <a class="nav-item" href="/kanban">
713
- <span class="icon">\u229E</span> Kanban
717
+ <span class="icon">
718
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-dashed-kanban-icon lucide-square-dashed-kanban"><path d="M8 7v7"/><path d="M12 7v4"/><path d="M16 7v9"/><path d="M5 3a2 2 0 0 0-2 2"/><path d="M9 3h1"/><path d="M14 3h1"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M21 9v1"/><path d="M21 14v1"/><path d="M21 19a2 2 0 0 1-2 2"/><path d="M14 21h1"/><path d="M9 21h1"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M3 14v1"/><path d="M3 9v1"/></svg>
719
+ </span> Kanban
714
720
  <span class="badge" id="kanban-count">0</span>
715
721
  </a>
716
722
 
717
723
  <div class="nav-label">Agents</div>
718
724
  <a class="nav-item" href="javascript:void(0)" onclick="scrollToSection('agents-table')">
719
- <span class="icon">\u25CE</span> Active Agents
725
+ <span class="icon">
726
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-activity-icon lucide-activity"><path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"/></svg>
727
+ </span> Active Agents
720
728
  </a>
721
729
  <a class="nav-item" href="javascript:void(0)" onclick="switchAgentTab('specialists'); scrollToSection('agents-table')">
722
- <span class="icon">\u25C7</span> Specialists
730
+ <span class="icon">
731
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hat-glasses-icon lucide-hat-glasses"><path d="M14 18a2 2 0 0 0-4 0"/><path d="m19 11-2.11-6.657a2 2 0 0 0-2.752-1.148l-1.276.61A2 2 0 0 1 12 4H8.5a2 2 0 0 0-1.925 1.456L5 11"/><path d="M2 11h20"/><circle cx="17" cy="18" r="3"/><circle cx="7" cy="18" r="3"/></svg>
732
+ </span> Specialists
723
733
  </a>
724
734
  <a class="nav-item" href="javascript:void(0)" onclick="scrollToSection('interventions-panel')">
725
- <span class="icon">\u26A0</span> Interventions
735
+ <span class="icon">
736
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-cog-icon lucide-user-cog"><path d="M10 15H6a4 4 0 0 0-4 4v2"/><path d="m14.305 16.53.923-.382"/><path d="m15.228 13.852-.923-.383"/><path d="m16.852 12.228-.383-.923"/><path d="m16.852 17.772-.383.924"/><path d="m19.148 12.228.383-.923"/><path d="m19.53 18.696-.382-.924"/><path d="m20.772 13.852.924-.383"/><path d="m20.772 16.148.924.383"/><circle cx="18" cy="15" r="3"/><circle cx="9" cy="7" r="4"/></svg>
737
+ </span> Interventions
726
738
  <span class="badge" id="intervention-count" style="background: var(--red);">0</span>
727
739
  </a>
728
740
 
729
741
  <div class="nav-label">Analysis</div>
730
742
  <a class="nav-item" href="javascript:void(0)" onclick="scrollToSection('issues-panel')">
731
- <span class="icon">\u{1F41B}</span> Bug Reports
743
+ <span class="icon">
744
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug-play-icon lucide-bug-play"><path d="M10 19.655A6 6 0 0 1 6 14v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 3.97"/><path d="M14 15.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z"/>
745
+ <path d="M14.12 3.88 16 2"/>
746
+ <path d="M21 5a4 4 0 0 1-3.55 3.97"/>
747
+ <path d="M3 21a4 4 0 0 1 3.81-4"/>
748
+ <path d="M3 5a4 4 0 0 0 3.55 3.97"/>
749
+ <path d="M6 13H2"/><path d="m8 2 1.88 1.88"/>
750
+ <path d="M9 7.13V6a3 3 0 1 1 6 0v1.13"/>
751
+ </svg>
752
+ </span> Bug Reports
732
753
  </a>
733
754
  <a class="nav-item" href="javascript:void(0)" onclick="switchChartTab('performance'); scrollToSection('chart-performance')">
734
- <span class="icon">\u26A1</span> Performance
755
+ <span class="icon">
756
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chart-spline-icon lucide-chart-spline"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M7 16c.5-2 1.5-7 4-7 2 0 2 3 4 3 2.5 0 4.5-5 5-7"/></svg>
757
+ </span> Performance
735
758
  </a>
736
759
  <a class="nav-item" href="javascript:void(0)" onclick="scrollToSection('activity-list')">
737
- <span class="icon">\u{1F4CB}</span> Logs
760
+ <span class="icon">
761
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scroll-text-icon lucide-scroll-text"><path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/></svg>
762
+ </span> Logs
738
763
  </a>
739
764
 
740
765
  <div class="nav-label">System</div>
741
766
  <a class="nav-item" href="/config">
742
- <span class="icon">\u2699</span> Config
767
+ <span class="icon">
768
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-columns3-cog-icon lucide-columns-3-cog"><path d="M10.5 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5.5"/><path d="m14.3 19.6 1-.4"/><path d="M15 3v7.5"/><path d="m15.2 16.9-.9-.3"/><path d="m16.6 21.7.3-.9"/><path d="m16.8 15.3-.4-1"/><path d="m19.1 15.2.3-.9"/><path d="m19.6 21.7-.4-1"/><path d="m20.7 16.8 1-.4"/><path d="m21.7 19.4-.9-.3"/><path d="M9 3v18"/><circle cx="18" cy="18" r="3"/></svg>
769
+ </span> Config
770
+ </a>
771
+ <a class="nav-item" href="/config/env">
772
+ <span class="icon">
773
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-columns3-cog-icon lucide-columns-3-cog"><path d="M10.5 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5.5"/><path d="m14.3 19.6 1-.4"/><path d="M15 3v7.5"/><path d="m15.2 16.9-.9-.3"/><path d="m16.6 21.7.3-.9"/><path d="m16.8 15.3-.4-1"/><path d="m19.1 15.2.3-.9"/><path d="m19.6 21.7-.4-1"/><path d="m20.7 16.8 1-.4"/><path d="m21.7 19.4-.9-.3"/><path d="M9 3v18"/><circle cx="18" cy="18" r="3"/></svg>
774
+ </span> Environment
743
775
  </a>
744
776
  </div>
745
777
 
@@ -771,7 +803,9 @@ function getDashboardHTML() {
771
803
  <div class="metric-card">
772
804
  <div class="metric-header">
773
805
  <div class="metric-label">Active Agents</div>
774
- <div class="metric-icon">\u{1F916}</div>
806
+ <div class="metric-icon">
807
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-terminal-icon lucide-square-terminal"><path d="m7 11 2-2-2-2"/><path d="M11 13h4"/><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/></svg>
808
+ </div>
775
809
  </div>
776
810
  <div class="metric-value" id="active-agents">0</div>
777
811
  <div class="metric-change positive" id="agents-change">\u2191 0 from last hour</div>
@@ -779,7 +813,9 @@ function getDashboardHTML() {
779
813
  <div class="metric-card">
780
814
  <div class="metric-header">
781
815
  <div class="metric-label">Total Actions</div>
782
- <div class="metric-icon">\u26A1</div>
816
+ <div class="metric-icon">
817
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-todo-icon lucide-list-todo"><path d="M13 5h8"/><path d="M13 12h8"/><path d="M13 19h8"/><path d="m3 17 2 2 4-4"/><rect x="3" y="4" width="6" height="6" rx="1"/></svg>
818
+ </div>
783
819
  </div>
784
820
  <div class="metric-value" id="total-actions">0</div>
785
821
  <div class="metric-change positive" id="actions-change">\u2191 0% this session</div>
@@ -787,7 +823,9 @@ function getDashboardHTML() {
787
823
  <div class="metric-card">
788
824
  <div class="metric-header">
789
825
  <div class="metric-label">Bugs Found</div>
790
- <div class="metric-icon">\u{1F41B}</div>
826
+ <div class="metric-icon">
827
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-todo-icon lucide-list-todo"><path d="M13 5h8"/><path d="M13 12h8"/><path d="M13 19h8"/><path d="m3 17 2 2 4-4"/><rect x="3" y="4" width="6" height="6" rx="1"/></svg>
828
+ </div>
791
829
  </div>
792
830
  <div class="metric-value" id="bugs-found">0</div>
793
831
  <div class="metric-change negative" id="bugs-change">\u2193 0 from yesterday</div>
@@ -795,7 +833,9 @@ function getDashboardHTML() {
795
833
  <div class="metric-card">
796
834
  <div class="metric-header">
797
835
  <div class="metric-label">Success Rate</div>
798
- <div class="metric-icon">\u2713</div>
836
+ <div class="metric-icon">
837
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-check-icon lucide-cloud-check"><path d="m17 15-5.5 5.5L9 18"/><path d="M5.516 16.07A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 3.501 7.327"/></svg>
838
+ </div>
799
839
  </div>
800
840
  <div class="metric-value" id="success-rate">\u2014</div>
801
841
  <div class="metric-change positive" id="rate-change">\u2191 0 pts improvement</div>
@@ -0,0 +1,391 @@
1
+ // cli/env-config.ts
2
+ var ENV_VARIABLES = [
3
+ // ============================================================================
4
+ // LLM CONFIGURATION
5
+ // ============================================================================
6
+ {
7
+ key: "LLM_PROVIDER",
8
+ type: "select",
9
+ category: "llm",
10
+ required: true,
11
+ description: "LLM provider to use for AI operations",
12
+ options: ["openai", "anthropic", "ollama"],
13
+ placeholder: "openai",
14
+ restartRequired: true
15
+ },
16
+ {
17
+ key: "OPENAI_API_KEY",
18
+ type: "password",
19
+ category: "llm",
20
+ required: false,
21
+ description: "OpenAI API key (required if LLM_PROVIDER=openai)",
22
+ placeholder: "sk-...",
23
+ sensitive: true,
24
+ testable: true,
25
+ validation: (value) => {
26
+ if (!value) return { valid: true };
27
+ if (!value.startsWith("sk-")) {
28
+ return { valid: false, error: 'OpenAI API key must start with "sk-"' };
29
+ }
30
+ if (value.length < 20) {
31
+ return { valid: false, error: "API key seems too short" };
32
+ }
33
+ return { valid: true };
34
+ },
35
+ restartRequired: true
36
+ },
37
+ {
38
+ key: "ANTHROPIC_API_KEY",
39
+ type: "password",
40
+ category: "llm",
41
+ required: false,
42
+ description: "Anthropic API key (required if LLM_PROVIDER=anthropic)",
43
+ placeholder: "sk-ant-...",
44
+ sensitive: true,
45
+ testable: true,
46
+ validation: (value) => {
47
+ if (!value) return { valid: true };
48
+ if (!value.startsWith("sk-ant-")) {
49
+ return { valid: false, error: 'Anthropic API key must start with "sk-ant-"' };
50
+ }
51
+ return { valid: true };
52
+ },
53
+ restartRequired: true
54
+ },
55
+ {
56
+ key: "OLLAMA_BASE_URL",
57
+ type: "url",
58
+ category: "llm",
59
+ required: false,
60
+ description: "Ollama server URL (required if LLM_PROVIDER=ollama)",
61
+ placeholder: "http://localhost:11434",
62
+ testable: true,
63
+ validation: (value) => {
64
+ if (!value) return { valid: true };
65
+ try {
66
+ new URL(value);
67
+ return { valid: true };
68
+ } catch {
69
+ return { valid: false, error: "Invalid URL format" };
70
+ }
71
+ },
72
+ restartRequired: true
73
+ },
74
+ {
75
+ key: "LLM_MODEL",
76
+ type: "text",
77
+ category: "llm",
78
+ required: false,
79
+ description: "LLM model to use (e.g., gpt-4, claude-3-opus, llama2)",
80
+ placeholder: "gpt-4",
81
+ restartRequired: true
82
+ },
83
+ // ============================================================================
84
+ // SECURITY
85
+ // ============================================================================
86
+ {
87
+ key: "OPENQA_JWT_SECRET",
88
+ type: "password",
89
+ category: "security",
90
+ required: true,
91
+ description: "Secret key for JWT token signing (min 32 characters)",
92
+ placeholder: "Generate with: openssl rand -hex 32",
93
+ sensitive: true,
94
+ validation: (value) => {
95
+ if (!value) return { valid: false, error: "JWT secret is required" };
96
+ if (value.length < 32) {
97
+ return { valid: false, error: "JWT secret must be at least 32 characters" };
98
+ }
99
+ return { valid: true };
100
+ },
101
+ restartRequired: true
102
+ },
103
+ {
104
+ key: "OPENQA_AUTH_DISABLED",
105
+ type: "boolean",
106
+ category: "security",
107
+ required: false,
108
+ description: "\u26A0\uFE0F DANGER: Disable authentication (NEVER use in production!)",
109
+ placeholder: "false",
110
+ validation: (value) => {
111
+ if (value === "true" && process.env.NODE_ENV === "production") {
112
+ return { valid: false, error: "Cannot disable auth in production!" };
113
+ }
114
+ return { valid: true };
115
+ },
116
+ restartRequired: true
117
+ },
118
+ {
119
+ key: "NODE_ENV",
120
+ type: "select",
121
+ category: "security",
122
+ required: false,
123
+ description: "Node environment (production enables security features)",
124
+ options: ["development", "production", "test"],
125
+ placeholder: "production",
126
+ restartRequired: true
127
+ },
128
+ // ============================================================================
129
+ // TARGET APPLICATION
130
+ // ============================================================================
131
+ {
132
+ key: "SAAS_URL",
133
+ type: "url",
134
+ category: "target",
135
+ required: true,
136
+ description: "URL of the application to test",
137
+ placeholder: "https://your-app.com",
138
+ testable: true,
139
+ validation: (value) => {
140
+ if (!value) return { valid: false, error: "Target URL is required" };
141
+ try {
142
+ const url = new URL(value);
143
+ if (!["http:", "https:"].includes(url.protocol)) {
144
+ return { valid: false, error: "URL must use http or https protocol" };
145
+ }
146
+ return { valid: true };
147
+ } catch {
148
+ return { valid: false, error: "Invalid URL format" };
149
+ }
150
+ }
151
+ },
152
+ {
153
+ key: "SAAS_AUTH_TYPE",
154
+ type: "select",
155
+ category: "target",
156
+ required: false,
157
+ description: "Authentication type for target application",
158
+ options: ["none", "basic", "session"],
159
+ placeholder: "none"
160
+ },
161
+ {
162
+ key: "SAAS_USERNAME",
163
+ type: "text",
164
+ category: "target",
165
+ required: false,
166
+ description: "Username for target application authentication",
167
+ placeholder: "test@example.com"
168
+ },
169
+ {
170
+ key: "SAAS_PASSWORD",
171
+ type: "password",
172
+ category: "target",
173
+ required: false,
174
+ description: "Password for target application authentication",
175
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
176
+ sensitive: true
177
+ },
178
+ // ============================================================================
179
+ // GITHUB INTEGRATION
180
+ // ============================================================================
181
+ {
182
+ key: "GITHUB_TOKEN",
183
+ type: "password",
184
+ category: "github",
185
+ required: false,
186
+ description: "GitHub personal access token for issue creation",
187
+ placeholder: "ghp_...",
188
+ sensitive: true,
189
+ testable: true,
190
+ validation: (value) => {
191
+ if (!value) return { valid: true };
192
+ if (!value.startsWith("ghp_") && !value.startsWith("github_pat_")) {
193
+ return { valid: false, error: 'GitHub token must start with "ghp_" or "github_pat_"' };
194
+ }
195
+ return { valid: true };
196
+ }
197
+ },
198
+ {
199
+ key: "GITHUB_OWNER",
200
+ type: "text",
201
+ category: "github",
202
+ required: false,
203
+ description: "GitHub repository owner/organization",
204
+ placeholder: "your-username"
205
+ },
206
+ {
207
+ key: "GITHUB_REPO",
208
+ type: "text",
209
+ category: "github",
210
+ required: false,
211
+ description: "GitHub repository name",
212
+ placeholder: "your-repo"
213
+ },
214
+ {
215
+ key: "GITHUB_BRANCH",
216
+ type: "text",
217
+ category: "github",
218
+ required: false,
219
+ description: "GitHub branch to monitor",
220
+ placeholder: "main"
221
+ },
222
+ // ============================================================================
223
+ // WEB SERVER
224
+ // ============================================================================
225
+ {
226
+ key: "WEB_PORT",
227
+ type: "number",
228
+ category: "web",
229
+ required: false,
230
+ description: "Port for web server",
231
+ placeholder: "4242",
232
+ validation: (value) => {
233
+ if (!value) return { valid: true };
234
+ const port = parseInt(value, 10);
235
+ if (isNaN(port) || port < 1 || port > 65535) {
236
+ return { valid: false, error: "Port must be between 1 and 65535" };
237
+ }
238
+ return { valid: true };
239
+ },
240
+ restartRequired: true
241
+ },
242
+ {
243
+ key: "WEB_HOST",
244
+ type: "text",
245
+ category: "web",
246
+ required: false,
247
+ description: "Host to bind web server (0.0.0.0 for all interfaces)",
248
+ placeholder: "0.0.0.0",
249
+ restartRequired: true
250
+ },
251
+ {
252
+ key: "CORS_ORIGINS",
253
+ type: "text",
254
+ category: "web",
255
+ required: false,
256
+ description: "Allowed CORS origins (comma-separated)",
257
+ placeholder: "https://your-domain.com,https://app.example.com",
258
+ restartRequired: true
259
+ },
260
+ // ============================================================================
261
+ // AGENT CONFIGURATION
262
+ // ============================================================================
263
+ {
264
+ key: "AGENT_AUTO_START",
265
+ type: "boolean",
266
+ category: "agent",
267
+ required: false,
268
+ description: "Auto-start agent on server launch",
269
+ placeholder: "false"
270
+ },
271
+ {
272
+ key: "AGENT_INTERVAL_MS",
273
+ type: "number",
274
+ category: "agent",
275
+ required: false,
276
+ description: "Agent run interval in milliseconds (1 hour = 3600000)",
277
+ placeholder: "3600000",
278
+ validation: (value) => {
279
+ if (!value) return { valid: true };
280
+ const interval = parseInt(value, 10);
281
+ if (isNaN(interval) || interval < 6e4) {
282
+ return { valid: false, error: "Interval must be at least 60000ms (1 minute)" };
283
+ }
284
+ return { valid: true };
285
+ }
286
+ },
287
+ {
288
+ key: "AGENT_MAX_ITERATIONS",
289
+ type: "number",
290
+ category: "agent",
291
+ required: false,
292
+ description: "Maximum iterations per agent session",
293
+ placeholder: "20",
294
+ validation: (value) => {
295
+ if (!value) return { valid: true };
296
+ const max = parseInt(value, 10);
297
+ if (isNaN(max) || max < 1 || max > 1e3) {
298
+ return { valid: false, error: "Max iterations must be between 1 and 1000" };
299
+ }
300
+ return { valid: true };
301
+ }
302
+ },
303
+ {
304
+ key: "GIT_LISTENER_ENABLED",
305
+ type: "boolean",
306
+ category: "agent",
307
+ required: false,
308
+ description: "Enable git merge/pipeline detection",
309
+ placeholder: "true"
310
+ },
311
+ {
312
+ key: "GIT_POLL_INTERVAL_MS",
313
+ type: "number",
314
+ category: "agent",
315
+ required: false,
316
+ description: "Git polling interval in milliseconds",
317
+ placeholder: "60000"
318
+ },
319
+ // ============================================================================
320
+ // DATABASE
321
+ // ============================================================================
322
+ {
323
+ key: "DB_PATH",
324
+ type: "text",
325
+ category: "database",
326
+ required: false,
327
+ description: "Path to SQLite database file",
328
+ placeholder: "./data/openqa.db",
329
+ restartRequired: true
330
+ },
331
+ // ============================================================================
332
+ // NOTIFICATIONS
333
+ // ============================================================================
334
+ {
335
+ key: "SLACK_WEBHOOK_URL",
336
+ type: "url",
337
+ category: "notifications",
338
+ required: false,
339
+ description: "Slack webhook URL for notifications",
340
+ placeholder: "https://hooks.slack.com/services/...",
341
+ sensitive: true,
342
+ testable: true,
343
+ validation: (value) => {
344
+ if (!value) return { valid: true };
345
+ if (!value.startsWith("https://hooks.slack.com/")) {
346
+ return { valid: false, error: "Invalid Slack webhook URL" };
347
+ }
348
+ return { valid: true };
349
+ }
350
+ },
351
+ {
352
+ key: "DISCORD_WEBHOOK_URL",
353
+ type: "url",
354
+ category: "notifications",
355
+ required: false,
356
+ description: "Discord webhook URL for notifications",
357
+ placeholder: "https://discord.com/api/webhooks/...",
358
+ sensitive: true,
359
+ testable: true,
360
+ validation: (value) => {
361
+ if (!value) return { valid: true };
362
+ if (!value.startsWith("https://discord.com/api/webhooks/")) {
363
+ return { valid: false, error: "Invalid Discord webhook URL" };
364
+ }
365
+ return { valid: true };
366
+ }
367
+ }
368
+ ];
369
+ function getEnvVariablesByCategory(category) {
370
+ return ENV_VARIABLES.filter((v) => v.category === category);
371
+ }
372
+ function getEnvVariable(key) {
373
+ return ENV_VARIABLES.find((v) => v.key === key);
374
+ }
375
+ function validateEnvValue(key, value) {
376
+ const envVar = getEnvVariable(key);
377
+ if (!envVar) return { valid: false, error: "Unknown environment variable" };
378
+ if (envVar.required && !value) {
379
+ return { valid: false, error: "This field is required" };
380
+ }
381
+ if (envVar.validation) {
382
+ return envVar.validation(value);
383
+ }
384
+ return { valid: true };
385
+ }
386
+ export {
387
+ ENV_VARIABLES,
388
+ getEnvVariable,
389
+ getEnvVariablesByCategory,
390
+ validateEnvValue
391
+ };