@hybridlabor-api/aos 4.4.2-beta.7 → 4.4.2-beta.9

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.
@@ -0,0 +1,291 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Live Preview Canvas — Interactive Mockup Template</title>
7
+ <!-- Tailwind CSS via CDN for rapid, consistent styling -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script>
10
+ tailwind.config = {
11
+ darkMode: 'class',
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ brand: {
16
+ 50: '#f5f7ff',
17
+ 500: '#4f46e5',
18
+ 600: '#4338ca',
19
+ 700: '#3730a3'
20
+ }
21
+ }
22
+ }
23
+ }
24
+ };
25
+ </script>
26
+ <style>
27
+ /* Baseline styles & reset */
28
+ body {
29
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
30
+ margin: 0;
31
+ padding: 0;
32
+ background-color: #0b0f19;
33
+ color: #e2e8f0;
34
+ min-height: 100vh;
35
+ display: flex;
36
+ flex-direction: column;
37
+ }
38
+ /* Stable highlighting for Plan Canvas element selection */
39
+ [data-preview-anchor]:hover {
40
+ outline: 2px dashed rgba(99, 102, 241, 0.4);
41
+ outline-offset: 2px;
42
+ }
43
+ </style>
44
+ </head>
45
+ <body class="bg-slate-950 text-slate-100 antialiased selection:bg-indigo-500 selection:text-white pb-24">
46
+
47
+ <!-- =========================================================================
48
+ 1. Top Header Bar
49
+ Semantic IDs and data-component attributes enable crystal-clear annotations
50
+ ========================================================================= -->
51
+ <header id="preview-header" data-component="header" class="border-b border-slate-800 bg-slate-900/80 backdrop-blur sticky top-0 z-40 px-6 py-4 flex items-center justify-between">
52
+ <div class="flex items-center space-x-3">
53
+ <div class="h-8 w-8 rounded-lg bg-indigo-600 flex items-center justify-center font-bold text-white shadow-lg shadow-indigo-500/30">
54
+ P
55
+ </div>
56
+ <div>
57
+ <h1 class="text-base font-semibold text-white leading-tight">Feature Preview: <span id="feature-title" class="text-indigo-400">Component Mockup</span></h1>
58
+ <p class="text-xs text-slate-400">Interactive live prototype for visual review & red-pinning</p>
59
+ </div>
60
+ </div>
61
+
62
+ <!-- State Indicator / Action pill -->
63
+ <div class="flex items-center space-x-3">
64
+ <span id="active-state-badge" class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-emerald-950 text-emerald-300 border border-emerald-800">
65
+ <span class="w-1.5 h-1.5 mr-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
66
+ Interactive (In-Memory)
67
+ </span>
68
+ <button id="btn-reset-mock" onclick="resetMockState()" class="text-xs bg-slate-800 hover:bg-slate-700 text-slate-300 px-3 py-1.5 rounded border border-slate-700 transition">
69
+ Reset State
70
+ </button>
71
+ </div>
72
+ </header>
73
+
74
+ <!-- =========================================================================
75
+ 2. Main Canvas Content Area
76
+ ========================================================================= -->
77
+ <main class="flex-1 max-w-5xl w-full mx-auto px-6 py-8">
78
+
79
+ <!-- Information banner for reviewers -->
80
+ <section id="review-banner" data-component="instruction-banner" class="mb-6 p-4 rounded-xl bg-slate-900 border border-slate-800 flex items-start justify-between">
81
+ <div>
82
+ <h2 class="text-sm font-semibold text-slate-200">Visual Audit Mode Active</h2>
83
+ <p class="text-xs text-slate-400 mt-1 leading-relaxed">
84
+ Click any element to add a pinned comment, or use the bottom switcher to evaluate different layout variants and error states.
85
+ </p>
86
+ </div>
87
+ <div class="text-right text-xs text-slate-500 font-mono">
88
+ Hotkeys: <kbd class="px-1.5 py-0.5 bg-slate-800 rounded border border-slate-700 text-slate-300">←</kbd> <kbd class="px-1.5 py-0.5 bg-slate-800 rounded border border-slate-700 text-slate-300">→</kbd> to cycle
89
+ </div>
90
+ </section>
91
+
92
+ <!-- VARIANT A: Standard View -->
93
+ <div id="view-variant-a" class="preview-variant space-y-6">
94
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
95
+ <!-- Card 1 -->
96
+ <article id="card-stats-1" data-component="stat-card" class="p-5 rounded-xl bg-slate-900 border border-slate-800 shadow-sm hover:border-slate-700 transition">
97
+ <div class="text-xs font-medium text-slate-400 uppercase tracking-wider">Metric Alpha</div>
98
+ <div class="text-2xl font-bold text-white mt-2">1,482</div>
99
+ <div class="mt-2 text-xs text-emerald-400 flex items-center font-medium">
100
+ <span>↑ +12.4%</span>
101
+ <span class="text-slate-500 ml-1.5">vs last week</span>
102
+ </div>
103
+ </article>
104
+
105
+ <!-- Card 2 -->
106
+ <article id="card-stats-2" data-component="stat-card" class="p-5 rounded-xl bg-slate-900 border border-slate-800 shadow-sm hover:border-slate-700 transition">
107
+ <div class="text-xs font-medium text-slate-400 uppercase tracking-wider">Latency / Cook Cost</div>
108
+ <div class="text-2xl font-bold text-white mt-2">4.2 ms</div>
109
+ <div class="mt-2 text-xs text-indigo-400 flex items-center font-medium">
110
+ <span>Optimal 60 FPS</span>
111
+ </div>
112
+ </article>
113
+
114
+ <!-- Card 3 -->
115
+ <article id="card-stats-3" data-component="stat-card" class="p-5 rounded-xl bg-slate-900 border border-slate-800 shadow-sm hover:border-slate-700 transition">
116
+ <div class="text-xs font-medium text-slate-400 uppercase tracking-wider">Active Handlers</div>
117
+ <div class="text-2xl font-bold text-white mt-2" id="counter-value">9</div>
118
+ <div class="mt-2 flex space-x-2">
119
+ <button onclick="adjustCounter(1)" class="text-xs bg-indigo-600/30 hover:bg-indigo-600 text-indigo-200 px-2.5 py-1 rounded transition">+ Increment</button>
120
+ <button onclick="adjustCounter(-1)" class="text-xs bg-slate-800 hover:bg-slate-700 text-slate-300 px-2.5 py-1 rounded transition">- Decrement</button>
121
+ </div>
122
+ </article>
123
+ </div>
124
+
125
+ <!-- Action Panel / Primary Form -->
126
+ <section id="main-panel" data-component="action-panel" class="p-6 rounded-xl bg-slate-900 border border-slate-800">
127
+ <h3 class="text-base font-semibold text-white mb-4">Interactive Controls & Configuration</h3>
128
+ <div class="space-y-4">
129
+ <div>
130
+ <label for="input-channel" class="block text-xs font-medium text-slate-300 mb-1.5">Channel Name</label>
131
+ <input type="text" id="input-channel" value="live-preview/stream-01" class="w-full bg-slate-950 border border-slate-700 rounded-lg px-3.5 py-2 text-sm text-slate-100 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition">
132
+ </div>
133
+ <div class="flex items-center space-x-4">
134
+ <button id="btn-primary-action" onclick="simulateAction()" class="bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-medium px-4 py-2 rounded-lg shadow-md shadow-indigo-600/20 transition flex items-center space-x-2">
135
+ <span>Execute Action</span>
136
+ </button>
137
+ <span id="action-status" class="text-xs text-slate-400">Ready</span>
138
+ </div>
139
+ </div>
140
+ </section>
141
+ </div>
142
+
143
+ <!-- VARIANT B: Alternative Layout (Dense List) -->
144
+ <div id="view-variant-b" class="preview-variant hidden space-y-4">
145
+ <div id="dense-list-container" data-component="dense-list" class="rounded-xl bg-slate-900 border border-slate-800 divide-y divide-slate-800">
146
+ <div class="p-4 flex items-center justify-between">
147
+ <div>
148
+ <span class="text-sm font-medium text-white">Item #1: Audio Reactive Engine</span>
149
+ <p class="text-xs text-slate-400">FFT Peak extraction on CHOP chain 4</p>
150
+ </div>
151
+ <span class="text-xs bg-emerald-950 text-emerald-400 px-2 py-0.5 rounded border border-emerald-800">Active</span>
152
+ </div>
153
+ <div class="p-4 flex items-center justify-between">
154
+ <div>
155
+ <span class="text-sm font-medium text-white">Item #2: DMX Fixture Router</span>
156
+ <p class="text-xs text-slate-400">Universe 1-4 mapped to ArtNet node</p>
157
+ </div>
158
+ <span class="text-xs bg-slate-800 text-slate-400 px-2 py-0.5 rounded">Standby</span>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ <!-- VARIANT C: Error / Edge-Case State -->
164
+ <div id="view-variant-c" class="preview-variant hidden">
165
+ <div id="error-boundary-mock" data-component="error-state" class="text-center py-12 px-6 rounded-xl bg-slate-900 border border-rose-900/40">
166
+ <div class="inline-flex h-12 w-12 rounded-full bg-rose-950/80 text-rose-400 items-center justify-center mb-3 border border-rose-800/60">
167
+ ⚠️
168
+ </div>
169
+ <h3 class="text-base font-semibold text-white">Connection Stream Interrupted</h3>
170
+ <p class="text-xs text-slate-400 max-w-sm mx-auto mt-1.5 mb-6">
171
+ The upstream WebRTC signal timed out after 3000ms. Check loopback connection or proxy gateway.
172
+ </p>
173
+ <button onclick="switchVariant('A')" class="bg-rose-600 hover:bg-rose-500 text-white text-xs font-semibold px-4 py-2 rounded-lg transition shadow-md shadow-rose-600/20">
174
+ Retry & Return to Variant A
175
+ </button>
176
+ </div>
177
+ </div>
178
+
179
+ </main>
180
+
181
+ <!-- =========================================================================
182
+ 3. Floating Prototype Switcher Bar (Prototype Skill UI.md convention)
183
+ Allows immediate flipping between variants and states
184
+ ========================================================================= -->
185
+ <footer id="prototype-switcher-bar" data-component="switcher-bar" class="fixed bottom-4 left-1/2 -translate-x-1/2 z-50 bg-slate-900/90 backdrop-blur-md border border-slate-700/80 shadow-2xl rounded-full px-3 py-2 flex items-center space-x-2">
186
+ <button onclick="cycleVariant(-1)" aria-label="Previous Variant" class="p-1.5 text-slate-400 hover:text-white hover:bg-slate-800 rounded-full transition">
187
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
188
+ </button>
189
+
190
+ <div class="flex items-center space-x-1">
191
+ <button onclick="switchVariant('A')" id="tab-btn-A" class="switcher-pill px-3 py-1 rounded-full text-xs font-medium transition bg-indigo-600 text-white shadow-sm">
192
+ Variant A (Grid)
193
+ </button>
194
+ <button onclick="switchVariant('B')" id="tab-btn-B" class="switcher-pill px-3 py-1 rounded-full text-xs font-medium transition text-slate-400 hover:text-slate-200">
195
+ Variant B (List)
196
+ </button>
197
+ <button onclick="switchVariant('C')" id="tab-btn-C" class="switcher-pill px-3 py-1 rounded-full text-xs font-medium transition text-slate-400 hover:text-slate-200">
198
+ Variant C (Error)
199
+ </button>
200
+ </div>
201
+
202
+ <button onclick="cycleVariant(1)" aria-label="Next Variant" class="p-1.5 text-slate-400 hover:text-white hover:bg-slate-800 rounded-full transition">
203
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
204
+ </button>
205
+ </footer>
206
+
207
+ <!-- =========================================================================
208
+ 4. In-Memory Mock Interaction Logic
209
+ ========================================================================= -->
210
+ <script>
211
+ const VARIANTS = ['A', 'B', 'C'];
212
+ let currentVariantIndex = 0;
213
+ let counter = 9;
214
+
215
+ function getVariantFromUrl() {
216
+ const params = new URLSearchParams(window.location.search);
217
+ const v = params.get('variant');
218
+ return (v && VARIANTS.includes(v.toUpperCase())) ? v.toUpperCase() : 'A';
219
+ }
220
+
221
+ function switchVariant(variantKey) {
222
+ const normalized = variantKey.toUpperCase();
223
+ currentVariantIndex = VARIANTS.indexOf(normalized);
224
+ if (currentVariantIndex === -1) currentVariantIndex = 0;
225
+
226
+ // Update visibility
227
+ document.querySelectorAll('.preview-variant').forEach(el => el.classList.add('hidden'));
228
+ const activeEl = document.getElementById(`view-variant-${normalized.toLowerCase()}`);
229
+ if (activeEl) activeEl.classList.remove('hidden');
230
+
231
+ // Update pills
232
+ document.querySelectorAll('.switcher-pill').forEach(pill => {
233
+ pill.classList.remove('bg-indigo-600', 'text-white', 'shadow-sm');
234
+ pill.classList.add('text-slate-400');
235
+ });
236
+ const activePill = document.getElementById(`tab-btn-${normalized}`);
237
+ if (activePill) {
238
+ activePill.classList.add('bg-indigo-600', 'text-white', 'shadow-sm');
239
+ activePill.classList.remove('text-slate-400');
240
+ }
241
+
242
+ // Sync URL search param cleanly without reloading
243
+ const url = new URL(window.location);
244
+ url.searchParams.set('variant', normalized);
245
+ window.history.replaceState({}, '', url);
246
+ }
247
+
248
+ function cycleVariant(delta) {
249
+ currentVariantIndex = (currentVariantIndex + delta + VARIANTS.length) % VARIANTS.length;
250
+ switchVariant(VARIANTS[currentVariantIndex]);
251
+ }
252
+
253
+ function adjustCounter(delta) {
254
+ counter += delta;
255
+ document.getElementById('counter-value').textContent = counter;
256
+ }
257
+
258
+ function simulateAction() {
259
+ const statusEl = document.getElementById('action-status');
260
+ statusEl.textContent = 'Processing in memory...';
261
+ statusEl.className = 'text-xs text-amber-400 animate-pulse';
262
+ setTimeout(() => {
263
+ statusEl.textContent = 'Action executed successfully!';
264
+ statusEl.className = 'text-xs text-emerald-400 font-medium';
265
+ }, 400);
266
+ }
267
+
268
+ function resetMockState() {
269
+ counter = 9;
270
+ document.getElementById('counter-value').textContent = counter;
271
+ const statusEl = document.getElementById('action-status');
272
+ statusEl.textContent = 'Ready';
273
+ statusEl.className = 'text-xs text-slate-400';
274
+ switchVariant('A');
275
+ }
276
+
277
+ // Keyboard navigation (Arrow keys cycle variants when not typing in inputs)
278
+ window.addEventListener('keydown', (e) => {
279
+ const tag = document.activeElement ? document.activeElement.tagName.toLowerCase() : '';
280
+ if (tag === 'input' || tag === 'textarea' || tag === 'select') return;
281
+ if (e.key === 'ArrowRight') cycleVariant(1);
282
+ if (e.key === 'ArrowLeft') cycleVariant(-1);
283
+ });
284
+
285
+ // Initialize variant on page load
286
+ window.addEventListener('DOMContentLoaded', () => {
287
+ switchVariant(getVariantFromUrl());
288
+ });
289
+ </script>
290
+ </body>
291
+ </html>
@@ -0,0 +1,178 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Live Preview Canvas</title>
7
+ <style>
8
+ /* Inject Prototype CSS Here */
9
+ body {
10
+ font-family: system-ui, -apple-system, sans-serif;
11
+ margin: 0;
12
+ padding: 0;
13
+ background-color: #f9fafb;
14
+ }
15
+
16
+ .prototype-container {
17
+ /* Put your prototype content here */
18
+ min-height: 100vh;
19
+ padding: 2rem;
20
+ }
21
+
22
+ /* --- Feedback Layer Styles --- */
23
+ #feedback-layer {
24
+ position: fixed;
25
+ top: 0;
26
+ left: 0;
27
+ width: 100vw;
28
+ height: 100vh;
29
+ pointer-events: none;
30
+ z-index: 9998;
31
+ }
32
+
33
+ .feedback-dot {
34
+ position: absolute;
35
+ width: 16px;
36
+ height: 16px;
37
+ background-color: #ef4444;
38
+ border: 2px solid white;
39
+ border-radius: 50%;
40
+ transform: translate(-50%, -50%);
41
+ cursor: pointer;
42
+ pointer-events: auto;
43
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
44
+ }
45
+
46
+ .feedback-dot:hover::after {
47
+ content: attr(data-comment);
48
+ position: absolute;
49
+ top: 100%;
50
+ left: 50%;
51
+ transform: translateX(-50%);
52
+ background: white;
53
+ padding: 8px;
54
+ border-radius: 4px;
55
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
56
+ white-space: nowrap;
57
+ color: black;
58
+ font-size: 12px;
59
+ z-index: 9999;
60
+ margin-top: 4px;
61
+ }
62
+
63
+ #feedback-controls {
64
+ position: fixed;
65
+ bottom: 20px;
66
+ right: 20px;
67
+ background: white;
68
+ padding: 16px;
69
+ border-radius: 8px;
70
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
71
+ z-index: 9999;
72
+ pointer-events: auto;
73
+ display: flex;
74
+ flex-direction: column;
75
+ gap: 8px;
76
+ border: 1px solid #e5e7eb;
77
+ }
78
+
79
+ #feedback-controls button {
80
+ padding: 8px 16px;
81
+ background: #3b82f6;
82
+ color: white;
83
+ border: none;
84
+ border-radius: 4px;
85
+ cursor: pointer;
86
+ font-weight: 500;
87
+ }
88
+
89
+ #feedback-controls button:hover {
90
+ background: #2563eb;
91
+ }
92
+
93
+ #feedback-output {
94
+ display: none;
95
+ width: 300px;
96
+ height: 150px;
97
+ font-family: monospace;
98
+ font-size: 12px;
99
+ resize: none;
100
+ padding: 8px;
101
+ border: 1px solid #d1d5db;
102
+ border-radius: 4px;
103
+ }
104
+ </style>
105
+ </head>
106
+ <body>
107
+
108
+ <!-- Inject Prototype HTML Here -->
109
+ <div class="prototype-container">
110
+ <h1>Prototype Area</h1>
111
+ <p>Click anywhere to leave a comment.</p>
112
+ </div>
113
+
114
+ <!-- Feedback Layer -->
115
+ <div id="feedback-layer"></div>
116
+ <div id="feedback-controls">
117
+ <button id="export-btn">Export Feedback</button>
118
+ <textarea id="feedback-output" readonly placeholder="Exported JSON will appear here..."></textarea>
119
+ </div>
120
+
121
+ <script>
122
+ const comments = [];
123
+ const layer = document.getElementById('feedback-layer');
124
+ const exportBtn = document.getElementById('export-btn');
125
+ const output = document.getElementById('feedback-output');
126
+ const controls = document.getElementById('feedback-controls');
127
+
128
+ // Handle clicks on the document to add feedback
129
+ document.addEventListener('click', (e) => {
130
+ // Ignore clicks inside the feedback controls or on existing dots
131
+ if (controls.contains(e.target) || e.target.classList.contains('feedback-dot')) {
132
+ return;
133
+ }
134
+
135
+ const text = prompt('Enter your feedback for this element:');
136
+ if (!text) return;
137
+
138
+ const x = e.clientX;
139
+ const y = e.clientY;
140
+
141
+ // Save to state
142
+ const comment = { id: Date.now(), x, y, text };
143
+ comments.push(comment);
144
+
145
+ // Draw dot
146
+ const dot = document.createElement('div');
147
+ dot.className = 'feedback-dot';
148
+ dot.style.left = `${x}px`;
149
+ dot.style.top = `${y}px`;
150
+ dot.setAttribute('data-comment', text);
151
+
152
+ // Allow deleting comment on right click
153
+ dot.addEventListener('contextmenu', (evt) => {
154
+ evt.preventDefault();
155
+ if(confirm('Delete this comment?')) {
156
+ const idx = comments.findIndex(c => c.id === comment.id);
157
+ if(idx > -1) comments.splice(idx, 1);
158
+ dot.remove();
159
+ }
160
+ });
161
+
162
+ layer.appendChild(dot);
163
+ });
164
+
165
+ // Export functionality
166
+ exportBtn.addEventListener('click', () => {
167
+ output.style.display = 'block';
168
+ output.value = JSON.stringify(comments, null, 2);
169
+ output.select();
170
+ document.execCommand('copy');
171
+ exportBtn.textContent = 'Copied to Clipboard!';
172
+ setTimeout(() => {
173
+ exportBtn.textContent = 'Export Feedback';
174
+ }, 2000);
175
+ });
176
+ </script>
177
+ </body>
178
+ </html>
@@ -26,35 +26,84 @@ try {
26
26
  Write-Host "Warning: pip install failed. Install google-genai manually." -ForegroundColor Yellow
27
27
  }
28
28
 
29
- # 3. Resolve API key
30
- $GeminiKey = $env:GEMINI_API_KEY
31
- if ([string]::IsNullOrWhiteSpace($GeminiKey)) {
32
- Write-Host ""
33
- $GeminiKey = Read-Host "Enter your Gemini API key (or press Enter to skip)"
34
- }
35
-
36
- if (-not [string]::IsNullOrWhiteSpace($GeminiKey)) {
37
- Write-Host "Verifying API key..." -ForegroundColor Yellow
38
- # verify_api_key.py reads GEMINI_API_KEY from its own process environment.
39
- # A key typed at the Read-Host prompt above exists only in $GeminiKey, so it
40
- # has to be published to this process before python is started - otherwise
41
- # the child sees nothing and every interactively entered key fails the check
42
- # and is discarded below.
43
- $env:GEMINI_API_KEY = $GeminiKey
44
- $VerifyScript = Join-Path $PSScriptRoot "verify_api_key.py"
45
- $VerifyOutput = & python $VerifyScript 2>&1
46
- $VerifyCode = $LASTEXITCODE
47
- if ($VerifyCode -eq 0 -and ($VerifyOutput -match "VERIFIED_OK")) {
48
- Write-Host " -> API key verified." -ForegroundColor Green
49
- [System.Environment]::SetEnvironmentVariable("GEMINI_API_KEY", $GeminiKey, "User")
29
+ # 3. Resolve provider and API key
30
+ $OpenwikiProvider = $env:OPENWIKI_PROVIDER
31
+ if ([string]::IsNullOrWhiteSpace($OpenwikiProvider)) {
32
+ $OpenwikiProvider = "google"
33
+ }
34
+
35
+ # Map provider to its API key environment variable name
36
+ $ApiKeyVar = switch ($OpenwikiProvider) {
37
+ "google" { "GEMINI_API_KEY" }
38
+ "groq" { "GROQ_API_KEY" }
39
+ "grok" { "XAI_API_KEY" }
40
+ "xai" { "XAI_API_KEY" }
41
+ "nvidia" { "NVIDIA_API_KEY" }
42
+ "openrouter" { "OPENROUTER_API_KEY" }
43
+ "openai" { "OPENAI_API_KEY" }
44
+ "custom" { "OPENWIKI_API_KEY" }
45
+ "ollama" { $null }
46
+ default { "GEMINI_API_KEY"; $OpenwikiProvider = "google" }
47
+ }
48
+
49
+ # For Google provider, use the existing interactive flow with verification
50
+ if ($OpenwikiProvider -eq "google") {
51
+ $ApiKey = $env:GEMINI_API_KEY
52
+ if ([string]::IsNullOrWhiteSpace($ApiKey)) {
53
+ Write-Host ""
54
+ $ApiKey = Read-Host "Enter your Gemini API key (or press Enter to skip)"
55
+ }
56
+
57
+ if (-not [string]::IsNullOrWhiteSpace($ApiKey)) {
58
+ Write-Host "Verifying API key..." -ForegroundColor Yellow
59
+ # verify_api_key.py reads GEMINI_API_KEY from its own process environment.
60
+ # A key typed at the Read-Host prompt above exists only in $ApiKey, so it
61
+ # has to be published to this process before python is started - otherwise
62
+ # the child sees nothing and every interactively entered key fails the check
63
+ # and is discarded below.
64
+ $env:GEMINI_API_KEY = $ApiKey
65
+ $VerifyScript = Join-Path $PSScriptRoot "verify_api_key.py"
66
+ $VerifyOutput = & python $VerifyScript 2>&1
67
+ $VerifyCode = $LASTEXITCODE
68
+ if ($VerifyCode -eq 0 -and ($VerifyOutput -match "VERIFIED_OK")) {
69
+ Write-Host " -> API key verified." -ForegroundColor Green
70
+ [System.Environment]::SetEnvironmentVariable("GEMINI_API_KEY", $ApiKey, "User")
71
+ } else {
72
+ Write-Host " -> WARNING: API key verification failed (with retry + TLS fallback)." -ForegroundColor Yellow
73
+ $VerifyOutput | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray }
74
+ Write-Host " The daemon will run in collect-only mode until a valid key is set." -ForegroundColor Yellow
75
+ $ApiKey = ""
76
+ }
50
77
  } else {
51
- Write-Host " -> WARNING: API key verification failed (with retry + TLS fallback)." -ForegroundColor Yellow
52
- $VerifyOutput | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray }
53
- Write-Host " The daemon will run in collect-only mode until a valid key is set." -ForegroundColor Yellow
54
- $GeminiKey = ""
78
+ Write-Host "No API key provided. Daemon will run in collect-only mode." -ForegroundColor Yellow
55
79
  }
56
80
  } else {
57
- Write-Host "No API key provided. Daemon will run in collect-only mode." -ForegroundColor Yellow
81
+ # For non-Google providers, skip verification and use the configured key
82
+ if ([string]::IsNullOrWhiteSpace($ApiKeyVar)) {
83
+ $ApiKey = ""
84
+ } else {
85
+ $ApiKey = (Get-Item -Path "env:$ApiKeyVar" -ErrorAction SilentlyContinue).Value
86
+ if ([string]::IsNullOrWhiteSpace($ApiKey)) {
87
+ $ApiKey = ""
88
+ }
89
+ }
90
+ if ([string]::IsNullOrWhiteSpace($ApiKey)) {
91
+ Write-Host "No API key provided for provider '$OpenwikiProvider'. Daemon will run in collect-only mode." -ForegroundColor Yellow
92
+ }
93
+ }
94
+
95
+ # Persist provider and model configuration
96
+ [System.Environment]::SetEnvironmentVariable("OPENWIKI_PROVIDER", $OpenwikiProvider, "User")
97
+ if (-not [string]::IsNullOrWhiteSpace($env:OPENWIKI_MODEL)) {
98
+ [System.Environment]::SetEnvironmentVariable("OPENWIKI_MODEL", $env:OPENWIKI_MODEL, "User")
99
+ }
100
+ if (-not [string]::IsNullOrWhiteSpace($env:OPENWIKI_BASE_URL)) {
101
+ [System.Environment]::SetEnvironmentVariable("OPENWIKI_BASE_URL", $env:OPENWIKI_BASE_URL, "User")
102
+ }
103
+
104
+ # Persist the appropriate API key
105
+ if (-not [string]::IsNullOrWhiteSpace($ApiKey) -and -not [string]::IsNullOrWhiteSpace($ApiKeyVar)) {
106
+ [System.Environment]::SetEnvironmentVariable($ApiKeyVar, $ApiKey, "User")
58
107
  }
59
108
 
60
109
  # 4. Ensure log directory
@@ -171,8 +220,12 @@ if ($Registered) {
171
220
  Write-Host " -> Projects config: $DaemonLogDir\projects.json" -ForegroundColor Yellow
172
221
  }
173
222
 
174
- if (-not [string]::IsNullOrWhiteSpace($GeminiKey)) {
175
- Write-Host " -> API key stored as the GEMINI_API_KEY user environment variable" -ForegroundColor Green
223
+ if (-not [string]::IsNullOrWhiteSpace($ApiKey)) {
224
+ if (-not [string]::IsNullOrWhiteSpace($ApiKeyVar)) {
225
+ Write-Host " -> API key stored as the $ApiKeyVar user environment variable" -ForegroundColor Green
226
+ } else {
227
+ Write-Host " -> Provider configured: $OpenwikiProvider" -ForegroundColor Green
228
+ }
176
229
  Write-Host " (not embedded in the task or the startup file)." -ForegroundColor Green
177
230
  }
178
231