haraps 1.0.5 β†’ 1.0.6

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/bin/index.js CHANGED
@@ -1,760 +1,1385 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { execSync } = require('child_process');
6
-
7
- console.log('πŸš€ Scaffold Project Environment (Hara-XY) πŸš€');
8
-
9
- function ensureDir(dir) {
10
- if (!fs.existsSync(dir)) {
11
- fs.mkdirSync(dir, { recursive: true });
12
- }
13
- }
14
-
15
- function writeFile(filePath, content) {
16
- const fullPath = path.resolve(process.cwd(), filePath);
17
- ensureDir(path.dirname(fullPath));
18
- fs.writeFileSync(fullPath, content.trim() + '\n', 'utf8');
19
- console.log(`βœ… Created: ${filePath}`);
20
- }
21
-
22
- // 1. Dev Scripts
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+
7
+ console.log('πŸš€ Scaffold Project Environment (Hara-XY) πŸš€');
8
+
9
+ function ensureDir(dir) {
10
+ if (!fs.existsSync(dir)) {
11
+ fs.mkdirSync(dir, { recursive: true });
12
+ }
13
+ }
14
+
15
+ function writeFile(filePath, content) {
16
+ const fullPath = path.resolve(process.cwd(), filePath);
17
+ ensureDir(path.dirname(fullPath));
18
+ fs.writeFileSync(fullPath, content.trim() + '\n', 'utf8');
19
+ console.log(`βœ… Created: ${filePath}`);
20
+ }
21
+
22
+ // 1. Dev Scripts
23
23
  writeFile('dev.sh', `
24
- #!/usr/bin/env bash
25
- # =============================================
26
- # Local Development Runner (Rust + Next.js)
27
- # =============================================
28
-
29
- echo -e "\\e[36m=============================================\\e[0m"
30
- echo -e "\\e[36m πŸš€ Local Dev Server (Rust + Next.js)\\e[0m"
31
- echo -e "\\e[36m=============================================\\e[0m"
32
-
33
- # Aggressive cleanup of ports 3000 (Next) and 8787 (Worker)
34
- for port in 8787 3000; do
35
- targetPid=$(lsof -ti :$port)
36
- if [ ! -z "$targetPid" ]; then
37
- echo -e "\\e[33m Freeing port $port (PID $targetPid)...\\e[0m"
38
- kill -9 $targetPid 2>/dev/null
39
- fi
40
- done
41
-
42
- # Run Next.js and Rust Worker concurrently
43
- npx concurrently \\
44
- -n "NEXT,RUST" \\
45
- -c "blue,green" \\
46
- "cd frontend && npm run dev" \\
47
- "cd backend && npx wrangler dev"
48
- `);
49
- fs.chmodSync(path.resolve(process.cwd(), 'dev.sh'), '755');
50
-
24
+ #!/usr/bin/env bash
25
+ # =============================================
26
+ # Medizys Local Development Runner
27
+ # =============================================
28
+ # Starts the Worker and Next.js frontend
29
+ # using concurrently in a single terminal window.
30
+
31
+ echo ""
32
+ echo -e "\\e[36m=============================================\\e[0m"
33
+ echo -e "\\e[36m πŸš€ Medizys Local Dev Server\\e[0m"
34
+ echo -e "\\e[36m=============================================\\e[0m"
35
+ echo ""
36
+ echo -e "\\e[32m Backend β†’ http://localhost:8787\\e[0m"
37
+ echo -e "\\e[34m Frontend β†’ http://localhost:3000\\e[0m"
38
+ echo ""
39
+ echo -e "\\e[33m Starting unified development environment...\\e[0m"
40
+ echo -e "\\e[36m=============================================\\e[0m"
41
+ echo ""
42
+
43
+ # Ensure Node 22 is used if nvm is available
44
+ if [ -s "$HOME/.nvm/nvm.sh" ]; then
45
+ export NVM_DIR="$HOME/.nvm"
46
+ source "$NVM_DIR/nvm.sh"
47
+ nvm use 22 > /dev/null 2>&1
48
+ fi
49
+
50
+ # Cleanup zombie worker processes and ports to prevent conflicts
51
+ echo -e "\\e[90m Performing aggressive process cleanup...\\e[0m"
52
+
53
+ # Define processes to kill
54
+ for procName in workerd node concurrently; do
55
+ if pgrep -x "$procName" > /dev/null; then
56
+ echo -e "\\e[33m Stopping $procName...\\e[0m"
57
+ pkill -f "$procName" 2>/dev/null
58
+ fi
59
+ done
60
+
61
+ # Active verification loop to ensure all target processes have fully exited
62
+ maxWaitSeconds=5
63
+ start=$(date +%s)
64
+ while [ $(( $(date +%s) - start )) -lt $maxWaitSeconds ]; do
65
+ if ! pgrep -f "workerd|concurrently" > /dev/null; then
66
+ break
67
+ fi
68
+ sleep 0.2
69
+ done
70
+
71
+ # Function to robustly remove a directory with retry logic
72
+ remove_cache_directory() {
73
+ local path=$1
74
+ local label=$2
75
+ if [ -d "$path" ]; then
76
+ echo -e "\\e[90m Purging $label cache ($path)...\\e[0m"
77
+ rm -rf "$path"
78
+ fi
79
+ }
80
+
81
+ # Hard clear of wrangler state and Next.js cache
82
+ remove_cache_directory "worker/.wrangler" "wrangler state"
83
+ remove_cache_directory "frontend/.next" "frontend Turbopack cache"
84
+
85
+ for port in 8787 3000; do
86
+ echo -e "\\e[90m Ensuring port $port is free...\\e[0m"
87
+ retryCount=0
88
+ while [ $retryCount -lt 5 ]; do
89
+ targetPid=$(lsof -ti :$port)
90
+ if [ ! -z "$targetPid" ]; then
91
+ echo -e "\\e[31m Port $port occupied by PID $targetPid. Terminating...\\e[0m"
92
+ kill -9 $targetPid 2>/dev/null
93
+ sleep 1
94
+ retryCount=$((retryCount+1))
95
+ else
96
+ break
97
+ fi
98
+ done
99
+ done
100
+
101
+ # Disable wrangler usage metrics prompt
102
+ export WRANGLER_SEND_METRICS="false"
103
+ # Limit Node.js memory
104
+ export NODE_OPTIONS="--max-old-space-size=4096"
105
+
106
+ echo -e "\\e[32mStarting development environment...\\e[0m"
107
+
108
+ runnerPath="/tmp/mediqueue-dev-runner.js"
109
+
110
+ cat << 'EOF' > "$runnerPath"
111
+ const { spawn, execSync } = require('child_process');
112
+ let buffer = '';
113
+ let workerChild;
114
+ let frontendChild;
115
+ let lastRestartTime = 0;
116
+ const startTime = Date.now();
117
+
118
+ function formatSize(bytes) {
119
+ if (bytes < 1024) return bytes + 'B';
120
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
121
+ return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
122
+ }
123
+
124
+ function formatUptime() {
125
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
126
+ const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
127
+ const s = (elapsed % 60).toString().padStart(2, '0');
128
+ const h = Math.floor(elapsed / 3600);
129
+ if (h > 0) return \`\${h}h \${m}m \${s}s\`;
130
+ return \`\${m}m \${s}s\`;
131
+ }
132
+
133
+ let lastRows = 0;
134
+ let lastCols = 0;
135
+
136
+ function updateLegend() {
137
+ if (!process.stdout.isTTY) return;
138
+ const rows = process.stdout.rows || 30;
139
+ const cols = process.stdout.columns || 80;
140
+
141
+ // Set scrolling region only if changed (DECSTBM homes the cursor to 1,1!)
142
+ if (rows !== lastRows || cols !== lastCols) {
143
+ process.stdout.write(\`\\x1b[1;\${rows - 2}r\`);
144
+ lastRows = rows;
145
+ lastCols = cols;
146
+ }
147
+
148
+ const lineCount = (buffer.match(/\\n/g) || []).length;
149
+ const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
150
+ const uptimeStr = formatUptime();
151
+ const legend1 = \` [Copy] 'c': All (\${lineCount}L / \${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors \`;
152
+ const legend2 = \` [System] Uptime: \${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit \`;
153
+
154
+ // Draw legend
155
+ process.stdout.write(\`\\x1b[s\`); // save cursor
156
+ process.stdout.write(\`\\x1b[\${rows - 1};1H\\x1b[42m\\x1b[30m\${legend1.padEnd(cols)}\\x1b[0m\`);
157
+ process.stdout.write(\`\\x1b[\${rows};1H\\x1b[42m\\x1b[30m\${legend2.padEnd(cols)}\\x1b[0m\`);
158
+ process.stdout.write(\`\\x1b[u\`); // restore cursor
159
+ }
160
+
161
+ let legendInterval;
162
+ function startLegendUpdater() {
163
+ if (legendInterval) clearInterval(legendInterval);
164
+ legendInterval = setInterval(() => {
165
+ updateLegend();
166
+ }, 1000);
167
+ }
168
+
169
+ function cleanupLegend() {
170
+ if (legendInterval) clearInterval(legendInterval);
171
+ if (!process.stdout.isTTY) return;
172
+ process.stdout.write('\\x1b[r'); // reset scrolling region
173
+ }
174
+
175
+ process.stdout.on('resize', () => {
176
+ if (!process.stdout.isTTY) return;
177
+ cleanupLegend();
178
+ console.clear();
179
+ lastRows = 0;
180
+ updateLegend();
181
+ });
182
+
183
+ function extractErrors(text) {
184
+ const lines = text.split('\\n');
185
+ const errorPattern = /(error|exception|fail|β¨―|βœ—|traceback)/i;
186
+ let inErrorBlock = false;
187
+ let blockStart = 0;
188
+ const blocks = [];
189
+
190
+ for (let i = 0; i < lines.length; i++) {
191
+ if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
192
+ if (!inErrorBlock) {
193
+ inErrorBlock = true;
194
+ blockStart = Math.max(0, i - 2);
195
+ }
196
+ } else {
197
+ if (inErrorBlock) {
198
+ if (lines[i].trim() === '') continue;
199
+ const blockEnd = Math.min(lines.length - 1, i + 2);
200
+ blocks.push(lines.slice(blockStart, blockEnd + 1).join('\\n'));
201
+ inErrorBlock = false;
202
+ }
203
+ }
204
+ }
205
+ if (inErrorBlock) {
206
+ blocks.push(lines.slice(blockStart).join('\\n'));
207
+ }
208
+
209
+ return blocks.length > 0 ? blocks.join('\\n\\n--- [Next Error Block] ---\\n\\n') : '';
210
+ }
211
+
212
+ function formatPrefix(isError, source) {
213
+ const d = new Date();
214
+ const ts = String(d.getHours()).padStart(2, '0') + ':' +
215
+ String(d.getMinutes()).padStart(2, '0') + ':' +
216
+ String(d.getSeconds()).padStart(2, '0');
217
+
218
+ const color = source === 'worker' ? '\\x1b[36m' : '\\x1b[35m';
219
+ const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
220
+ return \`\\x1b[90m[\${ts}]\\x1b[0m \${color}\${tag}\\x1b[0m \`;
221
+ }
222
+
223
+ function createStreamHandler(isError, source) {
224
+ let remainder = '';
225
+ return (d) => {
226
+ const chunk = remainder + d.toString();
227
+ const lines = chunk.split('\\n');
228
+ remainder = lines.pop();
229
+ for (let line of lines) {
230
+ line = line.replace(/\\x1b\\[[23]J/g, '').replace(/\\x1b\\[[0-9;]*H/g, '').replace(/\\x1bc/g, '');
231
+ line = line.replace(/\\x1b\\[[0-9]*[A-G]/g, '').replace(/\\x1b\\[[0-9]*[K]/g, '');
232
+ line = line.replace(/\\r/g, '');
233
+ line = line.replace(/^\\[[^\\]]+\\]\\s*/, '');
234
+
235
+ if (line.trim() === '') continue;
236
+
237
+ const prefix = formatPrefix(isError, source);
238
+ const out = prefix + line + '\\n';
239
+
240
+ if (isError) process.stderr.write(out);
241
+ else process.stdout.write(out);
242
+ buffer += out;
243
+ }
244
+ if (buffer.length > 1000000) {
245
+ const idx = buffer.indexOf('\\n', buffer.length - 500000);
246
+ buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
247
+ }
248
+ };
249
+ }
250
+
251
+ function startWorker() {
252
+ workerChild = spawn('npx', ['concurrently', '-c', 'cyan', '-n', 'worker', '"npm run dev --prefix worker"'], {
253
+ env: { ...process.env, FORCE_COLOR: '1' },
254
+ stdio: ['ignore', 'pipe', 'pipe'],
255
+ shell: true
256
+ });
257
+ workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
258
+ workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
259
+ }
260
+
261
+ function startFrontend() {
262
+ frontendChild = spawn('npx', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"npm run dev --prefix frontend"'], {
263
+ env: { ...process.env, FORCE_COLOR: '1' },
264
+ stdio: ['ignore', 'pipe', 'pipe'],
265
+ shell: true
266
+ });
267
+ frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
268
+ frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
269
+ }
270
+
271
+ function startServices() {
272
+ startWorker();
273
+ startFrontend();
274
+ }
275
+
276
+ console.clear();
277
+ updateLegend();
278
+ startLegendUpdater();
279
+ startServices();
280
+
281
+ function copyToClipboard(text) {
282
+ const platform = process.platform;
283
+ let cmd = '';
284
+ if (platform === 'darwin') cmd = 'pbcopy';
285
+ else if (platform === 'win32') cmd = 'clip';
286
+ else cmd = 'xclip -selection clipboard';
287
+ try {
288
+ execSync(cmd, { input: text });
289
+ return true;
290
+ } catch (e) {
291
+ return false;
292
+ }
293
+ }
294
+
295
+ if (process.stdin.isTTY) {
296
+ process.stdin.setRawMode(true);
297
+ process.stdin.resume();
298
+ process.stdin.setEncoding('utf8');
299
+ process.stdin.on('data', k => {
300
+ const now = Date.now();
301
+ if (k === 'c' || k === 'C') {
302
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
303
+ if (!clean.trim()) {
304
+ console.log('\\n\\x1b[33m⚠️ Buffer is empty.\\x1b[0m\\n');
305
+ return;
306
+ }
307
+ if (copyToClipboard(clean)) console.log('\\n\\x1b[32mπŸ“‹ Copied ALL logs to clipboard!\\x1b[0m\\n');
308
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy. Ensure xclip/pbcopy is installed.\\x1b[0m\\n');
309
+ } else if (k === 's' || k === 'S') {
310
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
311
+ const shortLines = clean.split('\\n').slice(-500).join('\\n');
312
+ if (copyToClipboard(shortLines)) console.log('\\n\\x1b[32mπŸ“‹ Copied last 500 lines to clipboard!\\x1b[0m\\n');
313
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy.\\x1b[0m\\n');
314
+ } else if (k === 'w' || k === 'W') {
315
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
316
+ const workerLogs = clean.split('\\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\\n');
317
+ if (copyToClipboard(workerLogs)) console.log('\\n\\x1b[32mπŸ“‹ Copied BACKEND logs to clipboard!\\x1b[0m\\n');
318
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy.\\x1b[0m\\n');
319
+ } else if (k === 'f' || k === 'F') {
320
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
321
+ const frontendLogs = clean.split('\\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\\n');
322
+ if (copyToClipboard(frontendLogs)) console.log('\\n\\x1b[32mπŸ“‹ Copied FRONTEND logs to clipboard!\\x1b[0m\\n');
323
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy.\\x1b[0m\\n');
324
+ } else if (k === 'e' || k === 'E') {
325
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
326
+ const errors = extractErrors(clean);
327
+ if (errors) {
328
+ if (copyToClipboard(errors)) console.log('\\n\\x1b[31m🚨 Errors copied to clipboard!\\x1b[0m\\n');
329
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy errors.\\x1b[0m\\n');
330
+ } else {
331
+ console.log('\\n\\x1b[32mβœ… No errors detected.\\x1b[0m\\n');
332
+ }
333
+ } else if (k === 'r' || k === 'R') {
334
+ if (now - lastRestartTime < 1000) return;
335
+ lastRestartTime = now;
336
+ console.log('\\n\\x1b[33mπŸ”„ Restarting ALL services...\\x1b[0m\\n');
337
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
338
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
339
+ startServices();
340
+ } else if (k === '1') {
341
+ if (now - lastRestartTime < 1000) return;
342
+ lastRestartTime = now;
343
+ console.log('\\n\\x1b[33mπŸ”„ Restarting FRONTEND...\\x1b[0m\\n');
344
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
345
+ startFrontend();
346
+ } else if (k === '2') {
347
+ if (now - lastRestartTime < 1000) return;
348
+ lastRestartTime = now;
349
+ console.log('\\n\\x1b[33mπŸ”„ Restarting BACKEND...\\x1b[0m\\n');
350
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
351
+ startWorker();
352
+ } else if (k === 'x' || k === 'X') {
353
+ console.clear();
354
+ buffer = '';
355
+ lastRows = 0;
356
+ updateLegend();
357
+ console.log('\\x1b[32m🧹 Terminal cleared.\\x1b[0m\\n');
358
+ } else if (k === '\\u0003' || k === 'q' || k === 'Q') {
359
+ console.log('\\n\\x1b[33mStopping services...\\x1b[0m');
360
+ cleanupLegend();
361
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
362
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
363
+ process.exit();
364
+ }
365
+ });
366
+ }
367
+
368
+ // Cleanup on unexpected exits
369
+ process.on('SIGINT', () => {
370
+ cleanupLegend();
371
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
372
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
373
+ process.exit();
374
+ });
375
+ process.on('exit', () => {
376
+ cleanupLegend();
377
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
378
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
379
+ });
380
+ EOF
381
+
382
+ node "$runnerPath"
383
+
384
+ `);
385
+ fs.chmodSync(path.resolve(process.cwd(), 'dev.sh'), '755');
386
+
51
387
  writeFile('dev.ps1', `
52
- # =============================================
53
- # Project Local Development Runner
54
- # =============================================
55
- # Starts the Backend and Next.js frontend
56
- # using concurrently in a single terminal window.
57
-
58
- Write-Host ""
59
- Write-Host "=============================================" -ForegroundColor Cyan
60
- Write-Host " πŸš€ Project Local Dev Server" -ForegroundColor Cyan
61
- Write-Host "=============================================" -ForegroundColor Cyan
62
- Write-Host ""
63
- Write-Host " Backend β†’ http://localhost:8787" -ForegroundColor Green
64
- Write-Host " Frontend β†’ http://localhost:3000" -ForegroundColor Blue
65
- Write-Host ""
66
- Write-Host " Starting unified development environment..." -ForegroundColor Yellow
67
- Write-Host "=============================================" -ForegroundColor Cyan
68
- Write-Host ""
69
-
70
- # Cleanup zombie worker processes and ports to prevent conflicts
71
- Write-Host " Performing aggressive process cleanup..." -ForegroundColor Gray
72
-
73
- # Define processes to kill
74
- $procNames = @("workerd", "node", "concurrently")
75
-
76
- foreach ($procName in $procNames) {
77
- $procs = Get-Process $procName -ErrorAction SilentlyContinue
78
- if ($procs) {
79
- Write-Host " Stopping $procName ($($procs.Count) instances)..." -ForegroundColor Yellow
80
- $procs | Stop-Process -Force -ErrorAction SilentlyContinue
81
- }
82
- }
83
-
84
- # Active verification loop to ensure all target processes have fully exited
85
- $maxWaitSeconds = 5
86
- $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
87
- while ($stopwatch.Elapsed.TotalSeconds -lt $maxWaitSeconds) {
88
- $remainingProcs = Get-Process $procNames -ErrorAction SilentlyContinue
89
- if (-not $remainingProcs) {
90
- break
91
- }
92
- Start-Sleep -Milliseconds 200
93
- }
94
-
95
- # Function to robustly remove a directory with retry logic
96
- function Remove-CacheDirectory($path, $label) {
97
- if (Test-Path $path) {
98
- Write-Host " Purging $label cache ($path)..." -ForegroundColor Gray
99
- $attempts = 0
100
- $maxAttempts = 5
101
- $success = $false
102
-
103
- while ($attempts -lt $maxAttempts -and -not $success) {
104
- try {
105
- Remove-Item -Recurse -Force $path -ErrorAction Stop
106
- $success = $true
107
- } catch {
108
- $attempts++
109
- if ($attempts -lt $maxAttempts) {
110
- Write-Host " Lock detected on $path. Retrying ($attempts/$maxAttempts)..." -ForegroundColor DarkYellow
111
- Start-Sleep -Milliseconds (300 * $attempts)
112
- } else {
113
- Write-Host " Warning: Could not fully purge $path. Some files may be locked." -ForegroundColor Red
114
- }
115
- }
116
- }
117
- }
118
- }
119
-
120
- # Hard clear of wrangler state and Next.js cache
121
- Remove-CacheDirectory "backend/.wrangler" "wrangler state"
122
- Remove-CacheDirectory "frontend/.next" "frontend Turbopack cache"
123
-
124
- $ports = @(8787, 3000)
125
- foreach ($port in $ports) {
126
- Write-Host " Ensuring port $port is free..." -ForegroundColor Gray
127
- $retryCount = 0
128
- while ($retryCount -lt 5) {
129
- $netstatOutput = netstat -ano | Select-String ":$port\s+.*LISTENING\s+(\d+)"
130
- if ($netstatOutput) {
131
- $targetPid = $netstatOutput.Matches[0].Groups[1].Value
132
- Write-Host " Port $port occupied by PID $targetPid. Terminating..." -ForegroundColor Red
133
- Stop-Process -Id $targetPid -Force -ErrorAction SilentlyContinue
134
- Start-Sleep -Seconds 1
135
- $retryCount++
136
- } else {
137
- break
138
- }
139
- }
140
- }
141
-
142
- $env:WRANGLER_SEND_METRICS = "false"
143
- $env:NODE_OPTIONS = "--max-old-space-size=4096"
144
-
145
- Write-Host "Starting development environment..." -ForegroundColor Green
146
- $runnerScript = @'
147
- const { spawn, execSync } = require('child_process');
148
- let buffer = '';
149
- let workerChild;
150
- let frontendChild;
151
- let lastRestartTime = 0;
152
- const startTime = Date.now();
153
-
154
- function formatSize(bytes) {
155
- if (bytes < 1024) return bytes + 'B';
156
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
157
- return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
158
- }
159
-
160
- function formatUptime() {
161
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
162
- const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
163
- const s = (elapsed % 60).toString().padStart(2, '0');
164
- const h = Math.floor(elapsed / 3600);
165
- if (h > 0) return \`\${h}h \${m}m \${s}s\`;
166
- return \`\${m}m \${s}s\`;
167
- }
168
-
169
- let lastRows = 0;
170
- let lastCols = 0;
171
-
172
- function updateLegend() {
173
- if (!process.stdout.isTTY) return;
174
- const rows = process.stdout.rows || 30;
175
- const cols = process.stdout.columns || 80;
176
-
177
- if (rows !== lastRows || cols !== lastCols) {
178
- process.stdout.write(\`\x1b[1;\${rows - 2}r\`);
179
- lastRows = rows;
180
- lastCols = cols;
181
- }
182
-
183
- const lineCount = (buffer.match(/\n/g) || []).length;
184
- const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
185
- const uptimeStr = formatUptime();
186
- const legend1 = \` [Copy] 'c': All (\${lineCount}L / \${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors \`;
187
- const legend2 = \` [System] Uptime: \${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit | 'Q': Clean & Quit \`;
188
-
189
- process.stdout.write(\`\x1b[s\`);
190
- process.stdout.write(\`\x1b[\${rows - 1};1H\x1b[42m\x1b[30m\${legend1.padEnd(cols)}\x1b[0m\`);
191
- process.stdout.write(\`\x1b[\${rows};1H\x1b[42m\x1b[30m\${legend2.padEnd(cols)}\x1b[0m\`);
192
- process.stdout.write(\`\x1b[u\`);
193
- }
194
-
195
- let legendInterval;
196
- function startLegendUpdater() {
197
- if (legendInterval) clearInterval(legendInterval);
198
- legendInterval = setInterval(() => {
199
- updateLegend();
200
- }, 1000);
201
- }
202
-
203
- function cleanupLegend() {
204
- if (legendInterval) clearInterval(legendInterval);
205
- if (!process.stdout.isTTY) return;
206
- process.stdout.write('\x1b[r');
207
- }
208
-
209
- process.stdout.on('resize', () => {
210
- if (!process.stdout.isTTY) return;
211
- cleanupLegend();
212
- console.clear();
213
- lastRows = 0;
214
- updateLegend();
215
- });
216
-
217
- function extractErrors(text) {
218
- const lines = text.split('\n');
219
- const errorPattern = /(error|exception|fail|β¨―|βœ—|traceback)/i;
220
- let inErrorBlock = false;
221
- let blockStart = 0;
222
- const blocks = [];
223
-
224
- for (let i = 0; i < lines.length; i++) {
225
- if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
226
- if (!inErrorBlock) {
227
- inErrorBlock = true;
228
- blockStart = Math.max(0, i - 2);
229
- }
230
- } else {
231
- if (inErrorBlock) {
232
- if (lines[i].trim() === '') continue;
233
- const blockEnd = Math.min(lines.length - 1, i + 2);
234
- blocks.push(lines.slice(blockStart, blockEnd + 1).join('\n'));
235
- inErrorBlock = false;
236
- }
237
- }
238
- }
239
- if (inErrorBlock) {
240
- blocks.push(lines.slice(blockStart).join('\n'));
241
- }
242
- return blocks.length > 0 ? blocks.join('\n\n--- [Next Error Block] ---\n\n') : '';
243
- }
244
-
245
- function formatPrefix(isError, source) {
246
- const d = new Date();
247
- const ts = String(d.getHours()).padStart(2, '0') + ':' +
248
- String(d.getMinutes()).padStart(2, '0') + ':' +
249
- String(d.getSeconds()).padStart(2, '0');
250
- const color = source === 'worker' ? '\x1b[36m' : '\x1b[35m';
251
- const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
252
- return \`\x1b[90m[\${ts}]\x1b[0m \${color}\${tag}\x1b[0m \`;
253
- }
254
-
255
- function createStreamHandler(isError, source) {
256
- let remainder = '';
257
- return (d) => {
258
- const chunk = remainder + d.toString();
259
- const lines = chunk.split('\n');
260
- remainder = lines.pop();
261
- for (let line of lines) {
262
- line = line.replace(/\x1b\[[23]J/g, '').replace(/\x1b\[[0-9;]*H/g, '').replace(/\x1bc/g, '');
263
- line = line.replace(/\x1b\[[0-9]*[A-G]/g, '').replace(/\x1b\[[0-9]*[K]/g, '');
264
- line = line.replace(/\r/g, '');
265
- line = line.replace(/^\[[^\]]+\]\s*/, '');
266
-
267
- if (line.trim() === '') continue;
268
-
269
- const prefix = formatPrefix(isError, source);
270
- const out = prefix + line + '\n';
271
-
272
- if (isError) process.stderr.write(out);
273
- else process.stdout.write(out);
274
- buffer += out;
275
- }
276
- if (buffer.length > 1000000) {
277
- const idx = buffer.indexOf('\n', buffer.length - 500000);
278
- buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
279
- }
280
- };
281
- }
282
-
283
- function startWorker() {
284
- workerChild = spawn('npx.cmd', ['concurrently', '-c', 'cyan', '-n', 'worker', '"cd backend && npx wrangler dev"'], {
285
- env: { ...process.env, FORCE_COLOR: '1' },
286
- stdio: ['ignore', 'pipe', 'pipe'],
287
- windowsHide: true,
288
- shell: true
289
- });
290
- workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
291
- workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
292
- }
293
-
294
- function startFrontend() {
295
- frontendChild = spawn('npx.cmd', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"cd frontend && npm run dev"'], {
296
- env: { ...process.env, FORCE_COLOR: '1' },
297
- stdio: ['ignore', 'pipe', 'pipe'],
298
- windowsHide: true,
299
- shell: true
300
- });
301
- frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
302
- frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
303
- }
304
-
305
- function startServices() {
306
- startWorker();
307
- startFrontend();
308
- }
309
-
310
- console.clear();
311
- updateLegend();
312
- startLegendUpdater();
313
- startServices();
314
-
315
- if (process.stdin.isTTY) {
316
- process.stdin.setRawMode(true);
317
- process.stdin.resume();
318
- process.stdin.setEncoding('utf8');
319
- process.stdin.on('data', k => {
320
- const now = Date.now();
321
- if (k === 'c' || k === 'C') {
322
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
323
- if (!clean.trim()) {
324
- console.log('\n\x1b[33m⚠️ Buffer is empty.\x1b[0m\n');
325
- return;
326
- }
327
- try {
328
- execSync('clip', { input: clean });
329
- console.log('\n\x1b[32mπŸ“‹ Copied ALL logs to clipboard!\x1b[0m\n');
330
- } catch (e) {}
331
- } else if (k === 's' || k === 'S') {
332
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
333
- const shortLines = clean.split('\n').slice(-500).join('\n');
334
- try {
335
- execSync('clip', { input: shortLines });
336
- console.log('\n\x1b[32mπŸ“‹ Copied last 500 lines to clipboard!\x1b[0m\n');
337
- } catch (e) {}
338
- } else if (k === 'w' || k === 'W') {
339
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
340
- const workerLogs = clean.split('\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\n');
341
- try {
342
- execSync('clip', { input: workerLogs });
343
- console.log('\n\x1b[32mπŸ“‹ Copied BACKEND logs to clipboard!\x1b[0m\n');
344
- } catch (e) {}
345
- } else if (k === 'f' || k === 'F') {
346
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
347
- const frontendLogs = clean.split('\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\n');
348
- try {
349
- execSync('clip', { input: frontendLogs });
350
- console.log('\n\x1b[32mπŸ“‹ Copied FRONTEND logs to clipboard!\x1b[0m\n');
351
- } catch (e) {}
352
- } else if (k === 'e' || k === 'E') {
353
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
354
- const errors = extractErrors(clean);
355
- if (errors) {
356
- try {
357
- execSync('clip', { input: errors });
358
- console.log('\n\x1b[31m🚨 Errors copied to clipboard!\x1b[0m\n');
359
- } catch (e) {}
360
- } else {
361
- console.log('\n\x1b[32mβœ… No errors detected.\x1b[0m\n');
362
- }
363
- } else if (k === 'r' || k === 'R') {
364
- if (now - lastRestartTime < 1000) return;
365
- lastRestartTime = now;
366
- console.log('\n\x1b[33mπŸ”„ Restarting ALL services...\x1b[0m\n');
367
- if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
368
- if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
369
- startServices();
370
- } else if (k === '1') {
371
- if (now - lastRestartTime < 1000) return;
372
- lastRestartTime = now;
373
- console.log('\n\x1b[33mπŸ”„ Restarting FRONTEND...\x1b[0m\n');
374
- if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
375
- startFrontend();
376
- } else if (k === '2') {
377
- if (now - lastRestartTime < 1000) return;
378
- lastRestartTime = now;
379
- console.log('\n\x1b[33mπŸ”„ Restarting BACKEND...\x1b[0m\n');
380
- if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
381
- startWorker();
382
- } else if (k === 'x' || k === 'X') {
383
- console.clear();
384
- buffer = '';
385
- lastRows = 0;
386
- updateLegend();
387
- console.log('\x1b[32m🧹 Terminal cleared.\x1b[0m\n');
388
- } else if (k === '\u0003' || k === 'q') {
389
- console.log('\n\x1b[33mStopping services...\x1b[0m');
390
- cleanupLegend();
391
- if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
392
- if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
393
- process.exit();
394
- } else if (k === 'Q') {
395
- console.log('\n\x1b[31m🧹 Cleaning cache and dependencies, then stopping services...\x1b[0m');
396
- cleanupLegend();
397
- if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
398
- if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
399
-
400
- console.log('\x1b[33mRemoving node_modules, .next, and .wrangler...\x1b[0m');
401
- const fs = require('fs');
402
- const pathsToClean = ['frontend/.next', 'backend/.wrangler', 'frontend/node_modules', 'backend/node_modules', 'node_modules'];
403
- for (const p of pathsToClean) {
404
- try { fs.rmSync(p, { recursive: true, force: true }); } catch(e) {}
405
- }
406
- console.log('\x1b[32mCleanup complete.\x1b[0m');
407
- process.exit();
408
- }
409
- });
410
- }
411
-
412
- process.on('SIGINT', () => {
413
- cleanupLegend();
414
- process.exit();
415
- });
416
- process.on('exit', () => {
417
- cleanupLegend();
418
- console.clear();
419
- });
420
- '@
421
- $runnerPath = Join-Path $env:TEMP "project-dev-runner.js"
422
- Set-Content -Path $runnerPath -Value $runnerScript -Encoding UTF8
423
- node $runnerPath
424
- `);
425
-
426
- // 2. Deploy Scripts
388
+ # =============================================
389
+ # Medizys Local Development Runner
390
+ # =============================================
391
+ # Starts the Worker and Next.js frontend
392
+ # using concurrently in a single terminal window.
393
+
394
+ Write-Host ""
395
+ Write-Host "=============================================" -ForegroundColor Cyan
396
+ Write-Host " πŸš€ Medizys Local Dev Server" -ForegroundColor Cyan
397
+ Write-Host "=============================================" -ForegroundColor Cyan
398
+ Write-Host ""
399
+ Write-Host " Backend β†’ http://localhost:8787" -ForegroundColor Green
400
+ Write-Host " Frontend β†’ http://localhost:3000" -ForegroundColor Blue
401
+ Write-Host ""
402
+ Write-Host " Starting unified development environment..." -ForegroundColor Yellow
403
+ Write-Host "=============================================" -ForegroundColor Cyan
404
+ Write-Host ""
405
+
406
+ # Cleanup zombie worker processes and ports to prevent conflicts
407
+ Write-Host " Performing aggressive process cleanup..." -ForegroundColor Gray
408
+
409
+ # Define processes to kill
410
+ $procNames = @("workerd", "node", "concurrently")
411
+
412
+ foreach ($procName in $procNames) {
413
+ $procs = Get-Process $procName -ErrorAction SilentlyContinue
414
+ if ($procs) {
415
+ Write-Host " Stopping $procName ($($procs.Count) instances)..." -ForegroundColor Yellow
416
+ $procs | Stop-Process -Force -ErrorAction SilentlyContinue
417
+ }
418
+ }
419
+
420
+ # Active verification loop to ensure all target processes have fully exited
421
+ $maxWaitSeconds = 5
422
+ $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
423
+ while ($stopwatch.Elapsed.TotalSeconds -lt $maxWaitSeconds) {
424
+ $remainingProcs = Get-Process $procNames -ErrorAction SilentlyContinue
425
+ if (-not $remainingProcs) {
426
+ break
427
+ }
428
+ Start-Sleep -Milliseconds 200
429
+ }
430
+
431
+ # Function to robustly remove a directory with retry logic
432
+ function Remove-CacheDirectory($path, $label) {
433
+ if (Test-Path $path) {
434
+ Write-Host " Purging $label cache ($path)..." -ForegroundColor Gray
435
+ $attempts = 0
436
+ $maxAttempts = 5
437
+ $success = $false
438
+
439
+ while ($attempts -lt $maxAttempts -and -not $success) {
440
+ try {
441
+ Remove-Item -Recurse -Force $path -ErrorAction Stop
442
+ $success = $true
443
+ } catch {
444
+ $attempts++
445
+ if ($attempts -lt $maxAttempts) {
446
+ Write-Host " Lock detected on $path. Retrying ($attempts/$maxAttempts)..." -ForegroundColor DarkYellow
447
+ Start-Sleep -Milliseconds (300 * $attempts)
448
+ } else {
449
+ Write-Host " Warning: Could not fully purge $path. Some files may be locked." -ForegroundColor Red
450
+ }
451
+ }
452
+ }
453
+ }
454
+ }
455
+
456
+ # Hard clear of wrangler state and Next.js cache to prevent Turbopack panics and corrupted dependency graphs
457
+ Remove-CacheDirectory "worker/.wrangler" "wrangler state"
458
+ Remove-CacheDirectory "frontend/.next" "frontend Turbopack cache"
459
+
460
+ $ports = @(8787, 3000)
461
+ foreach ($port in $ports) {
462
+ Write-Host " Ensuring port $port is free..." -ForegroundColor Gray
463
+ $retryCount = 0
464
+ while ($retryCount -lt 5) {
465
+ $netstatOutput = netstat -ano | Select-String ":$port\\s+.*LISTENING\\s+(\\d+)"
466
+ if ($netstatOutput) {
467
+ $targetPid = $netstatOutput.Matches[0].Groups[1].Value
468
+ Write-Host " Port $port occupied by PID $targetPid. Terminating..." -ForegroundColor Red
469
+ Stop-Process -Id $targetPid -Force -ErrorAction SilentlyContinue
470
+ Start-Sleep -Seconds 1
471
+ $retryCount++
472
+ } else {
473
+ break
474
+ }
475
+ }
476
+ }
477
+
478
+ # Telegram watcher disabled by user request
479
+ # Write-Host " Starting Telegram watcher in background..." -ForegroundColor Magenta
480
+ # $watcherJob = Start-Job -ScriptBlock {
481
+ # & "$using:PSScriptRoot\\scripts\\telegram-watcher.ps1"
482
+ # }
483
+ # Write-Host " Telegram watcher running (Job ID: $($watcherJob.Id))" -ForegroundColor Magenta
484
+ # Write-Host " Reply to @mediisysbot on Telegram to queue instructions." -ForegroundColor DarkMagenta
485
+ # Write-Host ""
486
+
487
+ # Disable wrangler usage metrics prompt to prevent concurrently from hanging
488
+ $env:WRANGLER_SEND_METRICS = "false"
489
+
490
+ # Limit Node.js memory to prevent OOM crashes while providing 4GB for Next.js App Router
491
+ $env:NODE_OPTIONS = "--max-old-space-size=4096"
492
+
493
+ # Run the monorepo dev command through a Node.js wrapper to capture logs
494
+ # and listen for the 'c' keypress to copy output to the clipboard.
495
+ Write-Host "Starting development environment..." -ForegroundColor Green
496
+ $runnerScript = @'
497
+ const { spawn, execSync } = require('child_process');
498
+ let buffer = '';
499
+ let workerChild;
500
+ let frontendChild;
501
+ let lastRestartTime = 0;
502
+ const startTime = Date.now();
503
+
504
+ function formatSize(bytes) {
505
+ if (bytes < 1024) return bytes + 'B';
506
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
507
+ return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
508
+ }
509
+
510
+ function formatUptime() {
511
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
512
+ const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
513
+ const s = (elapsed % 60).toString().padStart(2, '0');
514
+ const h = Math.floor(elapsed / 3600);
515
+ if (h > 0) return \`\${h}h \${m}m \${s}s\`;
516
+ return \`\${m}m \${s}s\`;
517
+ }
518
+
519
+ let lastRows = 0;
520
+ let lastCols = 0;
521
+
522
+ function updateLegend() {
523
+ if (!process.stdout.isTTY) return;
524
+ const rows = process.stdout.rows || 30;
525
+ const cols = process.stdout.columns || 80;
526
+
527
+ // Set scrolling region only if changed (DECSTBM homes the cursor to 1,1!)
528
+ if (rows !== lastRows || cols !== lastCols) {
529
+ process.stdout.write(\`\\x1b[1;\${rows - 2}r\`);
530
+ lastRows = rows;
531
+ lastCols = cols;
532
+ }
533
+
534
+ const lineCount = (buffer.match(/\\n/g) || []).length;
535
+ const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
536
+ const uptimeStr = formatUptime();
537
+ const legend1 = \` [Copy] 'c': All (\${lineCount}L / \${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors \`;
538
+ const legend2 = \` [System] Uptime: \${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit | 'Q': Clean & Quit \`;
539
+
540
+ // Draw legend
541
+ process.stdout.write(\`\\x1b[s\`); // save cursor
542
+ process.stdout.write(\`\\x1b[\${rows - 1};1H\\x1b[42m\\x1b[30m\${legend1.padEnd(cols)}\\x1b[0m\`);
543
+ process.stdout.write(\`\\x1b[\${rows};1H\\x1b[42m\\x1b[30m\${legend2.padEnd(cols)}\\x1b[0m\`);
544
+ process.stdout.write(\`\\x1b[u\`); // restore cursor
545
+ }
546
+
547
+ let legendInterval;
548
+ function startLegendUpdater() {
549
+ if (legendInterval) clearInterval(legendInterval);
550
+ legendInterval = setInterval(() => {
551
+ updateLegend();
552
+ }, 1000);
553
+ }
554
+
555
+ function cleanupLegend() {
556
+ if (legendInterval) clearInterval(legendInterval);
557
+ if (!process.stdout.isTTY) return;
558
+ process.stdout.write('\\x1b[r'); // reset scrolling region
559
+ }
560
+
561
+ process.stdout.on('resize', () => {
562
+ if (!process.stdout.isTTY) return;
563
+ cleanupLegend();
564
+ console.clear();
565
+ lastRows = 0;
566
+ updateLegend();
567
+ });
568
+
569
+ function extractErrors(text) {
570
+ const lines = text.split('\\n');
571
+ const errorPattern = /(error|exception|fail|β¨―|βœ—|traceback)/i;
572
+ let inErrorBlock = false;
573
+ let blockStart = 0;
574
+ const blocks = [];
575
+
576
+ for (let i = 0; i < lines.length; i++) {
577
+ // Check for error keywords or stack trace lines
578
+ if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
579
+ if (!inErrorBlock) {
580
+ inErrorBlock = true;
581
+ blockStart = Math.max(0, i - 2); // 2 lines of context before
582
+ }
583
+ } else {
584
+ if (inErrorBlock) {
585
+ // If the next line is empty, we might just be between stack traces
586
+ if (lines[i].trim() === '') continue;
587
+
588
+ // End of error block
589
+ const blockEnd = Math.min(lines.length - 1, i + 2); // 2 lines of context after
590
+ blocks.push(lines.slice(blockStart, blockEnd + 1).join('\\n'));
591
+ inErrorBlock = false;
592
+ }
593
+ }
594
+ }
595
+ if (inErrorBlock) {
596
+ blocks.push(lines.slice(blockStart).join('\\n'));
597
+ }
598
+
599
+ return blocks.length > 0 ? blocks.join('\\n\\n--- [Next Error Block] ---\\n\\n') : '';
600
+ }
601
+
602
+ function formatPrefix(isError, source) {
603
+ const d = new Date();
604
+ const ts = String(d.getHours()).padStart(2, '0') + ':' +
605
+ String(d.getMinutes()).padStart(2, '0') + ':' +
606
+ String(d.getSeconds()).padStart(2, '0');
607
+
608
+ // Cyan for worker, Magenta for frontend
609
+ const color = source === 'worker' ? '\\x1b[36m' : '\\x1b[35m';
610
+ const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
611
+ return \`\\x1b[90m[\${ts}]\\x1b[0m \${color}\${tag}\\x1b[0m \`;
612
+ }
613
+
614
+ function createStreamHandler(isError, source) {
615
+ let remainder = '';
616
+ return (d) => {
617
+ const chunk = remainder + d.toString();
618
+ const lines = chunk.split('\\n');
619
+ remainder = lines.pop();
620
+ for (let line of lines) {
621
+ // Strip terminal-clearing and cursor-resetting escape codes
622
+ line = line.replace(/\\x1b\\[[23]J/g, '').replace(/\\x1b\\[[0-9;]*H/g, '').replace(/\\x1bc/g, '');
623
+ // Strip cursor up/down and line clear codes
624
+ line = line.replace(/\\x1b\\[[0-9]*[A-G]/g, '').replace(/\\x1b\\[[0-9]*[K]/g, '');
625
+ // Strip carriage returns which cause overwrite from start of line
626
+ line = line.replace(/\\r/g, '');
627
+
628
+ // Remove concurrently's prefix so we can add our own clean one
629
+ line = line.replace(/^\\[[^\\]]+\\]\\s*/, '');
630
+
631
+ if (line.trim() === '') continue;
632
+
633
+ const prefix = formatPrefix(isError, source);
634
+ const out = prefix + line + '\\n';
635
+
636
+ if (isError) process.stderr.write(out);
637
+ else process.stdout.write(out);
638
+ buffer += out;
639
+ }
640
+ if (buffer.length > 1000000) {
641
+ const idx = buffer.indexOf('\\n', buffer.length - 500000);
642
+ buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
643
+ }
644
+ };
645
+ }
646
+
647
+ function startWorker() {
648
+ workerChild = spawn('npx.cmd', ['concurrently', '-c', 'cyan', '-n', 'worker', '"npm run dev --prefix worker"'], {
649
+ env: { ...process.env, FORCE_COLOR: '1' },
650
+ stdio: ['ignore', 'pipe', 'pipe'],
651
+ windowsHide: true,
652
+ shell: true
653
+ });
654
+ workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
655
+ workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
656
+ }
657
+
658
+ function startFrontend() {
659
+ frontendChild = spawn('npx.cmd', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"npm run dev --prefix frontend"'], {
660
+ env: { ...process.env, FORCE_COLOR: '1' },
661
+ stdio: ['ignore', 'pipe', 'pipe'],
662
+ windowsHide: true,
663
+ shell: true
664
+ });
665
+ frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
666
+ frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
667
+ }
668
+
669
+ function startServices() {
670
+ startWorker();
671
+ startFrontend();
672
+ }
673
+
674
+ console.clear();
675
+ updateLegend();
676
+ startLegendUpdater();
677
+ startServices();
678
+
679
+ if (process.stdin.isTTY) {
680
+ process.stdin.setRawMode(true);
681
+ process.stdin.resume();
682
+ process.stdin.setEncoding('utf8');
683
+ process.stdin.on('data', k => {
684
+ const now = Date.now();
685
+ if (k === 'c' || k === 'C') {
686
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
687
+ if (!clean.trim()) {
688
+ console.log('\\n\\x1b[33m⚠️ Buffer is empty.\\x1b[0m\\n');
689
+ return;
690
+ }
691
+ try {
692
+ execSync('clip', { input: clean });
693
+ console.log('\\n\\x1b[32mπŸ“‹ Copied ALL logs to clipboard!\\x1b[0m\\n');
694
+ } catch (e) {}
695
+ } else if (k === 's' || k === 'S') {
696
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
697
+ const shortLines = clean.split('\\n').slice(-500).join('\\n');
698
+ try {
699
+ execSync('clip', { input: shortLines });
700
+ console.log('\\n\\x1b[32mπŸ“‹ Copied last 500 lines to clipboard!\\x1b[0m\\n');
701
+ } catch (e) {}
702
+ } else if (k === 'w' || k === 'W') {
703
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
704
+ const workerLogs = clean.split('\\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\\n');
705
+ try {
706
+ execSync('clip', { input: workerLogs });
707
+ console.log('\\n\\x1b[32mπŸ“‹ Copied BACKEND logs to clipboard!\\x1b[0m\\n');
708
+ } catch (e) {}
709
+ } else if (k === 'f' || k === 'F') {
710
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
711
+ const frontendLogs = clean.split('\\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\\n');
712
+ try {
713
+ execSync('clip', { input: frontendLogs });
714
+ console.log('\\n\\x1b[32mπŸ“‹ Copied FRONTEND logs to clipboard!\\x1b[0m\\n');
715
+ } catch (e) {}
716
+ } else if (k === 'e' || k === 'E') {
717
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
718
+ const errors = extractErrors(clean);
719
+ if (errors) {
720
+ try {
721
+ execSync('clip', { input: errors });
722
+ console.log('\\n\\x1b[31m🚨 Errors copied to clipboard!\\x1b[0m\\n');
723
+ } catch (e) {}
724
+ } else {
725
+ console.log('\\n\\x1b[32mβœ… No errors detected.\\x1b[0m\\n');
726
+ }
727
+ } else if (k === 'r' || k === 'R') {
728
+ if (now - lastRestartTime < 1000) return;
729
+ lastRestartTime = now;
730
+ console.log('\\n\\x1b[33mπŸ”„ Restarting ALL services...\\x1b[0m\\n');
731
+ if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
732
+ if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
733
+ startServices();
734
+ } else if (k === '1') {
735
+ if (now - lastRestartTime < 1000) return;
736
+ lastRestartTime = now;
737
+ console.log('\\n\\x1b[33mπŸ”„ Restarting FRONTEND...\\x1b[0m\\n');
738
+ if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
739
+ startFrontend();
740
+ } else if (k === '2') {
741
+ if (now - lastRestartTime < 1000) return;
742
+ lastRestartTime = now;
743
+ console.log('\\n\\x1b[33mπŸ”„ Restarting BACKEND...\\x1b[0m\\n');
744
+ if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
745
+ startWorker();
746
+ } else if (k === 'x' || k === 'X') {
747
+ console.clear();
748
+ buffer = '';
749
+ lastRows = 0;
750
+ updateLegend();
751
+ console.log('\\x1b[32m🧹 Terminal cleared.\\x1b[0m\\n');
752
+ } else if (k === '\\u0003' || k === 'q') {
753
+ console.log('\\n\\x1b[33mStopping services...\\x1b[0m');
754
+ cleanupLegend();
755
+ if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
756
+ if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
757
+ process.exit();
758
+ } else if (k === 'Q') {
759
+ console.log('\\n\\x1b[31m🧹 Cleaning cache and dependencies, then stopping services...\\x1b[0m');
760
+ cleanupLegend();
761
+ if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
762
+ if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
763
+
764
+ console.log('\\x1b[33mRemoving node_modules, .next, and .wrangler...\\x1b[0m');
765
+ const fs = require('fs');
766
+ const pathsToClean = ['frontend/.next', 'worker/.wrangler', 'frontend/node_modules', 'worker/node_modules', 'node_modules'];
767
+ for (const p of pathsToClean) {
768
+ try { fs.rmSync(p, { recursive: true, force: true }); } catch(e) {}
769
+ }
770
+ console.log('\\x1b[32mCleanup complete.\\x1b[0m');
771
+ process.exit();
772
+ }
773
+ });
774
+ }
775
+
776
+ // Cleanup on unexpected exits
777
+ process.on('SIGINT', () => {
778
+ cleanupLegend();
779
+ process.exit();
780
+ });
781
+ process.on('exit', () => {
782
+ cleanupLegend();
783
+ console.clear();
784
+ });
785
+ '@
786
+ $runnerPath = Join-Path $env:TEMP "mediqueue-dev-runner.js"
787
+ Set-Content -Path $runnerPath -Value $runnerScript -Encoding UTF8
788
+ node $runnerPath
789
+
790
+ # Cleanup watcher on exit
791
+ if ($null -ne (Get-Variable -Name watcherJob -ErrorAction SilentlyContinue)) {
792
+ if ($watcherJob) {
793
+ Stop-Job -Job $watcherJob -ErrorAction SilentlyContinue
794
+ Remove-Job -Job $watcherJob -ErrorAction SilentlyContinue
795
+ }
796
+ }
797
+
798
+ `);
799
+
800
+ // 2. Deploy Scripts
427
801
  writeFile('deploy.sh', `
428
- #!/usr/bin/env bash
429
- set -e
430
-
431
- echo -e "\\e[36m=============================================\\e[0m"
432
- echo -e "\\e[36m πŸš€ Deploying System (Rust + Next.js)\\e[0m"
433
- echo -e "\\e[36m=============================================\\e[0m"
434
-
435
- echo -e "\\e[32m[1/2] Deploying Rust Cloudflare Worker...\\e[0m"
436
- cd backend
437
- npx wrangler deploy
438
- cd ..
439
-
440
- echo -e "\\e[34m[2/2] Deploying Next.js Frontend...\\e[0m"
441
- cd frontend
442
- npm run build
443
- npx vercel deploy --prod
444
- cd ..
445
-
446
- echo -e "\\e[32mDeployment Complete!\\e[0m"
447
- `);
448
- fs.chmodSync(path.resolve(process.cwd(), 'deploy.sh'), '755');
449
-
802
+ TARGET=$(readlink -f ~/mediqueue_linux/mediqueue/frontend/node_modules/@cloudflare/next-on-pages/dist/index.js)
803
+ sed -i 's/await (0, import_esbuild.build)({/await (0, import_esbuild.build)({ logLevel: "error",/g' $TARGET
804
+ sed -i 's/await (0, import_esbuild2.build)({/await (0, import_esbuild2.build)({ logLevel: "error",/g' $TARGET
805
+ cd ~/mediqueue_linux/mediqueue/frontend
806
+ export NODE_OPTIONS="--max_old_space_size=16384"
807
+ npx @cloudflare/next-on-pages
808
+ cp -r .vercel /mnt/c/Users/shivg/Desktop/Hara-XY/mediqueue-main/mediqueue/frontend/
809
+ cd /mnt/c/Users/shivg/Desktop/Hara-XY/mediqueue-main/mediqueue/frontend
810
+ $env:WRANGLER_SEND_METRICS="false"
811
+ npx wrangler pages deploy .vercel/output/static
812
+
813
+ `);
814
+ fs.chmodSync(path.resolve(process.cwd(), 'deploy.sh'), '755');
815
+
450
816
  writeFile('deploy.ps1', `
451
- param(
452
- [switch]$SkipFrontend = $false,
453
- [switch]$SkipBackend = $false,
454
- [switch]$Fast = $false
455
- )
456
-
457
- if ($Fast) {
458
- Write-Host "[FAST] Fast mode enabled." -ForegroundColor Cyan
459
- }
460
-
461
- $ErrorActionPreference = 'Stop'
462
- Set-Location $PSScriptRoot
463
-
464
- # Load .env files if present
465
- $envFiles = @(".env", ".env.local", "frontend\\.env.local")
466
- foreach ($file in $envFiles) {
467
- $envFile = Join-Path $PSScriptRoot $file
468
- if (Test-Path $envFile) {
469
- Write-Host "[RESILIENCE] Loading environment variables from $file..." -ForegroundColor Cyan
470
- Get-Content $envFile | Where-Object { $_ -match '^\s*([^#\s][^=]+)=(.*)' } | ForEach-Object {
471
- $name = $matches[1].Trim()
472
- $value = $matches[2].Trim()
473
- if ($value -match '^"(.*)"$') { $value = $matches[1] }
474
- elseif ($value -match "^'(.*)'$") { $value = $matches[1] }
475
- [Environment]::SetEnvironmentVariable($name, $value, "Process")
476
- }
477
- }
478
- }
479
-
480
- Write-Host "[RESILIENCE] Terminating conflicting processes to prevent locks..." -ForegroundColor Yellow
481
- $conflictingProcs = Get-WmiObject Win32_Process | Where-Object {
482
- $_.Name -match 'node.exe' -and
483
- ($_.CommandLine -match 'next build' -or $_.CommandLine -match 'tsc' -or $_.CommandLine -match 'wrangler deploy')
484
- }
485
- foreach ($p in $conflictingProcs) {
486
- Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
487
- }
488
-
489
- $GlobalReport = [PSCustomObject]@{
490
- StartTime = (Get-Date)
491
- Status = "Running"
492
- Steps = @()
493
- Error = $null
494
- }
495
-
496
- $ReportsDir = Join-Path $PSScriptRoot "deployment_reports"
497
- if (!(Test-Path $ReportsDir)) { New-Item -ItemType Directory -Path $ReportsDir | Out-Null }
498
-
499
- function Add-Step {
500
- param([string]$Name, [string]$Status, [string]$Details = "")
501
- $GlobalReport.Steps += [PSCustomObject]@{
502
- Name = $Name
503
- Status = $Status
504
- Details = $Details
505
- Time = (Get-Date).ToString("T")
506
- }
507
- }
508
-
509
- function Generate-FinalReport {
510
- param([string]$FinalStatus)
511
- $GlobalReport.Status = $FinalStatus
512
-
513
- $reportText = "=================================================\`n"
514
- $reportText += "PROJECT DEPLOYMENT REPORT\`n"
515
- $reportText += "=================================================\`n"
516
- $reportText += "Status: $FinalStatus\`n"
517
- $reportText += "Duration: $((Get-Date) - $GlobalReport.StartTime)\`n"
518
- $reportText += "-------------------------------------------------\`n"
519
-
520
- foreach ($step in $GlobalReport.Steps) {
521
- $indicator = if ($step.Status -eq "SUCCESS") { "[OK]" } else { "[FAIL]" }
522
- $reportText += "$indicator [$($step.Time)] $($step.Name): $($step.Status)\`n"
523
- if ($step.Details) { $reportText += " > $($step.Details)\`n" }
524
- }
525
-
526
- if ($GlobalReport.Error) {
527
- $reportText += "-------------------------------------------------\`n"
528
- $reportText += "CRITICAL ERROR:\`n$($GlobalReport.Error)\`n"
529
- }
530
- $reportText += "=================================================\`n"
531
-
532
- $statusIndicator = if ($FinalStatus -eq "SUCCESS") { "SUCCESS" } else { "FAILED" }
533
- $humanTime = (Get-Date).ToString("ddMMM_hh-mmtt")
534
- $fileName = "Deploy_\${statusIndicator}_\${humanTime}.txt"
535
- $filePath = Join-Path $ReportsDir $fileName
536
- $reportText | Out-File -FilePath $filePath
537
-
538
- try { $reportText | Set-Clipboard } catch {}
539
- return $reportText
540
- }
541
-
542
- function Invoke-ModernCommand {
543
- param([string]$Step, [scriptblock]$Script, [switch]$NoExit)
544
- Write-Host ""
545
- Write-Host "[STEP] $($Step)..." -ForegroundColor Yellow
546
- try {
547
- $global:LASTEXITCODE = 0
548
- & $Script
549
- $exitCode = $LASTEXITCODE
550
-
551
- if ($exitCode -eq 0) {
552
- Add-Step $Step "SUCCESS"
553
- Write-Host "[OK] $Step completed successfully." -ForegroundColor Green
554
- } else {
555
- throw "Command failed with exit code $exitCode."
556
- }
557
- } catch {
558
- $errorMsg = $_.ToString()
559
- Add-Step $Step "FAILED" "Critical Error: See Global Report"
560
- $GlobalReport.Error = "Step: $Step\`nError: $errorMsg"
561
-
562
- if ($NoExit) {
563
- Write-Host "[WARN] $Step failed, but continuing." -ForegroundColor Yellow
564
- throw $errorMsg
565
- }
566
-
567
- $finalReport = Generate-FinalReport "FAILED"
568
- Write-Host "\`nDEPLOYMENT FAILED!\`n$errorMsg" -ForegroundColor Red
569
- Write-Host "[CLIPBOARD] Full error report copied to your clipboard!" -ForegroundColor Cyan
570
-
571
- Set-Location $PSScriptRoot
572
- exit 1
573
- }
574
- }
575
-
576
- if (!$SkipBackend) {
577
- try {
578
- Push-Location backend
579
- Invoke-ModernCommand "Rust Backend Deployment" { npx wrangler deploy }
580
- } finally {
581
- Pop-Location
582
- }
583
- } else {
584
- Add-Step "Backend Deployment" "SKIPPED"
585
- }
586
-
587
- if (!$SkipFrontend) {
588
- try {
589
- Push-Location frontend
590
- $env:NODE_OPTIONS = "--max-old-space-size=8192"
591
- $lockFile = Join-Path (Get-Location) ".next\lock"
592
- if (Test-Path $lockFile) { Remove-Item -Force $lockFile -ErrorAction SilentlyContinue }
593
-
594
- Invoke-ModernCommand "Frontend Build" { npm run build }
595
- Invoke-ModernCommand "Frontend Vercel Deploy" { npx vercel deploy --prod }
596
- } finally {
597
- Pop-Location
598
- }
599
- } else {
600
- Add-Step "Frontend Deployment" "SKIPPED"
601
- }
602
-
603
- $finalReport = Generate-FinalReport "SUCCESS"
604
- Write-Host "\`nDEPLOYMENT SUCCESSFUL!" -ForegroundColor Green
605
- Write-Host $finalReport
606
- Write-Host "[CLIPBOARD] Full report copied to your clipboard!" -ForegroundColor Cyan
607
- `);
608
-
609
- // 3. Agent Rules
610
- writeFile('AGENTS.md', `
611
- # High-Performance Engineering Standards
612
- 1. **Ponytail, lazy senior dev mode**: The best code is the code never written. YAGNI. Question complex requests, reuse existing standards, prefer deletion over addition, and write the minimal correct code.
613
- 2. **Edge-First Execution**: Backend logic must be optimized for serverless edge deployment (Cloudflare Workers).
614
- 3. **Data Layer Optimization**: Never execute untuned queries. Use connection pooling and batching.
615
- 4. **Minimalist Frontend Render**: Ensure frontend implementations are highly optimized for Next.js, favoring SSR where appropriate, avoiding heavy client bundles, and ensuring mobile-first responsive design.
616
- 5. **Master Debugging**: Trace issues to their root cause before writing a fix. Use the "Five Whys" protocol.
617
- 6. **Strict Typing**: TypeScript and Rust types must be explicit, avoid \`any\` or \`unwrap()\` where possible.
618
- 7. **Structured Logging**: Implement structured logging, avoiding excessive console.logs in production.
619
- 8. **Atomic Commits**: Ensure commits are small, atomic, and pass CI checks before pushing.
620
- 9. **Error Boundaries**: Frontend must wrap critical components in Error Boundaries to prevent full app crashes.
621
- 10. **No Placeholder Code**: Never write generic "TODO" or placeholder logic if the requirements are known.
622
- 11. **Security Best Practices**: Enforce strict input validation, rate limiting, and secure credential handling by default.
623
- 12. **Automated Testing & TDD**: Require unit and integration tests for critical paths. Code is not done until it's tested.
624
- 13. **Strict Linting & Formatting**: Enforce ESLint, Prettier, and Clippy with zero warnings allowed.
625
- 14. **Accessibility (a11y) First**: Require ARIA labels, semantic HTML, and keyboard navigation for all frontend components.
626
- 15. **Documentation Standards**: Mandate JSDoc (frontend) and Rustdoc (backend) for all public APIs and reusable components.
627
- 16. **CI/CD Pipeline Setup**: Ensure GitHub Actions (or similar) are configured for automated checks, tests, and deployments.
628
- 17. **State Management Rules**: Prefer local state or React Context; enforce strict rules against unnecessary global state.
629
- 18. **Environment Variable Validation**: Fail fast at startup if required \`.env\` variables are missing.
630
- 19. **Kill AI Slop**: Remove aesthetic clichΓ©s (indigo gradients, glowing cards, excessive emojis) that serve no functional purpose.
631
- 20. **Security First Principle**: Treat all external input as malicious; leverage the Security Review skill for all external-facing endpoints.
632
-
633
- # Start Project Protocol
634
- When the user says "start project", you MUST immediately:
635
- 1. Ask the user for the name of the project and a brief description of what they want to build.
636
- 2. Wait for their response.
637
- 3. Once they respond, use their details to initialize the Next.js frontend (in \`frontend/\`) and Rust backend (in \`backend/\`), adhering to all High-Performance Engineering Standards.
638
- 4. Initialize a new git repository in the root directory (\`git init\`) and create an initial commit.
639
- `);
640
-
641
- // 4. Workflows
642
- writeFile('.agents/workflows/git-it.md', `
643
- # Safe Git Sync Workflow
644
- 1. Stage all changes (\`git add .\`).
645
- 2. Write a descriptive, conventional commit message based on the recent changes.
646
- 3. Commit the changes (\`git commit -m "..."\`).
647
- 4. Pull with rebase (\`git pull --rebase\`).
648
- 5. Push to the remote branch (\`git push\`).
649
- `);
650
-
651
- writeFile('.agents/workflows/deploy-and-talk.md', `
652
- # Deploy and Talk Workflow
653
- 1. Execute the deployment script (\`./deploy.ps1\` or \`./deploy.sh\`).
654
- 2. Verify successful deployment output.
655
- 3. Send a Telegram notification using \`Invoke-RestMethod\` to alert the team that the deployment is complete. Include a brief changelog.
656
- `);
657
-
658
- // 5. Skills
659
- writeFile('.agents/skills/ponytail/SKILL.md', `
660
- ---
661
- name: ponytail
662
- description: Enforces lazy senior dev principles. Efficient, not careless. Deletion over addition.
663
- ---
664
- # Ponytail, lazy senior dev mode
665
-
666
- You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
667
-
668
- Before writing any code, stop at the first rung that holds:
669
- 1. Does this need to be built at all? (YAGNI)
670
- 2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
671
- 3. Does the standard library already do this? Use it.
672
- 4. Does a native platform feature cover it? Use it.
673
- 5. Does an already-installed dependency solve it? Use it.
674
- 6. Can this be one line? Make it one line.
675
- 7. Only then: write the minimum code that works.
676
-
677
- The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
678
-
679
- Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once.
680
-
681
- Rules:
682
- - No abstractions that weren't explicitly requested.
683
- - No new dependency if it can be avoided.
684
- - No boilerplate nobody asked for.
685
- - Deletion over addition. Boring over clever. Fewest files possible.
686
- - Shortest working diff wins, but only once you understand the problem.
687
- - Question complex requests: "Do you actually need X, or does Y cover it?"
688
- - Mark deliberate simplifications that cut a real corner with a known ceiling with a \`ponytail:\` comment naming the ceiling and upgrade path.
689
- `);
690
-
691
- writeFile('.agents/skills/ui-sniper-telegram/SKILL.md', `
692
- ---
693
- name: ui-sniper-telegram
694
- description: Automates the setup of a UI Sniper system integrated with a Telegram bot for capturing frontend UI feedback directly to a Telegram group.
695
- ---
696
- # UI Sniper Telegram Integration Skill
697
- 1. **Ask for Credentials**: Prompt the user for their \`TELEGRAM_BOT_TOKEN\` and \`TELEGRAM_CHAT_ID\`. Wait for their response.
698
- 2. **Install**: Install \`ui-sniper\` and scaffold the Next.js API route (\`app/api/ui-sniper-webhook/route.ts\`), and set up the \`UISniperWrapper.tsx\` component.
699
- 3. **Configure**: Add the wrapper to \`app/layout.tsx\` and place the credentials in \`.env.local\`.
700
- `);
701
-
702
- writeFile('.agents/skills/kill-ai-slop/SKILL.md', `
703
- ---
704
- name: kill-ai-slop
705
- description: Scans a web project to detect and strip out AI-generated aesthetic clichΓ©s (indigo gradients, excessive emojis, glowing cards) replacing them with clean, tasteful, and accessible design patterns.
706
- ---
707
-
708
- # Kill AI Slop Skill
709
- Run the following command in the terminal to execute the scanner:
710
- \`\`\`bash
711
- npx --yes skills add yetone/kill-ai-slop
712
- \`\`\`
713
- Or execute the scanner directly on the project directory:
714
- \`\`\`bash
715
- npx --yes kill-ai-slop .
716
- \`\`\`
717
- Review the reported "tells" and apply the suggested clean fixes (e.g., removing indigo gradients, replacing emojis with proper SVG icons, removing glowing cards in favor of paper/ink aesthetics).
718
- `);
719
-
720
- writeFile('.agents/skills/security-review/SKILL.md', `
721
- ---
722
- name: security-review
723
- description: 'AI-powered codebase security scanner that reasons about code like a security researcher β€” tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.'
724
- ---
725
-
726
- # Security Review
727
-
728
- An AI-powered security scanner that reasons about your codebase the way a human security researcher would β€” tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.
729
-
730
- ## Execution Workflow
731
- 1. **Scope Resolution**: Determine what to scan (specific path or entire project).
732
- 2. **Dependency Audit**: Audit dependencies first (check package.json, Cargo.toml for known vulnerable packages).
733
- 3. **Secrets & Exposure Scan**: Scan ALL files for hardcoded API keys, tokens, passwords, and \`.env\` files.
734
- 4. **Vulnerability Deep Scan**: Check for Injection Flaws, Authentication/Access Control issues, Data Handling weaknesses, and Cryptography issues.
735
- 5. **Cross-File Data Flow Analysis**: Trace user-controlled input from entry points to dangerous sinks.
736
- 6. **Self-Verification Pass**: Filter false positives.
737
- 7. **Generate Security Report**: Output a findings summary table and structured report with severities (CRITICAL, HIGH, MEDIUM, LOW, INFO).
738
- 8. **Propose Patches**: For every CRITICAL and HIGH finding, generate a concrete patch for human review. **Never auto-apply any patch.**
739
- `);
740
-
741
- console.log('βœ… All skills, workflows, dev scripts, and rules generated successfully.');
742
- console.log('');
743
- console.log('πŸ“¦ Installing additional NPM packages in the current project...');
744
-
745
- try {
746
- // If there is a package.json, install UI dependencies
747
- if (fs.existsSync('package.json')) {
748
- console.log(' - Installing ui-sniper and html2canvas...');
749
- execSync('npm install ui-sniper html2canvas', { stdio: 'inherit' });
750
- } else {
751
- console.log(' - No package.json found. Skipping ui-sniper installation.');
752
- }
753
- } catch (error) {
754
- console.warn('⚠️ Could not install npm dependencies automatically.');
755
- }
756
-
757
- console.log('');
758
- console.log("\nπŸŽ‰ Scaffolding Complete! πŸŽ‰");
759
- console.log("πŸ‘‰ The workspace is fully loaded with Ponytail, Security Review, and Kill AI Slop skills.");
760
- console.log("πŸ‘‰ Go to your coding agent and say: 'start project' to begin! πŸš€");
817
+ param(
818
+ [switch]$SkipSentry = $false,
819
+ [switch]$SkipSeed = $false,
820
+ [switch]$SkipApp = $false,
821
+ [switch]$SkipRouter = $false,
822
+ [switch]$SkipFrontend = $false,
823
+ [switch]$SkipDBSync = $false,
824
+ [switch]$SkipVisual = $false,
825
+ [switch]$Fast = $false
826
+ )
827
+
828
+ if ($Fast) {
829
+ Write-Host "[FAST] Fast mode enabled. Skipping DB, Router, and App releases." -ForegroundColor Cyan
830
+ $SkipSeed = $true
831
+ $SkipApp = $true
832
+ $SkipRouter = $true
833
+ $SkipFrontend = $false
834
+ $SkipSentry = $true
835
+ $SkipDBSync = $true
836
+ $SkipVisual = $true
837
+ }
838
+
839
+ # Medizys Modernized Deployment Script
840
+ # Features: Schema Sync, Consolidated Reporting, Timestamped Logs, Clipboard Integration
841
+
842
+ $ErrorActionPreference = 'Stop'
843
+
844
+ # Ensure we are in the root of the project
845
+ Set-Location $PSScriptRoot
846
+
847
+ # Load .env and .env.local files if present
848
+ $envFiles = @(".env", ".env.local", "frontend\\.env.local")
849
+ foreach ($file in $envFiles) {
850
+ $envFile = Join-Path $PSScriptRoot $file
851
+ if (Test-Path $envFile) {
852
+ Write-Host "[RESILIENCE] Loading environment variables from $file..." -ForegroundColor Cyan
853
+ Get-Content $envFile | Where-Object { $_ -match '^\\s*([^#\\s][^=]+)=(.*)' } | ForEach-Object {
854
+ $name = $matches[1].Trim()
855
+ $value = $matches[2].Trim()
856
+ if ($value -match '^"(.*)"$') { $value = $matches[1] }
857
+ elseif ($value -match "^'(.*)'$") { $value = $matches[1] }
858
+ [Environment]::SetEnvironmentVariable($name, $value, "Process")
859
+ }
860
+ }
861
+ }
862
+
863
+ # PROACTIVE COLLISION PREVENTION: Kill competing deployment tasks to prevent file locks
864
+ Write-Host "[RESILIENCE] Terminating conflicting build processes to prevent locks..." -ForegroundColor Yellow
865
+ $conflictingProcs = Get-WmiObject Win32_Process | Where-Object {
866
+ $_.Name -match 'node.exe' -and
867
+ ($_.CommandLine -match 'next build' -or $_.CommandLine -match 'tsc' -or $_.CommandLine -match 'wrangler deploy')
868
+ }
869
+ foreach ($p in $conflictingProcs) {
870
+ Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
871
+ }
872
+
873
+ $GlobalReport = [PSCustomObject]@{
874
+ StartTime = (Get-Date)
875
+ Status = "Running"
876
+ Steps = @()
877
+ Error = $null
878
+ }
879
+
880
+ # Create reports directory in root
881
+ $ReportsDir = Join-Path $PSScriptRoot "deployment_reports"
882
+ if (!(Test-Path $ReportsDir)) { New-Item -ItemType Directory -Path $ReportsDir | Out-Null }
883
+
884
+ function Add-Step {
885
+ param([string]$Name, [string]$Status, [string]$Details = "")
886
+ $GlobalReport.Steps += [PSCustomObject]@{
887
+ Name = $Name
888
+ Status = $Status
889
+ Details = $Details
890
+ Time = (Get-Date).ToString("T")
891
+ }
892
+ }
893
+
894
+ function Generate-FinalReport {
895
+ param([string]$FinalStatus)
896
+ $GlobalReport.Status = $FinalStatus
897
+
898
+ # Get IP and IST Time
899
+ $ipStr = "Unknown"
900
+ try { $ipStr = (Invoke-RestMethod -Uri 'https://api.ipify.org' -TimeoutSec 3).Trim() } catch { }
901
+
902
+ $istTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("India Standard Time")
903
+ $istStartTime = [System.TimeZoneInfo]::ConvertTimeFromUtc($GlobalReport.StartTime.ToUniversalTime(), $istTimeZone)
904
+
905
+ $reportText = "=================================================\`n"
906
+ $reportText += "MEDIQUEUE DEPLOYMENT REPORT\`n"
907
+ $reportText += "=================================================\`n"
908
+ $reportText += "Status: $FinalStatus\`n"
909
+ $reportText += "IP: $ipStr\`n"
910
+ $reportText += "Timestamp: $($istStartTime.ToString('MM/dd/yyyy HH:mm:ss')) (IST)\`n"
911
+ $reportText += "Duration: $((Get-Date) - $GlobalReport.StartTime)\`n"
912
+ $reportText += "-------------------------------------------------\`n"
913
+
914
+ foreach ($step in $GlobalReport.Steps) {
915
+ $indicator = if ($step.Status -eq "SUCCESS") { "[OK]" } else { "[FAIL]" }
916
+ $reportText += "$indicator [$($step.Time)] $($step.Name): $($step.Status)\`n"
917
+ if ($step.Details) { $reportText += " > $($step.Details)\`n" }
918
+ }
919
+
920
+ if ($GlobalReport.Error) {
921
+ $reportText += "-------------------------------------------------\`n"
922
+ $reportText += "CRITICAL ERROR:\`n$($GlobalReport.Error)\`n"
923
+ }
924
+ $reportText += "=================================================\`n"
925
+
926
+ # Save to file
927
+ $statusIndicator = if ($FinalStatus -eq "SUCCESS") { "SUCCESS" } else { "FAILED" }
928
+ $hour = (Get-Date).Hour
929
+ $timeOfDay = if ($hour -ge 5 -and $hour -lt 12) { "Morning" }
930
+ elseif ($hour -ge 12 -and $hour -lt 17) { "Afternoon" }
931
+ elseif ($hour -ge 17 -and $hour -lt 21) { "Evening" }
932
+ else { "Night" }
933
+
934
+ $humanTime = (Get-Date).ToString("ddMMM_hh-mmtt") # e.g. 30Mar_02-30PM
935
+ $fileName = "Deploy_\${statusIndicator}_\${timeOfDay}_$humanTime.txt"
936
+ $filePath = Join-Path $ReportsDir $fileName
937
+ $reportText | Out-File -FilePath $filePath
938
+
939
+ # Copy to clipboard
940
+ try { $reportText | Set-Clipboard } catch {}
941
+
942
+ return $reportText
943
+ }
944
+
945
+ function Send-Notification {
946
+ param([string]$Status, [string]$Report)
947
+ try {
948
+ Write-Host "[NOTIF] Dispatching deployment pulse..." -ForegroundColor Magenta
949
+ $notifPayload = (@{
950
+ version = "v2.5.2-stable"
951
+ env = "production"
952
+ status = $Status
953
+ report = $Report
954
+ chatId = "-1003863805343"
955
+ } | ConvertTo-Json)
956
+
957
+ Invoke-RestMethod -Uri "https://api.mediqueue.hara-xy.com/api/platform/notif/deploy" -Method Post -Body $notifPayload -Headers @{ "X-Requested-With" = "XMLHttpRequest" } -ContentType "application/json"
958
+ Write-Host "Dispatch confirmed." -ForegroundColor Green
959
+ } catch {
960
+ Write-Host "Notification dispatch failed." -ForegroundColor Yellow
961
+ }
962
+ }
963
+
964
+ function Send-AntigravityChat {
965
+ param([string]$Status, [string]$Report)
966
+ try {
967
+ Write-Host "[ANTIGRAVITY] Sending deployment report to chat..." -ForegroundColor Magenta
968
+ $emoji = if ($Status -eq "SUCCESS") { "βœ…" } else { "❌" }
969
+ $chatMessage = "$emoji Medizys Deployment $Status\`n\`n$Report"
970
+
971
+ # Append newline so the Antigravity Bridge auto-submits the message
972
+ $payload = @{ text = "$chatMessage\`n" } | ConvertTo-Json
973
+
974
+ Invoke-RestMethod \`
975
+ -Uri "http://localhost:5000/send_command" \`
976
+ -Method Post \`
977
+ -Body $payload \`
978
+ -ContentType "application/json" \`
979
+ -ErrorAction SilentlyContinue | Out-Null
980
+
981
+ Write-Host "[ANTIGRAVITY] Report dispatched to chat." -ForegroundColor Green
982
+ } catch {
983
+ Write-Host "[ANTIGRAVITY] Chat dispatch skipped (bridge not running)." -ForegroundColor DarkGray
984
+ }
985
+ }
986
+
987
+ function Invoke-ModernCommand {
988
+ param([string]$Step, [scriptblock]$Script, [switch]$NoExit)
989
+ Write-Host ""
990
+ Write-Host "[STEP] $($Step)..." -ForegroundColor Yellow
991
+ try {
992
+ # Execute script correctly
993
+ $global:LASTEXITCODE = 0
994
+ & $Script
995
+ $exitCode = $LASTEXITCODE
996
+
997
+ if ($exitCode -eq 0) {
998
+ Add-Step $Step "SUCCESS"
999
+ Write-Host "[OK] $Step completed successfully." -ForegroundColor Green
1000
+ } else {
1001
+ throw "Command failed with exit code $exitCode."
1002
+ }
1003
+ } catch {
1004
+ $errorMsg = $_.ToString()
1005
+ Add-Step $Step "FAILED" "Critical Error: See Global Report"
1006
+ $GlobalReport.Error = "Step: $Step\`nError: $errorMsg"
1007
+
1008
+ if ($NoExit) {
1009
+ Write-Host "[WARN] $Step failed, but continuing (NoExit enabled)." -ForegroundColor Yellow
1010
+ throw $errorMsg
1011
+ }
1012
+
1013
+ $finalReport = Generate-FinalReport "FAILED"
1014
+
1015
+ # Send failure notification
1016
+ Send-Notification -Status "FAILED" -Report $finalReport
1017
+ Send-AntigravityChat -Status "FAILED" -Report $finalReport
1018
+
1019
+ Write-Host ""
1020
+ Write-Host "DEPLOYMENT FAILED!" -ForegroundColor Red
1021
+ Write-Host "-------------------------------------------------"
1022
+ Write-Host $errorMsg -ForegroundColor Gray
1023
+ Write-Host "-------------------------------------------------"
1024
+ Write-Host "[CLIPBOARD] Full error report copied to your clipboard!" -ForegroundColor Cyan
1025
+
1026
+ # Ensure we return to root before exiting
1027
+ Set-Location $PSScriptRoot
1028
+ exit 1
1029
+ }
1030
+ }
1031
+
1032
+ # Phase -1: Security Audits & Clean Install
1033
+ Invoke-ModernCommand "Lockfile Linting" { pnpm run lint:lockfile }
1034
+ Invoke-ModernCommand "Clean Installation (Strict Lockfile)" { pnpm install --frozen-lockfile }
1035
+
1036
+ # Phase 0: Pre-flight Sync
1037
+ Invoke-ModernCommand "Sync Frontend Routes" { node "$PSScriptRoot\\scripts\\sync-routes.js" }
1038
+ Invoke-ModernCommand "Sync Shared Types" { Copy-Item "$PSScriptRoot\\worker\\src\\shared-types.ts" "$PSScriptRoot\\frontend\\lib\\shared-types.ts" -Force }
1039
+ Invoke-ModernCommand "Worker ESLint Audit" { pnpm --filter mediqueue-worker run lint }
1040
+
1041
+ # Phase 1 & 2: Worker
1042
+ try {
1043
+ Push-Location worker
1044
+ # Database Schema Sync (using latest Drizzle patterns)
1045
+ if (!$SkipDBSync) {
1046
+ Invoke-ModernCommand "Database Enum Drift Audit" { pnpm run db:audit }
1047
+ Invoke-ModernCommand "Database Migrations (HTTP)" { pnpm run db:migrate }
1048
+ Invoke-ModernCommand "Worker Type Check" { pnpm run typecheck }
1049
+ } else {
1050
+ Write-Host "[SKIP] Database Schema Sync skipped." -ForegroundColor Gray
1051
+ Add-Step "Database Enum Drift Audit" "SKIPPED"
1052
+ Add-Step "Database Schema Sync" "SKIPPED"
1053
+ }
1054
+
1055
+ # Seed initial data (Achievements, etc.)
1056
+ if (!$SkipSeed) {
1057
+ Invoke-ModernCommand "Database Seeding" { pnpm run db:seed }
1058
+ } else {
1059
+ Write-Host "[SKIP] Database Seeding skipped." -ForegroundColor Gray
1060
+ Add-Step "Database Seeding" "SKIPPED"
1061
+ }
1062
+
1063
+ # Phase 2: Worker Deployment
1064
+ if ($SkipSentry) {
1065
+ Invoke-ModernCommand "Worker Deployment (Fast)" { pnpm run deploy:fast }
1066
+ } else {
1067
+ try {
1068
+ Invoke-ModernCommand "Worker Deployment (Sentry)" { pnpm run deploy:sentry } -NoExit
1069
+ } catch {
1070
+ Write-Host "[WARN] Sentry deployment failed (likely timeout or config). Falling back to fast deploy..." -ForegroundColor Yellow
1071
+ Invoke-ModernCommand "Worker Deployment (Fallback)" { pnpm run deploy:fast }
1072
+ }
1073
+ }
1074
+ } finally {
1075
+ Pop-Location
1076
+ }
1077
+
1078
+ # Phase 2.5: Router Worker Deployment
1079
+ if (!$SkipRouter) {
1080
+ try {
1081
+ Push-Location router-worker
1082
+ Invoke-ModernCommand "Router Worker Deployment" { npx wrangler@4.80.0 deploy }
1083
+ } finally {
1084
+ Pop-Location
1085
+ }
1086
+ } else {
1087
+ Write-Host "[SKIP] Router Worker Deployment skipped." -ForegroundColor Gray
1088
+ Add-Step "Router Worker Deployment" "SKIPPED"
1089
+ }
1090
+
1091
+ # Phase 3: Frontend Deployment
1092
+ if (!$SkipFrontend) {
1093
+
1094
+ try {
1095
+ Push-Location frontend
1096
+ $env:NODE_OPTIONS = "--max-old-space-size=8192"
1097
+ Write-Host "Cleaning previous build artifacts..." -ForegroundColor Gray
1098
+
1099
+ # PROACTIVE LOCK CLEANUP: Remove .next/lock if it exists to prevent build timeouts
1100
+ $lockFile = Join-Path (Get-Location) ".next\\lock"
1101
+ if (Test-Path $lockFile) {
1102
+ Write-Host "[RESILIENCE] Removing stale .next/lock..." -ForegroundColor Yellow
1103
+ Remove-Item -Force $lockFile -ErrorAction SilentlyContinue
1104
+ }
1105
+
1106
+ if (!$Fast) {
1107
+ Write-Host "Cleaning previous build artifacts (Full Mode)..." -ForegroundColor Gray
1108
+ if (Test-Path out) { Remove-Item -Recurse -Force out -ErrorAction SilentlyContinue }
1109
+ } else {
1110
+ Write-Host "Preserving build cache for faster iterations (Fast Mode)..." -ForegroundColor Cyan
1111
+ if (Test-Path out) { Remove-Item -Recurse -Force out -ErrorAction SilentlyContinue }
1112
+ }
1113
+
1114
+ # Generate Deployment Versioning for Awareness
1115
+ $deployId = (Get-Date).ToString("yyyyMMddHHmmss")
1116
+ $versionJson = @{
1117
+ version = "v2.5.2-stable"
1118
+ deployId = $deployId
1119
+ timestamp = (Get-Date).ToString("o")
1120
+ notes = "Portal boundaries and operational context audit."
1121
+ } | ConvertTo-Json
1122
+ $versionJson | Out-File -FilePath "public/version.json" -Encoding utf8
1123
+ Write-Host "[OK] Deployment version $deployId generated." -ForegroundColor Green
1124
+
1125
+ Invoke-ModernCommand "Frontend Type Check" { pnpm run typecheck }
1126
+
1127
+ if (!$SkipVisual) {
1128
+ Write-Host "\`n[STEP] Frontend Visual Tests..." -ForegroundColor Yellow
1129
+ $global:LASTEXITCODE = 0
1130
+ & pnpm run test:visual
1131
+ if ($global:LASTEXITCODE -eq 0) {
1132
+ Add-Step "Frontend Visual Tests" "SUCCESS"
1133
+ Write-Host "[OK] Frontend Visual Tests completed successfully." -ForegroundColor Green
1134
+ } else {
1135
+ Add-Step "Frontend Visual Tests" "FAILED (Ignored)"
1136
+ Write-Host "[WARN] Frontend Visual Tests failed, but continuing deployment as requested." -ForegroundColor Yellow
1137
+ $global:LASTEXITCODE = 0
1138
+ }
1139
+ } else {
1140
+ Write-Host "[SKIP] Frontend Visual Tests skipped." -ForegroundColor Gray
1141
+ Add-Step "Frontend Visual Tests" "SKIPPED"
1142
+ }
1143
+
1144
+ Invoke-ModernCommand "Frontend Build" { pnpm run build }
1145
+
1146
+ Write-Host "[RESILIENCE] Ensuring hidden public assets (e.g. .well-known) are copied to out..." -ForegroundColor Gray
1147
+ if (Test-Path "public\\.well-known") {
1148
+ if (!(Test-Path "out\\.well-known")) {
1149
+ New-Item -ItemType Directory -Path "out\\.well-known" | Out-Null
1150
+ }
1151
+ try {
1152
+ Copy-Item -Recurse -Force "public\\.well-known\\*" "out\\.well-known" -ErrorAction Stop
1153
+ } catch {
1154
+ Write-Host "[WARN] Could not copy .well-known: $($_.Exception.Message)" -ForegroundColor Yellow
1155
+ }
1156
+ Write-Host "[RESILIENCE] Copied .well-known to out directory." -ForegroundColor Green
1157
+ }
1158
+
1159
+ if (!(Test-Path "out\\index.html")) { New-Item -Path "out\\index.html" -ItemType File -Force | Out-Null }
1160
+ Invoke-ModernCommand "Mobile App Sync (Capacitor)" { pnpm exec cap sync }
1161
+ # Ensure bash + pnpm are resolvable for @cloudflare/next-on-pages (shellac dependency)
1162
+ $gitBashBin = "C:\\Program Files\\Git\\bin"
1163
+ $pnpmBin1 = "C:\\Program Files\\nodejs"
1164
+ $pnpmBin2 = "$env:LOCALAPPDATA\\pnpm"
1165
+ $originalPath = $env:PATH
1166
+ $env:PATH = "$gitBashBin;$pnpmBin1;$pnpmBin2;$env:PATH"
1167
+ Get-ChildItem -Path "out" -Recurse -Filter "__next*" -File | Remove-Item -Force -ErrorAction SilentlyContinue; Invoke-ModernCommand "Frontend Pages Upload" { pnpm run deploy }
1168
+ $env:PATH = $originalPath
1169
+ } finally {
1170
+ Pop-Location
1171
+ }
1172
+ } else {
1173
+ Write-Host "[SKIP] Frontend Deployment skipped." -ForegroundColor Gray
1174
+ Add-Step "Frontend Deployment" "SKIPPED"
1175
+ }
1176
+
1177
+ # Phase 3.5: Edge Verification
1178
+ if (!$SkipFrontend -and !$SkipRouter) {
1179
+ try {
1180
+ Push-Location frontend
1181
+
1182
+ # PROACTIVE PLAYWRIGHT RESILIENCE: Kill zombies and remove locks
1183
+ Write-Host "[RESILIENCE] Preparing clean state for Playwright..." -ForegroundColor Yellow
1184
+ $pwProcs = Get-WmiObject Win32_Process | Where-Object {
1185
+ $_.Name -match 'node.exe' -and
1186
+ ($_.CommandLine -match 'playwright' -or $_.CommandLine -match 'next dev')
1187
+ }
1188
+ foreach ($p in $pwProcs) {
1189
+ Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
1190
+ }
1191
+ $lockFile = Join-Path (Get-Location) ".next\\lock"
1192
+ if (Test-Path $lockFile) {
1193
+ Remove-Item -Force $lockFile -ErrorAction SilentlyContinue
1194
+ }
1195
+
1196
+ Invoke-ModernCommand "Edge Route Verification (Playwright)" { npx playwright test e2e/edge-router.spec.ts }
1197
+ } catch {
1198
+ Write-Host "[WARN] Edge Route Verification failed! Please check Cloudflare Worker logs." -ForegroundColor Red
1199
+ throw $_
1200
+ } finally {
1201
+ Pop-Location
1202
+ }
1203
+ } else {
1204
+ Write-Host "[SKIP] Edge Route Verification skipped (requires Frontend and Router sync)." -ForegroundColor Gray
1205
+ Add-Step "Edge Route Verification" "SKIPPED"
1206
+ }
1207
+
1208
+ # Phase 4: Mobile App Release
1209
+ if (!$SkipApp) {
1210
+ Invoke-ModernCommand "Mobile App Release" { & "$PSScriptRoot\\scripts\\release-mobile.ps1" }
1211
+ } else {
1212
+ Write-Host "[SKIP] Mobile App Release skipped." -ForegroundColor Gray
1213
+ Add-Step "Mobile App Release" "SKIPPED"
1214
+ }
1215
+
1216
+ # Phase 5: Finalize
1217
+ $finalReport = Generate-FinalReport "SUCCESS"
1218
+ Write-Host ""
1219
+ Write-Host "DEPLOYMENT SUCCESSFUL!" -ForegroundColor Green
1220
+
1221
+ # Telegram Deployment Notification
1222
+ Send-Notification -Status "SUCCESS" -Report $finalReport
1223
+
1224
+ # Antigravity Chat Notification
1225
+ Send-AntigravityChat -Status "SUCCESS" -Report $finalReport
1226
+
1227
+ Write-Host $finalReport
1228
+ Write-Host "[CLIPBOARD] Full report copied to your clipboard!" -ForegroundColor Cyan
1229
+ Write-Host ""
1230
+ Write-Host ">>> Deployment localized and verified." -ForegroundColor Cyan
1231
+
1232
+ `);
1233
+
1234
+ // 3. Agent Rules
1235
+ writeFile('AGENTS.md', `
1236
+ # High-Performance Engineering Standards
1237
+ 1. **Ponytail, lazy senior dev mode**: The best code is the code never written. YAGNI. Question complex requests, reuse existing standards, prefer deletion over addition, and write the minimal correct code.
1238
+ 2. **Edge-First Execution**: Backend logic must be optimized for serverless edge deployment (Cloudflare Workers).
1239
+ 3. **Data Layer Optimization**: Never execute untuned queries. Use connection pooling and batching.
1240
+ 4. **Minimalist Frontend Render**: Ensure frontend implementations are highly optimized for Next.js, favoring SSR where appropriate, avoiding heavy client bundles, and ensuring mobile-first responsive design.
1241
+ 5. **Master Debugging**: Trace issues to their root cause before writing a fix. Use the "Five Whys" protocol.
1242
+ 6. **Strict Typing**: TypeScript and Rust types must be explicit, avoid \`any\` or \`unwrap()\` where possible.
1243
+ 7. **Structured Logging**: Implement structured logging, avoiding excessive console.logs in production.
1244
+ 8. **Atomic Commits**: Ensure commits are small, atomic, and pass CI checks before pushing.
1245
+ 9. **Error Boundaries**: Frontend must wrap critical components in Error Boundaries to prevent full app crashes.
1246
+ 10. **No Placeholder Code**: Never write generic "TODO" or placeholder logic if the requirements are known.
1247
+ 11. **Security Best Practices**: Enforce strict input validation, rate limiting, and secure credential handling by default.
1248
+ 12. **Automated Testing & TDD**: Require unit and integration tests for critical paths. Code is not done until it's tested.
1249
+ 13. **Strict Linting & Formatting**: Enforce ESLint, Prettier, and Clippy with zero warnings allowed.
1250
+ 14. **Accessibility (a11y) First**: Require ARIA labels, semantic HTML, and keyboard navigation for all frontend components.
1251
+ 15. **Documentation Standards**: Mandate JSDoc (frontend) and Rustdoc (backend) for all public APIs and reusable components.
1252
+ 16. **CI/CD Pipeline Setup**: Ensure GitHub Actions (or similar) are configured for automated checks, tests, and deployments.
1253
+ 17. **State Management Rules**: Prefer local state or React Context; enforce strict rules against unnecessary global state.
1254
+ 18. **Environment Variable Validation**: Fail fast at startup if required \`.env\` variables are missing.
1255
+ 19. **Kill AI Slop**: Remove aesthetic clichΓ©s (indigo gradients, glowing cards, excessive emojis) that serve no functional purpose.
1256
+ 20. **Security First Principle**: Treat all external input as malicious; leverage the Security Review skill for all external-facing endpoints.
1257
+
1258
+ # Start Project Protocol
1259
+ When the user says "start project", you MUST immediately:
1260
+ 1. Ask the user for the name of the project and a brief description of what they want to build.
1261
+ 2. Wait for their response.
1262
+ 3. Once they respond, use their details to initialize the Next.js frontend (in \`frontend/\`) and Rust backend (in \`backend/\`), adhering to all High-Performance Engineering Standards.
1263
+ 4. Initialize a new git repository in the root directory (\`git init\`) and create an initial commit.
1264
+ `);
1265
+
1266
+ // 4. Workflows
1267
+ writeFile('.agents/workflows/git-it.md', `
1268
+ # Safe Git Sync Workflow
1269
+ 1. Stage all changes (\`git add .\`).
1270
+ 2. Write a descriptive, conventional commit message based on the recent changes.
1271
+ 3. Commit the changes (\`git commit -m "..."\`).
1272
+ 4. Pull with rebase (\`git pull --rebase\`).
1273
+ 5. Push to the remote branch (\`git push\`).
1274
+ `);
1275
+
1276
+ writeFile('.agents/workflows/deploy-and-talk.md', `
1277
+ # Deploy and Talk Workflow
1278
+ 1. Execute the deployment script (\`./deploy.ps1\` or \`./deploy.sh\`).
1279
+ 2. Verify successful deployment output.
1280
+ 3. Send a Telegram notification using \`Invoke-RestMethod\` to alert the team that the deployment is complete. Include a brief changelog.
1281
+ `);
1282
+
1283
+ // 5. Skills
1284
+ writeFile('.agents/skills/ponytail/SKILL.md', `
1285
+ ---
1286
+ name: ponytail
1287
+ description: Enforces lazy senior dev principles. Efficient, not careless. Deletion over addition.
1288
+ ---
1289
+ # Ponytail, lazy senior dev mode
1290
+
1291
+ You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
1292
+
1293
+ Before writing any code, stop at the first rung that holds:
1294
+ 1. Does this need to be built at all? (YAGNI)
1295
+ 2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
1296
+ 3. Does the standard library already do this? Use it.
1297
+ 4. Does a native platform feature cover it? Use it.
1298
+ 5. Does an already-installed dependency solve it? Use it.
1299
+ 6. Can this be one line? Make it one line.
1300
+ 7. Only then: write the minimum code that works.
1301
+
1302
+ The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
1303
+
1304
+ Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once.
1305
+
1306
+ Rules:
1307
+ - No abstractions that weren't explicitly requested.
1308
+ - No new dependency if it can be avoided.
1309
+ - No boilerplate nobody asked for.
1310
+ - Deletion over addition. Boring over clever. Fewest files possible.
1311
+ - Shortest working diff wins, but only once you understand the problem.
1312
+ - Question complex requests: "Do you actually need X, or does Y cover it?"
1313
+ - Mark deliberate simplifications that cut a real corner with a known ceiling with a \`ponytail:\` comment naming the ceiling and upgrade path.
1314
+ `);
1315
+
1316
+ writeFile('.agents/skills/ui-sniper-telegram/SKILL.md', `
1317
+ ---
1318
+ name: ui-sniper-telegram
1319
+ description: Automates the setup of a UI Sniper system integrated with a Telegram bot for capturing frontend UI feedback directly to a Telegram group.
1320
+ ---
1321
+ # UI Sniper Telegram Integration Skill
1322
+ 1. **Ask for Credentials**: Prompt the user for their \`TELEGRAM_BOT_TOKEN\` and \`TELEGRAM_CHAT_ID\`. Wait for their response.
1323
+ 2. **Install**: Install \`ui-sniper\` and scaffold the Next.js API route (\`app/api/ui-sniper-webhook/route.ts\`), and set up the \`UISniperWrapper.tsx\` component.
1324
+ 3. **Configure**: Add the wrapper to \`app/layout.tsx\` and place the credentials in \`.env.local\`.
1325
+ `);
1326
+
1327
+ writeFile('.agents/skills/kill-ai-slop/SKILL.md', `
1328
+ ---
1329
+ name: kill-ai-slop
1330
+ description: Scans a web project to detect and strip out AI-generated aesthetic clichΓ©s (indigo gradients, excessive emojis, glowing cards) replacing them with clean, tasteful, and accessible design patterns.
1331
+ ---
1332
+
1333
+ # Kill AI Slop Skill
1334
+ Run the following command in the terminal to execute the scanner:
1335
+ \`\`\`bash
1336
+ npx --yes skills add yetone/kill-ai-slop
1337
+ \`\`\`
1338
+ Or execute the scanner directly on the project directory:
1339
+ \`\`\`bash
1340
+ npx --yes kill-ai-slop .
1341
+ \`\`\`
1342
+ Review the reported "tells" and apply the suggested clean fixes (e.g., removing indigo gradients, replacing emojis with proper SVG icons, removing glowing cards in favor of paper/ink aesthetics).
1343
+ `);
1344
+
1345
+ writeFile('.agents/skills/security-review/SKILL.md', `
1346
+ ---
1347
+ name: security-review
1348
+ description: 'AI-powered codebase security scanner that reasons about code like a security researcher β€” tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.'
1349
+ ---
1350
+
1351
+ # Security Review
1352
+
1353
+ An AI-powered security scanner that reasons about your codebase the way a human security researcher would β€” tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.
1354
+
1355
+ ## Execution Workflow
1356
+ 1. **Scope Resolution**: Determine what to scan (specific path or entire project).
1357
+ 2. **Dependency Audit**: Audit dependencies first (check package.json, Cargo.toml for known vulnerable packages).
1358
+ 3. **Secrets & Exposure Scan**: Scan ALL files for hardcoded API keys, tokens, passwords, and \`.env\` files.
1359
+ 4. **Vulnerability Deep Scan**: Check for Injection Flaws, Authentication/Access Control issues, Data Handling weaknesses, and Cryptography issues.
1360
+ 5. **Cross-File Data Flow Analysis**: Trace user-controlled input from entry points to dangerous sinks.
1361
+ 6. **Self-Verification Pass**: Filter false positives.
1362
+ 7. **Generate Security Report**: Output a findings summary table and structured report with severities (CRITICAL, HIGH, MEDIUM, LOW, INFO).
1363
+ 8. **Propose Patches**: For every CRITICAL and HIGH finding, generate a concrete patch for human review. **Never auto-apply any patch.**
1364
+ `);
1365
+
1366
+ console.log('βœ… All skills, workflows, dev scripts, and rules generated successfully.');
1367
+ console.log('');
1368
+ console.log('πŸ“¦ Installing additional NPM packages in the current project...');
1369
+
1370
+ try {
1371
+ // If there is a package.json, install UI dependencies
1372
+ if (fs.existsSync('package.json')) {
1373
+ console.log(' - Installing ui-sniper and html2canvas...');
1374
+ execSync('npm install ui-sniper html2canvas', { stdio: 'inherit' });
1375
+ } else {
1376
+ console.log(' - No package.json found. Skipping ui-sniper installation.');
1377
+ }
1378
+ } catch (error) {
1379
+ console.warn('⚠️ Could not install npm dependencies automatically.');
1380
+ }
1381
+
1382
+ console.log('');
1383
+ console.log("\nπŸŽ‰ Scaffolding Complete! πŸŽ‰");
1384
+ console.log("πŸ‘‰ The workspace is fully loaded with Ponytail, Security Review, and Kill AI Slop skills.");
1385
+ console.log("πŸ‘‰ Go to your coding agent and say: 'start project' to begin! πŸš€");