openmagic 0.1.0 → 0.3.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/dist/cli.js CHANGED
@@ -5,6 +5,8 @@ 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";
@@ -286,85 +288,550 @@ function getProjectTree(roots) {
286
288
 
287
289
  // src/llm/registry.ts
288
290
  var MODEL_REGISTRY = {
291
+ // ─── OpenAI ───────────────────────────────────────────────────
289
292
  openai: {
290
293
  name: "OpenAI",
291
294
  models: [
292
- { id: "gpt-4.1", name: "GPT-4.1", vision: true, context: 1047576 },
293
- { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", vision: true, context: 1047576 },
294
- { id: "gpt-4.1-nano", name: "GPT-4.1 Nano", vision: true, context: 1047576 },
295
- { id: "gpt-4o", name: "GPT-4o", vision: true, context: 128e3 },
296
- { id: "gpt-4o-mini", name: "GPT-4o Mini", vision: true, context: 128e3 },
297
- { id: "o3", name: "o3 (Reasoning)", vision: true, context: 2e5 },
298
- { id: "o4-mini", name: "o4-mini (Reasoning)", vision: true, context: 2e5 }
295
+ // GPT-5.4 family (March 2026 latest flagship)
296
+ {
297
+ id: "gpt-5.4",
298
+ name: "GPT-5.4",
299
+ vision: true,
300
+ context: 105e4,
301
+ maxOutput: 128e3,
302
+ thinking: {
303
+ supported: true,
304
+ paramName: "reasoning_effort",
305
+ paramType: "level",
306
+ levels: ["none", "low", "medium", "high", "xhigh"],
307
+ defaultLevel: "medium"
308
+ }
309
+ },
310
+ {
311
+ id: "gpt-5.4-pro",
312
+ name: "GPT-5.4 Pro",
313
+ vision: true,
314
+ context: 105e4,
315
+ maxOutput: 128e3,
316
+ thinking: {
317
+ supported: true,
318
+ paramName: "reasoning_effort",
319
+ paramType: "level",
320
+ levels: ["none", "low", "medium", "high", "xhigh"],
321
+ defaultLevel: "high"
322
+ }
323
+ },
324
+ {
325
+ id: "gpt-5.4-mini",
326
+ name: "GPT-5.4 Mini",
327
+ vision: true,
328
+ context: 4e5,
329
+ maxOutput: 128e3,
330
+ thinking: {
331
+ supported: true,
332
+ paramName: "reasoning_effort",
333
+ paramType: "level",
334
+ levels: ["none", "low", "medium", "high"],
335
+ defaultLevel: "medium"
336
+ }
337
+ },
338
+ {
339
+ id: "gpt-5.4-nano",
340
+ name: "GPT-5.4 Nano",
341
+ vision: true,
342
+ context: 4e5,
343
+ maxOutput: 128e3,
344
+ thinking: {
345
+ supported: true,
346
+ paramName: "reasoning_effort",
347
+ paramType: "level",
348
+ levels: ["none", "low", "medium", "high"],
349
+ defaultLevel: "low"
350
+ }
351
+ },
352
+ // GPT-5.2 family (reasoning-focused)
353
+ {
354
+ id: "gpt-5.2",
355
+ name: "GPT-5.2 Thinking",
356
+ vision: true,
357
+ context: 272e3,
358
+ maxOutput: 128e3,
359
+ thinking: {
360
+ supported: true,
361
+ paramName: "reasoning_effort",
362
+ paramType: "level",
363
+ levels: ["none", "low", "medium", "high", "xhigh"],
364
+ defaultLevel: "high"
365
+ }
366
+ },
367
+ {
368
+ id: "gpt-5.2-pro",
369
+ name: "GPT-5.2 Pro",
370
+ vision: true,
371
+ context: 272e3,
372
+ maxOutput: 128e3,
373
+ thinking: {
374
+ supported: true,
375
+ paramName: "reasoning_effort",
376
+ paramType: "level",
377
+ levels: ["none", "low", "medium", "high", "xhigh"],
378
+ defaultLevel: "high"
379
+ }
380
+ },
381
+ // o-series reasoning models
382
+ {
383
+ id: "o3",
384
+ name: "o3 (Reasoning)",
385
+ vision: true,
386
+ context: 2e5,
387
+ maxOutput: 1e5,
388
+ thinking: {
389
+ supported: true,
390
+ paramName: "reasoning_effort",
391
+ paramType: "level",
392
+ levels: ["low", "medium", "high"],
393
+ defaultLevel: "medium"
394
+ }
395
+ },
396
+ {
397
+ id: "o4-mini",
398
+ name: "o4-mini (Reasoning)",
399
+ vision: true,
400
+ context: 2e5,
401
+ maxOutput: 1e5,
402
+ thinking: {
403
+ supported: true,
404
+ paramName: "reasoning_effort",
405
+ paramType: "level",
406
+ levels: ["low", "medium", "high"],
407
+ defaultLevel: "medium"
408
+ }
409
+ },
410
+ // GPT-4.1 family
411
+ {
412
+ id: "gpt-4.1",
413
+ name: "GPT-4.1",
414
+ vision: true,
415
+ context: 1047576,
416
+ maxOutput: 32768
417
+ },
418
+ {
419
+ id: "gpt-4.1-mini",
420
+ name: "GPT-4.1 Mini",
421
+ vision: true,
422
+ context: 1047576,
423
+ maxOutput: 32768
424
+ },
425
+ {
426
+ id: "gpt-4.1-nano",
427
+ name: "GPT-4.1 Nano",
428
+ vision: true,
429
+ context: 1047576,
430
+ maxOutput: 32768
431
+ },
432
+ // Codex
433
+ {
434
+ id: "codex-mini-latest",
435
+ name: "Codex Mini",
436
+ vision: false,
437
+ context: 192e3,
438
+ maxOutput: 1e5,
439
+ thinking: {
440
+ supported: true,
441
+ paramName: "reasoning_effort",
442
+ paramType: "level",
443
+ levels: ["low", "medium", "high"],
444
+ defaultLevel: "high"
445
+ }
446
+ }
299
447
  ],
300
448
  apiBase: "https://api.openai.com/v1",
301
449
  keyPrefix: "sk-",
302
450
  keyPlaceholder: "sk-..."
303
451
  },
452
+ // ─── Anthropic ────────────────────────────────────────────────
304
453
  anthropic: {
305
454
  name: "Anthropic",
306
455
  models: [
307
- { id: "claude-opus-4-20250514", name: "Claude Opus 4", vision: true, context: 2e5 },
308
- { id: "claude-sonnet-4-20250514", name: "Claude Sonnet 4", vision: true, context: 2e5 },
309
- { id: "claude-haiku-4-20250514", name: "Claude Haiku 4", vision: true, context: 2e5 }
456
+ // Claude 4.6 (latest Feb 2026)
457
+ {
458
+ id: "claude-opus-4-6",
459
+ name: "Claude Opus 4.6",
460
+ vision: true,
461
+ context: 1e6,
462
+ maxOutput: 128e3,
463
+ thinking: {
464
+ supported: true,
465
+ paramName: "budget_tokens",
466
+ paramType: "budget",
467
+ defaultBudget: 1e4,
468
+ maxBudget: 128e3
469
+ }
470
+ },
471
+ {
472
+ id: "claude-sonnet-4-6",
473
+ name: "Claude Sonnet 4.6",
474
+ vision: true,
475
+ context: 1e6,
476
+ maxOutput: 64e3,
477
+ thinking: {
478
+ supported: true,
479
+ paramName: "budget_tokens",
480
+ paramType: "budget",
481
+ defaultBudget: 8e3,
482
+ maxBudget: 64e3
483
+ }
484
+ },
485
+ // Claude 4.5
486
+ {
487
+ id: "claude-haiku-4-5-20251001",
488
+ name: "Claude Haiku 4.5",
489
+ vision: true,
490
+ context: 2e5,
491
+ maxOutput: 64e3,
492
+ thinking: {
493
+ supported: true,
494
+ paramName: "budget_tokens",
495
+ paramType: "budget",
496
+ defaultBudget: 5e3,
497
+ maxBudget: 64e3
498
+ }
499
+ },
500
+ {
501
+ id: "claude-sonnet-4-5-20250929",
502
+ name: "Claude Sonnet 4.5",
503
+ vision: true,
504
+ context: 1e6,
505
+ maxOutput: 64e3,
506
+ thinking: {
507
+ supported: true,
508
+ paramName: "budget_tokens",
509
+ paramType: "budget",
510
+ defaultBudget: 8e3,
511
+ maxBudget: 64e3
512
+ }
513
+ },
514
+ {
515
+ id: "claude-opus-4-5-20251101",
516
+ name: "Claude Opus 4.5",
517
+ vision: true,
518
+ context: 2e5,
519
+ maxOutput: 64e3,
520
+ thinking: {
521
+ supported: true,
522
+ paramName: "budget_tokens",
523
+ paramType: "budget",
524
+ defaultBudget: 1e4,
525
+ maxBudget: 64e3
526
+ }
527
+ },
528
+ // Claude 4.0
529
+ {
530
+ id: "claude-sonnet-4-20250514",
531
+ name: "Claude Sonnet 4",
532
+ vision: true,
533
+ context: 2e5,
534
+ maxOutput: 64e3,
535
+ thinking: {
536
+ supported: true,
537
+ paramName: "budget_tokens",
538
+ paramType: "budget",
539
+ defaultBudget: 8e3,
540
+ maxBudget: 64e3
541
+ }
542
+ },
543
+ {
544
+ id: "claude-opus-4-20250514",
545
+ name: "Claude Opus 4",
546
+ vision: true,
547
+ context: 2e5,
548
+ maxOutput: 32e3,
549
+ thinking: {
550
+ supported: true,
551
+ paramName: "budget_tokens",
552
+ paramType: "budget",
553
+ defaultBudget: 1e4,
554
+ maxBudget: 32e3
555
+ }
556
+ }
310
557
  ],
311
558
  apiBase: "https://api.anthropic.com/v1",
312
559
  keyPrefix: "sk-ant-",
313
560
  keyPlaceholder: "sk-ant-..."
314
561
  },
562
+ // ─── Google Gemini ────────────────────────────────────────────
315
563
  google: {
316
564
  name: "Google Gemini",
317
565
  models: [
318
- { id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", vision: true, context: 1048576 },
319
- { id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", vision: true, context: 1048576 },
320
- { id: "gemini-2.0-flash", name: "Gemini 2.0 Flash", vision: true, context: 1048576 }
566
+ // Gemini 3.1 (latest Feb-Mar 2026)
567
+ {
568
+ id: "gemini-3.1-pro-preview",
569
+ name: "Gemini 3.1 Pro",
570
+ vision: true,
571
+ context: 1048576,
572
+ maxOutput: 65536,
573
+ thinking: {
574
+ supported: true,
575
+ paramName: "thinking_level",
576
+ paramType: "level",
577
+ levels: ["none", "low", "medium", "high"],
578
+ defaultLevel: "medium"
579
+ }
580
+ },
581
+ // Gemini 3.0
582
+ {
583
+ id: "gemini-3-flash-preview",
584
+ name: "Gemini 3 Flash",
585
+ vision: true,
586
+ context: 1048576,
587
+ maxOutput: 65536,
588
+ thinking: {
589
+ supported: true,
590
+ paramName: "thinking_level",
591
+ paramType: "level",
592
+ levels: ["none", "low", "medium", "high"],
593
+ defaultLevel: "low"
594
+ }
595
+ },
596
+ {
597
+ id: "gemini-3.1-flash-lite-preview",
598
+ name: "Gemini 3.1 Flash Lite",
599
+ vision: true,
600
+ context: 1048576,
601
+ maxOutput: 65536
602
+ },
603
+ // Gemini 2.5
604
+ {
605
+ id: "gemini-2.5-pro",
606
+ name: "Gemini 2.5 Pro",
607
+ vision: true,
608
+ context: 1048576,
609
+ maxOutput: 65536,
610
+ thinking: {
611
+ supported: true,
612
+ paramName: "thinking_level",
613
+ paramType: "level",
614
+ levels: ["none", "low", "medium", "high"],
615
+ defaultLevel: "medium"
616
+ }
617
+ },
618
+ {
619
+ id: "gemini-2.5-flash",
620
+ name: "Gemini 2.5 Flash",
621
+ vision: true,
622
+ context: 1048576,
623
+ maxOutput: 65536,
624
+ thinking: {
625
+ supported: true,
626
+ paramName: "thinking_level",
627
+ paramType: "level",
628
+ levels: ["none", "low", "medium", "high"],
629
+ defaultLevel: "low"
630
+ }
631
+ },
632
+ {
633
+ id: "gemini-2.5-flash-lite",
634
+ name: "Gemini 2.5 Flash Lite",
635
+ vision: true,
636
+ context: 1048576,
637
+ maxOutput: 65536
638
+ }
321
639
  ],
322
640
  apiBase: "https://generativelanguage.googleapis.com/v1beta",
323
641
  keyPrefix: "AI",
324
642
  keyPlaceholder: "AIza..."
325
643
  },
644
+ // ─── xAI (Grok) ──────────────────────────────────────────────
645
+ xai: {
646
+ name: "xAI (Grok)",
647
+ models: [
648
+ {
649
+ id: "grok-4.20-0309-reasoning",
650
+ name: "Grok 4.20 Reasoning",
651
+ vision: true,
652
+ context: 2e6,
653
+ maxOutput: 128e3,
654
+ thinking: {
655
+ supported: true,
656
+ paramName: "reasoning_effort",
657
+ paramType: "level",
658
+ levels: ["low", "medium", "high"],
659
+ defaultLevel: "medium"
660
+ }
661
+ },
662
+ {
663
+ id: "grok-4.20-0309-non-reasoning",
664
+ name: "Grok 4.20",
665
+ vision: true,
666
+ context: 2e6,
667
+ maxOutput: 128e3
668
+ },
669
+ {
670
+ id: "grok-4-1-fast-reasoning",
671
+ name: "Grok 4.1 Fast Reasoning",
672
+ vision: true,
673
+ context: 2e6,
674
+ maxOutput: 128e3,
675
+ thinking: {
676
+ supported: true,
677
+ paramName: "reasoning_effort",
678
+ paramType: "level",
679
+ levels: ["low", "medium", "high"],
680
+ defaultLevel: "low"
681
+ }
682
+ },
683
+ {
684
+ id: "grok-4-1-fast-non-reasoning",
685
+ name: "Grok 4.1 Fast",
686
+ vision: true,
687
+ context: 2e6,
688
+ maxOutput: 128e3
689
+ }
690
+ ],
691
+ apiBase: "https://api.x.ai/v1",
692
+ keyPrefix: "xai-",
693
+ keyPlaceholder: "xai-..."
694
+ },
695
+ // ─── DeepSeek ─────────────────────────────────────────────────
326
696
  deepseek: {
327
697
  name: "DeepSeek",
328
698
  models: [
329
- { id: "deepseek-chat", name: "DeepSeek V3", vision: false, context: 65536 },
330
- { id: "deepseek-reasoner", name: "DeepSeek R1", vision: false, context: 65536 }
699
+ {
700
+ id: "deepseek-chat",
701
+ name: "DeepSeek V3.2",
702
+ vision: false,
703
+ context: 128e3,
704
+ maxOutput: 8192
705
+ },
706
+ {
707
+ id: "deepseek-reasoner",
708
+ name: "DeepSeek R1",
709
+ vision: false,
710
+ context: 128e3,
711
+ maxOutput: 8192,
712
+ thinking: {
713
+ supported: true,
714
+ paramName: "reasoning_effort",
715
+ paramType: "level",
716
+ levels: ["low", "medium", "high"],
717
+ defaultLevel: "medium"
718
+ }
719
+ }
331
720
  ],
332
721
  apiBase: "https://api.deepseek.com/v1",
333
722
  keyPrefix: "sk-",
334
723
  keyPlaceholder: "sk-..."
335
724
  },
336
- groq: {
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
- },
725
+ // ─── Mistral ──────────────────────────────────────────────────
347
726
  mistral: {
348
727
  name: "Mistral",
349
728
  models: [
350
- { id: "mistral-large-latest", name: "Mistral Large", vision: false, context: 131072 },
351
- { id: "mistral-small-latest", name: "Mistral Small", vision: false, context: 131072 },
352
- { id: "codestral-latest", name: "Codestral", vision: false, context: 262144 }
729
+ {
730
+ id: "mistral-large-3-25-12",
731
+ name: "Mistral Large 3",
732
+ vision: true,
733
+ context: 131072,
734
+ maxOutput: 32768
735
+ },
736
+ {
737
+ id: "mistral-small-4-0-26-03",
738
+ name: "Mistral Small 4",
739
+ vision: true,
740
+ context: 131072,
741
+ maxOutput: 32768
742
+ },
743
+ {
744
+ id: "mistral-small-3-2-25-06",
745
+ name: "Mistral Small 3.2",
746
+ vision: true,
747
+ context: 131072,
748
+ maxOutput: 32768
749
+ },
750
+ {
751
+ id: "codestral-2508",
752
+ name: "Codestral",
753
+ vision: false,
754
+ context: 262144,
755
+ maxOutput: 32768
756
+ },
757
+ {
758
+ id: "devstral-2-25-12",
759
+ name: "Devstral 2",
760
+ vision: false,
761
+ context: 131072,
762
+ maxOutput: 32768
763
+ },
764
+ {
765
+ id: "magistral-medium-1-2-25-09",
766
+ name: "Magistral Medium (Reasoning)",
767
+ vision: false,
768
+ context: 131072,
769
+ maxOutput: 32768,
770
+ thinking: {
771
+ supported: true,
772
+ paramName: "reasoning_effort",
773
+ paramType: "level",
774
+ levels: ["low", "medium", "high"],
775
+ defaultLevel: "medium"
776
+ }
777
+ },
778
+ {
779
+ id: "magistral-small-1-2-25-09",
780
+ name: "Magistral Small (Reasoning)",
781
+ vision: false,
782
+ context: 131072,
783
+ maxOutput: 32768,
784
+ thinking: {
785
+ supported: true,
786
+ paramName: "reasoning_effort",
787
+ paramType: "level",
788
+ levels: ["low", "medium", "high"],
789
+ defaultLevel: "medium"
790
+ }
791
+ }
353
792
  ],
354
793
  apiBase: "https://api.mistral.ai/v1",
355
794
  keyPrefix: "",
356
795
  keyPlaceholder: "Enter API key..."
357
796
  },
358
- xai: {
359
- name: "xAI (Grok)",
797
+ // ─── Groq ─────────────────────────────────────────────────────
798
+ groq: {
799
+ name: "Groq",
360
800
  models: [
361
- { id: "grok-3", name: "Grok 3", vision: true, context: 131072 },
362
- { id: "grok-3-mini", name: "Grok 3 Mini", vision: true, context: 131072 }
801
+ {
802
+ id: "meta-llama/llama-4-scout-17b-16e-instruct",
803
+ name: "Llama 4 Scout 17B",
804
+ vision: true,
805
+ context: 131072,
806
+ maxOutput: 8192
807
+ },
808
+ {
809
+ id: "llama-3.3-70b-versatile",
810
+ name: "Llama 3.3 70B",
811
+ vision: false,
812
+ context: 131072,
813
+ maxOutput: 32768
814
+ },
815
+ {
816
+ id: "llama-3.1-8b-instant",
817
+ name: "Llama 3.1 8B Instant",
818
+ vision: false,
819
+ context: 131072,
820
+ maxOutput: 8192
821
+ },
822
+ {
823
+ id: "qwen/qwen3-32b",
824
+ name: "Qwen 3 32B",
825
+ vision: false,
826
+ context: 131072,
827
+ maxOutput: 8192
828
+ }
363
829
  ],
364
- apiBase: "https://api.x.ai/v1",
365
- keyPrefix: "xai-",
366
- keyPlaceholder: "xai-..."
830
+ apiBase: "https://api.groq.com/openai/v1",
831
+ keyPrefix: "gsk_",
832
+ keyPlaceholder: "gsk_..."
367
833
  },
834
+ // ─── Ollama (Local) ───────────────────────────────────────────
368
835
  ollama: {
369
836
  name: "Ollama (Local)",
370
837
  models: [],
@@ -373,6 +840,7 @@ var MODEL_REGISTRY = {
373
840
  keyPlaceholder: "not required",
374
841
  local: true
375
842
  },
843
+ // ─── OpenRouter (200+ models) ─────────────────────────────────
376
844
  openrouter: {
377
845
  name: "OpenRouter",
378
846
  models: [],
@@ -492,8 +960,8 @@ async function chatOpenAICompatible(provider, model, apiKey, messages, context,
492
960
  contextParts.consoleLogs = context.consoleLogs.map((l) => `[${l.level}] ${l.args.join(" ")}`).join("\n");
493
961
  }
494
962
  const enrichedContent = buildUserMessage(msg.content, contextParts);
495
- const modelInfo = providerConfig.models.find((m) => m.id === model);
496
- if (context.screenshot && modelInfo?.vision) {
963
+ const modelInfo2 = providerConfig.models.find((m) => m.id === model);
964
+ if (context.screenshot && modelInfo2?.vision) {
497
965
  apiMessages.push({
498
966
  role: "user",
499
967
  content: [
@@ -520,6 +988,11 @@ async function chatOpenAICompatible(provider, model, apiKey, messages, context,
520
988
  stream: true,
521
989
  max_tokens: 4096
522
990
  };
991
+ const modelInfo = providerConfig.models.find((m) => m.id === model);
992
+ if (modelInfo?.thinking?.supported && modelInfo.thinking.paramType === "level") {
993
+ body.reasoning_effort = modelInfo.thinking.defaultLevel || "medium";
994
+ body.max_tokens = Math.min(modelInfo.maxOutput, 16384);
995
+ }
523
996
  try {
524
997
  const headers = {
525
998
  "Content-Type": "application/json"
@@ -627,13 +1100,22 @@ async function chatAnthropic(model, apiKey, messages, context, onChunk, onDone,
627
1100
  });
628
1101
  }
629
1102
  }
1103
+ const providerConfig = MODEL_REGISTRY.anthropic;
1104
+ const modelInfo = providerConfig?.models.find((m) => m.id === model);
1105
+ const thinkingBudget = modelInfo?.thinking?.defaultBudget || 0;
630
1106
  const body = {
631
1107
  model,
632
- max_tokens: 4096,
1108
+ max_tokens: thinkingBudget > 0 ? Math.max(thinkingBudget + 4096, 16384) : 4096,
633
1109
  system: SYSTEM_PROMPT,
634
1110
  messages: apiMessages,
635
1111
  stream: true
636
1112
  };
1113
+ if (thinkingBudget > 0) {
1114
+ body.thinking = {
1115
+ type: "enabled",
1116
+ budget_tokens: thinkingBudget
1117
+ };
1118
+ }
637
1119
  try {
638
1120
  const response = await fetch(url, {
639
1121
  method: "POST",
@@ -728,14 +1210,21 @@ async function chatGoogle(model, apiKey, messages, context, onChunk, onDone, onE
728
1210
  });
729
1211
  }
730
1212
  }
1213
+ const providerConfig = MODEL_REGISTRY.google;
1214
+ const modelInfo = providerConfig?.models.find((m) => m.id === model);
1215
+ const thinkingLevel = modelInfo?.thinking?.defaultLevel;
1216
+ const generationConfig = {
1217
+ maxOutputTokens: 8192
1218
+ };
1219
+ if (thinkingLevel && thinkingLevel !== "none") {
1220
+ generationConfig.thinking_level = thinkingLevel.toUpperCase();
1221
+ }
731
1222
  const body = {
732
1223
  system_instruction: {
733
1224
  parts: [{ text: SYSTEM_PROMPT }]
734
1225
  },
735
1226
  contents,
736
- generationConfig: {
737
- maxOutputTokens: 4096
738
- }
1227
+ generationConfig
739
1228
  };
740
1229
  try {
741
1230
  const response = await fetch(url, {
@@ -840,7 +1329,7 @@ function createOpenMagicServer(proxyPort, roots) {
840
1329
  "Content-Type": "application/json",
841
1330
  "Access-Control-Allow-Origin": "*"
842
1331
  });
843
- res.end(JSON.stringify({ status: "ok", version: "0.1.0" }));
1332
+ res.end(JSON.stringify({ status: "ok", version: "0.3.0" }));
844
1333
  return;
845
1334
  }
846
1335
  res.writeHead(404);
@@ -893,7 +1382,7 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
893
1382
  id: msg.id,
894
1383
  type: "handshake.ok",
895
1384
  payload: {
896
- version: "0.1.0",
1385
+ version: "0.3.0",
897
1386
  roots,
898
1387
  config: {
899
1388
  provider: config.provider,
@@ -1044,6 +1533,8 @@ function serveToolbarBundle(res) {
1044
1533
 
1045
1534
  // src/detect.ts
1046
1535
  import { createConnection } from "net";
1536
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1537
+ import { join as join4 } from "path";
1047
1538
  var COMMON_DEV_PORTS = [
1048
1539
  3e3,
1049
1540
  // React (CRA), Next.js, Express
@@ -1063,8 +1554,16 @@ var COMMON_DEV_PORTS = [
1063
1554
  // Common alternate
1064
1555
  4e3,
1065
1556
  // Phoenix, generic
1066
- 1234
1557
+ 1234,
1067
1558
  // Parcel
1559
+ 4321,
1560
+ // Astro
1561
+ 3333,
1562
+ // Remix
1563
+ 8081,
1564
+ // Metro (React Native)
1565
+ 9e3
1566
+ // generic
1068
1567
  ];
1069
1568
  function checkPort(port, host = "127.0.0.1") {
1070
1569
  return new Promise((resolve3) => {
@@ -1108,9 +1607,98 @@ async function findAvailablePort(startPort) {
1108
1607
  }
1109
1608
  return port;
1110
1609
  }
1610
+ var FRAMEWORK_PATTERNS = [
1611
+ { match: /\bnext\b/, framework: "Next.js", defaultPort: 3e3 },
1612
+ { match: /\bvite\b/, framework: "Vite", defaultPort: 5173 },
1613
+ { match: /\bnuxt\b/, framework: "Nuxt", defaultPort: 3e3 },
1614
+ { match: /\bng\s+serve\b/, framework: "Angular", defaultPort: 4200 },
1615
+ { match: /\bvue-cli-service\s+serve\b/, framework: "Vue CLI", defaultPort: 8080 },
1616
+ { match: /\bsvelte-kit\b/, framework: "SvelteKit", defaultPort: 5173 },
1617
+ { match: /\bastro\b/, framework: "Astro", defaultPort: 4321 },
1618
+ { match: /\bremix\b/, framework: "Remix", defaultPort: 3e3 },
1619
+ { match: /\breact-scripts\s+start\b/, framework: "Create React App", defaultPort: 3e3 },
1620
+ { match: /\bparcel\b/, framework: "Parcel", defaultPort: 1234 },
1621
+ { match: /\bwebpack\s+serve\b|webpack-dev-server/, framework: "Webpack", defaultPort: 8080 },
1622
+ { match: /\bgatsby\b/, framework: "Gatsby", defaultPort: 8e3 },
1623
+ { match: /\bturborepo\b|\bturbo\b.*dev/, framework: "Turborepo", defaultPort: 3e3 },
1624
+ { match: /\bexpo\b/, framework: "Expo", defaultPort: 8081 },
1625
+ { match: /\bnodemon\b|\bts-node\b|\bnode\b/, framework: "Node.js", defaultPort: 3e3 },
1626
+ { match: /\bflask\b/, framework: "Flask", defaultPort: 5e3 },
1627
+ { match: /\bdjango\b|manage\.py\s+runserver/, framework: "Django", defaultPort: 8e3 },
1628
+ { match: /\brails\b/, framework: "Rails", defaultPort: 3e3 },
1629
+ { match: /\bphp\s+.*serve\b|artisan\s+serve/, framework: "PHP/Laravel", defaultPort: 8e3 }
1630
+ ];
1631
+ var DEV_SCRIPT_NAMES = ["dev", "start", "serve", "develop", "dev:start", "start:dev"];
1632
+ function detectDevScripts(cwd = process.cwd()) {
1633
+ const pkgPath = join4(cwd, "package.json");
1634
+ if (!existsSync4(pkgPath)) return [];
1635
+ let pkg;
1636
+ try {
1637
+ pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1638
+ } catch {
1639
+ return [];
1640
+ }
1641
+ if (!pkg.scripts) return [];
1642
+ const scripts = [];
1643
+ for (const name of DEV_SCRIPT_NAMES) {
1644
+ const command = pkg.scripts[name];
1645
+ if (!command) continue;
1646
+ let framework = "Unknown";
1647
+ let defaultPort = 3e3;
1648
+ for (const pattern of FRAMEWORK_PATTERNS) {
1649
+ if (pattern.match.test(command)) {
1650
+ framework = pattern.framework;
1651
+ defaultPort = pattern.defaultPort;
1652
+ break;
1653
+ }
1654
+ }
1655
+ const portMatch = command.match(/(?:--port|-p)\s+(\d+)/);
1656
+ if (portMatch) {
1657
+ defaultPort = parseInt(portMatch[1], 10);
1658
+ }
1659
+ scripts.push({ name, command, framework, defaultPort });
1660
+ }
1661
+ return scripts;
1662
+ }
1663
+ function getProjectName(cwd = process.cwd()) {
1664
+ const pkgPath = join4(cwd, "package.json");
1665
+ if (!existsSync4(pkgPath)) return "this project";
1666
+ try {
1667
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1668
+ return pkg.name || "this project";
1669
+ } catch {
1670
+ return "this project";
1671
+ }
1672
+ }
1111
1673
 
1112
1674
  // src/cli.ts
1113
- var VERSION = "0.1.0";
1675
+ var VERSION = "0.3.0";
1676
+ function ask(question) {
1677
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1678
+ return new Promise((resolve3) => {
1679
+ rl.question(question, (answer) => {
1680
+ rl.close();
1681
+ resolve3(answer.trim());
1682
+ });
1683
+ });
1684
+ }
1685
+ function waitForPort(port, timeoutMs = 3e4) {
1686
+ const start = Date.now();
1687
+ return new Promise((resolve3) => {
1688
+ const check = async () => {
1689
+ if (await isPortOpen(port)) {
1690
+ resolve3(true);
1691
+ return;
1692
+ }
1693
+ if (Date.now() - start > timeoutMs) {
1694
+ resolve3(false);
1695
+ return;
1696
+ }
1697
+ setTimeout(check, 500);
1698
+ };
1699
+ check();
1700
+ });
1701
+ }
1114
1702
  var program = new Command();
1115
1703
  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
1704
  "-l, --listen <port>",
@@ -1131,43 +1719,35 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
1131
1719
  targetPort = parseInt(opts.port, 10);
1132
1720
  const isRunning = await isPortOpen(targetPort);
1133
1721
  if (!isRunning) {
1134
- console.log(
1135
- chalk.yellow(
1136
- ` \u26A0 No server found at ${targetHost}:${targetPort}`
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);
1722
+ const started = await offerToStartDevServer(targetPort);
1723
+ if (!started) {
1724
+ process.exit(1);
1725
+ }
1146
1726
  }
1147
1727
  } else {
1148
1728
  console.log(chalk.dim(" Scanning for dev server..."));
1149
1729
  const detected = await detectDevServer();
1150
- if (!detected) {
1151
- console.log(
1152
- chalk.yellow(
1153
- " \u26A0 No dev server detected on common ports (3000, 5173, 8080, etc.)"
1154
- )
1155
- );
1156
- console.log("");
1157
- console.log(
1158
- chalk.white(" Specify the port manually:")
1159
- );
1160
- console.log(
1161
- chalk.cyan(" npx openmagic --port 3000")
1162
- );
1163
- console.log("");
1164
- process.exit(1);
1730
+ if (detected) {
1731
+ targetPort = detected.port;
1732
+ targetHost = detected.host;
1733
+ } else {
1734
+ const started = await offerToStartDevServer();
1735
+ if (!started) {
1736
+ process.exit(1);
1737
+ }
1738
+ const redetected = await detectDevServer();
1739
+ if (!redetected) {
1740
+ console.log(chalk.red(" \u2717 Could not detect the dev server after starting."));
1741
+ console.log(chalk.dim(" Try specifying the port: npx openmagic --port 3000"));
1742
+ console.log("");
1743
+ process.exit(1);
1744
+ }
1745
+ targetPort = redetected.port;
1746
+ targetHost = redetected.host;
1165
1747
  }
1166
- targetPort = detected.port;
1167
- targetHost = detected.host;
1168
1748
  }
1169
1749
  console.log(
1170
- chalk.green(` \u2713 Dev server found at ${targetHost}:${targetPort}`)
1750
+ chalk.green(` \u2713 Dev server running at ${targetHost}:${targetPort}`)
1171
1751
  );
1172
1752
  const roots = (opts.root || [process.cwd()]).map(
1173
1753
  (r) => resolve2(r)
@@ -1218,5 +1798,149 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
1218
1798
  process.on("SIGINT", shutdown);
1219
1799
  process.on("SIGTERM", shutdown);
1220
1800
  });
1801
+ async function offerToStartDevServer(expectedPort) {
1802
+ const projectName = getProjectName();
1803
+ const scripts = detectDevScripts();
1804
+ if (scripts.length === 0) {
1805
+ console.log(
1806
+ chalk.yellow(" \u26A0 No dev server detected and no dev scripts found in package.json")
1807
+ );
1808
+ console.log("");
1809
+ console.log(chalk.white(" Start your dev server manually, then run:"));
1810
+ console.log(chalk.cyan(" npx openmagic --port <your-port>"));
1811
+ console.log("");
1812
+ return false;
1813
+ }
1814
+ let chosen = scripts[0];
1815
+ if (scripts.length === 1) {
1816
+ console.log(
1817
+ chalk.yellow(" \u26A0 No dev server detected.")
1818
+ );
1819
+ console.log("");
1820
+ console.log(
1821
+ chalk.white(` Found `) + chalk.cyan(`npm run ${chosen.name}`) + chalk.white(` in ${projectName}`) + chalk.dim(` (${chosen.framework})`)
1822
+ );
1823
+ console.log(chalk.dim(` \u2192 ${chosen.command}`));
1824
+ console.log("");
1825
+ const answer = await ask(
1826
+ chalk.white(` Start it now? `) + chalk.dim("(Y/n) ")
1827
+ );
1828
+ if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
1829
+ console.log("");
1830
+ console.log(chalk.dim(" Start your dev server first, then run openmagic again."));
1831
+ console.log("");
1832
+ return false;
1833
+ }
1834
+ } else {
1835
+ console.log(
1836
+ chalk.yellow(" \u26A0 No dev server detected.")
1837
+ );
1838
+ console.log("");
1839
+ console.log(
1840
+ chalk.white(` Found ${scripts.length} dev scripts in ${projectName}:`)
1841
+ );
1842
+ console.log("");
1843
+ scripts.forEach((s, i) => {
1844
+ console.log(
1845
+ chalk.cyan(` ${i + 1}) `) + chalk.white(`npm run ${s.name}`) + chalk.dim(` \u2014 ${s.framework} (port ${s.defaultPort})`)
1846
+ );
1847
+ console.log(chalk.dim(` ${s.command}`));
1848
+ });
1849
+ console.log("");
1850
+ const answer = await ask(
1851
+ chalk.white(` Which one to start? `) + chalk.dim(`(1-${scripts.length}, or n to cancel) `)
1852
+ );
1853
+ if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no" || answer === "") {
1854
+ console.log("");
1855
+ console.log(chalk.dim(" Start your dev server first, then run openmagic again."));
1856
+ console.log("");
1857
+ return false;
1858
+ }
1859
+ const idx = parseInt(answer, 10) - 1;
1860
+ if (idx < 0 || idx >= scripts.length || isNaN(idx)) {
1861
+ chosen = scripts[0];
1862
+ } else {
1863
+ chosen = scripts[idx];
1864
+ }
1865
+ }
1866
+ const port = expectedPort || chosen.defaultPort;
1867
+ console.log("");
1868
+ console.log(
1869
+ chalk.dim(` Starting `) + chalk.cyan(`npm run ${chosen.name}`) + chalk.dim("...")
1870
+ );
1871
+ const child = spawn("npm", ["run", chosen.name], {
1872
+ cwd: process.cwd(),
1873
+ stdio: ["ignore", "pipe", "pipe"],
1874
+ detached: false,
1875
+ shell: true,
1876
+ env: {
1877
+ ...process.env,
1878
+ PORT: String(port),
1879
+ // CRA, Express
1880
+ BROWSER: "none",
1881
+ // Prevent CRA from opening browser
1882
+ BROWSER_NONE: "true"
1883
+ // Some frameworks
1884
+ }
1885
+ });
1886
+ child.stdout?.on("data", (data) => {
1887
+ const lines = data.toString().trim().split("\n");
1888
+ for (const line of lines) {
1889
+ if (line.trim()) {
1890
+ process.stdout.write(chalk.dim(` \u2502 ${line}
1891
+ `));
1892
+ }
1893
+ }
1894
+ });
1895
+ child.stderr?.on("data", (data) => {
1896
+ const lines = data.toString().trim().split("\n");
1897
+ for (const line of lines) {
1898
+ if (line.trim()) {
1899
+ process.stdout.write(chalk.dim(` \u2502 ${line}
1900
+ `));
1901
+ }
1902
+ }
1903
+ });
1904
+ child.on("error", (err) => {
1905
+ console.log(chalk.red(` \u2717 Failed to start: ${err.message}`));
1906
+ });
1907
+ child.on("exit", (code) => {
1908
+ if (code !== null && code !== 0) {
1909
+ console.log(chalk.red(` \u2717 Dev server exited with code ${code}`));
1910
+ }
1911
+ });
1912
+ const cleanup = () => {
1913
+ try {
1914
+ child.kill("SIGTERM");
1915
+ } catch {
1916
+ }
1917
+ };
1918
+ process.on("exit", cleanup);
1919
+ process.on("SIGINT", cleanup);
1920
+ process.on("SIGTERM", cleanup);
1921
+ console.log(
1922
+ chalk.dim(` Waiting for port ${port}...`)
1923
+ );
1924
+ const isUp = await waitForPort(port, 3e4);
1925
+ if (!isUp) {
1926
+ console.log(
1927
+ chalk.yellow(` \u26A0 Port ${port} didn't open after 30s.`)
1928
+ );
1929
+ console.log(
1930
+ chalk.dim(` The server might use a different port. Check the output above.`)
1931
+ );
1932
+ console.log("");
1933
+ const detected = await detectDevServer();
1934
+ if (detected) {
1935
+ console.log(
1936
+ chalk.green(` \u2713 Found server on port ${detected.port} instead.`)
1937
+ );
1938
+ return true;
1939
+ }
1940
+ return false;
1941
+ }
1942
+ console.log("");
1943
+ return true;
1944
+ }
1221
1945
  program.parse();
1222
1946
  //# sourceMappingURL=cli.js.map