openmagic 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +20 -14
- package/dist/cli.js +1071 -121
- package/dist/cli.js.map +1 -1
- package/dist/toolbar/index.global.js +78 -13
- package/dist/toolbar/index.global.js.map +1 -1
- package/package.json +10 -2
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,12 @@ import { Command } from "commander";
|
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import open from "open";
|
|
7
7
|
import { resolve as resolve2 } from "path";
|
|
8
|
+
import { spawn } from "child_process";
|
|
9
|
+
import { createInterface } from "readline";
|
|
8
10
|
|
|
9
11
|
// src/proxy.ts
|
|
10
12
|
import http from "http";
|
|
13
|
+
import { createGunzip, createInflate, createBrotliDecompress } from "zlib";
|
|
11
14
|
import httpProxy from "http-proxy";
|
|
12
15
|
|
|
13
16
|
// src/security.ts
|
|
@@ -43,10 +46,7 @@ function createProxyServer(targetHost, targetPort, serverPort) {
|
|
|
43
46
|
proxyRes.pipe(res);
|
|
44
47
|
return;
|
|
45
48
|
}
|
|
46
|
-
|
|
47
|
-
proxyRes.on("data", (chunk) => chunks.push(chunk));
|
|
48
|
-
proxyRes.on("end", () => {
|
|
49
|
-
let body = Buffer.concat(chunks).toString("utf-8");
|
|
49
|
+
collectBody(proxyRes).then((body) => {
|
|
50
50
|
const toolbarScript = buildInjectionScript(serverPort, token);
|
|
51
51
|
if (body.includes("</body>")) {
|
|
52
52
|
body = body.replace("</body>", `${toolbarScript}</body>`);
|
|
@@ -58,18 +58,27 @@ function createProxyServer(targetHost, targetPort, serverPort) {
|
|
|
58
58
|
const headers = { ...proxyRes.headers };
|
|
59
59
|
delete headers["content-length"];
|
|
60
60
|
delete headers["content-encoding"];
|
|
61
|
+
delete headers["transfer-encoding"];
|
|
61
62
|
res.writeHead(proxyRes.statusCode || 200, headers);
|
|
62
63
|
res.end(body);
|
|
64
|
+
}).catch(() => {
|
|
65
|
+
try {
|
|
66
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
67
|
+
res.end();
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
63
70
|
});
|
|
64
71
|
});
|
|
65
72
|
proxy.on("error", (err, _req, res) => {
|
|
66
|
-
console.error("[OpenMagic] Proxy error:", err.message);
|
|
67
73
|
if (res instanceof http.ServerResponse && !res.headersSent) {
|
|
68
|
-
res.writeHead(502, { "Content-Type": "text/
|
|
74
|
+
res.writeHead(502, { "Content-Type": "text/html" });
|
|
69
75
|
res.end(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
`<html><body style="font-family:system-ui;padding:40px;background:#1a1a2e;color:#e0e0e0;">
|
|
77
|
+
<h2 style="color:#e94560;">OpenMagic \u2014 Cannot connect to dev server</h2>
|
|
78
|
+
<p>Could not reach <code>${targetHost}:${targetPort}</code></p>
|
|
79
|
+
<p style="color:#888;">Make sure your dev server is running, then refresh this page.</p>
|
|
80
|
+
<p style="color:#666;font-size:13px;">${err.message}</p>
|
|
81
|
+
</body></html>`
|
|
73
82
|
);
|
|
74
83
|
}
|
|
75
84
|
});
|
|
@@ -88,6 +97,58 @@ Make sure your dev server is running.`
|
|
|
88
97
|
});
|
|
89
98
|
return server;
|
|
90
99
|
}
|
|
100
|
+
function collectBody(stream) {
|
|
101
|
+
return new Promise((resolve3, reject) => {
|
|
102
|
+
const encoding = (stream.headers["content-encoding"] || "").toLowerCase();
|
|
103
|
+
const chunks = [];
|
|
104
|
+
let source = stream;
|
|
105
|
+
if (encoding === "gzip" || encoding === "x-gzip") {
|
|
106
|
+
const gunzip = createGunzip();
|
|
107
|
+
stream.pipe(gunzip);
|
|
108
|
+
source = gunzip;
|
|
109
|
+
gunzip.on("error", () => {
|
|
110
|
+
collectRaw(stream).then(resolve3).catch(reject);
|
|
111
|
+
});
|
|
112
|
+
} else if (encoding === "deflate") {
|
|
113
|
+
const inflate = createInflate();
|
|
114
|
+
stream.pipe(inflate);
|
|
115
|
+
source = inflate;
|
|
116
|
+
inflate.on("error", () => {
|
|
117
|
+
collectRaw(stream).then(resolve3).catch(reject);
|
|
118
|
+
});
|
|
119
|
+
} else if (encoding === "br") {
|
|
120
|
+
const brotli = createBrotliDecompress();
|
|
121
|
+
stream.pipe(brotli);
|
|
122
|
+
source = brotli;
|
|
123
|
+
brotli.on("error", () => {
|
|
124
|
+
collectRaw(stream).then(resolve3).catch(reject);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
source.on("data", (chunk) => chunks.push(chunk));
|
|
128
|
+
source.on("end", () => {
|
|
129
|
+
try {
|
|
130
|
+
resolve3(Buffer.concat(chunks).toString("utf-8"));
|
|
131
|
+
} catch {
|
|
132
|
+
reject(new Error("Failed to decode response body"));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
source.on("error", (err) => reject(err));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function collectRaw(stream) {
|
|
139
|
+
return new Promise((resolve3, reject) => {
|
|
140
|
+
const chunks = [];
|
|
141
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
142
|
+
stream.on("end", () => {
|
|
143
|
+
try {
|
|
144
|
+
resolve3(Buffer.concat(chunks).toString("utf-8"));
|
|
145
|
+
} catch {
|
|
146
|
+
reject(new Error("Failed to decode raw body"));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
stream.on("error", reject);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
91
152
|
function handleToolbarAsset(_req, res, _serverPort) {
|
|
92
153
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
93
154
|
res.end("Not found");
|
|
@@ -141,10 +202,14 @@ function loadConfig() {
|
|
|
141
202
|
}
|
|
142
203
|
}
|
|
143
204
|
function saveConfig(updates) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
205
|
+
try {
|
|
206
|
+
ensureConfigDir();
|
|
207
|
+
const existing = loadConfig();
|
|
208
|
+
const merged = { ...existing, ...updates };
|
|
209
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), "utf-8");
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.warn(`[OpenMagic] Warning: Could not save config to ${CONFIG_FILE}: ${e.message}`);
|
|
212
|
+
}
|
|
148
213
|
}
|
|
149
214
|
|
|
150
215
|
// src/filesystem.ts
|
|
@@ -286,85 +351,550 @@ function getProjectTree(roots) {
|
|
|
286
351
|
|
|
287
352
|
// src/llm/registry.ts
|
|
288
353
|
var MODEL_REGISTRY = {
|
|
354
|
+
// ─── OpenAI ───────────────────────────────────────────────────
|
|
289
355
|
openai: {
|
|
290
356
|
name: "OpenAI",
|
|
291
357
|
models: [
|
|
292
|
-
|
|
293
|
-
{
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
358
|
+
// GPT-5.4 family (March 2026 — latest flagship)
|
|
359
|
+
{
|
|
360
|
+
id: "gpt-5.4",
|
|
361
|
+
name: "GPT-5.4",
|
|
362
|
+
vision: true,
|
|
363
|
+
context: 105e4,
|
|
364
|
+
maxOutput: 128e3,
|
|
365
|
+
thinking: {
|
|
366
|
+
supported: true,
|
|
367
|
+
paramName: "reasoning_effort",
|
|
368
|
+
paramType: "level",
|
|
369
|
+
levels: ["none", "low", "medium", "high", "xhigh"],
|
|
370
|
+
defaultLevel: "medium"
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: "gpt-5.4-pro",
|
|
375
|
+
name: "GPT-5.4 Pro",
|
|
376
|
+
vision: true,
|
|
377
|
+
context: 105e4,
|
|
378
|
+
maxOutput: 128e3,
|
|
379
|
+
thinking: {
|
|
380
|
+
supported: true,
|
|
381
|
+
paramName: "reasoning_effort",
|
|
382
|
+
paramType: "level",
|
|
383
|
+
levels: ["none", "low", "medium", "high", "xhigh"],
|
|
384
|
+
defaultLevel: "high"
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "gpt-5.4-mini",
|
|
389
|
+
name: "GPT-5.4 Mini",
|
|
390
|
+
vision: true,
|
|
391
|
+
context: 4e5,
|
|
392
|
+
maxOutput: 128e3,
|
|
393
|
+
thinking: {
|
|
394
|
+
supported: true,
|
|
395
|
+
paramName: "reasoning_effort",
|
|
396
|
+
paramType: "level",
|
|
397
|
+
levels: ["none", "low", "medium", "high"],
|
|
398
|
+
defaultLevel: "medium"
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
id: "gpt-5.4-nano",
|
|
403
|
+
name: "GPT-5.4 Nano",
|
|
404
|
+
vision: true,
|
|
405
|
+
context: 4e5,
|
|
406
|
+
maxOutput: 128e3,
|
|
407
|
+
thinking: {
|
|
408
|
+
supported: true,
|
|
409
|
+
paramName: "reasoning_effort",
|
|
410
|
+
paramType: "level",
|
|
411
|
+
levels: ["none", "low", "medium", "high"],
|
|
412
|
+
defaultLevel: "low"
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
// GPT-5.2 family (reasoning-focused)
|
|
416
|
+
{
|
|
417
|
+
id: "gpt-5.2",
|
|
418
|
+
name: "GPT-5.2 Thinking",
|
|
419
|
+
vision: true,
|
|
420
|
+
context: 272e3,
|
|
421
|
+
maxOutput: 128e3,
|
|
422
|
+
thinking: {
|
|
423
|
+
supported: true,
|
|
424
|
+
paramName: "reasoning_effort",
|
|
425
|
+
paramType: "level",
|
|
426
|
+
levels: ["none", "low", "medium", "high", "xhigh"],
|
|
427
|
+
defaultLevel: "high"
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
id: "gpt-5.2-pro",
|
|
432
|
+
name: "GPT-5.2 Pro",
|
|
433
|
+
vision: true,
|
|
434
|
+
context: 272e3,
|
|
435
|
+
maxOutput: 128e3,
|
|
436
|
+
thinking: {
|
|
437
|
+
supported: true,
|
|
438
|
+
paramName: "reasoning_effort",
|
|
439
|
+
paramType: "level",
|
|
440
|
+
levels: ["none", "low", "medium", "high", "xhigh"],
|
|
441
|
+
defaultLevel: "high"
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
// o-series reasoning models
|
|
445
|
+
{
|
|
446
|
+
id: "o3",
|
|
447
|
+
name: "o3 (Reasoning)",
|
|
448
|
+
vision: true,
|
|
449
|
+
context: 2e5,
|
|
450
|
+
maxOutput: 1e5,
|
|
451
|
+
thinking: {
|
|
452
|
+
supported: true,
|
|
453
|
+
paramName: "reasoning_effort",
|
|
454
|
+
paramType: "level",
|
|
455
|
+
levels: ["low", "medium", "high"],
|
|
456
|
+
defaultLevel: "medium"
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: "o4-mini",
|
|
461
|
+
name: "o4-mini (Reasoning)",
|
|
462
|
+
vision: true,
|
|
463
|
+
context: 2e5,
|
|
464
|
+
maxOutput: 1e5,
|
|
465
|
+
thinking: {
|
|
466
|
+
supported: true,
|
|
467
|
+
paramName: "reasoning_effort",
|
|
468
|
+
paramType: "level",
|
|
469
|
+
levels: ["low", "medium", "high"],
|
|
470
|
+
defaultLevel: "medium"
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
// GPT-4.1 family
|
|
474
|
+
{
|
|
475
|
+
id: "gpt-4.1",
|
|
476
|
+
name: "GPT-4.1",
|
|
477
|
+
vision: true,
|
|
478
|
+
context: 1047576,
|
|
479
|
+
maxOutput: 32768
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
id: "gpt-4.1-mini",
|
|
483
|
+
name: "GPT-4.1 Mini",
|
|
484
|
+
vision: true,
|
|
485
|
+
context: 1047576,
|
|
486
|
+
maxOutput: 32768
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
id: "gpt-4.1-nano",
|
|
490
|
+
name: "GPT-4.1 Nano",
|
|
491
|
+
vision: true,
|
|
492
|
+
context: 1047576,
|
|
493
|
+
maxOutput: 32768
|
|
494
|
+
},
|
|
495
|
+
// Codex
|
|
496
|
+
{
|
|
497
|
+
id: "codex-mini-latest",
|
|
498
|
+
name: "Codex Mini",
|
|
499
|
+
vision: false,
|
|
500
|
+
context: 192e3,
|
|
501
|
+
maxOutput: 1e5,
|
|
502
|
+
thinking: {
|
|
503
|
+
supported: true,
|
|
504
|
+
paramName: "reasoning_effort",
|
|
505
|
+
paramType: "level",
|
|
506
|
+
levels: ["low", "medium", "high"],
|
|
507
|
+
defaultLevel: "high"
|
|
508
|
+
}
|
|
509
|
+
}
|
|
299
510
|
],
|
|
300
511
|
apiBase: "https://api.openai.com/v1",
|
|
301
512
|
keyPrefix: "sk-",
|
|
302
513
|
keyPlaceholder: "sk-..."
|
|
303
514
|
},
|
|
515
|
+
// ─── Anthropic ────────────────────────────────────────────────
|
|
304
516
|
anthropic: {
|
|
305
517
|
name: "Anthropic",
|
|
306
518
|
models: [
|
|
307
|
-
|
|
308
|
-
{
|
|
309
|
-
|
|
519
|
+
// Claude 4.6 (latest — Feb 2026)
|
|
520
|
+
{
|
|
521
|
+
id: "claude-opus-4-6",
|
|
522
|
+
name: "Claude Opus 4.6",
|
|
523
|
+
vision: true,
|
|
524
|
+
context: 1e6,
|
|
525
|
+
maxOutput: 128e3,
|
|
526
|
+
thinking: {
|
|
527
|
+
supported: true,
|
|
528
|
+
paramName: "budget_tokens",
|
|
529
|
+
paramType: "budget",
|
|
530
|
+
defaultBudget: 1e4,
|
|
531
|
+
maxBudget: 128e3
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
id: "claude-sonnet-4-6",
|
|
536
|
+
name: "Claude Sonnet 4.6",
|
|
537
|
+
vision: true,
|
|
538
|
+
context: 1e6,
|
|
539
|
+
maxOutput: 64e3,
|
|
540
|
+
thinking: {
|
|
541
|
+
supported: true,
|
|
542
|
+
paramName: "budget_tokens",
|
|
543
|
+
paramType: "budget",
|
|
544
|
+
defaultBudget: 8e3,
|
|
545
|
+
maxBudget: 64e3
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
// Claude 4.5
|
|
549
|
+
{
|
|
550
|
+
id: "claude-haiku-4-5-20251001",
|
|
551
|
+
name: "Claude Haiku 4.5",
|
|
552
|
+
vision: true,
|
|
553
|
+
context: 2e5,
|
|
554
|
+
maxOutput: 64e3,
|
|
555
|
+
thinking: {
|
|
556
|
+
supported: true,
|
|
557
|
+
paramName: "budget_tokens",
|
|
558
|
+
paramType: "budget",
|
|
559
|
+
defaultBudget: 5e3,
|
|
560
|
+
maxBudget: 64e3
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
id: "claude-sonnet-4-5-20250929",
|
|
565
|
+
name: "Claude Sonnet 4.5",
|
|
566
|
+
vision: true,
|
|
567
|
+
context: 1e6,
|
|
568
|
+
maxOutput: 64e3,
|
|
569
|
+
thinking: {
|
|
570
|
+
supported: true,
|
|
571
|
+
paramName: "budget_tokens",
|
|
572
|
+
paramType: "budget",
|
|
573
|
+
defaultBudget: 8e3,
|
|
574
|
+
maxBudget: 64e3
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: "claude-opus-4-5-20251101",
|
|
579
|
+
name: "Claude Opus 4.5",
|
|
580
|
+
vision: true,
|
|
581
|
+
context: 2e5,
|
|
582
|
+
maxOutput: 64e3,
|
|
583
|
+
thinking: {
|
|
584
|
+
supported: true,
|
|
585
|
+
paramName: "budget_tokens",
|
|
586
|
+
paramType: "budget",
|
|
587
|
+
defaultBudget: 1e4,
|
|
588
|
+
maxBudget: 64e3
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
// Claude 4.0
|
|
592
|
+
{
|
|
593
|
+
id: "claude-sonnet-4-20250514",
|
|
594
|
+
name: "Claude Sonnet 4",
|
|
595
|
+
vision: true,
|
|
596
|
+
context: 2e5,
|
|
597
|
+
maxOutput: 64e3,
|
|
598
|
+
thinking: {
|
|
599
|
+
supported: true,
|
|
600
|
+
paramName: "budget_tokens",
|
|
601
|
+
paramType: "budget",
|
|
602
|
+
defaultBudget: 8e3,
|
|
603
|
+
maxBudget: 64e3
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
id: "claude-opus-4-20250514",
|
|
608
|
+
name: "Claude Opus 4",
|
|
609
|
+
vision: true,
|
|
610
|
+
context: 2e5,
|
|
611
|
+
maxOutput: 32e3,
|
|
612
|
+
thinking: {
|
|
613
|
+
supported: true,
|
|
614
|
+
paramName: "budget_tokens",
|
|
615
|
+
paramType: "budget",
|
|
616
|
+
defaultBudget: 1e4,
|
|
617
|
+
maxBudget: 32e3
|
|
618
|
+
}
|
|
619
|
+
}
|
|
310
620
|
],
|
|
311
621
|
apiBase: "https://api.anthropic.com/v1",
|
|
312
622
|
keyPrefix: "sk-ant-",
|
|
313
623
|
keyPlaceholder: "sk-ant-..."
|
|
314
624
|
},
|
|
625
|
+
// ─── Google Gemini ────────────────────────────────────────────
|
|
315
626
|
google: {
|
|
316
627
|
name: "Google Gemini",
|
|
317
628
|
models: [
|
|
318
|
-
|
|
319
|
-
{
|
|
320
|
-
|
|
629
|
+
// Gemini 3.1 (latest — Feb-Mar 2026)
|
|
630
|
+
{
|
|
631
|
+
id: "gemini-3.1-pro-preview",
|
|
632
|
+
name: "Gemini 3.1 Pro",
|
|
633
|
+
vision: true,
|
|
634
|
+
context: 1048576,
|
|
635
|
+
maxOutput: 65536,
|
|
636
|
+
thinking: {
|
|
637
|
+
supported: true,
|
|
638
|
+
paramName: "thinking_level",
|
|
639
|
+
paramType: "level",
|
|
640
|
+
levels: ["none", "low", "medium", "high"],
|
|
641
|
+
defaultLevel: "medium"
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
// Gemini 3.0
|
|
645
|
+
{
|
|
646
|
+
id: "gemini-3-flash-preview",
|
|
647
|
+
name: "Gemini 3 Flash",
|
|
648
|
+
vision: true,
|
|
649
|
+
context: 1048576,
|
|
650
|
+
maxOutput: 65536,
|
|
651
|
+
thinking: {
|
|
652
|
+
supported: true,
|
|
653
|
+
paramName: "thinking_level",
|
|
654
|
+
paramType: "level",
|
|
655
|
+
levels: ["none", "low", "medium", "high"],
|
|
656
|
+
defaultLevel: "low"
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
id: "gemini-3.1-flash-lite-preview",
|
|
661
|
+
name: "Gemini 3.1 Flash Lite",
|
|
662
|
+
vision: true,
|
|
663
|
+
context: 1048576,
|
|
664
|
+
maxOutput: 65536
|
|
665
|
+
},
|
|
666
|
+
// Gemini 2.5
|
|
667
|
+
{
|
|
668
|
+
id: "gemini-2.5-pro",
|
|
669
|
+
name: "Gemini 2.5 Pro",
|
|
670
|
+
vision: true,
|
|
671
|
+
context: 1048576,
|
|
672
|
+
maxOutput: 65536,
|
|
673
|
+
thinking: {
|
|
674
|
+
supported: true,
|
|
675
|
+
paramName: "thinking_level",
|
|
676
|
+
paramType: "level",
|
|
677
|
+
levels: ["none", "low", "medium", "high"],
|
|
678
|
+
defaultLevel: "medium"
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
id: "gemini-2.5-flash",
|
|
683
|
+
name: "Gemini 2.5 Flash",
|
|
684
|
+
vision: true,
|
|
685
|
+
context: 1048576,
|
|
686
|
+
maxOutput: 65536,
|
|
687
|
+
thinking: {
|
|
688
|
+
supported: true,
|
|
689
|
+
paramName: "thinking_level",
|
|
690
|
+
paramType: "level",
|
|
691
|
+
levels: ["none", "low", "medium", "high"],
|
|
692
|
+
defaultLevel: "low"
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
id: "gemini-2.5-flash-lite",
|
|
697
|
+
name: "Gemini 2.5 Flash Lite",
|
|
698
|
+
vision: true,
|
|
699
|
+
context: 1048576,
|
|
700
|
+
maxOutput: 65536
|
|
701
|
+
}
|
|
321
702
|
],
|
|
322
703
|
apiBase: "https://generativelanguage.googleapis.com/v1beta",
|
|
323
704
|
keyPrefix: "AI",
|
|
324
705
|
keyPlaceholder: "AIza..."
|
|
325
706
|
},
|
|
707
|
+
// ─── xAI (Grok) ──────────────────────────────────────────────
|
|
708
|
+
xai: {
|
|
709
|
+
name: "xAI (Grok)",
|
|
710
|
+
models: [
|
|
711
|
+
{
|
|
712
|
+
id: "grok-4.20-0309-reasoning",
|
|
713
|
+
name: "Grok 4.20 Reasoning",
|
|
714
|
+
vision: true,
|
|
715
|
+
context: 2e6,
|
|
716
|
+
maxOutput: 128e3,
|
|
717
|
+
thinking: {
|
|
718
|
+
supported: true,
|
|
719
|
+
paramName: "reasoning_effort",
|
|
720
|
+
paramType: "level",
|
|
721
|
+
levels: ["low", "medium", "high"],
|
|
722
|
+
defaultLevel: "medium"
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
id: "grok-4.20-0309-non-reasoning",
|
|
727
|
+
name: "Grok 4.20",
|
|
728
|
+
vision: true,
|
|
729
|
+
context: 2e6,
|
|
730
|
+
maxOutput: 128e3
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
id: "grok-4-1-fast-reasoning",
|
|
734
|
+
name: "Grok 4.1 Fast Reasoning",
|
|
735
|
+
vision: true,
|
|
736
|
+
context: 2e6,
|
|
737
|
+
maxOutput: 128e3,
|
|
738
|
+
thinking: {
|
|
739
|
+
supported: true,
|
|
740
|
+
paramName: "reasoning_effort",
|
|
741
|
+
paramType: "level",
|
|
742
|
+
levels: ["low", "medium", "high"],
|
|
743
|
+
defaultLevel: "low"
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
id: "grok-4-1-fast-non-reasoning",
|
|
748
|
+
name: "Grok 4.1 Fast",
|
|
749
|
+
vision: true,
|
|
750
|
+
context: 2e6,
|
|
751
|
+
maxOutput: 128e3
|
|
752
|
+
}
|
|
753
|
+
],
|
|
754
|
+
apiBase: "https://api.x.ai/v1",
|
|
755
|
+
keyPrefix: "xai-",
|
|
756
|
+
keyPlaceholder: "xai-..."
|
|
757
|
+
},
|
|
758
|
+
// ─── DeepSeek ─────────────────────────────────────────────────
|
|
326
759
|
deepseek: {
|
|
327
760
|
name: "DeepSeek",
|
|
328
761
|
models: [
|
|
329
|
-
{
|
|
330
|
-
|
|
762
|
+
{
|
|
763
|
+
id: "deepseek-chat",
|
|
764
|
+
name: "DeepSeek V3.2",
|
|
765
|
+
vision: false,
|
|
766
|
+
context: 128e3,
|
|
767
|
+
maxOutput: 8192
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
id: "deepseek-reasoner",
|
|
771
|
+
name: "DeepSeek R1",
|
|
772
|
+
vision: false,
|
|
773
|
+
context: 128e3,
|
|
774
|
+
maxOutput: 8192,
|
|
775
|
+
thinking: {
|
|
776
|
+
supported: true,
|
|
777
|
+
paramName: "reasoning_effort",
|
|
778
|
+
paramType: "level",
|
|
779
|
+
levels: ["low", "medium", "high"],
|
|
780
|
+
defaultLevel: "medium"
|
|
781
|
+
}
|
|
782
|
+
}
|
|
331
783
|
],
|
|
332
784
|
apiBase: "https://api.deepseek.com/v1",
|
|
333
785
|
keyPrefix: "sk-",
|
|
334
786
|
keyPlaceholder: "sk-..."
|
|
335
787
|
},
|
|
336
|
-
|
|
337
|
-
name: "Groq",
|
|
338
|
-
models: [
|
|
339
|
-
{ id: "llama-3.3-70b-versatile", name: "Llama 3.3 70B", vision: false, context: 131072 },
|
|
340
|
-
{ id: "llama-3.1-8b-instant", name: "Llama 3.1 8B", vision: false, context: 131072 },
|
|
341
|
-
{ id: "gemma2-9b-it", name: "Gemma 2 9B", vision: false, context: 8192 }
|
|
342
|
-
],
|
|
343
|
-
apiBase: "https://api.groq.com/openai/v1",
|
|
344
|
-
keyPrefix: "gsk_",
|
|
345
|
-
keyPlaceholder: "gsk_..."
|
|
346
|
-
},
|
|
788
|
+
// ─── Mistral ──────────────────────────────────────────────────
|
|
347
789
|
mistral: {
|
|
348
790
|
name: "Mistral",
|
|
349
791
|
models: [
|
|
350
|
-
{
|
|
351
|
-
|
|
352
|
-
|
|
792
|
+
{
|
|
793
|
+
id: "mistral-large-3-25-12",
|
|
794
|
+
name: "Mistral Large 3",
|
|
795
|
+
vision: true,
|
|
796
|
+
context: 131072,
|
|
797
|
+
maxOutput: 32768
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
id: "mistral-small-4-0-26-03",
|
|
801
|
+
name: "Mistral Small 4",
|
|
802
|
+
vision: true,
|
|
803
|
+
context: 131072,
|
|
804
|
+
maxOutput: 32768
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
id: "mistral-small-3-2-25-06",
|
|
808
|
+
name: "Mistral Small 3.2",
|
|
809
|
+
vision: true,
|
|
810
|
+
context: 131072,
|
|
811
|
+
maxOutput: 32768
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
id: "codestral-2508",
|
|
815
|
+
name: "Codestral",
|
|
816
|
+
vision: false,
|
|
817
|
+
context: 262144,
|
|
818
|
+
maxOutput: 32768
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
id: "devstral-2-25-12",
|
|
822
|
+
name: "Devstral 2",
|
|
823
|
+
vision: false,
|
|
824
|
+
context: 131072,
|
|
825
|
+
maxOutput: 32768
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
id: "magistral-medium-1-2-25-09",
|
|
829
|
+
name: "Magistral Medium (Reasoning)",
|
|
830
|
+
vision: false,
|
|
831
|
+
context: 131072,
|
|
832
|
+
maxOutput: 32768,
|
|
833
|
+
thinking: {
|
|
834
|
+
supported: true,
|
|
835
|
+
paramName: "reasoning_effort",
|
|
836
|
+
paramType: "level",
|
|
837
|
+
levels: ["low", "medium", "high"],
|
|
838
|
+
defaultLevel: "medium"
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
id: "magistral-small-1-2-25-09",
|
|
843
|
+
name: "Magistral Small (Reasoning)",
|
|
844
|
+
vision: false,
|
|
845
|
+
context: 131072,
|
|
846
|
+
maxOutput: 32768,
|
|
847
|
+
thinking: {
|
|
848
|
+
supported: true,
|
|
849
|
+
paramName: "reasoning_effort",
|
|
850
|
+
paramType: "level",
|
|
851
|
+
levels: ["low", "medium", "high"],
|
|
852
|
+
defaultLevel: "medium"
|
|
853
|
+
}
|
|
854
|
+
}
|
|
353
855
|
],
|
|
354
856
|
apiBase: "https://api.mistral.ai/v1",
|
|
355
857
|
keyPrefix: "",
|
|
356
858
|
keyPlaceholder: "Enter API key..."
|
|
357
859
|
},
|
|
358
|
-
|
|
359
|
-
|
|
860
|
+
// ─── Groq ─────────────────────────────────────────────────────
|
|
861
|
+
groq: {
|
|
862
|
+
name: "Groq",
|
|
360
863
|
models: [
|
|
361
|
-
{
|
|
362
|
-
|
|
864
|
+
{
|
|
865
|
+
id: "meta-llama/llama-4-scout-17b-16e-instruct",
|
|
866
|
+
name: "Llama 4 Scout 17B",
|
|
867
|
+
vision: true,
|
|
868
|
+
context: 131072,
|
|
869
|
+
maxOutput: 8192
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
id: "llama-3.3-70b-versatile",
|
|
873
|
+
name: "Llama 3.3 70B",
|
|
874
|
+
vision: false,
|
|
875
|
+
context: 131072,
|
|
876
|
+
maxOutput: 32768
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
id: "llama-3.1-8b-instant",
|
|
880
|
+
name: "Llama 3.1 8B Instant",
|
|
881
|
+
vision: false,
|
|
882
|
+
context: 131072,
|
|
883
|
+
maxOutput: 8192
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
id: "qwen/qwen3-32b",
|
|
887
|
+
name: "Qwen 3 32B",
|
|
888
|
+
vision: false,
|
|
889
|
+
context: 131072,
|
|
890
|
+
maxOutput: 8192
|
|
891
|
+
}
|
|
363
892
|
],
|
|
364
|
-
apiBase: "https://api.
|
|
365
|
-
keyPrefix: "
|
|
366
|
-
keyPlaceholder: "
|
|
893
|
+
apiBase: "https://api.groq.com/openai/v1",
|
|
894
|
+
keyPrefix: "gsk_",
|
|
895
|
+
keyPlaceholder: "gsk_..."
|
|
367
896
|
},
|
|
897
|
+
// ─── Ollama (Local) ───────────────────────────────────────────
|
|
368
898
|
ollama: {
|
|
369
899
|
name: "Ollama (Local)",
|
|
370
900
|
models: [],
|
|
@@ -373,6 +903,7 @@ var MODEL_REGISTRY = {
|
|
|
373
903
|
keyPlaceholder: "not required",
|
|
374
904
|
local: true
|
|
375
905
|
},
|
|
906
|
+
// ─── OpenRouter (200+ models) ─────────────────────────────────
|
|
376
907
|
openrouter: {
|
|
377
908
|
name: "OpenRouter",
|
|
378
909
|
models: [],
|
|
@@ -492,8 +1023,8 @@ async function chatOpenAICompatible(provider, model, apiKey, messages, context,
|
|
|
492
1023
|
contextParts.consoleLogs = context.consoleLogs.map((l) => `[${l.level}] ${l.args.join(" ")}`).join("\n");
|
|
493
1024
|
}
|
|
494
1025
|
const enrichedContent = buildUserMessage(msg.content, contextParts);
|
|
495
|
-
const
|
|
496
|
-
if (context.screenshot &&
|
|
1026
|
+
const modelInfo2 = providerConfig.models.find((m) => m.id === model);
|
|
1027
|
+
if (context.screenshot && modelInfo2?.vision) {
|
|
497
1028
|
apiMessages.push({
|
|
498
1029
|
role: "user",
|
|
499
1030
|
content: [
|
|
@@ -520,6 +1051,11 @@ async function chatOpenAICompatible(provider, model, apiKey, messages, context,
|
|
|
520
1051
|
stream: true,
|
|
521
1052
|
max_tokens: 4096
|
|
522
1053
|
};
|
|
1054
|
+
const modelInfo = providerConfig.models.find((m) => m.id === model);
|
|
1055
|
+
if (modelInfo?.thinking?.supported && modelInfo.thinking.paramType === "level") {
|
|
1056
|
+
body.reasoning_effort = modelInfo.thinking.defaultLevel || "medium";
|
|
1057
|
+
body.max_tokens = Math.min(modelInfo.maxOutput, 16384);
|
|
1058
|
+
}
|
|
523
1059
|
try {
|
|
524
1060
|
const headers = {
|
|
525
1061
|
"Content-Type": "application/json"
|
|
@@ -534,8 +1070,14 @@ async function chatOpenAICompatible(provider, model, apiKey, messages, context,
|
|
|
534
1070
|
body: JSON.stringify(body)
|
|
535
1071
|
});
|
|
536
1072
|
if (!response.ok) {
|
|
537
|
-
const errorText = await response.text();
|
|
538
|
-
|
|
1073
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
1074
|
+
if (response.status === 401 || response.status === 403) {
|
|
1075
|
+
onError(`Invalid API key for ${providerConfig.name}. Check your key in Settings.`);
|
|
1076
|
+
} else if (response.status === 429) {
|
|
1077
|
+
onError(`Rate limit exceeded for ${providerConfig.name}. Wait a moment and try again.`);
|
|
1078
|
+
} else {
|
|
1079
|
+
onError(`${providerConfig.name} API error ${response.status}: ${errorText.slice(0, 200)}`);
|
|
1080
|
+
}
|
|
539
1081
|
return;
|
|
540
1082
|
}
|
|
541
1083
|
if (!response.body) {
|
|
@@ -627,13 +1169,22 @@ async function chatAnthropic(model, apiKey, messages, context, onChunk, onDone,
|
|
|
627
1169
|
});
|
|
628
1170
|
}
|
|
629
1171
|
}
|
|
1172
|
+
const providerConfig = MODEL_REGISTRY.anthropic;
|
|
1173
|
+
const modelInfo = providerConfig?.models.find((m) => m.id === model);
|
|
1174
|
+
const thinkingBudget = modelInfo?.thinking?.defaultBudget || 0;
|
|
630
1175
|
const body = {
|
|
631
1176
|
model,
|
|
632
|
-
max_tokens: 4096,
|
|
1177
|
+
max_tokens: thinkingBudget > 0 ? Math.max(thinkingBudget + 4096, 16384) : 4096,
|
|
633
1178
|
system: SYSTEM_PROMPT,
|
|
634
1179
|
messages: apiMessages,
|
|
635
1180
|
stream: true
|
|
636
1181
|
};
|
|
1182
|
+
if (thinkingBudget > 0) {
|
|
1183
|
+
body.thinking = {
|
|
1184
|
+
type: "enabled",
|
|
1185
|
+
budget_tokens: thinkingBudget
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
637
1188
|
try {
|
|
638
1189
|
const response = await fetch(url, {
|
|
639
1190
|
method: "POST",
|
|
@@ -645,8 +1196,14 @@ async function chatAnthropic(model, apiKey, messages, context, onChunk, onDone,
|
|
|
645
1196
|
body: JSON.stringify(body)
|
|
646
1197
|
});
|
|
647
1198
|
if (!response.ok) {
|
|
648
|
-
const errorText = await response.text();
|
|
649
|
-
|
|
1199
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
1200
|
+
if (response.status === 401 || response.status === 403) {
|
|
1201
|
+
onError("Invalid Anthropic API key. Check your key in Settings.");
|
|
1202
|
+
} else if (response.status === 429) {
|
|
1203
|
+
onError("Anthropic rate limit exceeded. Wait a moment and try again.");
|
|
1204
|
+
} else {
|
|
1205
|
+
onError(`Anthropic API error ${response.status}: ${errorText.slice(0, 200)}`);
|
|
1206
|
+
}
|
|
650
1207
|
return;
|
|
651
1208
|
}
|
|
652
1209
|
if (!response.body) {
|
|
@@ -728,14 +1285,21 @@ async function chatGoogle(model, apiKey, messages, context, onChunk, onDone, onE
|
|
|
728
1285
|
});
|
|
729
1286
|
}
|
|
730
1287
|
}
|
|
1288
|
+
const providerConfig = MODEL_REGISTRY.google;
|
|
1289
|
+
const modelInfo = providerConfig?.models.find((m) => m.id === model);
|
|
1290
|
+
const thinkingLevel = modelInfo?.thinking?.defaultLevel;
|
|
1291
|
+
const generationConfig = {
|
|
1292
|
+
maxOutputTokens: 8192
|
|
1293
|
+
};
|
|
1294
|
+
if (thinkingLevel && thinkingLevel !== "none") {
|
|
1295
|
+
generationConfig.thinking_level = thinkingLevel.toUpperCase();
|
|
1296
|
+
}
|
|
731
1297
|
const body = {
|
|
732
1298
|
system_instruction: {
|
|
733
1299
|
parts: [{ text: SYSTEM_PROMPT }]
|
|
734
1300
|
},
|
|
735
1301
|
contents,
|
|
736
|
-
generationConfig
|
|
737
|
-
maxOutputTokens: 4096
|
|
738
|
-
}
|
|
1302
|
+
generationConfig
|
|
739
1303
|
};
|
|
740
1304
|
try {
|
|
741
1305
|
const response = await fetch(url, {
|
|
@@ -744,8 +1308,14 @@ async function chatGoogle(model, apiKey, messages, context, onChunk, onDone, onE
|
|
|
744
1308
|
body: JSON.stringify(body)
|
|
745
1309
|
});
|
|
746
1310
|
if (!response.ok) {
|
|
747
|
-
const errorText = await response.text();
|
|
748
|
-
|
|
1311
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
1312
|
+
if (response.status === 401 || response.status === 403) {
|
|
1313
|
+
onError("Invalid Google API key. Check your key in Settings.");
|
|
1314
|
+
} else if (response.status === 429) {
|
|
1315
|
+
onError("Google API rate limit exceeded. Wait a moment and try again.");
|
|
1316
|
+
} else {
|
|
1317
|
+
onError(`Google API error ${response.status}: ${errorText.slice(0, 200)}`);
|
|
1318
|
+
}
|
|
749
1319
|
return;
|
|
750
1320
|
}
|
|
751
1321
|
if (!response.body) {
|
|
@@ -807,23 +1377,32 @@ async function handleLlmChat(params, onChunk, onDone, onError) {
|
|
|
807
1377
|
}
|
|
808
1378
|
onDone({ content: result.content, modifications });
|
|
809
1379
|
};
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1380
|
+
try {
|
|
1381
|
+
if (provider === "anthropic") {
|
|
1382
|
+
await chatAnthropic(model, apiKey, messages, context, onChunk, wrappedOnDone, onError);
|
|
1383
|
+
} else if (provider === "google") {
|
|
1384
|
+
await chatGoogle(model, apiKey, messages, context, onChunk, wrappedOnDone, onError);
|
|
1385
|
+
} else if (OPENAI_COMPATIBLE_PROVIDERS.has(provider)) {
|
|
1386
|
+
await chatOpenAICompatible(
|
|
1387
|
+
provider,
|
|
1388
|
+
model,
|
|
1389
|
+
apiKey,
|
|
1390
|
+
messages,
|
|
1391
|
+
context,
|
|
1392
|
+
onChunk,
|
|
1393
|
+
wrappedOnDone,
|
|
1394
|
+
onError
|
|
1395
|
+
);
|
|
1396
|
+
} else {
|
|
1397
|
+
onError(`Unsupported provider: ${provider}. Check your Settings.`);
|
|
1398
|
+
}
|
|
1399
|
+
} catch (e) {
|
|
1400
|
+
const msg = e.message || "Unknown error";
|
|
1401
|
+
if (msg.includes("fetch") || msg.includes("ECONNREFUSED") || msg.includes("network")) {
|
|
1402
|
+
onError(`Network error: Could not reach the ${provider} API. Check your internet connection.`);
|
|
1403
|
+
} else {
|
|
1404
|
+
onError(`Unexpected error with ${provider}: ${msg}`);
|
|
1405
|
+
}
|
|
827
1406
|
}
|
|
828
1407
|
}
|
|
829
1408
|
|
|
@@ -840,7 +1419,7 @@ function createOpenMagicServer(proxyPort, roots) {
|
|
|
840
1419
|
"Content-Type": "application/json",
|
|
841
1420
|
"Access-Control-Allow-Origin": "*"
|
|
842
1421
|
});
|
|
843
|
-
res.end(JSON.stringify({ status: "ok", version: "0.
|
|
1422
|
+
res.end(JSON.stringify({ status: "ok", version: "0.4.0" }));
|
|
844
1423
|
return;
|
|
845
1424
|
}
|
|
846
1425
|
res.writeHead(404);
|
|
@@ -882,6 +1461,11 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
|
|
|
882
1461
|
switch (msg.type) {
|
|
883
1462
|
case "handshake": {
|
|
884
1463
|
const payload = msg.payload;
|
|
1464
|
+
if (!payload?.token) {
|
|
1465
|
+
sendError(ws, "invalid_payload", "Missing token in handshake", msg.id);
|
|
1466
|
+
ws.close();
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
885
1469
|
if (!validateToken(payload.token)) {
|
|
886
1470
|
sendError(ws, "auth_failed", "Invalid token", msg.id);
|
|
887
1471
|
ws.close();
|
|
@@ -893,7 +1477,7 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
|
|
|
893
1477
|
id: msg.id,
|
|
894
1478
|
type: "handshake.ok",
|
|
895
1479
|
payload: {
|
|
896
|
-
version: "0.
|
|
1480
|
+
version: "0.4.0",
|
|
897
1481
|
roots,
|
|
898
1482
|
config: {
|
|
899
1483
|
provider: config.provider,
|
|
@@ -906,6 +1490,10 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
|
|
|
906
1490
|
}
|
|
907
1491
|
case "fs.read": {
|
|
908
1492
|
const payload = msg.payload;
|
|
1493
|
+
if (!payload?.path) {
|
|
1494
|
+
sendError(ws, "invalid_payload", "Missing path", msg.id);
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
909
1497
|
const result = readFileSafe(payload.path, roots);
|
|
910
1498
|
if ("error" in result) {
|
|
911
1499
|
sendError(ws, "fs_error", result.error, msg.id);
|
|
@@ -1017,15 +1605,19 @@ function serveToolbarBundle(res) {
|
|
|
1017
1605
|
join3(__dirname, "..", "dist", "toolbar", "index.global.js")
|
|
1018
1606
|
];
|
|
1019
1607
|
for (const bundlePath of bundlePaths) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1608
|
+
try {
|
|
1609
|
+
if (existsSync3(bundlePath)) {
|
|
1610
|
+
const content = readFileSync3(bundlePath, "utf-8");
|
|
1611
|
+
res.writeHead(200, {
|
|
1612
|
+
"Content-Type": "application/javascript",
|
|
1613
|
+
"Access-Control-Allow-Origin": "*",
|
|
1614
|
+
"Cache-Control": "no-cache"
|
|
1615
|
+
});
|
|
1616
|
+
res.end(content);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
} catch {
|
|
1620
|
+
continue;
|
|
1029
1621
|
}
|
|
1030
1622
|
}
|
|
1031
1623
|
res.writeHead(200, {
|
|
@@ -1044,6 +1636,8 @@ function serveToolbarBundle(res) {
|
|
|
1044
1636
|
|
|
1045
1637
|
// src/detect.ts
|
|
1046
1638
|
import { createConnection } from "net";
|
|
1639
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
1640
|
+
import { join as join4 } from "path";
|
|
1047
1641
|
var COMMON_DEV_PORTS = [
|
|
1048
1642
|
3e3,
|
|
1049
1643
|
// React (CRA), Next.js, Express
|
|
@@ -1063,8 +1657,16 @@ var COMMON_DEV_PORTS = [
|
|
|
1063
1657
|
// Common alternate
|
|
1064
1658
|
4e3,
|
|
1065
1659
|
// Phoenix, generic
|
|
1066
|
-
1234
|
|
1660
|
+
1234,
|
|
1067
1661
|
// Parcel
|
|
1662
|
+
4321,
|
|
1663
|
+
// Astro
|
|
1664
|
+
3333,
|
|
1665
|
+
// Remix
|
|
1666
|
+
8081,
|
|
1667
|
+
// Metro (React Native)
|
|
1668
|
+
9e3
|
|
1669
|
+
// generic
|
|
1068
1670
|
];
|
|
1069
1671
|
function checkPort(port, host = "127.0.0.1") {
|
|
1070
1672
|
return new Promise((resolve3) => {
|
|
@@ -1108,9 +1710,169 @@ async function findAvailablePort(startPort) {
|
|
|
1108
1710
|
}
|
|
1109
1711
|
return port;
|
|
1110
1712
|
}
|
|
1713
|
+
var FRAMEWORK_PATTERNS = [
|
|
1714
|
+
{ match: /\bnext\b/, framework: "Next.js", defaultPort: 3e3 },
|
|
1715
|
+
{ match: /\bvite\b/, framework: "Vite", defaultPort: 5173 },
|
|
1716
|
+
{ match: /\bnuxt\b/, framework: "Nuxt", defaultPort: 3e3 },
|
|
1717
|
+
{ match: /\bng\s+serve\b/, framework: "Angular", defaultPort: 4200 },
|
|
1718
|
+
{ match: /\bvue-cli-service\s+serve\b/, framework: "Vue CLI", defaultPort: 8080 },
|
|
1719
|
+
{ match: /\bsvelte-kit\b/, framework: "SvelteKit", defaultPort: 5173 },
|
|
1720
|
+
{ match: /\bastro\b/, framework: "Astro", defaultPort: 4321 },
|
|
1721
|
+
{ match: /\bremix\b/, framework: "Remix", defaultPort: 3e3 },
|
|
1722
|
+
{ match: /\breact-scripts\s+start\b/, framework: "Create React App", defaultPort: 3e3 },
|
|
1723
|
+
{ match: /\bparcel\b/, framework: "Parcel", defaultPort: 1234 },
|
|
1724
|
+
{ match: /\bwebpack\s+serve\b|webpack-dev-server/, framework: "Webpack", defaultPort: 8080 },
|
|
1725
|
+
{ match: /\bgatsby\b/, framework: "Gatsby", defaultPort: 8e3 },
|
|
1726
|
+
{ match: /\bturborepo\b|\bturbo\b.*dev/, framework: "Turborepo", defaultPort: 3e3 },
|
|
1727
|
+
{ match: /\bexpo\b/, framework: "Expo", defaultPort: 8081 },
|
|
1728
|
+
{ match: /\bnodemon\b|\bts-node\b|\bnode\b/, framework: "Node.js", defaultPort: 3e3 },
|
|
1729
|
+
{ match: /\bflask\b/, framework: "Flask", defaultPort: 5e3 },
|
|
1730
|
+
{ match: /\bdjango\b|manage\.py\s+runserver/, framework: "Django", defaultPort: 8e3 },
|
|
1731
|
+
{ match: /\brails\b/, framework: "Rails", defaultPort: 3e3 },
|
|
1732
|
+
{ match: /\bphp\s+.*serve\b|artisan\s+serve/, framework: "PHP/Laravel", defaultPort: 8e3 }
|
|
1733
|
+
];
|
|
1734
|
+
var DEV_SCRIPT_NAMES = ["dev", "start", "serve", "develop", "dev:start", "start:dev"];
|
|
1735
|
+
function detectDevScripts(cwd = process.cwd()) {
|
|
1736
|
+
const pkgPath = join4(cwd, "package.json");
|
|
1737
|
+
if (!existsSync4(pkgPath)) return [];
|
|
1738
|
+
let pkg;
|
|
1739
|
+
try {
|
|
1740
|
+
pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
1741
|
+
} catch {
|
|
1742
|
+
return [];
|
|
1743
|
+
}
|
|
1744
|
+
if (!pkg.scripts) return [];
|
|
1745
|
+
const scripts = [];
|
|
1746
|
+
for (const name of DEV_SCRIPT_NAMES) {
|
|
1747
|
+
const command = pkg.scripts[name];
|
|
1748
|
+
if (!command) continue;
|
|
1749
|
+
let framework = "Unknown";
|
|
1750
|
+
let defaultPort = 3e3;
|
|
1751
|
+
for (const pattern of FRAMEWORK_PATTERNS) {
|
|
1752
|
+
if (pattern.match.test(command)) {
|
|
1753
|
+
framework = pattern.framework;
|
|
1754
|
+
defaultPort = pattern.defaultPort;
|
|
1755
|
+
break;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
const portMatch = command.match(/(?:--port|-p)\s+(\d+)/);
|
|
1759
|
+
if (portMatch) {
|
|
1760
|
+
defaultPort = parseInt(portMatch[1], 10);
|
|
1761
|
+
}
|
|
1762
|
+
scripts.push({ name, command, framework, defaultPort });
|
|
1763
|
+
}
|
|
1764
|
+
return scripts;
|
|
1765
|
+
}
|
|
1766
|
+
function getProjectName(cwd = process.cwd()) {
|
|
1767
|
+
const pkgPath = join4(cwd, "package.json");
|
|
1768
|
+
if (!existsSync4(pkgPath)) return "this project";
|
|
1769
|
+
try {
|
|
1770
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
1771
|
+
return pkg.name || "this project";
|
|
1772
|
+
} catch {
|
|
1773
|
+
return "this project";
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
var LOCK_FILES = [
|
|
1777
|
+
{ file: "pnpm-lock.yaml", pm: "pnpm" },
|
|
1778
|
+
{ file: "yarn.lock", pm: "yarn" },
|
|
1779
|
+
{ file: "bun.lockb", pm: "bun" },
|
|
1780
|
+
{ file: "bun.lock", pm: "bun" },
|
|
1781
|
+
{ file: "package-lock.json", pm: "npm" }
|
|
1782
|
+
];
|
|
1783
|
+
var INSTALL_COMMANDS = {
|
|
1784
|
+
npm: "npm install",
|
|
1785
|
+
yarn: "yarn install",
|
|
1786
|
+
pnpm: "pnpm install",
|
|
1787
|
+
bun: "bun install"
|
|
1788
|
+
};
|
|
1789
|
+
function checkDependenciesInstalled(cwd = process.cwd()) {
|
|
1790
|
+
const hasNodeModules = existsSync4(join4(cwd, "node_modules"));
|
|
1791
|
+
let pm = "npm";
|
|
1792
|
+
for (const { file, pm: detectedPm } of LOCK_FILES) {
|
|
1793
|
+
if (existsSync4(join4(cwd, file))) {
|
|
1794
|
+
pm = detectedPm;
|
|
1795
|
+
break;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return {
|
|
1799
|
+
installed: hasNodeModules,
|
|
1800
|
+
packageManager: pm,
|
|
1801
|
+
installCommand: INSTALL_COMMANDS[pm]
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1111
1804
|
|
|
1112
1805
|
// src/cli.ts
|
|
1113
|
-
|
|
1806
|
+
process.on("unhandledRejection", (err) => {
|
|
1807
|
+
console.error(chalk.red("\n [OpenMagic] Unhandled error:"), err?.message || err);
|
|
1808
|
+
console.error(chalk.dim(" Please report this at https://github.com/Kalmuraee/OpenMagic/issues"));
|
|
1809
|
+
});
|
|
1810
|
+
process.on("uncaughtException", (err) => {
|
|
1811
|
+
console.error(chalk.red("\n [OpenMagic] Fatal error:"), err.message);
|
|
1812
|
+
console.error(chalk.dim(" Please report this at https://github.com/Kalmuraee/OpenMagic/issues"));
|
|
1813
|
+
process.exit(1);
|
|
1814
|
+
});
|
|
1815
|
+
var childProcesses = [];
|
|
1816
|
+
var VERSION = "0.4.0";
|
|
1817
|
+
function ask(question) {
|
|
1818
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1819
|
+
return new Promise((resolve3) => {
|
|
1820
|
+
rl.question(question, (answer) => {
|
|
1821
|
+
rl.close();
|
|
1822
|
+
resolve3(answer.trim());
|
|
1823
|
+
});
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
function waitForPort(port, timeoutMs = 3e4, shouldAbort) {
|
|
1827
|
+
const start = Date.now();
|
|
1828
|
+
return new Promise((resolve3) => {
|
|
1829
|
+
const check = async () => {
|
|
1830
|
+
if (shouldAbort?.()) {
|
|
1831
|
+
resolve3(false);
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
if (await isPortOpen(port)) {
|
|
1835
|
+
resolve3(true);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
if (Date.now() - start > timeoutMs) {
|
|
1839
|
+
resolve3(false);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
setTimeout(check, 500);
|
|
1843
|
+
};
|
|
1844
|
+
check();
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
function runCommand(cmd, args, cwd = process.cwd()) {
|
|
1848
|
+
return new Promise((resolve3) => {
|
|
1849
|
+
try {
|
|
1850
|
+
const child = spawn(cmd, args, {
|
|
1851
|
+
cwd,
|
|
1852
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1853
|
+
shell: true
|
|
1854
|
+
});
|
|
1855
|
+
child.stdout?.on("data", (data) => {
|
|
1856
|
+
const lines = data.toString().trim().split("\n");
|
|
1857
|
+
for (const line of lines) {
|
|
1858
|
+
if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
1859
|
+
`));
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
child.stderr?.on("data", (data) => {
|
|
1863
|
+
const lines = data.toString().trim().split("\n");
|
|
1864
|
+
for (const line of lines) {
|
|
1865
|
+
if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
1866
|
+
`));
|
|
1867
|
+
}
|
|
1868
|
+
});
|
|
1869
|
+
child.on("error", () => resolve3(false));
|
|
1870
|
+
child.on("close", (code) => resolve3(code === 0));
|
|
1871
|
+
} catch {
|
|
1872
|
+
resolve3(false);
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1114
1876
|
var program = new Command();
|
|
1115
1877
|
program.name("openmagic").description("AI-powered coding toolbar for any web application").version(VERSION).option("-p, --port <port>", "Dev server port to proxy", "").option(
|
|
1116
1878
|
"-l, --listen <port>",
|
|
@@ -1131,43 +1893,35 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
1131
1893
|
targetPort = parseInt(opts.port, 10);
|
|
1132
1894
|
const isRunning = await isPortOpen(targetPort);
|
|
1133
1895
|
if (!isRunning) {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
);
|
|
1139
|
-
console.log(
|
|
1140
|
-
chalk.dim(
|
|
1141
|
-
" Start your dev server first, then run openmagic again."
|
|
1142
|
-
)
|
|
1143
|
-
);
|
|
1144
|
-
console.log("");
|
|
1145
|
-
process.exit(1);
|
|
1896
|
+
const started = await offerToStartDevServer(targetPort);
|
|
1897
|
+
if (!started) {
|
|
1898
|
+
process.exit(1);
|
|
1899
|
+
}
|
|
1146
1900
|
}
|
|
1147
1901
|
} else {
|
|
1148
1902
|
console.log(chalk.dim(" Scanning for dev server..."));
|
|
1149
1903
|
const detected = await detectDevServer();
|
|
1150
|
-
if (
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
)
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
)
|
|
1160
|
-
|
|
1161
|
-
chalk.
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1904
|
+
if (detected) {
|
|
1905
|
+
targetPort = detected.port;
|
|
1906
|
+
targetHost = detected.host;
|
|
1907
|
+
} else {
|
|
1908
|
+
const started = await offerToStartDevServer();
|
|
1909
|
+
if (!started) {
|
|
1910
|
+
process.exit(1);
|
|
1911
|
+
}
|
|
1912
|
+
const redetected = await detectDevServer();
|
|
1913
|
+
if (!redetected) {
|
|
1914
|
+
console.log(chalk.red(" \u2717 Could not detect the dev server after starting."));
|
|
1915
|
+
console.log(chalk.dim(" Try specifying the port: npx openmagic --port 3000"));
|
|
1916
|
+
console.log("");
|
|
1917
|
+
process.exit(1);
|
|
1918
|
+
}
|
|
1919
|
+
targetPort = redetected.port;
|
|
1920
|
+
targetHost = redetected.host;
|
|
1165
1921
|
}
|
|
1166
|
-
targetPort = detected.port;
|
|
1167
|
-
targetHost = detected.host;
|
|
1168
1922
|
}
|
|
1169
1923
|
console.log(
|
|
1170
|
-
chalk.green(` \u2713 Dev server
|
|
1924
|
+
chalk.green(` \u2713 Dev server running at ${targetHost}:${targetPort}`)
|
|
1171
1925
|
);
|
|
1172
1926
|
const roots = (opts.root || [process.cwd()]).map(
|
|
1173
1927
|
(r) => resolve2(r)
|
|
@@ -1218,5 +1972,201 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
1218
1972
|
process.on("SIGINT", shutdown);
|
|
1219
1973
|
process.on("SIGTERM", shutdown);
|
|
1220
1974
|
});
|
|
1975
|
+
async function offerToStartDevServer(expectedPort) {
|
|
1976
|
+
const projectName = getProjectName();
|
|
1977
|
+
const scripts = detectDevScripts();
|
|
1978
|
+
if (scripts.length === 0) {
|
|
1979
|
+
console.log(
|
|
1980
|
+
chalk.yellow(" \u26A0 No dev server detected and no dev scripts found in package.json")
|
|
1981
|
+
);
|
|
1982
|
+
console.log("");
|
|
1983
|
+
console.log(chalk.white(" Start your dev server manually, then run:"));
|
|
1984
|
+
console.log(chalk.cyan(" npx openmagic --port <your-port>"));
|
|
1985
|
+
console.log("");
|
|
1986
|
+
return false;
|
|
1987
|
+
}
|
|
1988
|
+
const deps = checkDependenciesInstalled();
|
|
1989
|
+
if (!deps.installed) {
|
|
1990
|
+
console.log(
|
|
1991
|
+
chalk.yellow(" \u26A0 node_modules/ not found. Dependencies need to be installed.")
|
|
1992
|
+
);
|
|
1993
|
+
console.log("");
|
|
1994
|
+
const answer = await ask(
|
|
1995
|
+
chalk.white(` Run `) + chalk.cyan(deps.installCommand) + chalk.white("? ") + chalk.dim("(Y/n) ")
|
|
1996
|
+
);
|
|
1997
|
+
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
|
|
1998
|
+
console.log("");
|
|
1999
|
+
console.log(chalk.dim(` Run ${deps.installCommand} manually, then try again.`));
|
|
2000
|
+
console.log("");
|
|
2001
|
+
return false;
|
|
2002
|
+
}
|
|
2003
|
+
console.log("");
|
|
2004
|
+
console.log(chalk.dim(` Installing dependencies with ${deps.packageManager}...`));
|
|
2005
|
+
const [installCmd, ...installArgs] = deps.installCommand.split(" ");
|
|
2006
|
+
const installed = await runCommand(installCmd, installArgs);
|
|
2007
|
+
if (!installed) {
|
|
2008
|
+
console.log(chalk.red(" \u2717 Dependency installation failed."));
|
|
2009
|
+
console.log(chalk.dim(` Try running ${deps.installCommand} manually.`));
|
|
2010
|
+
console.log("");
|
|
2011
|
+
return false;
|
|
2012
|
+
}
|
|
2013
|
+
console.log(chalk.green(" \u2713 Dependencies installed."));
|
|
2014
|
+
console.log("");
|
|
2015
|
+
}
|
|
2016
|
+
let chosen = scripts[0];
|
|
2017
|
+
if (scripts.length === 1) {
|
|
2018
|
+
console.log(
|
|
2019
|
+
chalk.yellow(" \u26A0 No dev server detected.")
|
|
2020
|
+
);
|
|
2021
|
+
console.log("");
|
|
2022
|
+
console.log(
|
|
2023
|
+
chalk.white(` Found `) + chalk.cyan(`npm run ${chosen.name}`) + chalk.white(` in ${projectName}`) + chalk.dim(` (${chosen.framework})`)
|
|
2024
|
+
);
|
|
2025
|
+
console.log(chalk.dim(` \u2192 ${chosen.command}`));
|
|
2026
|
+
console.log("");
|
|
2027
|
+
const answer = await ask(
|
|
2028
|
+
chalk.white(` Start it now? `) + chalk.dim("(Y/n) ")
|
|
2029
|
+
);
|
|
2030
|
+
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
|
|
2031
|
+
console.log("");
|
|
2032
|
+
console.log(chalk.dim(" Start your dev server first, then run openmagic again."));
|
|
2033
|
+
console.log("");
|
|
2034
|
+
return false;
|
|
2035
|
+
}
|
|
2036
|
+
} else {
|
|
2037
|
+
console.log(
|
|
2038
|
+
chalk.yellow(" \u26A0 No dev server detected.")
|
|
2039
|
+
);
|
|
2040
|
+
console.log("");
|
|
2041
|
+
console.log(
|
|
2042
|
+
chalk.white(` Found ${scripts.length} dev scripts in ${projectName}:`)
|
|
2043
|
+
);
|
|
2044
|
+
console.log("");
|
|
2045
|
+
scripts.forEach((s, i) => {
|
|
2046
|
+
console.log(
|
|
2047
|
+
chalk.cyan(` ${i + 1}) `) + chalk.white(`npm run ${s.name}`) + chalk.dim(` \u2014 ${s.framework} (port ${s.defaultPort})`)
|
|
2048
|
+
);
|
|
2049
|
+
console.log(chalk.dim(` ${s.command}`));
|
|
2050
|
+
});
|
|
2051
|
+
console.log("");
|
|
2052
|
+
const answer = await ask(
|
|
2053
|
+
chalk.white(` Which one to start? `) + chalk.dim(`(1-${scripts.length}, or n to cancel) `)
|
|
2054
|
+
);
|
|
2055
|
+
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no" || answer === "") {
|
|
2056
|
+
console.log("");
|
|
2057
|
+
console.log(chalk.dim(" Start your dev server first, then run openmagic again."));
|
|
2058
|
+
console.log("");
|
|
2059
|
+
return false;
|
|
2060
|
+
}
|
|
2061
|
+
const idx = parseInt(answer, 10) - 1;
|
|
2062
|
+
if (idx < 0 || idx >= scripts.length || isNaN(idx)) {
|
|
2063
|
+
chosen = scripts[0];
|
|
2064
|
+
} else {
|
|
2065
|
+
chosen = scripts[idx];
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
const port = expectedPort || chosen.defaultPort;
|
|
2069
|
+
console.log("");
|
|
2070
|
+
console.log(
|
|
2071
|
+
chalk.dim(` Starting `) + chalk.cyan(`npm run ${chosen.name}`) + chalk.dim("...")
|
|
2072
|
+
);
|
|
2073
|
+
const depsInfo = checkDependenciesInstalled();
|
|
2074
|
+
const runCmd = depsInfo.packageManager === "yarn" ? "yarn" : depsInfo.packageManager === "pnpm" ? "pnpm" : depsInfo.packageManager === "bun" ? "bun" : "npm";
|
|
2075
|
+
const runArgs = runCmd === "npm" ? ["run", chosen.name] : [chosen.name];
|
|
2076
|
+
let child;
|
|
2077
|
+
try {
|
|
2078
|
+
child = spawn(runCmd, runArgs, {
|
|
2079
|
+
cwd: process.cwd(),
|
|
2080
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2081
|
+
detached: false,
|
|
2082
|
+
shell: true,
|
|
2083
|
+
env: {
|
|
2084
|
+
...process.env,
|
|
2085
|
+
PORT: String(port),
|
|
2086
|
+
BROWSER: "none",
|
|
2087
|
+
BROWSER_NONE: "true"
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
} catch (e) {
|
|
2091
|
+
console.log(chalk.red(` \u2717 Failed to start: ${e.message}`));
|
|
2092
|
+
return false;
|
|
2093
|
+
}
|
|
2094
|
+
childProcesses.push(child);
|
|
2095
|
+
let childExited = false;
|
|
2096
|
+
child.stdout?.on("data", (data) => {
|
|
2097
|
+
for (const line of data.toString().trim().split("\n")) {
|
|
2098
|
+
if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
2099
|
+
`));
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
child.stderr?.on("data", (data) => {
|
|
2103
|
+
for (const line of data.toString().trim().split("\n")) {
|
|
2104
|
+
if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
2105
|
+
`));
|
|
2106
|
+
}
|
|
2107
|
+
});
|
|
2108
|
+
child.on("error", (err) => {
|
|
2109
|
+
childExited = true;
|
|
2110
|
+
console.log(chalk.red(` \u2717 Failed to start: ${err.message}`));
|
|
2111
|
+
});
|
|
2112
|
+
child.on("exit", (code) => {
|
|
2113
|
+
childExited = true;
|
|
2114
|
+
if (code !== null && code !== 0) {
|
|
2115
|
+
console.log(chalk.red(` \u2717 Dev server exited with code ${code}`));
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
const cleanup = () => {
|
|
2119
|
+
for (const cp of childProcesses) {
|
|
2120
|
+
try {
|
|
2121
|
+
cp.kill("SIGTERM");
|
|
2122
|
+
} catch {
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
setTimeout(() => {
|
|
2126
|
+
for (const cp of childProcesses) {
|
|
2127
|
+
try {
|
|
2128
|
+
cp.kill("SIGKILL");
|
|
2129
|
+
} catch {
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}, 3e3);
|
|
2133
|
+
};
|
|
2134
|
+
process.on("exit", cleanup);
|
|
2135
|
+
process.on("SIGINT", cleanup);
|
|
2136
|
+
process.on("SIGTERM", cleanup);
|
|
2137
|
+
console.log(
|
|
2138
|
+
chalk.dim(` Waiting for port ${port}...`)
|
|
2139
|
+
);
|
|
2140
|
+
const isUp = await waitForPort(port, 3e4, () => childExited);
|
|
2141
|
+
if (childExited && !isUp) {
|
|
2142
|
+
console.log(
|
|
2143
|
+
chalk.red(` \u2717 Dev server exited before it was ready.`)
|
|
2144
|
+
);
|
|
2145
|
+
console.log(
|
|
2146
|
+
chalk.dim(` Check the error output above and fix the issue.`)
|
|
2147
|
+
);
|
|
2148
|
+
console.log("");
|
|
2149
|
+
return false;
|
|
2150
|
+
}
|
|
2151
|
+
if (!isUp) {
|
|
2152
|
+
console.log(
|
|
2153
|
+
chalk.yellow(` \u26A0 Port ${port} didn't open after 30s.`)
|
|
2154
|
+
);
|
|
2155
|
+
console.log(
|
|
2156
|
+
chalk.dim(` The server might use a different port. Check the output above.`)
|
|
2157
|
+
);
|
|
2158
|
+
console.log("");
|
|
2159
|
+
const detected = await detectDevServer();
|
|
2160
|
+
if (detected) {
|
|
2161
|
+
console.log(
|
|
2162
|
+
chalk.green(` \u2713 Found server on port ${detected.port} instead.`)
|
|
2163
|
+
);
|
|
2164
|
+
return true;
|
|
2165
|
+
}
|
|
2166
|
+
return false;
|
|
2167
|
+
}
|
|
2168
|
+
console.log("");
|
|
2169
|
+
return true;
|
|
2170
|
+
}
|
|
1221
2171
|
program.parse();
|
|
1222
2172
|
//# sourceMappingURL=cli.js.map
|