formalconf 2.0.1 → 2.0.2

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.
Files changed (2) hide show
  1. package/dist/formalconf.js +934 -869
  2. package/package.json +1 -1
@@ -1,78 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  // src/cli/formalconf.tsx
3
- import { useState as useState5, useEffect as useEffect4, useMemo as useMemo2, useCallback, useRef } from "react";
4
- import { render, Box as Box12, Text as Text11, useApp, useInput as useInput5 } from "ink";
5
- import { Spinner } from "@inkjs/ui";
6
-
7
- // src/components/ui/VimSelect.tsx
8
- import { useState } from "react";
9
- import { Box, Text, useInput } from "ink";
10
-
11
- // src/lib/theme.ts
12
- var colors = {
13
- primary: "#5eead4",
14
- primaryDim: "#2dd4bf",
15
- accent: "#06b6d4",
16
- success: "#22c55e",
17
- error: "#ef4444",
18
- warning: "#f59e0b",
19
- info: "#3b82f6",
20
- text: "white",
21
- textDim: "gray",
22
- border: "#374151",
23
- borderLight: "#4b5563"
24
- };
25
- var borderStyles = {
26
- panel: "round",
27
- header: "round",
28
- footer: "single"
29
- };
30
-
31
- // src/components/ui/VimSelect.tsx
32
- import { jsxDEV } from "react/jsx-dev-runtime";
33
- function VimSelect({ options, onChange, isDisabled = false }) {
34
- const [index, setIndex] = useState(0);
35
- useInput((input, key) => {
36
- if (isDisabled)
37
- return;
38
- if (input === "j" || key.downArrow) {
39
- setIndex((i) => i < options.length - 1 ? i + 1 : i);
40
- }
41
- if (input === "k" || key.upArrow) {
42
- setIndex((i) => i > 0 ? i - 1 : i);
43
- }
44
- if (input === "l" || key.return) {
45
- onChange(options[index].value);
46
- }
47
- });
48
- return /* @__PURE__ */ jsxDEV(Box, {
49
- flexDirection: "column",
50
- children: options.map((opt, i) => /* @__PURE__ */ jsxDEV(Box, {
51
- children: /* @__PURE__ */ jsxDEV(Text, {
52
- color: i === index ? colors.primary : undefined,
53
- children: [
54
- i === index ? "❯" : " ",
55
- " ",
56
- opt.label
57
- ]
58
- }, undefined, true, undefined, this)
59
- }, opt.value, false, undefined, this))
60
- }, undefined, false, undefined, this);
61
- }
62
-
63
- // src/cli/formalconf.tsx
64
- import { readdirSync as readdirSync5, existsSync as existsSync6 } from "fs";
65
- import { join as join5 } from "path";
3
+ import { useState as useState9, useEffect as useEffect6 } from "react";
4
+ import { render, useApp as useApp2, useInput as useInput9 } from "ink";
5
+ import { Spinner as Spinner2 } from "@inkjs/ui";
66
6
 
67
7
  // src/components/layout/Layout.tsx
68
- import { Box as Box6 } from "ink";
8
+ import { Box as Box5 } from "ink";
69
9
 
70
10
  // src/hooks/useTerminalSize.ts
71
11
  import { useStdout } from "ink";
72
- import { useState as useState2, useEffect } from "react";
12
+ import { useState, useEffect } from "react";
73
13
  function useTerminalSize() {
74
14
  const { stdout } = useStdout();
75
- const [size, setSize] = useState2({
15
+ const [size, setSize] = useState({
76
16
  columns: stdout.columns || 80,
77
17
  rows: stdout.rows || 24
78
18
  });
@@ -92,10 +32,10 @@ function useTerminalSize() {
92
32
  }
93
33
 
94
34
  // src/components/Header.tsx
95
- import { Box as Box3, Text as Text3 } from "ink";
35
+ import { Box as Box2, Text as Text2 } from "ink";
96
36
 
97
37
  // src/hooks/useSystemStatus.ts
98
- import { useState as useState3, useEffect as useEffect2 } from "react";
38
+ import { useState as useState2, useEffect as useEffect2 } from "react";
99
39
  import { existsSync, readlinkSync, readdirSync, lstatSync } from "fs";
100
40
 
101
41
  // src/lib/paths.ts
@@ -358,7 +298,7 @@ async function ensureConfigDir() {
358
298
  // src/hooks/useSystemStatus.ts
359
299
  import { basename, dirname as dirname2, join as join2 } from "path";
360
300
  function useSystemStatus() {
361
- const [status, setStatus] = useState3({
301
+ const [status, setStatus] = useState2({
362
302
  currentTheme: null,
363
303
  configsLinked: false,
364
304
  loading: true
@@ -395,8 +335,30 @@ function useSystemStatus() {
395
335
  }
396
336
 
397
337
  // src/components/ui/StatusIndicator.tsx
398
- import { Box as Box2, Text as Text2 } from "ink";
399
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
338
+ import { Box, Text } from "ink";
339
+
340
+ // src/lib/theme.ts
341
+ var colors = {
342
+ primary: "#5eead4",
343
+ primaryDim: "#2dd4bf",
344
+ accent: "#06b6d4",
345
+ success: "#22c55e",
346
+ error: "#ef4444",
347
+ warning: "#f59e0b",
348
+ info: "#3b82f6",
349
+ text: "white",
350
+ textDim: "gray",
351
+ border: "#374151",
352
+ borderLight: "#4b5563"
353
+ };
354
+ var borderStyles = {
355
+ panel: "round",
356
+ header: "round",
357
+ footer: "single"
358
+ };
359
+
360
+ // src/components/ui/StatusIndicator.tsx
361
+ import { jsxDEV } from "react/jsx-dev-runtime";
400
362
  function StatusIndicator({
401
363
  label,
402
364
  value,
@@ -414,17 +376,17 @@ function StatusIndicator({
414
376
  error: "●",
415
377
  neutral: "○"
416
378
  };
417
- return /* @__PURE__ */ jsxDEV2(Box2, {
379
+ return /* @__PURE__ */ jsxDEV(Box, {
418
380
  gap: 1,
419
381
  children: [
420
- /* @__PURE__ */ jsxDEV2(Text2, {
382
+ /* @__PURE__ */ jsxDEV(Text, {
421
383
  dimColor: true,
422
384
  children: [
423
385
  label,
424
386
  ":"
425
387
  ]
426
388
  }, undefined, true, undefined, this),
427
- /* @__PURE__ */ jsxDEV2(Text2, {
389
+ /* @__PURE__ */ jsxDEV(Text, {
428
390
  color: statusColors[status],
429
391
  children: [
430
392
  icon[status],
@@ -438,7 +400,7 @@ function StatusIndicator({
438
400
  // package.json
439
401
  var package_default = {
440
402
  name: "formalconf",
441
- version: "2.0.1",
403
+ version: "2.0.2",
442
404
  description: "Dotfiles management TUI for macOS - config management, package sync, and theme switching",
443
405
  type: "module",
444
406
  main: "./dist/formalconf.js",
@@ -491,11 +453,11 @@ var package_default = {
491
453
  };
492
454
 
493
455
  // src/components/Header.tsx
494
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
456
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
495
457
  function Header() {
496
458
  const { columns } = useTerminalSize();
497
459
  const { currentTheme, configsLinked, loading } = useSystemStatus();
498
- return /* @__PURE__ */ jsxDEV3(Box3, {
460
+ return /* @__PURE__ */ jsxDEV2(Box2, {
499
461
  flexDirection: "column",
500
462
  width: columns - 2,
501
463
  borderStyle: borderStyles.header,
@@ -503,24 +465,24 @@ function Header() {
503
465
  paddingX: 2,
504
466
  marginBottom: 1,
505
467
  children: [
506
- /* @__PURE__ */ jsxDEV3(Box3, {
468
+ /* @__PURE__ */ jsxDEV2(Box2, {
507
469
  justifyContent: "space-between",
508
470
  width: "100%",
509
471
  children: [
510
- /* @__PURE__ */ jsxDEV3(Box3, {
472
+ /* @__PURE__ */ jsxDEV2(Box2, {
511
473
  children: [
512
- /* @__PURE__ */ jsxDEV3(Text3, {
474
+ /* @__PURE__ */ jsxDEV2(Text2, {
513
475
  bold: true,
514
476
  color: colors.primary,
515
477
  children: "FormalConf"
516
478
  }, undefined, false, undefined, this),
517
- /* @__PURE__ */ jsxDEV3(Text3, {
479
+ /* @__PURE__ */ jsxDEV2(Text2, {
518
480
  dimColor: true,
519
481
  children: " - Dotfiles Manager"
520
482
  }, undefined, false, undefined, this)
521
483
  ]
522
484
  }, undefined, true, undefined, this),
523
- /* @__PURE__ */ jsxDEV3(Text3, {
485
+ /* @__PURE__ */ jsxDEV2(Text2, {
524
486
  dimColor: true,
525
487
  children: [
526
488
  "v",
@@ -529,16 +491,16 @@ function Header() {
529
491
  }, undefined, true, undefined, this)
530
492
  ]
531
493
  }, undefined, true, undefined, this),
532
- !loading && /* @__PURE__ */ jsxDEV3(Box3, {
494
+ !loading && /* @__PURE__ */ jsxDEV2(Box2, {
533
495
  marginTop: 1,
534
496
  gap: 4,
535
497
  children: [
536
- /* @__PURE__ */ jsxDEV3(StatusIndicator, {
498
+ /* @__PURE__ */ jsxDEV2(StatusIndicator, {
537
499
  label: "Theme",
538
500
  value: currentTheme,
539
501
  status: currentTheme ? "success" : "neutral"
540
502
  }, undefined, false, undefined, this),
541
- /* @__PURE__ */ jsxDEV3(StatusIndicator, {
503
+ /* @__PURE__ */ jsxDEV2(StatusIndicator, {
542
504
  label: "Configs",
543
505
  value: configsLinked ? "Linked" : "Not linked",
544
506
  status: configsLinked ? "success" : "warning"
@@ -550,8 +512,8 @@ function Header() {
550
512
  }
551
513
 
552
514
  // src/components/layout/Footer.tsx
553
- import { Box as Box4, Text as Text4 } from "ink";
554
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
515
+ import { Box as Box3, Text as Text3 } from "ink";
516
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
555
517
  var defaultShortcuts = [
556
518
  { key: "↑↓/jk", label: "Navigate" },
557
519
  { key: "Enter/l", label: "Select" },
@@ -560,7 +522,7 @@ var defaultShortcuts = [
560
522
  ];
561
523
  function Footer({ shortcuts = defaultShortcuts }) {
562
524
  const { columns } = useTerminalSize();
563
- return /* @__PURE__ */ jsxDEV4(Box4, {
525
+ return /* @__PURE__ */ jsxDEV3(Box3, {
564
526
  width: columns - 2,
565
527
  borderStyle: borderStyles.footer,
566
528
  borderColor: colors.border,
@@ -568,15 +530,15 @@ function Footer({ shortcuts = defaultShortcuts }) {
568
530
  marginTop: 1,
569
531
  justifyContent: "center",
570
532
  gap: 2,
571
- children: shortcuts.map((shortcut, index) => /* @__PURE__ */ jsxDEV4(Box4, {
533
+ children: shortcuts.map((shortcut, index) => /* @__PURE__ */ jsxDEV3(Box3, {
572
534
  gap: 1,
573
535
  children: [
574
- /* @__PURE__ */ jsxDEV4(Text4, {
536
+ /* @__PURE__ */ jsxDEV3(Text3, {
575
537
  color: colors.primary,
576
538
  bold: true,
577
539
  children: shortcut.key
578
540
  }, undefined, false, undefined, this),
579
- /* @__PURE__ */ jsxDEV4(Text4, {
541
+ /* @__PURE__ */ jsxDEV3(Text3, {
580
542
  dimColor: true,
581
543
  children: shortcut.label
582
544
  }, undefined, false, undefined, this)
@@ -586,14 +548,14 @@ function Footer({ shortcuts = defaultShortcuts }) {
586
548
  }
587
549
 
588
550
  // src/components/layout/Breadcrumb.tsx
589
- import React2 from "react";
590
- import { Box as Box5, Text as Text5 } from "ink";
591
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
551
+ import React from "react";
552
+ import { Box as Box4, Text as Text4 } from "ink";
553
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
592
554
  function Breadcrumb({ path }) {
593
- return /* @__PURE__ */ jsxDEV5(Box5, {
594
- children: path.map((segment, index) => /* @__PURE__ */ jsxDEV5(React2.Fragment, {
555
+ return /* @__PURE__ */ jsxDEV4(Box4, {
556
+ children: path.map((segment, index) => /* @__PURE__ */ jsxDEV4(React.Fragment, {
595
557
  children: [
596
- index > 0 && /* @__PURE__ */ jsxDEV5(Text5, {
558
+ index > 0 && /* @__PURE__ */ jsxDEV4(Text4, {
597
559
  color: colors.textDim,
598
560
  children: [
599
561
  " ",
@@ -601,7 +563,7 @@ function Breadcrumb({ path }) {
601
563
  " "
602
564
  ]
603
565
  }, undefined, true, undefined, this),
604
- /* @__PURE__ */ jsxDEV5(Text5, {
566
+ /* @__PURE__ */ jsxDEV4(Text4, {
605
567
  color: index === path.length - 1 ? colors.primary : colors.textDim,
606
568
  bold: index === path.length - 1,
607
569
  children: segment
@@ -612,39 +574,39 @@ function Breadcrumb({ path }) {
612
574
  }
613
575
 
614
576
  // src/components/layout/Layout.tsx
615
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
577
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
616
578
  function Layout({
617
579
  children,
618
580
  breadcrumb = ["Main"],
619
581
  showFooter = true
620
582
  }) {
621
583
  const { columns } = useTerminalSize();
622
- return /* @__PURE__ */ jsxDEV6(Box6, {
584
+ return /* @__PURE__ */ jsxDEV5(Box5, {
623
585
  flexDirection: "column",
624
586
  width: columns,
625
587
  padding: 1,
626
588
  children: [
627
- /* @__PURE__ */ jsxDEV6(Header, {}, undefined, false, undefined, this),
628
- breadcrumb.length > 1 && /* @__PURE__ */ jsxDEV6(Box6, {
589
+ /* @__PURE__ */ jsxDEV5(Header, {}, undefined, false, undefined, this),
590
+ breadcrumb.length > 1 && /* @__PURE__ */ jsxDEV5(Box5, {
629
591
  marginBottom: 1,
630
592
  marginLeft: 1,
631
- children: /* @__PURE__ */ jsxDEV6(Breadcrumb, {
593
+ children: /* @__PURE__ */ jsxDEV5(Breadcrumb, {
632
594
  path: breadcrumb
633
595
  }, undefined, false, undefined, this)
634
596
  }, undefined, false, undefined, this),
635
- /* @__PURE__ */ jsxDEV6(Box6, {
597
+ /* @__PURE__ */ jsxDEV5(Box5, {
636
598
  flexDirection: "column",
637
599
  flexGrow: 1,
638
600
  children
639
601
  }, undefined, false, undefined, this),
640
- showFooter && /* @__PURE__ */ jsxDEV6(Footer, {}, undefined, false, undefined, this)
602
+ showFooter && /* @__PURE__ */ jsxDEV5(Footer, {}, undefined, false, undefined, this)
641
603
  ]
642
604
  }, undefined, true, undefined, this);
643
605
  }
644
606
 
645
607
  // src/components/layout/Panel.tsx
646
- import { Box as Box7, Text as Text6 } from "ink";
647
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
608
+ import { Box as Box6, Text as Text5 } from "ink";
609
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
648
610
  function Panel({
649
611
  title,
650
612
  children,
@@ -652,7 +614,7 @@ function Panel({
652
614
  flexGrow,
653
615
  borderColor = colors.border
654
616
  }) {
655
- return /* @__PURE__ */ jsxDEV7(Box7, {
617
+ return /* @__PURE__ */ jsxDEV6(Box6, {
656
618
  flexDirection: "column",
657
619
  width,
658
620
  flexGrow,
@@ -660,9 +622,9 @@ function Panel({
660
622
  borderColor,
661
623
  paddingX: 1,
662
624
  children: [
663
- title && /* @__PURE__ */ jsxDEV7(Box7, {
625
+ title && /* @__PURE__ */ jsxDEV6(Box6, {
664
626
  marginBottom: 1,
665
- children: /* @__PURE__ */ jsxDEV7(Text6, {
627
+ children: /* @__PURE__ */ jsxDEV6(Text5, {
666
628
  bold: true,
667
629
  color: colors.primary,
668
630
  children: title
@@ -673,34 +635,144 @@ function Panel({
673
635
  }, undefined, true, undefined, this);
674
636
  }
675
637
 
676
- // src/components/CommandOutput.tsx
638
+ // src/components/PrerequisiteError.tsx
639
+ import { Box as Box7, Text as Text6, useInput } from "ink";
640
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
641
+ function PrerequisiteError({ missing, onExit }) {
642
+ useInput(() => onExit());
643
+ return /* @__PURE__ */ jsxDEV7(Layout, {
644
+ breadcrumb: ["Error"],
645
+ children: /* @__PURE__ */ jsxDEV7(Panel, {
646
+ title: "Missing Prerequisites",
647
+ borderColor: colors.error,
648
+ children: [
649
+ /* @__PURE__ */ jsxDEV7(Text6, {
650
+ color: colors.error,
651
+ children: "Required tools are not installed:"
652
+ }, undefined, false, undefined, this),
653
+ /* @__PURE__ */ jsxDEV7(Box7, {
654
+ flexDirection: "column",
655
+ marginTop: 1,
656
+ children: missing.map((dep) => /* @__PURE__ */ jsxDEV7(Box7, {
657
+ children: [
658
+ /* @__PURE__ */ jsxDEV7(Text6, {
659
+ color: colors.warning,
660
+ children: [
661
+ "• ",
662
+ dep.name
663
+ ]
664
+ }, undefined, true, undefined, this),
665
+ /* @__PURE__ */ jsxDEV7(Text6, {
666
+ dimColor: true,
667
+ children: [
668
+ " — Install: ",
669
+ dep.install
670
+ ]
671
+ }, undefined, true, undefined, this)
672
+ ]
673
+ }, dep.name, true, undefined, this))
674
+ }, undefined, false, undefined, this),
675
+ /* @__PURE__ */ jsxDEV7(Box7, {
676
+ marginTop: 1,
677
+ children: /* @__PURE__ */ jsxDEV7(Text6, {
678
+ dimColor: true,
679
+ children: "Press any key to exit..."
680
+ }, undefined, false, undefined, this)
681
+ }, undefined, false, undefined, this)
682
+ ]
683
+ }, undefined, true, undefined, this)
684
+ }, undefined, false, undefined, this);
685
+ }
686
+
687
+ // src/components/menus/MainMenu.tsx
688
+ import { useApp } from "ink";
689
+
690
+ // src/components/ui/VimSelect.tsx
691
+ import { useState as useState3 } from "react";
677
692
  import { Box as Box8, Text as Text7, useInput as useInput2 } from "ink";
678
693
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
694
+ function VimSelect({ options, onChange, isDisabled = false }) {
695
+ const [index, setIndex] = useState3(0);
696
+ useInput2((input, key) => {
697
+ if (isDisabled)
698
+ return;
699
+ if (input === "j" || key.downArrow) {
700
+ setIndex((i) => i < options.length - 1 ? i + 1 : i);
701
+ }
702
+ if (input === "k" || key.upArrow) {
703
+ setIndex((i) => i > 0 ? i - 1 : i);
704
+ }
705
+ if (input === "l" || key.return) {
706
+ onChange(options[index].value);
707
+ }
708
+ });
709
+ return /* @__PURE__ */ jsxDEV8(Box8, {
710
+ flexDirection: "column",
711
+ children: options.map((opt, i) => /* @__PURE__ */ jsxDEV8(Box8, {
712
+ children: /* @__PURE__ */ jsxDEV8(Text7, {
713
+ color: i === index ? colors.primary : undefined,
714
+ children: [
715
+ i === index ? "❯" : " ",
716
+ " ",
717
+ opt.label
718
+ ]
719
+ }, undefined, true, undefined, this)
720
+ }, opt.value, false, undefined, this))
721
+ }, undefined, false, undefined, this);
722
+ }
723
+
724
+ // src/components/menus/MainMenu.tsx
725
+ import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
726
+ function MainMenu({ onSelect }) {
727
+ const { exit } = useApp();
728
+ return /* @__PURE__ */ jsxDEV9(Panel, {
729
+ title: "Main Menu",
730
+ children: /* @__PURE__ */ jsxDEV9(VimSelect, {
731
+ options: [
732
+ { label: "Config Manager", value: "config" },
733
+ { label: "Package Sync", value: "packages" },
734
+ { label: "Set Theme", value: "themes" },
735
+ { label: "Exit", value: "exit" }
736
+ ],
737
+ onChange: (value) => {
738
+ if (value === "exit") {
739
+ exit();
740
+ return;
741
+ }
742
+ onSelect(value);
743
+ }
744
+ }, undefined, false, undefined, this)
745
+ }, undefined, false, undefined, this);
746
+ }
747
+
748
+ // src/components/CommandOutput.tsx
749
+ import { Box as Box9, Text as Text8, useInput as useInput3 } from "ink";
750
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
679
751
  function CommandOutput({
680
752
  title,
681
753
  output,
682
754
  success = true,
683
755
  onDismiss
684
756
  }) {
685
- useInput2(() => {
757
+ useInput3(() => {
686
758
  onDismiss();
687
759
  });
688
- return /* @__PURE__ */ jsxDEV8(Panel, {
760
+ return /* @__PURE__ */ jsxDEV10(Panel, {
689
761
  title,
690
762
  borderColor: success ? colors.success : colors.error,
691
763
  children: [
692
- output && /* @__PURE__ */ jsxDEV8(Box8, {
764
+ output && /* @__PURE__ */ jsxDEV10(Box9, {
693
765
  flexDirection: "column",
694
766
  marginBottom: 1,
695
- children: /* @__PURE__ */ jsxDEV8(Text7, {
767
+ children: /* @__PURE__ */ jsxDEV10(Text8, {
696
768
  children: output
697
769
  }, undefined, false, undefined, this)
698
770
  }, undefined, false, undefined, this),
699
- /* @__PURE__ */ jsxDEV8(Text7, {
771
+ /* @__PURE__ */ jsxDEV10(Text8, {
700
772
  color: success ? colors.success : colors.error,
701
773
  children: success ? "Done" : "Failed"
702
774
  }, undefined, false, undefined, this),
703
- /* @__PURE__ */ jsxDEV8(Text7, {
775
+ /* @__PURE__ */ jsxDEV10(Text8, {
704
776
  dimColor: true,
705
777
  children: "Press any key to continue..."
706
778
  }, undefined, false, undefined, this)
@@ -708,246 +780,61 @@ function CommandOutput({
708
780
  }, undefined, true, undefined, this);
709
781
  }
710
782
 
711
- // src/components/ThemeCard.tsx
712
- import { Box as Box9, Text as Text8 } from "ink";
713
- import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
714
- function ThemeCard({ theme, isSelected, width }) {
715
- const borderColor = isSelected ? colors.accent : colors.border;
716
- const nameColor = isSelected ? colors.primary : colors.text;
717
- const indicators = [];
718
- if (theme.hasBackgrounds)
719
- indicators.push("bg");
720
- if (theme.isLightMode)
721
- indicators.push("light");
722
- const indicatorText = indicators.length > 0 ? ` [${indicators.join(" ")}]` : "";
723
- return /* @__PURE__ */ jsxDEV9(Box9, {
724
- flexDirection: "column",
725
- width,
726
- borderStyle: borderStyles.panel,
727
- borderColor,
728
- paddingX: 1,
729
- children: /* @__PURE__ */ jsxDEV9(Box9, {
730
- children: [
731
- /* @__PURE__ */ jsxDEV9(Text8, {
732
- color: isSelected ? colors.accent : colors.primaryDim,
733
- children: isSelected ? "● " : " "
734
- }, undefined, false, undefined, this),
735
- /* @__PURE__ */ jsxDEV9(Text8, {
736
- color: nameColor,
737
- bold: true,
738
- wrap: "truncate",
739
- children: theme.name
740
- }, undefined, false, undefined, this),
741
- /* @__PURE__ */ jsxDEV9(Text8, {
742
- color: colors.primaryDim,
743
- children: indicatorText
744
- }, undefined, false, undefined, this)
745
- ]
746
- }, undefined, true, undefined, this)
783
+ // src/components/LoadingPanel.tsx
784
+ import { Spinner } from "@inkjs/ui";
785
+ import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
786
+ function LoadingPanel({ title, label = "Processing..." }) {
787
+ return /* @__PURE__ */ jsxDEV11(Panel, {
788
+ title,
789
+ children: /* @__PURE__ */ jsxDEV11(Spinner, {
790
+ label
791
+ }, undefined, false, undefined, this)
747
792
  }, undefined, false, undefined, this);
748
793
  }
749
794
 
750
- // src/components/ScrollableLog.tsx
751
- import { useState as useState4, useEffect as useEffect3, useMemo } from "react";
752
- import { Box as Box10, Text as Text9, useInput as useInput3 } from "ink";
753
- import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
754
- function ScrollableLog({
755
- lines,
756
- maxHeight,
757
- autoScroll = true,
758
- showScrollHint = true
795
+ // src/hooks/useMenuAction.ts
796
+ import { useState as useState4, useCallback } from "react";
797
+ function useMenuAction() {
798
+ const [state, setState] = useState4("menu");
799
+ const [output, setOutput] = useState4("");
800
+ const [success, setSuccess] = useState4(true);
801
+ const execute = useCallback(async (action) => {
802
+ setState("running");
803
+ const result = await action();
804
+ setOutput(result.output);
805
+ setSuccess(result.success);
806
+ setState("result");
807
+ }, []);
808
+ const reset = useCallback(() => {
809
+ setState("menu");
810
+ }, []);
811
+ return {
812
+ state,
813
+ output,
814
+ success,
815
+ isRunning: state === "running",
816
+ isResult: state === "result",
817
+ execute,
818
+ reset
819
+ };
820
+ }
821
+
822
+ // src/hooks/useBackNavigation.ts
823
+ import { useInput as useInput4 } from "ink";
824
+ function useBackNavigation({
825
+ enabled = true,
826
+ onBack
759
827
  }) {
760
- const { rows } = useTerminalSize();
761
- const visibleLines = maxHeight || Math.max(5, rows - 12);
762
- const [scrollOffset, setScrollOffset] = useState4(0);
763
- const [isAutoScrolling, setIsAutoScrolling] = useState4(autoScroll);
764
- const totalLines = lines.length;
765
- const maxOffset = Math.max(0, totalLines - visibleLines);
766
- useEffect3(() => {
767
- if (isAutoScrolling) {
768
- setScrollOffset(maxOffset);
769
- }
770
- }, [totalLines, maxOffset, isAutoScrolling]);
771
- useInput3((input, key) => {
772
- if (key.downArrow || input === "j") {
773
- setIsAutoScrolling(false);
774
- setScrollOffset((prev) => Math.min(prev + 1, maxOffset));
775
- }
776
- if (key.upArrow || input === "k") {
777
- setIsAutoScrolling(false);
778
- setScrollOffset((prev) => Math.max(prev - 1, 0));
779
- }
780
- if (input === "G") {
781
- setIsAutoScrolling(true);
782
- setScrollOffset(maxOffset);
783
- }
784
- if (input === "g") {
785
- setIsAutoScrolling(false);
786
- setScrollOffset(0);
787
- }
788
- });
789
- const visibleContent = useMemo(() => {
790
- return lines.slice(scrollOffset, scrollOffset + visibleLines);
791
- }, [lines, scrollOffset, visibleLines]);
792
- const showScrollUp = scrollOffset > 0;
793
- const showScrollDown = scrollOffset < maxOffset;
794
- return /* @__PURE__ */ jsxDEV10(Box10, {
795
- flexDirection: "column",
796
- children: [
797
- showScrollHint && showScrollUp && /* @__PURE__ */ jsxDEV10(Text9, {
798
- dimColor: true,
799
- children: [
800
- " ↑ ",
801
- scrollOffset,
802
- " more line",
803
- scrollOffset !== 1 ? "s" : ""
804
- ]
805
- }, undefined, true, undefined, this),
806
- /* @__PURE__ */ jsxDEV10(Box10, {
807
- flexDirection: "column",
808
- height: visibleLines,
809
- overflow: "hidden",
810
- children: visibleContent.map((line, i) => /* @__PURE__ */ jsxDEV10(Text9, {
811
- children: line
812
- }, scrollOffset + i, false, undefined, this))
813
- }, undefined, false, undefined, this),
814
- showScrollHint && showScrollDown && /* @__PURE__ */ jsxDEV10(Text9, {
815
- dimColor: true,
816
- children: [
817
- " ↓ ",
818
- maxOffset - scrollOffset,
819
- " more line",
820
- maxOffset - scrollOffset !== 1 ? "s" : ""
821
- ]
822
- }, undefined, true, undefined, this),
823
- showScrollHint && totalLines > visibleLines && /* @__PURE__ */ jsxDEV10(Text9, {
824
- dimColor: true,
825
- children: [
826
- "j/k scroll • g top • G bottom ",
827
- isAutoScrolling ? "(auto-scroll)" : ""
828
- ]
829
- }, undefined, true, undefined, this)
830
- ]
831
- }, undefined, true, undefined, this);
832
- }
833
-
834
- // src/components/PromptInput.tsx
835
- import { Box as Box11, Text as Text10, useInput as useInput4 } from "ink";
836
- import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
837
- function PromptInput({
838
- question,
839
- options = ["y", "n"],
840
- onAnswer
841
- }) {
842
- useInput4((input) => {
843
- const lower = input.toLowerCase();
844
- if (options.includes(lower)) {
845
- onAnswer(lower);
828
+ useInput4((input, key) => {
829
+ if (enabled && (key.escape || key.leftArrow || input === "h")) {
830
+ onBack();
846
831
  }
847
832
  });
848
- return /* @__PURE__ */ jsxDEV11(Box11, {
849
- marginTop: 1,
850
- borderStyle: "single",
851
- borderColor: colors.accent,
852
- paddingX: 1,
853
- children: /* @__PURE__ */ jsxDEV11(Text10, {
854
- children: [
855
- question,
856
- " ",
857
- /* @__PURE__ */ jsxDEV11(Text10, {
858
- color: colors.accent,
859
- children: [
860
- "[",
861
- options.join("/"),
862
- "]"
863
- ]
864
- }, undefined, true, undefined, this),
865
- /* @__PURE__ */ jsxDEV11(Text10, {
866
- dimColor: true,
867
- children: ": "
868
- }, undefined, false, undefined, this)
869
- ]
870
- }, undefined, true, undefined, this)
871
- }, undefined, false, undefined, this);
872
- }
873
-
874
- // src/lib/theme-parser.ts
875
- import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
876
- import { join as join3 } from "path";
877
- function parseYaml(content) {
878
- const result = {};
879
- const lines = content.split(`
880
- `);
881
- let currentSection = null;
882
- let currentKey = "";
883
- for (const line of lines) {
884
- const trimmed = line.trim();
885
- if (!trimmed || trimmed.startsWith("#"))
886
- continue;
887
- const indentLevel = line.search(/\S/);
888
- const match = trimmed.match(/^([\w-]+):\s*(.*)$/);
889
- if (match) {
890
- const [, key, value] = match;
891
- if (indentLevel === 0) {
892
- if (value) {
893
- result[key] = value.replace(/^["']|["']$/g, "");
894
- } else {
895
- currentKey = key;
896
- currentSection = {};
897
- result[key] = currentSection;
898
- }
899
- } else if (currentSection) {
900
- currentSection[key] = value.replace(/^["']|["']$/g, "");
901
- }
902
- }
903
- }
904
- return result;
905
- }
906
- async function parseThemeMetadata(themePath) {
907
- const yamlPath = join3(themePath, "theme.yaml");
908
- if (!existsSync2(yamlPath)) {
909
- return;
910
- }
911
- try {
912
- const content = await readText(yamlPath);
913
- const parsed = parseYaml(content);
914
- return {
915
- name: parsed.name || "",
916
- author: parsed.author,
917
- description: parsed.description,
918
- version: parsed.version,
919
- source: parsed.source,
920
- colors: parsed.colors
921
- };
922
- } catch {
923
- return;
924
- }
925
- }
926
- function parseThemeFiles(themePath) {
927
- const entries = readdirSync2(themePath, { withFileTypes: true });
928
- return entries.filter((e) => e.isFile() && !e.name.startsWith(".") && e.name !== "theme.yaml" && e.name !== "light.mode").map((e) => ({
929
- name: e.name,
930
- path: join3(themePath, e.name),
931
- application: e.name.replace(/\.(conf|theme|lua|toml|css|json|ini)$/, "")
932
- }));
933
- }
934
- async function parseTheme(themePath, themeName) {
935
- const files = parseThemeFiles(themePath);
936
- const metadata = await parseThemeMetadata(themePath);
937
- return {
938
- name: metadata?.name || themeName,
939
- path: themePath,
940
- files,
941
- metadata,
942
- hasBackgrounds: existsSync2(join3(themePath, "backgrounds")),
943
- hasPreview: existsSync2(join3(themePath, "preview.png")),
944
- isLightMode: existsSync2(join3(themePath, "light.mode"))
945
- };
946
833
  }
947
834
 
948
835
  // src/cli/config-manager.ts
949
836
  import { parseArgs } from "util";
950
- import { readdirSync as readdirSync3, existsSync as existsSync3, lstatSync as lstatSync2, readlinkSync as readlinkSync2 } from "fs";
837
+ import { readdirSync as readdirSync2, existsSync as existsSync2, lstatSync as lstatSync2, readlinkSync as readlinkSync2 } from "fs";
951
838
  var colors2 = {
952
839
  red: "\x1B[0;31m",
953
840
  green: "\x1B[0;32m",
@@ -963,7 +850,7 @@ async function checkStow() {
963
850
  }
964
851
  }
965
852
  function listPackages() {
966
- const entries = readdirSync3(CONFIGS_DIR, { withFileTypes: true });
853
+ const entries = readdirSync2(CONFIGS_DIR, { withFileTypes: true });
967
854
  return entries.filter((e) => e.isDirectory()).map((e) => ({
968
855
  name: e.name,
969
856
  path: `${CONFIGS_DIR}/${e.name}`,
@@ -972,12 +859,12 @@ function listPackages() {
972
859
  }
973
860
  function checkPackageStowed(packageName) {
974
861
  const packageDir = `${CONFIGS_DIR}/${packageName}`;
975
- if (!existsSync3(packageDir))
862
+ if (!existsSync2(packageDir))
976
863
  return false;
977
- const entries = readdirSync3(packageDir, { withFileTypes: true });
864
+ const entries = readdirSync2(packageDir, { withFileTypes: true });
978
865
  for (const entry of entries) {
979
866
  const targetPath = `${HOME_DIR}/${entry.name}`;
980
- if (!existsSync3(targetPath))
867
+ if (!existsSync2(targetPath))
981
868
  return false;
982
869
  try {
983
870
  const stat = lstatSync2(targetPath);
@@ -1242,73 +1129,241 @@ if (isMainModule) {
1242
1129
  main().catch(console.error);
1243
1130
  }
1244
1131
 
1245
- // src/cli/pkg-sync.ts
1246
- import { parseArgs as parseArgs2 } from "util";
1247
-
1248
- // src/lib/config.ts
1249
- import { existsSync as existsSync4 } from "fs";
1250
- var DEFAULT_CONFIG = {
1251
- config: {
1252
- purge: false,
1253
- purgeInteractive: true,
1254
- autoUpdate: true
1255
- },
1256
- taps: [],
1257
- packages: [],
1258
- casks: [],
1259
- mas: {}
1260
- };
1261
- async function loadPkgConfig(path) {
1262
- await ensureConfigDir();
1263
- const configPath = path || PKG_CONFIG_PATH;
1264
- if (!existsSync4(configPath)) {
1265
- await savePkgConfig(DEFAULT_CONFIG, configPath);
1266
- return DEFAULT_CONFIG;
1132
+ // src/components/menus/ConfigMenu.tsx
1133
+ import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
1134
+ function ConfigMenu({ onBack }) {
1135
+ const { state, output, success, isRunning, isResult, execute, reset } = useMenuAction();
1136
+ useBackNavigation({ enabled: state === "menu", onBack });
1137
+ const handleAction = async (action) => {
1138
+ if (action === "back") {
1139
+ onBack();
1140
+ return;
1141
+ }
1142
+ await execute(() => runConfigManager([action]));
1143
+ };
1144
+ if (isRunning) {
1145
+ return /* @__PURE__ */ jsxDEV12(LoadingPanel, {
1146
+ title: "Config Manager"
1147
+ }, undefined, false, undefined, this);
1267
1148
  }
1268
- return readJson(configPath);
1269
- }
1270
- async function savePkgConfig(config, path) {
1271
- await ensureConfigDir();
1272
- const configPath = path || PKG_CONFIG_PATH;
1273
- await writeFile(configPath, JSON.stringify(config, null, 2));
1274
- }
1275
- async function loadPkgLock() {
1276
- if (!existsSync4(PKG_LOCK_PATH)) {
1277
- return null;
1149
+ if (isResult) {
1150
+ return /* @__PURE__ */ jsxDEV12(CommandOutput, {
1151
+ title: "Config Manager",
1152
+ output,
1153
+ success,
1154
+ onDismiss: reset
1155
+ }, undefined, false, undefined, this);
1278
1156
  }
1279
- return readJson(PKG_LOCK_PATH);
1280
- }
1281
- async function savePkgLock(lock) {
1282
- await ensureConfigDir();
1283
- await writeFile(PKG_LOCK_PATH, JSON.stringify(lock, null, 2));
1157
+ return /* @__PURE__ */ jsxDEV12(Panel, {
1158
+ title: "Config Manager",
1159
+ children: /* @__PURE__ */ jsxDEV12(VimSelect, {
1160
+ options: [
1161
+ { label: "Stow all packages", value: "stow-all" },
1162
+ { label: "Unstow all packages", value: "unstow-all" },
1163
+ { label: "Check status", value: "status" },
1164
+ { label: "List packages", value: "list" },
1165
+ { label: "Back", value: "back" }
1166
+ ],
1167
+ onChange: handleAction
1168
+ }, undefined, false, undefined, this)
1169
+ }, undefined, false, undefined, this);
1284
1170
  }
1285
1171
 
1286
- // src/lib/lockfile.ts
1287
- async function fetchInstalledVersions() {
1288
- const config = await loadPkgConfig();
1289
- const now = new Date().toISOString();
1290
- const formulas = {};
1291
- const casks = {};
1292
- if (config.packages.length > 0) {
1293
- const result = await exec([
1294
- "brew",
1295
- "info",
1296
- "--json=v2",
1297
- ...config.packages
1298
- ]);
1299
- if (result.success && result.stdout) {
1300
- const info = JSON.parse(result.stdout);
1301
- for (const formula of info.formulae) {
1302
- if (formula.installed.length > 0) {
1303
- formulas[formula.name] = {
1304
- version: formula.installed[0].version,
1305
- tap: formula.tap,
1306
- installedAt: now
1307
- };
1308
- }
1309
- }
1172
+ // src/components/menus/PackageMenu.tsx
1173
+ import { useState as useState6, useCallback as useCallback2, useMemo as useMemo2, useRef } from "react";
1174
+ import { Box as Box12, Text as Text11, useInput as useInput7 } from "ink";
1175
+
1176
+ // src/components/ScrollableLog.tsx
1177
+ import { useState as useState5, useEffect as useEffect3, useMemo } from "react";
1178
+ import { Box as Box10, Text as Text9, useInput as useInput5 } from "ink";
1179
+ import { jsxDEV as jsxDEV13 } from "react/jsx-dev-runtime";
1180
+ function ScrollableLog({
1181
+ lines,
1182
+ maxHeight,
1183
+ autoScroll = true,
1184
+ showScrollHint = true
1185
+ }) {
1186
+ const { rows } = useTerminalSize();
1187
+ const visibleLines = maxHeight || Math.max(5, rows - 12);
1188
+ const [scrollOffset, setScrollOffset] = useState5(0);
1189
+ const [isAutoScrolling, setIsAutoScrolling] = useState5(autoScroll);
1190
+ const totalLines = lines.length;
1191
+ const maxOffset = Math.max(0, totalLines - visibleLines);
1192
+ useEffect3(() => {
1193
+ if (isAutoScrolling) {
1194
+ setScrollOffset(maxOffset);
1310
1195
  }
1311
- }
1196
+ }, [totalLines, maxOffset, isAutoScrolling]);
1197
+ useInput5((input, key) => {
1198
+ if (key.downArrow || input === "j") {
1199
+ setIsAutoScrolling(false);
1200
+ setScrollOffset((prev) => Math.min(prev + 1, maxOffset));
1201
+ }
1202
+ if (key.upArrow || input === "k") {
1203
+ setIsAutoScrolling(false);
1204
+ setScrollOffset((prev) => Math.max(prev - 1, 0));
1205
+ }
1206
+ if (input === "G") {
1207
+ setIsAutoScrolling(true);
1208
+ setScrollOffset(maxOffset);
1209
+ }
1210
+ if (input === "g") {
1211
+ setIsAutoScrolling(false);
1212
+ setScrollOffset(0);
1213
+ }
1214
+ });
1215
+ const visibleContent = useMemo(() => {
1216
+ return lines.slice(scrollOffset, scrollOffset + visibleLines);
1217
+ }, [lines, scrollOffset, visibleLines]);
1218
+ const showScrollUp = scrollOffset > 0;
1219
+ const showScrollDown = scrollOffset < maxOffset;
1220
+ return /* @__PURE__ */ jsxDEV13(Box10, {
1221
+ flexDirection: "column",
1222
+ children: [
1223
+ showScrollHint && showScrollUp && /* @__PURE__ */ jsxDEV13(Text9, {
1224
+ dimColor: true,
1225
+ children: [
1226
+ " ↑ ",
1227
+ scrollOffset,
1228
+ " more line",
1229
+ scrollOffset !== 1 ? "s" : ""
1230
+ ]
1231
+ }, undefined, true, undefined, this),
1232
+ /* @__PURE__ */ jsxDEV13(Box10, {
1233
+ flexDirection: "column",
1234
+ height: visibleLines,
1235
+ overflow: "hidden",
1236
+ children: visibleContent.map((line, i) => /* @__PURE__ */ jsxDEV13(Text9, {
1237
+ children: line
1238
+ }, scrollOffset + i, false, undefined, this))
1239
+ }, undefined, false, undefined, this),
1240
+ showScrollHint && showScrollDown && /* @__PURE__ */ jsxDEV13(Text9, {
1241
+ dimColor: true,
1242
+ children: [
1243
+ " ↓ ",
1244
+ maxOffset - scrollOffset,
1245
+ " more line",
1246
+ maxOffset - scrollOffset !== 1 ? "s" : ""
1247
+ ]
1248
+ }, undefined, true, undefined, this),
1249
+ showScrollHint && totalLines > visibleLines && /* @__PURE__ */ jsxDEV13(Text9, {
1250
+ dimColor: true,
1251
+ children: [
1252
+ "j/k scroll • g top • G bottom ",
1253
+ isAutoScrolling ? "(auto-scroll)" : ""
1254
+ ]
1255
+ }, undefined, true, undefined, this)
1256
+ ]
1257
+ }, undefined, true, undefined, this);
1258
+ }
1259
+
1260
+ // src/components/PromptInput.tsx
1261
+ import { Box as Box11, Text as Text10, useInput as useInput6 } from "ink";
1262
+ import { jsxDEV as jsxDEV14 } from "react/jsx-dev-runtime";
1263
+ function PromptInput({
1264
+ question,
1265
+ options = ["y", "n"],
1266
+ onAnswer
1267
+ }) {
1268
+ useInput6((input) => {
1269
+ const lower = input.toLowerCase();
1270
+ if (options.includes(lower)) {
1271
+ onAnswer(lower);
1272
+ }
1273
+ });
1274
+ return /* @__PURE__ */ jsxDEV14(Box11, {
1275
+ marginTop: 1,
1276
+ borderStyle: "single",
1277
+ borderColor: colors.accent,
1278
+ paddingX: 1,
1279
+ children: /* @__PURE__ */ jsxDEV14(Text10, {
1280
+ children: [
1281
+ question,
1282
+ " ",
1283
+ /* @__PURE__ */ jsxDEV14(Text10, {
1284
+ color: colors.accent,
1285
+ children: [
1286
+ "[",
1287
+ options.join("/"),
1288
+ "]"
1289
+ ]
1290
+ }, undefined, true, undefined, this),
1291
+ /* @__PURE__ */ jsxDEV14(Text10, {
1292
+ dimColor: true,
1293
+ children: ": "
1294
+ }, undefined, false, undefined, this)
1295
+ ]
1296
+ }, undefined, true, undefined, this)
1297
+ }, undefined, false, undefined, this);
1298
+ }
1299
+
1300
+ // src/cli/pkg-sync.ts
1301
+ import { parseArgs as parseArgs2 } from "util";
1302
+
1303
+ // src/lib/config.ts
1304
+ import { existsSync as existsSync3 } from "fs";
1305
+ var DEFAULT_CONFIG = {
1306
+ config: {
1307
+ purge: false,
1308
+ purgeInteractive: true,
1309
+ autoUpdate: true
1310
+ },
1311
+ taps: [],
1312
+ packages: [],
1313
+ casks: [],
1314
+ mas: {}
1315
+ };
1316
+ async function loadPkgConfig(path) {
1317
+ await ensureConfigDir();
1318
+ const configPath = path || PKG_CONFIG_PATH;
1319
+ if (!existsSync3(configPath)) {
1320
+ await savePkgConfig(DEFAULT_CONFIG, configPath);
1321
+ return DEFAULT_CONFIG;
1322
+ }
1323
+ return readJson(configPath);
1324
+ }
1325
+ async function savePkgConfig(config, path) {
1326
+ await ensureConfigDir();
1327
+ const configPath = path || PKG_CONFIG_PATH;
1328
+ await writeFile(configPath, JSON.stringify(config, null, 2));
1329
+ }
1330
+ async function loadPkgLock() {
1331
+ if (!existsSync3(PKG_LOCK_PATH)) {
1332
+ return null;
1333
+ }
1334
+ return readJson(PKG_LOCK_PATH);
1335
+ }
1336
+ async function savePkgLock(lock) {
1337
+ await ensureConfigDir();
1338
+ await writeFile(PKG_LOCK_PATH, JSON.stringify(lock, null, 2));
1339
+ }
1340
+
1341
+ // src/lib/lockfile.ts
1342
+ async function fetchInstalledVersions() {
1343
+ const config = await loadPkgConfig();
1344
+ const now = new Date().toISOString();
1345
+ const formulas = {};
1346
+ const casks = {};
1347
+ if (config.packages.length > 0) {
1348
+ const result = await exec([
1349
+ "brew",
1350
+ "info",
1351
+ "--json=v2",
1352
+ ...config.packages
1353
+ ]);
1354
+ if (result.success && result.stdout) {
1355
+ const info = JSON.parse(result.stdout);
1356
+ for (const formula of info.formulae) {
1357
+ if (formula.installed.length > 0) {
1358
+ formulas[formula.name] = {
1359
+ version: formula.installed[0].version,
1360
+ tap: formula.tap,
1361
+ installedAt: now
1362
+ };
1363
+ }
1364
+ }
1365
+ }
1366
+ }
1312
1367
  if (config.casks.length > 0) {
1313
1368
  const result = await exec([
1314
1369
  "brew",
@@ -2054,277 +2109,36 @@ if (isMainModule3) {
2054
2109
  main3().catch(console.error);
2055
2110
  }
2056
2111
 
2057
- // src/cli/set-theme.ts
2058
- import { parseArgs as parseArgs4 } from "util";
2059
- import { readdirSync as readdirSync4, existsSync as existsSync5, rmSync, symlinkSync, unlinkSync } from "fs";
2060
- import { join as join4 } from "path";
2061
- var colors5 = {
2062
- red: "\x1B[0;31m",
2063
- green: "\x1B[0;32m",
2064
- blue: "\x1B[0;34m",
2065
- yellow: "\x1B[1;33m",
2066
- cyan: "\x1B[0;36m",
2067
- dim: "\x1B[2m",
2068
- reset: "\x1B[0m"
2069
- };
2070
- async function listThemes() {
2071
- await ensureConfigDir();
2072
- if (!existsSync5(THEMES_DIR)) {
2073
- return [];
2074
- }
2075
- const entries = readdirSync4(THEMES_DIR, { withFileTypes: true });
2076
- const themes = [];
2077
- for (const entry of entries) {
2078
- if (entry.isDirectory()) {
2079
- const themePath = join4(THEMES_DIR, entry.name);
2080
- const theme = await parseTheme(themePath, entry.name);
2081
- themes.push(theme);
2112
+ // src/components/menus/PackageMenu.tsx
2113
+ import { jsxDEV as jsxDEV15 } from "react/jsx-dev-runtime";
2114
+ function PackageMenu({ onBack }) {
2115
+ const [state, setState] = useState6("menu");
2116
+ const [lines, setLines] = useState6([]);
2117
+ const [output, setOutput] = useState6("");
2118
+ const [isStreamingOp, setIsStreamingOp] = useState6(true);
2119
+ const [pendingPrompt, setPendingPrompt] = useState6(null);
2120
+ const [success, setSuccess] = useState6(true);
2121
+ const isRunningRef = useRef(false);
2122
+ useInput7((input, key) => {
2123
+ if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
2124
+ onBack();
2082
2125
  }
2083
- }
2084
- return themes;
2085
- }
2086
- function clearDirectory(dir) {
2087
- if (existsSync5(dir)) {
2088
- const entries = readdirSync4(dir, { withFileTypes: true });
2089
- for (const entry of entries) {
2090
- const fullPath = join4(dir, entry.name);
2091
- if (entry.isSymbolicLink() || entry.isFile()) {
2092
- unlinkSync(fullPath);
2093
- } else if (entry.isDirectory()) {
2094
- rmSync(fullPath, { recursive: true, force: true });
2095
- }
2126
+ if (state === "result") {
2127
+ setState("menu");
2128
+ setLines([]);
2096
2129
  }
2097
- }
2098
- }
2099
- function createSymlink(source, target) {
2100
- if (existsSync5(target)) {
2101
- unlinkSync(target);
2102
- }
2103
- symlinkSync(source, target);
2104
- }
2105
- async function applyTheme(themeName) {
2106
- const themeDir = join4(THEMES_DIR, themeName);
2107
- if (!existsSync5(themeDir)) {
2108
- return { output: `Theme '${themeName}' not found`, success: false };
2109
- }
2110
- await ensureConfigDir();
2111
- await ensureDir2(THEME_TARGET_DIR);
2112
- const theme = await parseTheme(themeDir, themeName);
2113
- clearDirectory(THEME_TARGET_DIR);
2114
- if (existsSync5(BACKGROUNDS_TARGET_DIR)) {
2115
- rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
2116
- }
2117
- const entries = readdirSync4(themeDir, { withFileTypes: true });
2118
- for (const entry of entries) {
2119
- const source = join4(themeDir, entry.name);
2120
- if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
2121
- const target = join4(THEME_TARGET_DIR, entry.name);
2122
- createSymlink(source, target);
2123
- }
2124
- }
2125
- if (theme.hasBackgrounds) {
2126
- const backgroundsSource = join4(themeDir, "backgrounds");
2127
- createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
2128
- }
2129
- let output = `Theme '${theme.name}' applied successfully`;
2130
- if (theme.metadata?.author) {
2131
- output += `
2132
- Author: ${theme.metadata.author}`;
2133
- }
2134
- if (theme.hasBackgrounds) {
2135
- output += `
2136
- Wallpapers available at: ~/.config/formalconf/current/backgrounds/`;
2137
- }
2138
- if (theme.isLightMode) {
2139
- output += `
2140
- Note: This is a light mode theme`;
2141
- }
2142
- return { output, success: true };
2143
- }
2144
- async function showThemeInfo(themeName) {
2145
- const themeDir = join4(THEMES_DIR, themeName);
2146
- if (!existsSync5(themeDir)) {
2147
- console.error(`${colors5.red}Error: Theme '${themeName}' not found${colors5.reset}`);
2148
- process.exit(1);
2149
- }
2150
- const theme = await parseTheme(themeDir, themeName);
2151
- console.log(`
2152
- ${colors5.cyan}Theme: ${theme.name}${colors5.reset}`);
2153
- if (theme.metadata) {
2154
- if (theme.metadata.author)
2155
- console.log(`Author: ${theme.metadata.author}`);
2156
- if (theme.metadata.description)
2157
- console.log(`Description: ${theme.metadata.description}`);
2158
- if (theme.metadata.version)
2159
- console.log(`Version: ${theme.metadata.version}`);
2160
- if (theme.metadata.source)
2161
- console.log(`Source: ${theme.metadata.source}`);
2162
- }
2163
- console.log(`
2164
- Files (${theme.files.length}):`);
2165
- for (const file of theme.files) {
2166
- console.log(` ${colors5.blue}•${colors5.reset} ${file.name}`);
2167
- }
2168
- if (theme.hasBackgrounds) {
2169
- console.log(`
2170
- ${colors5.green}Has wallpapers${colors5.reset}`);
2171
- }
2172
- if (theme.hasPreview) {
2173
- console.log(`${colors5.green}Has preview image${colors5.reset}`);
2174
- }
2175
- if (theme.isLightMode) {
2176
- console.log(`${colors5.yellow}Light mode theme${colors5.reset}`);
2177
- }
2178
- }
2179
- async function runSetTheme(themeName) {
2180
- return applyTheme(themeName);
2181
- }
2182
- async function main4() {
2183
- const { positionals, values } = parseArgs4({
2184
- args: process.argv.slice(2),
2185
- options: {
2186
- info: { type: "boolean", short: "i" }
2187
- },
2188
- allowPositionals: true
2189
- });
2190
- const [themeName] = positionals;
2191
- if (!themeName) {
2192
- const themes = await listThemes();
2193
- if (themes.length === 0) {
2194
- console.log(`${colors5.yellow}No themes available.${colors5.reset}`);
2195
- console.log(`This system is compatible with omarchy themes.`);
2196
- console.log(`
2197
- Add themes to: ${colors5.cyan}~/.config/formalconf/themes/${colors5.reset}`);
2198
- process.exit(0);
2199
- }
2200
- console.log(`${colors5.cyan}Usage: formalconf theme <theme-name>${colors5.reset}`);
2201
- console.log(` formalconf theme --info <theme-name>
2202
- `);
2203
- console.log("Available themes:");
2204
- for (const theme of themes) {
2205
- const extras = [];
2206
- if (theme.hasBackgrounds)
2207
- extras.push("wallpapers");
2208
- if (theme.isLightMode)
2209
- extras.push("light");
2210
- const suffix = extras.length ? ` ${colors5.dim}(${extras.join(", ")})${colors5.reset}` : "";
2211
- console.log(` ${colors5.blue}•${colors5.reset} ${theme.name}${suffix}`);
2212
- }
2213
- process.exit(0);
2214
- }
2215
- if (values.info) {
2216
- await showThemeInfo(themeName);
2217
- } else {
2218
- const result = await applyTheme(themeName);
2219
- console.log(result.success ? `${colors5.green}${result.output}${colors5.reset}` : `${colors5.red}${result.output}${colors5.reset}`);
2220
- }
2221
- }
2222
- var isMainModule4 = process.argv[1]?.includes("set-theme");
2223
- if (isMainModule4) {
2224
- main4().catch(console.error);
2225
- }
2226
-
2227
- // src/cli/formalconf.tsx
2228
- import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
2229
- function MainMenu({ onSelect }) {
2230
- const { exit } = useApp();
2231
- return /* @__PURE__ */ jsxDEV12(Panel, {
2232
- title: "Main Menu",
2233
- children: /* @__PURE__ */ jsxDEV12(VimSelect, {
2234
- options: [
2235
- { label: "Config Manager", value: "config" },
2236
- { label: "Package Sync", value: "packages" },
2237
- { label: "Set Theme", value: "themes" },
2238
- { label: "Exit", value: "exit" }
2239
- ],
2240
- onChange: (value) => {
2241
- if (value === "exit") {
2242
- exit();
2243
- return;
2244
- }
2245
- onSelect(value);
2246
- }
2247
- }, undefined, false, undefined, this)
2248
- }, undefined, false, undefined, this);
2249
- }
2250
- function ConfigMenu({ onBack }) {
2251
- const [state, setState] = useState5("menu");
2252
- const [output, setOutput] = useState5("");
2253
- const [success, setSuccess] = useState5(true);
2254
- useInput5((input, key) => {
2255
- if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
2256
- onBack();
2257
- }
2258
- });
2259
- const handleAction = async (action) => {
2260
- if (action === "back") {
2261
- onBack();
2262
- return;
2263
- }
2264
- setState("running");
2265
- const result = await runConfigManager([action]);
2266
- setOutput(result.output);
2267
- setSuccess(result.success);
2268
- setState("result");
2269
- };
2270
- if (state === "running") {
2271
- return /* @__PURE__ */ jsxDEV12(Panel, {
2272
- title: "Config Manager",
2273
- children: /* @__PURE__ */ jsxDEV12(Spinner, {
2274
- label: "Processing..."
2275
- }, undefined, false, undefined, this)
2276
- }, undefined, false, undefined, this);
2277
- }
2278
- if (state === "result") {
2279
- return /* @__PURE__ */ jsxDEV12(CommandOutput, {
2280
- title: "Config Manager",
2281
- output,
2282
- success,
2283
- onDismiss: () => setState("menu")
2284
- }, undefined, false, undefined, this);
2285
- }
2286
- return /* @__PURE__ */ jsxDEV12(Panel, {
2287
- title: "Config Manager",
2288
- children: /* @__PURE__ */ jsxDEV12(VimSelect, {
2289
- options: [
2290
- { label: "Stow all packages", value: "stow-all" },
2291
- { label: "Unstow all packages", value: "unstow-all" },
2292
- { label: "Check status", value: "status" },
2293
- { label: "List packages", value: "list" },
2294
- { label: "Back", value: "back" }
2295
- ],
2296
- onChange: handleAction
2297
- }, undefined, false, undefined, this)
2298
- }, undefined, false, undefined, this);
2299
- }
2300
- function PackageMenu({ onBack }) {
2301
- const [state, setState] = useState5("menu");
2302
- const [lines, setLines] = useState5([]);
2303
- const [output, setOutput] = useState5("");
2304
- const [isStreamingOp, setIsStreamingOp] = useState5(true);
2305
- const [pendingPrompt, setPendingPrompt] = useState5(null);
2306
- const [success, setSuccess] = useState5(true);
2307
- const isRunningRef = useRef(false);
2308
- useInput5((input, key) => {
2309
- if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
2310
- onBack();
2311
- }
2312
- if (state === "result") {
2313
- setState("menu");
2314
- setLines([]);
2315
- }
2316
- });
2317
- const callbacks = useMemo2(() => ({
2318
- onLog: (line) => {
2319
- setLines((prev) => [...prev, line]);
2320
- },
2321
- onPrompt: (question, options) => {
2322
- return new Promise((resolve) => {
2323
- setPendingPrompt({ question, options, resolve });
2324
- });
2130
+ });
2131
+ const callbacks = useMemo2(() => ({
2132
+ onLog: (line) => {
2133
+ setLines((prev) => [...prev, line]);
2134
+ },
2135
+ onPrompt: (question, options) => {
2136
+ return new Promise((resolve) => {
2137
+ setPendingPrompt({ question, options, resolve });
2138
+ });
2325
2139
  }
2326
2140
  }), []);
2327
- const handlePromptAnswer = useCallback((answer) => {
2141
+ const handlePromptAnswer = useCallback2((answer) => {
2328
2142
  if (pendingPrompt) {
2329
2143
  setLines((prev) => [...prev, `> ${answer}`]);
2330
2144
  pendingPrompt.resolve(answer);
@@ -2382,20 +2196,17 @@ function PackageMenu({ onBack }) {
2382
2196
  };
2383
2197
  if (state === "running") {
2384
2198
  if (!isStreamingOp) {
2385
- return /* @__PURE__ */ jsxDEV12(Panel, {
2386
- title: "Package Sync",
2387
- children: /* @__PURE__ */ jsxDEV12(Spinner, {
2388
- label: "Processing..."
2389
- }, undefined, false, undefined, this)
2199
+ return /* @__PURE__ */ jsxDEV15(LoadingPanel, {
2200
+ title: "Package Sync"
2390
2201
  }, undefined, false, undefined, this);
2391
2202
  }
2392
- return /* @__PURE__ */ jsxDEV12(Panel, {
2203
+ return /* @__PURE__ */ jsxDEV15(Panel, {
2393
2204
  title: "Package Sync",
2394
2205
  children: [
2395
- /* @__PURE__ */ jsxDEV12(ScrollableLog, {
2206
+ /* @__PURE__ */ jsxDEV15(ScrollableLog, {
2396
2207
  lines
2397
2208
  }, undefined, false, undefined, this),
2398
- pendingPrompt && /* @__PURE__ */ jsxDEV12(PromptInput, {
2209
+ pendingPrompt && /* @__PURE__ */ jsxDEV15(PromptInput, {
2399
2210
  question: pendingPrompt.question,
2400
2211
  options: pendingPrompt.options,
2401
2212
  onAnswer: handlePromptAnswer
@@ -2405,38 +2216,38 @@ function PackageMenu({ onBack }) {
2405
2216
  }
2406
2217
  if (state === "result") {
2407
2218
  if (!isStreamingOp) {
2408
- return /* @__PURE__ */ jsxDEV12(CommandOutput, {
2219
+ return /* @__PURE__ */ jsxDEV15(CommandOutput, {
2409
2220
  title: "Package Sync",
2410
2221
  output,
2411
2222
  success,
2412
2223
  onDismiss: () => setState("menu")
2413
2224
  }, undefined, false, undefined, this);
2414
2225
  }
2415
- return /* @__PURE__ */ jsxDEV12(Panel, {
2226
+ return /* @__PURE__ */ jsxDEV15(Panel, {
2416
2227
  title: "Package Sync",
2417
2228
  borderColor: success ? colors.success : colors.error,
2418
2229
  children: [
2419
- /* @__PURE__ */ jsxDEV12(ScrollableLog, {
2230
+ /* @__PURE__ */ jsxDEV15(ScrollableLog, {
2420
2231
  lines,
2421
2232
  autoScroll: false
2422
2233
  }, undefined, false, undefined, this),
2423
- /* @__PURE__ */ jsxDEV12(Box12, {
2234
+ /* @__PURE__ */ jsxDEV15(Box12, {
2424
2235
  marginTop: 1,
2425
- children: /* @__PURE__ */ jsxDEV12(Text11, {
2236
+ children: /* @__PURE__ */ jsxDEV15(Text11, {
2426
2237
  color: success ? colors.success : colors.error,
2427
2238
  children: success ? "Done" : "Failed"
2428
2239
  }, undefined, false, undefined, this)
2429
2240
  }, undefined, false, undefined, this),
2430
- /* @__PURE__ */ jsxDEV12(Text11, {
2241
+ /* @__PURE__ */ jsxDEV15(Text11, {
2431
2242
  dimColor: true,
2432
2243
  children: "Press any key to continue..."
2433
2244
  }, undefined, false, undefined, this)
2434
2245
  ]
2435
2246
  }, undefined, true, undefined, this);
2436
2247
  }
2437
- return /* @__PURE__ */ jsxDEV12(Panel, {
2248
+ return /* @__PURE__ */ jsxDEV15(Panel, {
2438
2249
  title: "Package Sync",
2439
- children: /* @__PURE__ */ jsxDEV12(VimSelect, {
2250
+ children: /* @__PURE__ */ jsxDEV15(VimSelect, {
2440
2251
  options: [
2441
2252
  { label: "Sync packages", value: "sync" },
2442
2253
  { label: "Sync with purge", value: "sync-purge" },
@@ -2450,32 +2261,74 @@ function PackageMenu({ onBack }) {
2450
2261
  }, undefined, false, undefined, this)
2451
2262
  }, undefined, false, undefined, this);
2452
2263
  }
2453
- function ThemeMenu({ onBack }) {
2454
- const [themes, setThemes] = useState5([]);
2455
- const [loading, setLoading] = useState5(true);
2456
- const [selectedIndex, setSelectedIndex] = useState5(0);
2457
- const [state, setState] = useState5("menu");
2458
- const [output, setOutput] = useState5("");
2459
- const [success, setSuccess] = useState5(true);
2264
+
2265
+ // src/components/menus/ThemeMenu.tsx
2266
+ import { useState as useState8, useEffect as useEffect5, useMemo as useMemo4 } from "react";
2267
+ import { Box as Box14, Text as Text13 } from "ink";
2268
+ import { existsSync as existsSync6, readdirSync as readdirSync5 } from "fs";
2269
+ import { join as join5 } from "path";
2270
+
2271
+ // src/components/ThemeCard.tsx
2272
+ import { Box as Box13, Text as Text12 } from "ink";
2273
+ import { jsxDEV as jsxDEV16 } from "react/jsx-dev-runtime";
2274
+ function ThemeCard({ theme, isSelected, width }) {
2275
+ const borderColor = isSelected ? colors.accent : colors.border;
2276
+ const nameColor = isSelected ? colors.primary : colors.text;
2277
+ const indicators = [];
2278
+ if (theme.hasBackgrounds)
2279
+ indicators.push("bg");
2280
+ if (theme.isLightMode)
2281
+ indicators.push("light");
2282
+ const indicatorText = indicators.length > 0 ? ` [${indicators.join(" ")}]` : "";
2283
+ return /* @__PURE__ */ jsxDEV16(Box13, {
2284
+ flexDirection: "column",
2285
+ width,
2286
+ borderStyle: borderStyles.panel,
2287
+ borderColor,
2288
+ paddingX: 1,
2289
+ children: /* @__PURE__ */ jsxDEV16(Box13, {
2290
+ children: [
2291
+ /* @__PURE__ */ jsxDEV16(Text12, {
2292
+ color: isSelected ? colors.accent : colors.primaryDim,
2293
+ children: isSelected ? "● " : " "
2294
+ }, undefined, false, undefined, this),
2295
+ /* @__PURE__ */ jsxDEV16(Text12, {
2296
+ color: nameColor,
2297
+ bold: true,
2298
+ wrap: "truncate",
2299
+ children: theme.name
2300
+ }, undefined, false, undefined, this),
2301
+ /* @__PURE__ */ jsxDEV16(Text12, {
2302
+ color: colors.primaryDim,
2303
+ children: indicatorText
2304
+ }, undefined, false, undefined, this)
2305
+ ]
2306
+ }, undefined, true, undefined, this)
2307
+ }, undefined, false, undefined, this);
2308
+ }
2309
+
2310
+ // src/hooks/useThemeGrid.ts
2311
+ import { useState as useState7, useEffect as useEffect4 } from "react";
2312
+ import { useInput as useInput8 } from "ink";
2313
+ function useThemeGrid({
2314
+ itemCount,
2315
+ cardHeight = 3,
2316
+ layoutOverhead = 20,
2317
+ minCardWidth = 28,
2318
+ onSelect,
2319
+ onBack,
2320
+ enabled = true
2321
+ }) {
2460
2322
  const { columns, rows } = useTerminalSize();
2461
- const CARD_HEIGHT = 3;
2462
- const LAYOUT_OVERHEAD = 20;
2463
- const cardWidth = useMemo2(() => {
2464
- const availableWidth = columns - 6;
2465
- const cardsPerRow2 = Math.max(1, Math.floor(availableWidth / 28));
2466
- return Math.floor(availableWidth / cardsPerRow2);
2467
- }, [columns]);
2468
- const cardsPerRow = useMemo2(() => {
2469
- const availableWidth = columns - 6;
2470
- return Math.max(1, Math.floor(availableWidth / 28));
2471
- }, [columns]);
2472
- const visibleRows = useMemo2(() => {
2473
- const availableHeight = rows - LAYOUT_OVERHEAD;
2474
- return Math.max(1, Math.floor(availableHeight / CARD_HEIGHT));
2475
- }, [rows]);
2323
+ const [selectedIndex, setSelectedIndex] = useState7(0);
2324
+ const [scrollOffset, setScrollOffset] = useState7(0);
2325
+ const availableWidth = columns - 6;
2326
+ const cardsPerRow = Math.max(1, Math.floor(availableWidth / minCardWidth));
2327
+ const cardWidth = Math.floor(availableWidth / cardsPerRow);
2328
+ const availableHeight = rows - layoutOverhead;
2329
+ const visibleRows = Math.max(1, Math.floor(availableHeight / cardHeight));
2476
2330
  const selectedRow = Math.floor(selectedIndex / cardsPerRow);
2477
- const totalRows = Math.ceil(themes.length / cardsPerRow);
2478
- const [scrollOffset, setScrollOffset] = useState5(0);
2331
+ const totalRows = Math.ceil(itemCount / cardsPerRow);
2479
2332
  useEffect4(() => {
2480
2333
  if (selectedRow < scrollOffset) {
2481
2334
  setScrollOffset(selectedRow);
@@ -2483,21 +2336,15 @@ function ThemeMenu({ onBack }) {
2483
2336
  setScrollOffset(selectedRow - visibleRows + 1);
2484
2337
  }
2485
2338
  }, [selectedRow, scrollOffset, visibleRows]);
2486
- const visibleThemes = useMemo2(() => {
2487
- const startIdx = scrollOffset * cardsPerRow;
2488
- const endIdx = (scrollOffset + visibleRows) * cardsPerRow;
2489
- return themes.slice(startIdx, endIdx);
2490
- }, [themes, scrollOffset, visibleRows, cardsPerRow]);
2491
- const visibleStartIndex = scrollOffset * cardsPerRow;
2492
- useInput5((input, key) => {
2493
- if (state !== "menu" || loading)
2339
+ useInput8((input, key) => {
2340
+ if (!enabled)
2494
2341
  return;
2495
- if (key.escape) {
2342
+ if (key.escape && onBack) {
2496
2343
  onBack();
2497
2344
  return;
2498
2345
  }
2499
2346
  if (key.rightArrow || input === "l") {
2500
- if (selectedIndex < themes.length - 1) {
2347
+ if (selectedIndex < itemCount - 1) {
2501
2348
  setSelectedIndex((i) => i + 1);
2502
2349
  }
2503
2350
  }
@@ -2508,7 +2355,7 @@ function ThemeMenu({ onBack }) {
2508
2355
  }
2509
2356
  if (key.downArrow || input === "j") {
2510
2357
  const nextIndex = selectedIndex + cardsPerRow;
2511
- if (nextIndex < themes.length) {
2358
+ if (nextIndex < itemCount) {
2512
2359
  setSelectedIndex(nextIndex);
2513
2360
  }
2514
2361
  }
@@ -2518,78 +2365,348 @@ function ThemeMenu({ onBack }) {
2518
2365
  setSelectedIndex(prevIndex);
2519
2366
  }
2520
2367
  }
2521
- if (key.return) {
2522
- applyTheme2(themes[selectedIndex]);
2368
+ if (key.return && onSelect) {
2369
+ onSelect(selectedIndex);
2523
2370
  }
2524
2371
  });
2525
- useEffect4(() => {
2526
- async function loadThemes() {
2527
- if (!existsSync6(THEMES_DIR)) {
2528
- setThemes([]);
2529
- setLoading(false);
2530
- return;
2531
- }
2532
- const entries = readdirSync5(THEMES_DIR, { withFileTypes: true });
2533
- const loadedThemes = [];
2534
- for (const entry of entries) {
2535
- if (entry.isDirectory()) {
2536
- const themePath = join5(THEMES_DIR, entry.name);
2537
- const theme = await parseTheme(themePath, entry.name);
2538
- loadedThemes.push(theme);
2372
+ const visibleStartIndex = scrollOffset * cardsPerRow;
2373
+ const visibleEndIndex = (scrollOffset + visibleRows) * cardsPerRow;
2374
+ return {
2375
+ cardsPerRow,
2376
+ cardWidth,
2377
+ visibleRows,
2378
+ scrollOffset,
2379
+ selectedIndex,
2380
+ visibleStartIndex,
2381
+ visibleEndIndex,
2382
+ showScrollUp: scrollOffset > 0,
2383
+ showScrollDown: scrollOffset + visibleRows < totalRows,
2384
+ gridHeight: visibleRows * cardHeight,
2385
+ totalRows
2386
+ };
2387
+ }
2388
+
2389
+ // src/lib/theme-parser.ts
2390
+ import { existsSync as existsSync4, readdirSync as readdirSync3 } from "fs";
2391
+ import { join as join3 } from "path";
2392
+ function parseYaml(content) {
2393
+ const result = {};
2394
+ const lines = content.split(`
2395
+ `);
2396
+ let currentSection = null;
2397
+ let currentKey = "";
2398
+ for (const line of lines) {
2399
+ const trimmed = line.trim();
2400
+ if (!trimmed || trimmed.startsWith("#"))
2401
+ continue;
2402
+ const indentLevel = line.search(/\S/);
2403
+ const match = trimmed.match(/^([\w-]+):\s*(.*)$/);
2404
+ if (match) {
2405
+ const [, key, value] = match;
2406
+ if (indentLevel === 0) {
2407
+ if (value) {
2408
+ result[key] = value.replace(/^["']|["']$/g, "");
2409
+ } else {
2410
+ currentKey = key;
2411
+ currentSection = {};
2412
+ result[key] = currentSection;
2539
2413
  }
2414
+ } else if (currentSection) {
2415
+ currentSection[key] = value.replace(/^["']|["']$/g, "");
2540
2416
  }
2541
- setThemes(loadedThemes);
2417
+ }
2418
+ }
2419
+ return result;
2420
+ }
2421
+ async function parseThemeMetadata(themePath) {
2422
+ const yamlPath = join3(themePath, "theme.yaml");
2423
+ if (!existsSync4(yamlPath)) {
2424
+ return;
2425
+ }
2426
+ try {
2427
+ const content = await readText(yamlPath);
2428
+ const parsed = parseYaml(content);
2429
+ return {
2430
+ name: parsed.name || "",
2431
+ author: parsed.author,
2432
+ description: parsed.description,
2433
+ version: parsed.version,
2434
+ source: parsed.source,
2435
+ colors: parsed.colors
2436
+ };
2437
+ } catch {
2438
+ return;
2439
+ }
2440
+ }
2441
+ function parseThemeFiles(themePath) {
2442
+ const entries = readdirSync3(themePath, { withFileTypes: true });
2443
+ return entries.filter((e) => e.isFile() && !e.name.startsWith(".") && e.name !== "theme.yaml" && e.name !== "light.mode").map((e) => ({
2444
+ name: e.name,
2445
+ path: join3(themePath, e.name),
2446
+ application: e.name.replace(/\.(conf|theme|lua|toml|css|json|ini)$/, "")
2447
+ }));
2448
+ }
2449
+ async function parseTheme(themePath, themeName) {
2450
+ const files = parseThemeFiles(themePath);
2451
+ const metadata = await parseThemeMetadata(themePath);
2452
+ return {
2453
+ name: metadata?.name || themeName,
2454
+ path: themePath,
2455
+ files,
2456
+ metadata,
2457
+ hasBackgrounds: existsSync4(join3(themePath, "backgrounds")),
2458
+ hasPreview: existsSync4(join3(themePath, "preview.png")),
2459
+ isLightMode: existsSync4(join3(themePath, "light.mode"))
2460
+ };
2461
+ }
2462
+
2463
+ // src/cli/set-theme.ts
2464
+ import { parseArgs as parseArgs4 } from "util";
2465
+ import { readdirSync as readdirSync4, existsSync as existsSync5, rmSync, symlinkSync, unlinkSync } from "fs";
2466
+ import { join as join4 } from "path";
2467
+ var colors5 = {
2468
+ red: "\x1B[0;31m",
2469
+ green: "\x1B[0;32m",
2470
+ blue: "\x1B[0;34m",
2471
+ yellow: "\x1B[1;33m",
2472
+ cyan: "\x1B[0;36m",
2473
+ dim: "\x1B[2m",
2474
+ reset: "\x1B[0m"
2475
+ };
2476
+ async function listThemes() {
2477
+ await ensureConfigDir();
2478
+ if (!existsSync5(THEMES_DIR)) {
2479
+ return [];
2480
+ }
2481
+ const entries = readdirSync4(THEMES_DIR, { withFileTypes: true });
2482
+ const themes = [];
2483
+ for (const entry of entries) {
2484
+ if (entry.isDirectory()) {
2485
+ const themePath = join4(THEMES_DIR, entry.name);
2486
+ const theme = await parseTheme(themePath, entry.name);
2487
+ themes.push(theme);
2488
+ }
2489
+ }
2490
+ return themes;
2491
+ }
2492
+ function clearDirectory(dir) {
2493
+ if (existsSync5(dir)) {
2494
+ const entries = readdirSync4(dir, { withFileTypes: true });
2495
+ for (const entry of entries) {
2496
+ const fullPath = join4(dir, entry.name);
2497
+ if (entry.isSymbolicLink() || entry.isFile()) {
2498
+ unlinkSync(fullPath);
2499
+ } else if (entry.isDirectory()) {
2500
+ rmSync(fullPath, { recursive: true, force: true });
2501
+ }
2502
+ }
2503
+ }
2504
+ }
2505
+ function createSymlink(source, target) {
2506
+ if (existsSync5(target)) {
2507
+ unlinkSync(target);
2508
+ }
2509
+ symlinkSync(source, target);
2510
+ }
2511
+ async function applyTheme(themeName) {
2512
+ const themeDir = join4(THEMES_DIR, themeName);
2513
+ if (!existsSync5(themeDir)) {
2514
+ return { output: `Theme '${themeName}' not found`, success: false };
2515
+ }
2516
+ await ensureConfigDir();
2517
+ await ensureDir2(THEME_TARGET_DIR);
2518
+ const theme = await parseTheme(themeDir, themeName);
2519
+ clearDirectory(THEME_TARGET_DIR);
2520
+ if (existsSync5(BACKGROUNDS_TARGET_DIR)) {
2521
+ rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
2522
+ }
2523
+ const entries = readdirSync4(themeDir, { withFileTypes: true });
2524
+ for (const entry of entries) {
2525
+ const source = join4(themeDir, entry.name);
2526
+ if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
2527
+ const target = join4(THEME_TARGET_DIR, entry.name);
2528
+ createSymlink(source, target);
2529
+ }
2530
+ }
2531
+ if (theme.hasBackgrounds) {
2532
+ const backgroundsSource = join4(themeDir, "backgrounds");
2533
+ createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
2534
+ }
2535
+ let output = `Theme '${theme.name}' applied successfully`;
2536
+ if (theme.metadata?.author) {
2537
+ output += `
2538
+ Author: ${theme.metadata.author}`;
2539
+ }
2540
+ if (theme.hasBackgrounds) {
2541
+ output += `
2542
+ Wallpapers available at: ~/.config/formalconf/current/backgrounds/`;
2543
+ }
2544
+ if (theme.isLightMode) {
2545
+ output += `
2546
+ Note: This is a light mode theme`;
2547
+ }
2548
+ return { output, success: true };
2549
+ }
2550
+ async function showThemeInfo(themeName) {
2551
+ const themeDir = join4(THEMES_DIR, themeName);
2552
+ if (!existsSync5(themeDir)) {
2553
+ console.error(`${colors5.red}Error: Theme '${themeName}' not found${colors5.reset}`);
2554
+ process.exit(1);
2555
+ }
2556
+ const theme = await parseTheme(themeDir, themeName);
2557
+ console.log(`
2558
+ ${colors5.cyan}Theme: ${theme.name}${colors5.reset}`);
2559
+ if (theme.metadata) {
2560
+ if (theme.metadata.author)
2561
+ console.log(`Author: ${theme.metadata.author}`);
2562
+ if (theme.metadata.description)
2563
+ console.log(`Description: ${theme.metadata.description}`);
2564
+ if (theme.metadata.version)
2565
+ console.log(`Version: ${theme.metadata.version}`);
2566
+ if (theme.metadata.source)
2567
+ console.log(`Source: ${theme.metadata.source}`);
2568
+ }
2569
+ console.log(`
2570
+ Files (${theme.files.length}):`);
2571
+ for (const file of theme.files) {
2572
+ console.log(` ${colors5.blue}•${colors5.reset} ${file.name}`);
2573
+ }
2574
+ if (theme.hasBackgrounds) {
2575
+ console.log(`
2576
+ ${colors5.green}Has wallpapers${colors5.reset}`);
2577
+ }
2578
+ if (theme.hasPreview) {
2579
+ console.log(`${colors5.green}Has preview image${colors5.reset}`);
2580
+ }
2581
+ if (theme.isLightMode) {
2582
+ console.log(`${colors5.yellow}Light mode theme${colors5.reset}`);
2583
+ }
2584
+ }
2585
+ async function runSetTheme(themeName) {
2586
+ return applyTheme(themeName);
2587
+ }
2588
+ async function main4() {
2589
+ const { positionals, values } = parseArgs4({
2590
+ args: process.argv.slice(2),
2591
+ options: {
2592
+ info: { type: "boolean", short: "i" }
2593
+ },
2594
+ allowPositionals: true
2595
+ });
2596
+ const [themeName] = positionals;
2597
+ if (!themeName) {
2598
+ const themes = await listThemes();
2599
+ if (themes.length === 0) {
2600
+ console.log(`${colors5.yellow}No themes available.${colors5.reset}`);
2601
+ console.log(`This system is compatible with omarchy themes.`);
2602
+ console.log(`
2603
+ Add themes to: ${colors5.cyan}~/.config/formalconf/themes/${colors5.reset}`);
2604
+ process.exit(0);
2605
+ }
2606
+ console.log(`${colors5.cyan}Usage: formalconf theme <theme-name>${colors5.reset}`);
2607
+ console.log(` formalconf theme --info <theme-name>
2608
+ `);
2609
+ console.log("Available themes:");
2610
+ for (const theme of themes) {
2611
+ const extras = [];
2612
+ if (theme.hasBackgrounds)
2613
+ extras.push("wallpapers");
2614
+ if (theme.isLightMode)
2615
+ extras.push("light");
2616
+ const suffix = extras.length ? ` ${colors5.dim}(${extras.join(", ")})${colors5.reset}` : "";
2617
+ console.log(` ${colors5.blue}•${colors5.reset} ${theme.name}${suffix}`);
2618
+ }
2619
+ process.exit(0);
2620
+ }
2621
+ if (values.info) {
2622
+ await showThemeInfo(themeName);
2623
+ } else {
2624
+ const result = await applyTheme(themeName);
2625
+ console.log(result.success ? `${colors5.green}${result.output}${colors5.reset}` : `${colors5.red}${result.output}${colors5.reset}`);
2626
+ }
2627
+ }
2628
+ var isMainModule4 = process.argv[1]?.includes("set-theme");
2629
+ if (isMainModule4) {
2630
+ main4().catch(console.error);
2631
+ }
2632
+
2633
+ // src/components/menus/ThemeMenu.tsx
2634
+ import { jsxDEV as jsxDEV17 } from "react/jsx-dev-runtime";
2635
+ function ThemeMenu({ onBack }) {
2636
+ const [themes, setThemes] = useState8([]);
2637
+ const [loading, setLoading] = useState8(true);
2638
+ const { state, output, success, isRunning, isResult, execute, reset } = useMenuAction();
2639
+ const grid = useThemeGrid({
2640
+ itemCount: themes.length,
2641
+ onSelect: (index) => applyTheme2(themes[index]),
2642
+ onBack,
2643
+ enabled: state === "menu" && !loading && themes.length > 0
2644
+ });
2645
+ useEffect5(() => {
2646
+ async function loadThemes() {
2647
+ if (!existsSync6(THEMES_DIR)) {
2648
+ setThemes([]);
2649
+ setLoading(false);
2650
+ return;
2651
+ }
2652
+ const entries = readdirSync5(THEMES_DIR, { withFileTypes: true });
2653
+ const loadedThemes = [];
2654
+ for (const entry of entries) {
2655
+ if (entry.isDirectory()) {
2656
+ const themePath = join5(THEMES_DIR, entry.name);
2657
+ const theme = await parseTheme(themePath, entry.name);
2658
+ loadedThemes.push(theme);
2659
+ }
2660
+ }
2661
+ setThemes(loadedThemes);
2542
2662
  setLoading(false);
2543
2663
  }
2544
2664
  loadThemes();
2545
2665
  }, []);
2546
2666
  const applyTheme2 = async (theme) => {
2547
- setState("running");
2548
2667
  const themeName = theme.path.split("/").pop();
2549
- const result = await runSetTheme(themeName);
2550
- setOutput(result.output);
2551
- setSuccess(result.success);
2552
- setState("result");
2668
+ await execute(() => runSetTheme(themeName));
2553
2669
  };
2554
- if (loading || state === "running") {
2555
- return /* @__PURE__ */ jsxDEV12(Panel, {
2670
+ const visibleThemes = useMemo4(() => {
2671
+ return themes.slice(grid.visibleStartIndex, grid.visibleEndIndex);
2672
+ }, [themes, grid.visibleStartIndex, grid.visibleEndIndex]);
2673
+ if (loading || isRunning) {
2674
+ return /* @__PURE__ */ jsxDEV17(LoadingPanel, {
2556
2675
  title: "Select Theme",
2557
- children: /* @__PURE__ */ jsxDEV12(Spinner, {
2558
- label: loading ? "Loading themes..." : "Applying theme..."
2559
- }, undefined, false, undefined, this)
2676
+ label: loading ? "Loading themes..." : "Applying theme..."
2560
2677
  }, undefined, false, undefined, this);
2561
2678
  }
2562
- if (state === "result") {
2563
- return /* @__PURE__ */ jsxDEV12(CommandOutput, {
2679
+ if (isResult) {
2680
+ return /* @__PURE__ */ jsxDEV17(CommandOutput, {
2564
2681
  title: "Select Theme",
2565
2682
  output,
2566
2683
  success,
2567
- onDismiss: () => setState("menu")
2684
+ onDismiss: reset
2568
2685
  }, undefined, false, undefined, this);
2569
2686
  }
2570
2687
  if (themes.length === 0) {
2571
- return /* @__PURE__ */ jsxDEV12(Panel, {
2688
+ return /* @__PURE__ */ jsxDEV17(Panel, {
2572
2689
  title: "Select Theme",
2573
2690
  children: [
2574
- /* @__PURE__ */ jsxDEV12(Box12, {
2691
+ /* @__PURE__ */ jsxDEV17(Box14, {
2575
2692
  flexDirection: "column",
2576
2693
  children: [
2577
- /* @__PURE__ */ jsxDEV12(Text11, {
2694
+ /* @__PURE__ */ jsxDEV17(Text13, {
2578
2695
  color: colors.warning,
2579
2696
  children: "No themes available."
2580
2697
  }, undefined, false, undefined, this),
2581
- /* @__PURE__ */ jsxDEV12(Text11, {
2698
+ /* @__PURE__ */ jsxDEV17(Text13, {
2582
2699
  children: "This system is compatible with omarchy themes."
2583
2700
  }, undefined, false, undefined, this),
2584
- /* @__PURE__ */ jsxDEV12(Text11, {
2701
+ /* @__PURE__ */ jsxDEV17(Text13, {
2585
2702
  dimColor: true,
2586
2703
  children: "Add themes to ~/.config/formalconf/themes/"
2587
2704
  }, undefined, false, undefined, this)
2588
2705
  ]
2589
2706
  }, undefined, true, undefined, this),
2590
- /* @__PURE__ */ jsxDEV12(Box12, {
2707
+ /* @__PURE__ */ jsxDEV17(Box14, {
2591
2708
  marginTop: 1,
2592
- children: /* @__PURE__ */ jsxDEV12(VimSelect, {
2709
+ children: /* @__PURE__ */ jsxDEV17(VimSelect, {
2593
2710
  options: [{ label: "Back", value: "back" }],
2594
2711
  onChange: () => onBack()
2595
2712
  }, undefined, false, undefined, this)
@@ -2597,44 +2714,43 @@ function ThemeMenu({ onBack }) {
2597
2714
  ]
2598
2715
  }, undefined, true, undefined, this);
2599
2716
  }
2600
- const showScrollUp = scrollOffset > 0;
2601
- const showScrollDown = scrollOffset + visibleRows < totalRows;
2602
- const gridHeight = visibleRows * CARD_HEIGHT;
2603
- return /* @__PURE__ */ jsxDEV12(Panel, {
2717
+ return /* @__PURE__ */ jsxDEV17(Panel, {
2604
2718
  title: "Select Theme",
2605
2719
  children: [
2606
- showScrollUp && /* @__PURE__ */ jsxDEV12(Text11, {
2720
+ grid.showScrollUp && /* @__PURE__ */ jsxDEV17(Text13, {
2607
2721
  dimColor: true,
2608
2722
  children: [
2609
- " ",
2610
- scrollOffset,
2723
+ " ",
2724
+ "↑ ",
2725
+ grid.scrollOffset,
2611
2726
  " more row",
2612
- scrollOffset > 1 ? "s" : ""
2727
+ grid.scrollOffset > 1 ? "s" : ""
2613
2728
  ]
2614
2729
  }, undefined, true, undefined, this),
2615
- /* @__PURE__ */ jsxDEV12(Box12, {
2730
+ /* @__PURE__ */ jsxDEV17(Box14, {
2616
2731
  flexDirection: "row",
2617
2732
  flexWrap: "wrap",
2618
- height: gridHeight,
2733
+ height: grid.gridHeight,
2619
2734
  overflow: "hidden",
2620
- children: visibleThemes.map((theme, index) => /* @__PURE__ */ jsxDEV12(ThemeCard, {
2735
+ children: visibleThemes.map((theme, index) => /* @__PURE__ */ jsxDEV17(ThemeCard, {
2621
2736
  theme,
2622
- isSelected: visibleStartIndex + index === selectedIndex,
2623
- width: cardWidth
2737
+ isSelected: grid.visibleStartIndex + index === grid.selectedIndex,
2738
+ width: grid.cardWidth
2624
2739
  }, theme.path, false, undefined, this))
2625
2740
  }, undefined, false, undefined, this),
2626
- showScrollDown && /* @__PURE__ */ jsxDEV12(Text11, {
2741
+ grid.showScrollDown && /* @__PURE__ */ jsxDEV17(Text13, {
2627
2742
  dimColor: true,
2628
2743
  children: [
2629
- " ",
2630
- totalRows - scrollOffset - visibleRows,
2744
+ " ",
2745
+ "↓ ",
2746
+ grid.totalRows - grid.scrollOffset - grid.visibleRows,
2631
2747
  " more row",
2632
- totalRows - scrollOffset - visibleRows > 1 ? "s" : ""
2748
+ grid.totalRows - grid.scrollOffset - grid.visibleRows > 1 ? "s" : ""
2633
2749
  ]
2634
2750
  }, undefined, true, undefined, this),
2635
- /* @__PURE__ */ jsxDEV12(Box12, {
2751
+ /* @__PURE__ */ jsxDEV17(Box14, {
2636
2752
  marginTop: 1,
2637
- children: /* @__PURE__ */ jsxDEV12(Text11, {
2753
+ children: /* @__PURE__ */ jsxDEV17(Text13, {
2638
2754
  dimColor: true,
2639
2755
  children: "←→↑↓/hjkl navigate • Enter select • Esc back"
2640
2756
  }, undefined, false, undefined, this)
@@ -2642,65 +2758,25 @@ function ThemeMenu({ onBack }) {
2642
2758
  ]
2643
2759
  }, undefined, true, undefined, this);
2644
2760
  }
2645
- function PrerequisiteError({
2646
- missing,
2647
- onExit
2648
- }) {
2649
- useInput5(() => onExit());
2650
- return /* @__PURE__ */ jsxDEV12(Layout, {
2651
- breadcrumb: ["Error"],
2652
- children: /* @__PURE__ */ jsxDEV12(Panel, {
2653
- title: "Missing Prerequisites",
2654
- borderColor: colors.error,
2655
- children: [
2656
- /* @__PURE__ */ jsxDEV12(Text11, {
2657
- color: colors.error,
2658
- children: "Required tools are not installed:"
2659
- }, undefined, false, undefined, this),
2660
- /* @__PURE__ */ jsxDEV12(Box12, {
2661
- flexDirection: "column",
2662
- marginTop: 1,
2663
- children: missing.map((dep) => /* @__PURE__ */ jsxDEV12(Box12, {
2664
- children: [
2665
- /* @__PURE__ */ jsxDEV12(Text11, {
2666
- color: colors.warning,
2667
- children: [
2668
- "• ",
2669
- dep.name
2670
- ]
2671
- }, undefined, true, undefined, this),
2672
- /* @__PURE__ */ jsxDEV12(Text11, {
2673
- dimColor: true,
2674
- children: [
2675
- " — Install: ",
2676
- dep.install
2677
- ]
2678
- }, undefined, true, undefined, this)
2679
- ]
2680
- }, dep.name, true, undefined, this))
2681
- }, undefined, false, undefined, this),
2682
- /* @__PURE__ */ jsxDEV12(Box12, {
2683
- marginTop: 1,
2684
- children: /* @__PURE__ */ jsxDEV12(Text11, {
2685
- dimColor: true,
2686
- children: "Press any key to exit..."
2687
- }, undefined, false, undefined, this)
2688
- }, undefined, false, undefined, this)
2689
- ]
2690
- }, undefined, true, undefined, this)
2691
- }, undefined, false, undefined, this);
2692
- }
2761
+
2762
+ // src/cli/formalconf.tsx
2763
+ import { jsxDEV as jsxDEV18 } from "react/jsx-dev-runtime";
2764
+ var BREADCRUMBS = {
2765
+ main: ["Main"],
2766
+ config: ["Main", "Config Manager"],
2767
+ packages: ["Main", "Package Sync"],
2768
+ themes: ["Main", "Themes"]
2769
+ };
2693
2770
  function App() {
2694
- const [appState, setAppState] = useState5("loading");
2695
- const [missingDeps, setMissingDeps] = useState5([]);
2696
- const [screen, setScreen] = useState5("main");
2697
- const { exit } = useApp();
2698
- useInput5((input) => {
2699
- if (input === "q") {
2771
+ const [appState, setAppState] = useState9("loading");
2772
+ const [missingDeps, setMissingDeps] = useState9([]);
2773
+ const [screen, setScreen] = useState9("main");
2774
+ const { exit } = useApp2();
2775
+ useInput9((input) => {
2776
+ if (input === "q")
2700
2777
  exit();
2701
- }
2702
2778
  });
2703
- useEffect4(() => {
2779
+ useEffect6(() => {
2704
2780
  async function init() {
2705
2781
  ensureConfigDir();
2706
2782
  const result = await checkPrerequisites();
@@ -2713,51 +2789,40 @@ function App() {
2713
2789
  }
2714
2790
  init();
2715
2791
  }, []);
2716
- const getBreadcrumb = () => {
2717
- switch (screen) {
2718
- case "config":
2719
- return ["Main", "Config Manager"];
2720
- case "packages":
2721
- return ["Main", "Package Sync"];
2722
- case "themes":
2723
- return ["Main", "Themes"];
2724
- default:
2725
- return ["Main"];
2726
- }
2727
- };
2728
2792
  if (appState === "loading") {
2729
- return /* @__PURE__ */ jsxDEV12(Layout, {
2793
+ return /* @__PURE__ */ jsxDEV18(Layout, {
2730
2794
  breadcrumb: ["Loading"],
2731
- children: /* @__PURE__ */ jsxDEV12(Panel, {
2795
+ children: /* @__PURE__ */ jsxDEV18(Panel, {
2732
2796
  title: "FormalConf",
2733
- children: /* @__PURE__ */ jsxDEV12(Spinner, {
2797
+ children: /* @__PURE__ */ jsxDEV18(Spinner2, {
2734
2798
  label: "Checking prerequisites..."
2735
2799
  }, undefined, false, undefined, this)
2736
2800
  }, undefined, false, undefined, this)
2737
2801
  }, undefined, false, undefined, this);
2738
2802
  }
2739
2803
  if (appState === "error") {
2740
- return /* @__PURE__ */ jsxDEV12(PrerequisiteError, {
2804
+ return /* @__PURE__ */ jsxDEV18(PrerequisiteError, {
2741
2805
  missing: missingDeps,
2742
2806
  onExit: exit
2743
2807
  }, undefined, false, undefined, this);
2744
2808
  }
2745
- return /* @__PURE__ */ jsxDEV12(Layout, {
2746
- breadcrumb: getBreadcrumb(),
2809
+ const goBack = () => setScreen("main");
2810
+ return /* @__PURE__ */ jsxDEV18(Layout, {
2811
+ breadcrumb: BREADCRUMBS[screen],
2747
2812
  children: [
2748
- screen === "main" && /* @__PURE__ */ jsxDEV12(MainMenu, {
2813
+ screen === "main" && /* @__PURE__ */ jsxDEV18(MainMenu, {
2749
2814
  onSelect: setScreen
2750
2815
  }, undefined, false, undefined, this),
2751
- screen === "config" && /* @__PURE__ */ jsxDEV12(ConfigMenu, {
2752
- onBack: () => setScreen("main")
2816
+ screen === "config" && /* @__PURE__ */ jsxDEV18(ConfigMenu, {
2817
+ onBack: goBack
2753
2818
  }, undefined, false, undefined, this),
2754
- screen === "packages" && /* @__PURE__ */ jsxDEV12(PackageMenu, {
2755
- onBack: () => setScreen("main")
2819
+ screen === "packages" && /* @__PURE__ */ jsxDEV18(PackageMenu, {
2820
+ onBack: goBack
2756
2821
  }, undefined, false, undefined, this),
2757
- screen === "themes" && /* @__PURE__ */ jsxDEV12(ThemeMenu, {
2758
- onBack: () => setScreen("main")
2822
+ screen === "themes" && /* @__PURE__ */ jsxDEV18(ThemeMenu, {
2823
+ onBack: goBack
2759
2824
  }, undefined, false, undefined, this)
2760
2825
  ]
2761
2826
  }, undefined, true, undefined, this);
2762
2827
  }
2763
- render(/* @__PURE__ */ jsxDEV12(App, {}, undefined, false, undefined, this));
2828
+ render(/* @__PURE__ */ jsxDEV18(App, {}, undefined, false, undefined, this));