formalconf 2.0.2 → 2.0.4

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 (3) hide show
  1. package/LICENSE +21 -0
  2. package/dist/formalconf.js +900 -156
  3. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // src/cli/formalconf.tsx
3
- import { useState as useState9, useEffect as useEffect6 } from "react";
4
- import { render, useApp as useApp2, useInput as useInput9 } from "ink";
3
+ import { useState as useState11, useEffect as useEffect6 } from "react";
4
+ import { render, useApp as useApp2, useInput as useInput11 } from "ink";
5
5
  import { Spinner as Spinner2 } from "@inkjs/ui";
6
6
 
7
7
  // src/components/layout/Layout.tsx
@@ -41,6 +41,7 @@ import { existsSync, readlinkSync, readdirSync, lstatSync } from "fs";
41
41
  // src/lib/paths.ts
42
42
  import { homedir } from "os";
43
43
  import { join } from "path";
44
+ import { readdir } from "fs/promises";
44
45
 
45
46
  // src/lib/runtime.ts
46
47
  import { spawn as nodeSpawn } from "child_process";
@@ -294,6 +295,19 @@ async function ensureConfigDir() {
294
295
  await ensureDir2(THEME_TARGET_DIR);
295
296
  await ensureDir2(BACKGROUNDS_TARGET_DIR);
296
297
  }
298
+ async function dirHasContents(path) {
299
+ try {
300
+ const entries = await readdir(path);
301
+ return entries.length > 0;
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+ async function isFirstRun() {
307
+ const configsExist = await dirHasContents(CONFIGS_DIR);
308
+ const themesExist = await dirHasContents(THEMES_DIR);
309
+ return !configsExist && !themesExist;
310
+ }
297
311
 
298
312
  // src/hooks/useSystemStatus.ts
299
313
  import { basename, dirname as dirname2, join as join2 } from "path";
@@ -400,7 +414,7 @@ function StatusIndicator({
400
414
  // package.json
401
415
  var package_default = {
402
416
  name: "formalconf",
403
- version: "2.0.2",
417
+ version: "2.0.4",
404
418
  description: "Dotfiles management TUI for macOS - config management, package sync, and theme switching",
405
419
  type: "module",
406
420
  main: "./dist/formalconf.js",
@@ -684,8 +698,9 @@ function PrerequisiteError({ missing, onExit }) {
684
698
  }, undefined, false, undefined, this);
685
699
  }
686
700
 
687
- // src/components/menus/MainMenu.tsx
688
- import { useApp } from "ink";
701
+ // src/components/Onboarding.tsx
702
+ import { useState as useState4 } from "react";
703
+ import { Box as Box9, Text as Text8, useInput as useInput3 } from "ink";
689
704
 
690
705
  // src/components/ui/VimSelect.tsx
691
706
  import { useState as useState3 } from "react";
@@ -721,13 +736,502 @@ function VimSelect({ options, onChange, isDisabled = false }) {
721
736
  }, undefined, false, undefined, this);
722
737
  }
723
738
 
724
- // src/components/menus/MainMenu.tsx
739
+ // src/lib/templates.ts
740
+ import { join as join3 } from "path";
741
+ import { existsSync as existsSync2 } from "fs";
742
+ var EXAMPLE_CONFIG_README = `# Example Stow Config Package
743
+
744
+ This is an example dotfiles package for use with GNU Stow.
745
+
746
+ ## Structure
747
+ Files are organized to mirror your home directory:
748
+ - \`.config/example-app/config.toml\` -> \`~/.config/example-app/config.toml\`
749
+ - \`.example-app-rc\` -> \`~/.example-app-rc\`
750
+
751
+ ## Usage
752
+ 1. Place your dotfiles in this directory structure
753
+ 2. Run \`formalconf\` and use Config Manager -> Stow
754
+ 3. Symlinks will be created from your home directory
755
+
756
+ ## Creating Your Own
757
+ 1. Copy this directory and rename it (e.g., \`git\`, \`zsh\`, \`nvim\`)
758
+ 2. Add your dotfiles mirroring your home directory structure
759
+ 3. Stow the package to create symlinks
760
+ `;
761
+ var EXAMPLE_CONFIG_TOML = `# Example configuration file
762
+ # This will be symlinked to ~/.config/example-app/config.toml
763
+
764
+ [settings]
765
+ theme = "default"
766
+ auto_save = true
767
+
768
+ [keybindings]
769
+ quit = "q"
770
+ save = "ctrl+s"
771
+ `;
772
+ var EXAMPLE_RC = `# Example rc file
773
+ # This will be symlinked to ~/.example-app-rc
774
+ export EXAMPLE_VAR="hello"
775
+ `;
776
+ var EXAMPLE_THEME_YAML = `name: Example Theme
777
+ author: Your Name
778
+ description: A template theme for FormalConf
779
+ version: 1.0.0
780
+
781
+ colors:
782
+ primary: "#5eead4"
783
+ secondary: "#2dd4bf"
784
+ background: "#1a1a2e"
785
+ foreground: "#e4e4e7"
786
+ accent: "#06b6d4"
787
+ `;
788
+ var EXAMPLE_NEOVIM_LUA = `-- Neovim colorscheme configuration
789
+ -- This file is symlinked when the theme is applied
790
+ return {
791
+ {
792
+ "your-colorscheme/nvim",
793
+ name = "example-theme",
794
+ priority = 1000,
795
+ },
796
+ {
797
+ "LazyVim/LazyVim",
798
+ opts = {
799
+ colorscheme = "example-theme",
800
+ },
801
+ },
802
+ }
803
+ `;
804
+ var EXAMPLE_GHOSTTY_CONF = `# Ghostty terminal theme
805
+ # Add your terminal colors here
806
+ theme = example-theme
807
+ `;
808
+ var THEME_README = `# Example Theme
809
+
810
+ This is a template theme for FormalConf.
811
+
812
+ ## Structure
813
+ - \`theme.yaml\` - Theme metadata and color definitions
814
+ - \`neovim.lua\` - Neovim colorscheme config
815
+ - \`ghostty.conf\` - Ghostty terminal theme
816
+ - \`backgrounds/\` - Wallpaper images (optional)
817
+
818
+ ## Creating Your Own Theme
819
+ 1. Copy this directory and rename it
820
+ 2. Update \`theme.yaml\` with your theme info
821
+ 3. Add config files for your applications
822
+ 4. Files are symlinked to ~/.config/formalconf/current/theme/
823
+ `;
824
+ var BACKGROUNDS_README = `# Backgrounds
825
+
826
+ Place wallpaper images here:
827
+ - Supported formats: PNG, JPG
828
+ - These will be available at ~/.config/formalconf/current/backgrounds/
829
+ `;
830
+ var CONFIGS_README = `# Configs Directory
831
+
832
+ This directory contains your stow packages - collections of dotfiles
833
+ that are symlinked to your home directory.
834
+
835
+ ## Creating a Config Package
836
+
837
+ 1. Create a new directory: \`mkdir my-app\`
838
+ 2. Add files mirroring your home directory structure
839
+ 3. Use FormalConf to stow the package
840
+
841
+ ## Example Structure
842
+ \`\`\`
843
+ my-app/
844
+ .config/
845
+ my-app/
846
+ config.toml -> ~/.config/my-app/config.toml
847
+ .my-app-rc -> ~/.my-app-rc
848
+ \`\`\`
849
+
850
+ ## Commands
851
+ - Stow: Creates symlinks from home directory to these files
852
+ - Unstow: Removes the symlinks
853
+ - Status: Shows which packages are stowed
854
+ `;
855
+ var THEMES_README = `# Themes Directory
856
+
857
+ Themes contain application-specific config files that define colors and styling.
858
+
859
+ ## Theme Structure
860
+ \`\`\`
861
+ my-theme/
862
+ theme.yaml # Theme metadata (required)
863
+ neovim.lua # Neovim colorscheme
864
+ ghostty.conf # Terminal theme
865
+ backgrounds/ # Wallpaper images
866
+ \`\`\`
867
+
868
+ ## Applying Themes
869
+ Select a theme in FormalConf to symlink its files to:
870
+ \`~/.config/formalconf/current/theme/\`
871
+
872
+ Your applications should source files from this location.
873
+ `;
874
+ async function installExampleConfig() {
875
+ const dest = join3(CONFIGS_DIR, "example-config");
876
+ if (existsSync2(dest))
877
+ return;
878
+ await ensureDir2(dest);
879
+ await ensureDir2(join3(dest, ".config", "example-app"));
880
+ await writeFile(join3(dest, "README.md"), EXAMPLE_CONFIG_README);
881
+ await writeFile(join3(dest, ".config", "example-app", "config.toml"), EXAMPLE_CONFIG_TOML);
882
+ await writeFile(join3(dest, ".example-app-rc"), EXAMPLE_RC);
883
+ }
884
+ async function installExampleTheme() {
885
+ const dest = join3(THEMES_DIR, "example-theme");
886
+ if (existsSync2(dest))
887
+ return;
888
+ await ensureDir2(dest);
889
+ await ensureDir2(join3(dest, "backgrounds"));
890
+ await writeFile(join3(dest, "theme.yaml"), EXAMPLE_THEME_YAML);
891
+ await writeFile(join3(dest, "neovim.lua"), EXAMPLE_NEOVIM_LUA);
892
+ await writeFile(join3(dest, "ghostty.conf"), EXAMPLE_GHOSTTY_CONF);
893
+ await writeFile(join3(dest, "backgrounds", "README.md"), BACKGROUNDS_README);
894
+ await writeFile(join3(dest, "README.md"), THEME_README);
895
+ }
896
+ async function installReadmes() {
897
+ const configsReadme = join3(CONFIGS_DIR, "README.md");
898
+ const themesReadme = join3(THEMES_DIR, "README.md");
899
+ if (!existsSync2(configsReadme)) {
900
+ await writeFile(configsReadme, CONFIGS_README);
901
+ }
902
+ if (!existsSync2(themesReadme)) {
903
+ await writeFile(themesReadme, THEMES_README);
904
+ }
905
+ }
906
+ var DEFAULT_PKG_CONFIG = {
907
+ config: {
908
+ purge: false,
909
+ purgeInteractive: true,
910
+ autoUpdate: true
911
+ },
912
+ taps: [],
913
+ packages: [],
914
+ casks: [],
915
+ mas: {}
916
+ };
917
+ async function installPkgConfig() {
918
+ if (existsSync2(PKG_CONFIG_PATH))
919
+ return;
920
+ await writeFile(PKG_CONFIG_PATH, JSON.stringify(DEFAULT_PKG_CONFIG, null, 2));
921
+ }
922
+
923
+ // src/components/Onboarding.tsx
725
924
  import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
925
+ function Onboarding({ onComplete }) {
926
+ const [step, setStep] = useState4("welcome");
927
+ const [createdItems, setCreatedItems] = useState4([]);
928
+ const addCreatedItem = (item) => {
929
+ setCreatedItems((prev) => [...prev, item]);
930
+ };
931
+ switch (step) {
932
+ case "welcome":
933
+ return /* @__PURE__ */ jsxDEV9(WelcomeStep, {
934
+ onNext: () => setStep("configs")
935
+ }, undefined, false, undefined, this);
936
+ case "configs":
937
+ return /* @__PURE__ */ jsxDEV9(ConfigsStep, {
938
+ onNext: () => setStep("themes"),
939
+ onCreate: async () => {
940
+ await installExampleConfig();
941
+ addCreatedItem("Example config package");
942
+ }
943
+ }, undefined, false, undefined, this);
944
+ case "themes":
945
+ return /* @__PURE__ */ jsxDEV9(ThemesStep, {
946
+ onNext: () => setStep("packages"),
947
+ onCreate: async () => {
948
+ await installExampleTheme();
949
+ addCreatedItem("Example theme");
950
+ }
951
+ }, undefined, false, undefined, this);
952
+ case "packages":
953
+ return /* @__PURE__ */ jsxDEV9(PackagesStep, {
954
+ onNext: async () => {
955
+ await installPkgConfig();
956
+ setStep("complete");
957
+ }
958
+ }, undefined, false, undefined, this);
959
+ case "complete":
960
+ return /* @__PURE__ */ jsxDEV9(CompleteStep, {
961
+ createdItems,
962
+ onComplete: async () => {
963
+ await installReadmes();
964
+ onComplete();
965
+ }
966
+ }, undefined, false, undefined, this);
967
+ }
968
+ }
969
+ function WelcomeStep({ onNext }) {
970
+ useInput3((_, key) => {
971
+ if (key.return)
972
+ onNext();
973
+ });
974
+ return /* @__PURE__ */ jsxDEV9(Layout, {
975
+ breadcrumb: ["Setup"],
976
+ showFooter: false,
977
+ children: /* @__PURE__ */ jsxDEV9(Panel, {
978
+ title: "Welcome to FormalConf",
979
+ children: /* @__PURE__ */ jsxDEV9(Box9, {
980
+ flexDirection: "column",
981
+ gap: 1,
982
+ children: [
983
+ /* @__PURE__ */ jsxDEV9(Text8, {
984
+ children: "FormalConf helps you manage your dotfiles and system configuration."
985
+ }, undefined, false, undefined, this),
986
+ /* @__PURE__ */ jsxDEV9(Text8, {
987
+ dimColor: true,
988
+ children: "This setup will walk you through the basics and optionally create example files to get you started."
989
+ }, undefined, false, undefined, this),
990
+ /* @__PURE__ */ jsxDEV9(Box9, {
991
+ marginTop: 1,
992
+ children: /* @__PURE__ */ jsxDEV9(Text8, {
993
+ color: colors.primary,
994
+ children: "Press Enter to continue..."
995
+ }, undefined, false, undefined, this)
996
+ }, undefined, false, undefined, this)
997
+ ]
998
+ }, undefined, true, undefined, this)
999
+ }, undefined, false, undefined, this)
1000
+ }, undefined, false, undefined, this);
1001
+ }
1002
+ function ConfigsStep({
1003
+ onNext,
1004
+ onCreate
1005
+ }) {
1006
+ const [isCreating, setIsCreating] = useState4(false);
1007
+ const handleSelect = async (value) => {
1008
+ if (value === "create") {
1009
+ setIsCreating(true);
1010
+ await onCreate();
1011
+ }
1012
+ onNext();
1013
+ };
1014
+ return /* @__PURE__ */ jsxDEV9(Layout, {
1015
+ breadcrumb: ["Setup", "Config Packages"],
1016
+ showFooter: false,
1017
+ children: /* @__PURE__ */ jsxDEV9(Panel, {
1018
+ title: "Config Packages",
1019
+ children: /* @__PURE__ */ jsxDEV9(Box9, {
1020
+ flexDirection: "column",
1021
+ gap: 1,
1022
+ children: [
1023
+ /* @__PURE__ */ jsxDEV9(Text8, {
1024
+ children: "Config packages are directories containing your dotfiles."
1025
+ }, undefined, false, undefined, this),
1026
+ /* @__PURE__ */ jsxDEV9(Text8, {
1027
+ dimColor: true,
1028
+ children: "FormalConf uses GNU Stow to create symlinks from your home directory."
1029
+ }, undefined, false, undefined, this),
1030
+ /* @__PURE__ */ jsxDEV9(Box9, {
1031
+ marginTop: 1,
1032
+ flexDirection: "column",
1033
+ children: [
1034
+ /* @__PURE__ */ jsxDEV9(Text8, {
1035
+ dimColor: true,
1036
+ children: "Example structure:"
1037
+ }, undefined, false, undefined, this),
1038
+ /* @__PURE__ */ jsxDEV9(Text8, {
1039
+ children: " my-config/"
1040
+ }, undefined, false, undefined, this),
1041
+ /* @__PURE__ */ jsxDEV9(Text8, {
1042
+ children: " .config/"
1043
+ }, undefined, false, undefined, this),
1044
+ /* @__PURE__ */ jsxDEV9(Text8, {
1045
+ children: " app/config.toml"
1046
+ }, undefined, false, undefined, this)
1047
+ ]
1048
+ }, undefined, true, undefined, this),
1049
+ /* @__PURE__ */ jsxDEV9(Box9, {
1050
+ marginTop: 1,
1051
+ children: /* @__PURE__ */ jsxDEV9(VimSelect, {
1052
+ options: [
1053
+ { label: "Create example config package", value: "create" },
1054
+ { label: "Skip", value: "skip" }
1055
+ ],
1056
+ onChange: handleSelect,
1057
+ isDisabled: isCreating
1058
+ }, undefined, false, undefined, this)
1059
+ }, undefined, false, undefined, this)
1060
+ ]
1061
+ }, undefined, true, undefined, this)
1062
+ }, undefined, false, undefined, this)
1063
+ }, undefined, false, undefined, this);
1064
+ }
1065
+ function ThemesStep({
1066
+ onNext,
1067
+ onCreate
1068
+ }) {
1069
+ const [isCreating, setIsCreating] = useState4(false);
1070
+ const handleSelect = async (value) => {
1071
+ if (value === "create") {
1072
+ setIsCreating(true);
1073
+ await onCreate();
1074
+ }
1075
+ onNext();
1076
+ };
1077
+ return /* @__PURE__ */ jsxDEV9(Layout, {
1078
+ breadcrumb: ["Setup", "Themes"],
1079
+ showFooter: false,
1080
+ children: /* @__PURE__ */ jsxDEV9(Panel, {
1081
+ title: "Themes",
1082
+ children: /* @__PURE__ */ jsxDEV9(Box9, {
1083
+ flexDirection: "column",
1084
+ gap: 1,
1085
+ children: [
1086
+ /* @__PURE__ */ jsxDEV9(Text8, {
1087
+ children: "Themes contain application configs for colors and styling."
1088
+ }, undefined, false, undefined, this),
1089
+ /* @__PURE__ */ jsxDEV9(Text8, {
1090
+ dimColor: true,
1091
+ children: "When applied, theme files are symlinked to a central location your apps can source from."
1092
+ }, undefined, false, undefined, this),
1093
+ /* @__PURE__ */ jsxDEV9(Box9, {
1094
+ marginTop: 1,
1095
+ flexDirection: "column",
1096
+ children: [
1097
+ /* @__PURE__ */ jsxDEV9(Text8, {
1098
+ dimColor: true,
1099
+ children: "Theme structure:"
1100
+ }, undefined, false, undefined, this),
1101
+ /* @__PURE__ */ jsxDEV9(Text8, {
1102
+ children: " my-theme/"
1103
+ }, undefined, false, undefined, this),
1104
+ /* @__PURE__ */ jsxDEV9(Text8, {
1105
+ children: " theme.yaml"
1106
+ }, undefined, false, undefined, this),
1107
+ /* @__PURE__ */ jsxDEV9(Text8, {
1108
+ children: " neovim.lua"
1109
+ }, undefined, false, undefined, this),
1110
+ /* @__PURE__ */ jsxDEV9(Text8, {
1111
+ children: " backgrounds/"
1112
+ }, undefined, false, undefined, this)
1113
+ ]
1114
+ }, undefined, true, undefined, this),
1115
+ /* @__PURE__ */ jsxDEV9(Box9, {
1116
+ marginTop: 1,
1117
+ children: /* @__PURE__ */ jsxDEV9(VimSelect, {
1118
+ options: [
1119
+ { label: "Create example theme", value: "create" },
1120
+ { label: "Skip", value: "skip" }
1121
+ ],
1122
+ onChange: handleSelect,
1123
+ isDisabled: isCreating
1124
+ }, undefined, false, undefined, this)
1125
+ }, undefined, false, undefined, this)
1126
+ ]
1127
+ }, undefined, true, undefined, this)
1128
+ }, undefined, false, undefined, this)
1129
+ }, undefined, false, undefined, this);
1130
+ }
1131
+ function PackagesStep({ onNext }) {
1132
+ const [isCreating, setIsCreating] = useState4(false);
1133
+ useInput3(async (_, key) => {
1134
+ if (key.return && !isCreating) {
1135
+ setIsCreating(true);
1136
+ await onNext();
1137
+ }
1138
+ });
1139
+ return /* @__PURE__ */ jsxDEV9(Layout, {
1140
+ breadcrumb: ["Setup", "Package Sync"],
1141
+ showFooter: false,
1142
+ children: /* @__PURE__ */ jsxDEV9(Panel, {
1143
+ title: "Package Sync",
1144
+ children: /* @__PURE__ */ jsxDEV9(Box9, {
1145
+ flexDirection: "column",
1146
+ gap: 1,
1147
+ children: [
1148
+ /* @__PURE__ */ jsxDEV9(Text8, {
1149
+ children: "FormalConf can sync your Homebrew packages from a config file."
1150
+ }, undefined, false, undefined, this),
1151
+ /* @__PURE__ */ jsxDEV9(Text8, {
1152
+ dimColor: true,
1153
+ children: "Edit ~/.config/formalconf/pkg-config.json to define your packages, then run Package Sync from the main menu."
1154
+ }, undefined, false, undefined, this),
1155
+ /* @__PURE__ */ jsxDEV9(Box9, {
1156
+ marginTop: 1,
1157
+ children: /* @__PURE__ */ jsxDEV9(Text8, {
1158
+ color: colors.primary,
1159
+ children: "Press Enter to continue..."
1160
+ }, undefined, false, undefined, this)
1161
+ }, undefined, false, undefined, this)
1162
+ ]
1163
+ }, undefined, true, undefined, this)
1164
+ }, undefined, false, undefined, this)
1165
+ }, undefined, false, undefined, this);
1166
+ }
1167
+ function CompleteStep({
1168
+ createdItems,
1169
+ onComplete
1170
+ }) {
1171
+ const [isFinishing, setIsFinishing] = useState4(false);
1172
+ useInput3(async (_, key) => {
1173
+ if (key.return && !isFinishing) {
1174
+ setIsFinishing(true);
1175
+ await onComplete();
1176
+ }
1177
+ });
1178
+ return /* @__PURE__ */ jsxDEV9(Layout, {
1179
+ breadcrumb: ["Setup", "Complete"],
1180
+ showFooter: false,
1181
+ children: /* @__PURE__ */ jsxDEV9(Panel, {
1182
+ title: "Setup Complete",
1183
+ children: /* @__PURE__ */ jsxDEV9(Box9, {
1184
+ flexDirection: "column",
1185
+ gap: 1,
1186
+ children: [
1187
+ /* @__PURE__ */ jsxDEV9(Text8, {
1188
+ color: colors.success,
1189
+ children: "You're all set!"
1190
+ }, undefined, false, undefined, this),
1191
+ createdItems.length > 0 && /* @__PURE__ */ jsxDEV9(Box9, {
1192
+ flexDirection: "column",
1193
+ marginTop: 1,
1194
+ children: [
1195
+ /* @__PURE__ */ jsxDEV9(Text8, {
1196
+ dimColor: true,
1197
+ children: "Created:"
1198
+ }, undefined, false, undefined, this),
1199
+ createdItems.map((item, i) => /* @__PURE__ */ jsxDEV9(Text8, {
1200
+ children: [
1201
+ " - ",
1202
+ item
1203
+ ]
1204
+ }, i, true, undefined, this))
1205
+ ]
1206
+ }, undefined, true, undefined, this),
1207
+ /* @__PURE__ */ jsxDEV9(Box9, {
1208
+ marginTop: 1,
1209
+ children: /* @__PURE__ */ jsxDEV9(Text8, {
1210
+ dimColor: true,
1211
+ children: "README files have been added to help you get started."
1212
+ }, undefined, false, undefined, this)
1213
+ }, undefined, false, undefined, this),
1214
+ /* @__PURE__ */ jsxDEV9(Box9, {
1215
+ marginTop: 1,
1216
+ children: /* @__PURE__ */ jsxDEV9(Text8, {
1217
+ color: colors.primary,
1218
+ children: "Press Enter to start..."
1219
+ }, undefined, false, undefined, this)
1220
+ }, undefined, false, undefined, this)
1221
+ ]
1222
+ }, undefined, true, undefined, this)
1223
+ }, undefined, false, undefined, this)
1224
+ }, undefined, false, undefined, this);
1225
+ }
1226
+
1227
+ // src/components/menus/MainMenu.tsx
1228
+ import { useApp } from "ink";
1229
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
726
1230
  function MainMenu({ onSelect }) {
727
1231
  const { exit } = useApp();
728
- return /* @__PURE__ */ jsxDEV9(Panel, {
1232
+ return /* @__PURE__ */ jsxDEV10(Panel, {
729
1233
  title: "Main Menu",
730
- children: /* @__PURE__ */ jsxDEV9(VimSelect, {
1234
+ children: /* @__PURE__ */ jsxDEV10(VimSelect, {
731
1235
  options: [
732
1236
  { label: "Config Manager", value: "config" },
733
1237
  { label: "Package Sync", value: "packages" },
@@ -746,33 +1250,33 @@ function MainMenu({ onSelect }) {
746
1250
  }
747
1251
 
748
1252
  // 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";
1253
+ import { Box as Box10, Text as Text9, useInput as useInput4 } from "ink";
1254
+ import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
751
1255
  function CommandOutput({
752
1256
  title,
753
1257
  output,
754
1258
  success = true,
755
1259
  onDismiss
756
1260
  }) {
757
- useInput3(() => {
1261
+ useInput4(() => {
758
1262
  onDismiss();
759
1263
  });
760
- return /* @__PURE__ */ jsxDEV10(Panel, {
1264
+ return /* @__PURE__ */ jsxDEV11(Panel, {
761
1265
  title,
762
1266
  borderColor: success ? colors.success : colors.error,
763
1267
  children: [
764
- output && /* @__PURE__ */ jsxDEV10(Box9, {
1268
+ output && /* @__PURE__ */ jsxDEV11(Box10, {
765
1269
  flexDirection: "column",
766
1270
  marginBottom: 1,
767
- children: /* @__PURE__ */ jsxDEV10(Text8, {
1271
+ children: /* @__PURE__ */ jsxDEV11(Text9, {
768
1272
  children: output
769
1273
  }, undefined, false, undefined, this)
770
1274
  }, undefined, false, undefined, this),
771
- /* @__PURE__ */ jsxDEV10(Text8, {
1275
+ /* @__PURE__ */ jsxDEV11(Text9, {
772
1276
  color: success ? colors.success : colors.error,
773
1277
  children: success ? "Done" : "Failed"
774
1278
  }, undefined, false, undefined, this),
775
- /* @__PURE__ */ jsxDEV10(Text8, {
1279
+ /* @__PURE__ */ jsxDEV11(Text9, {
776
1280
  dimColor: true,
777
1281
  children: "Press any key to continue..."
778
1282
  }, undefined, false, undefined, this)
@@ -782,22 +1286,22 @@ function CommandOutput({
782
1286
 
783
1287
  // src/components/LoadingPanel.tsx
784
1288
  import { Spinner } from "@inkjs/ui";
785
- import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
1289
+ import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
786
1290
  function LoadingPanel({ title, label = "Processing..." }) {
787
- return /* @__PURE__ */ jsxDEV11(Panel, {
1291
+ return /* @__PURE__ */ jsxDEV12(Panel, {
788
1292
  title,
789
- children: /* @__PURE__ */ jsxDEV11(Spinner, {
1293
+ children: /* @__PURE__ */ jsxDEV12(Spinner, {
790
1294
  label
791
1295
  }, undefined, false, undefined, this)
792
1296
  }, undefined, false, undefined, this);
793
1297
  }
794
1298
 
795
1299
  // src/hooks/useMenuAction.ts
796
- import { useState as useState4, useCallback } from "react";
1300
+ import { useState as useState5, useCallback } from "react";
797
1301
  function useMenuAction() {
798
- const [state, setState] = useState4("menu");
799
- const [output, setOutput] = useState4("");
800
- const [success, setSuccess] = useState4(true);
1302
+ const [state, setState] = useState5("menu");
1303
+ const [output, setOutput] = useState5("");
1304
+ const [success, setSuccess] = useState5(true);
801
1305
  const execute = useCallback(async (action) => {
802
1306
  setState("running");
803
1307
  const result = await action();
@@ -820,12 +1324,12 @@ function useMenuAction() {
820
1324
  }
821
1325
 
822
1326
  // src/hooks/useBackNavigation.ts
823
- import { useInput as useInput4 } from "ink";
1327
+ import { useInput as useInput5 } from "ink";
824
1328
  function useBackNavigation({
825
1329
  enabled = true,
826
1330
  onBack
827
1331
  }) {
828
- useInput4((input, key) => {
1332
+ useInput5((input, key) => {
829
1333
  if (enabled && (key.escape || key.leftArrow || input === "h")) {
830
1334
  onBack();
831
1335
  }
@@ -834,7 +1338,7 @@ function useBackNavigation({
834
1338
 
835
1339
  // src/cli/config-manager.ts
836
1340
  import { parseArgs } from "util";
837
- import { readdirSync as readdirSync2, existsSync as existsSync2, lstatSync as lstatSync2, readlinkSync as readlinkSync2 } from "fs";
1341
+ import { readdirSync as readdirSync2, existsSync as existsSync3, lstatSync as lstatSync2, readlinkSync as readlinkSync2 } from "fs";
838
1342
  var colors2 = {
839
1343
  red: "\x1B[0;31m",
840
1344
  green: "\x1B[0;32m",
@@ -859,12 +1363,12 @@ function listPackages() {
859
1363
  }
860
1364
  function checkPackageStowed(packageName) {
861
1365
  const packageDir = `${CONFIGS_DIR}/${packageName}`;
862
- if (!existsSync2(packageDir))
1366
+ if (!existsSync3(packageDir))
863
1367
  return false;
864
1368
  const entries = readdirSync2(packageDir, { withFileTypes: true });
865
1369
  for (const entry of entries) {
866
1370
  const targetPath = `${HOME_DIR}/${entry.name}`;
867
- if (!existsSync2(targetPath))
1371
+ if (!existsSync3(targetPath))
868
1372
  return false;
869
1373
  try {
870
1374
  const stat = lstatSync2(targetPath);
@@ -1130,7 +1634,7 @@ if (isMainModule) {
1130
1634
  }
1131
1635
 
1132
1636
  // src/components/menus/ConfigMenu.tsx
1133
- import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
1637
+ import { jsxDEV as jsxDEV13 } from "react/jsx-dev-runtime";
1134
1638
  function ConfigMenu({ onBack }) {
1135
1639
  const { state, output, success, isRunning, isResult, execute, reset } = useMenuAction();
1136
1640
  useBackNavigation({ enabled: state === "menu", onBack });
@@ -1142,21 +1646,21 @@ function ConfigMenu({ onBack }) {
1142
1646
  await execute(() => runConfigManager([action]));
1143
1647
  };
1144
1648
  if (isRunning) {
1145
- return /* @__PURE__ */ jsxDEV12(LoadingPanel, {
1649
+ return /* @__PURE__ */ jsxDEV13(LoadingPanel, {
1146
1650
  title: "Config Manager"
1147
1651
  }, undefined, false, undefined, this);
1148
1652
  }
1149
1653
  if (isResult) {
1150
- return /* @__PURE__ */ jsxDEV12(CommandOutput, {
1654
+ return /* @__PURE__ */ jsxDEV13(CommandOutput, {
1151
1655
  title: "Config Manager",
1152
1656
  output,
1153
1657
  success,
1154
1658
  onDismiss: reset
1155
1659
  }, undefined, false, undefined, this);
1156
1660
  }
1157
- return /* @__PURE__ */ jsxDEV12(Panel, {
1661
+ return /* @__PURE__ */ jsxDEV13(Panel, {
1158
1662
  title: "Config Manager",
1159
- children: /* @__PURE__ */ jsxDEV12(VimSelect, {
1663
+ children: /* @__PURE__ */ jsxDEV13(VimSelect, {
1160
1664
  options: [
1161
1665
  { label: "Stow all packages", value: "stow-all" },
1162
1666
  { label: "Unstow all packages", value: "unstow-all" },
@@ -1170,13 +1674,13 @@ function ConfigMenu({ onBack }) {
1170
1674
  }
1171
1675
 
1172
1676
  // 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";
1677
+ import { useState as useState8, useCallback as useCallback2, useMemo as useMemo2, useRef } from "react";
1678
+ import { Box as Box14, Text as Text13, useInput as useInput9 } from "ink";
1175
1679
 
1176
1680
  // 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";
1681
+ import { useState as useState6, useEffect as useEffect3, useMemo } from "react";
1682
+ import { Box as Box11, Text as Text10, useInput as useInput6 } from "ink";
1683
+ import { jsxDEV as jsxDEV14 } from "react/jsx-dev-runtime";
1180
1684
  function ScrollableLog({
1181
1685
  lines,
1182
1686
  maxHeight,
@@ -1185,8 +1689,8 @@ function ScrollableLog({
1185
1689
  }) {
1186
1690
  const { rows } = useTerminalSize();
1187
1691
  const visibleLines = maxHeight || Math.max(5, rows - 12);
1188
- const [scrollOffset, setScrollOffset] = useState5(0);
1189
- const [isAutoScrolling, setIsAutoScrolling] = useState5(autoScroll);
1692
+ const [scrollOffset, setScrollOffset] = useState6(0);
1693
+ const [isAutoScrolling, setIsAutoScrolling] = useState6(autoScroll);
1190
1694
  const totalLines = lines.length;
1191
1695
  const maxOffset = Math.max(0, totalLines - visibleLines);
1192
1696
  useEffect3(() => {
@@ -1194,7 +1698,7 @@ function ScrollableLog({
1194
1698
  setScrollOffset(maxOffset);
1195
1699
  }
1196
1700
  }, [totalLines, maxOffset, isAutoScrolling]);
1197
- useInput5((input, key) => {
1701
+ useInput6((input, key) => {
1198
1702
  if (key.downArrow || input === "j") {
1199
1703
  setIsAutoScrolling(false);
1200
1704
  setScrollOffset((prev) => Math.min(prev + 1, maxOffset));
@@ -1217,10 +1721,10 @@ function ScrollableLog({
1217
1721
  }, [lines, scrollOffset, visibleLines]);
1218
1722
  const showScrollUp = scrollOffset > 0;
1219
1723
  const showScrollDown = scrollOffset < maxOffset;
1220
- return /* @__PURE__ */ jsxDEV13(Box10, {
1724
+ return /* @__PURE__ */ jsxDEV14(Box11, {
1221
1725
  flexDirection: "column",
1222
1726
  children: [
1223
- showScrollHint && showScrollUp && /* @__PURE__ */ jsxDEV13(Text9, {
1727
+ showScrollHint && showScrollUp && /* @__PURE__ */ jsxDEV14(Text10, {
1224
1728
  dimColor: true,
1225
1729
  children: [
1226
1730
  " ↑ ",
@@ -1229,15 +1733,15 @@ function ScrollableLog({
1229
1733
  scrollOffset !== 1 ? "s" : ""
1230
1734
  ]
1231
1735
  }, undefined, true, undefined, this),
1232
- /* @__PURE__ */ jsxDEV13(Box10, {
1736
+ /* @__PURE__ */ jsxDEV14(Box11, {
1233
1737
  flexDirection: "column",
1234
1738
  height: visibleLines,
1235
1739
  overflow: "hidden",
1236
- children: visibleContent.map((line, i) => /* @__PURE__ */ jsxDEV13(Text9, {
1740
+ children: visibleContent.map((line, i) => /* @__PURE__ */ jsxDEV14(Text10, {
1237
1741
  children: line
1238
1742
  }, scrollOffset + i, false, undefined, this))
1239
1743
  }, undefined, false, undefined, this),
1240
- showScrollHint && showScrollDown && /* @__PURE__ */ jsxDEV13(Text9, {
1744
+ showScrollHint && showScrollDown && /* @__PURE__ */ jsxDEV14(Text10, {
1241
1745
  dimColor: true,
1242
1746
  children: [
1243
1747
  " ↓ ",
@@ -1246,7 +1750,7 @@ function ScrollableLog({
1246
1750
  maxOffset - scrollOffset !== 1 ? "s" : ""
1247
1751
  ]
1248
1752
  }, undefined, true, undefined, this),
1249
- showScrollHint && totalLines > visibleLines && /* @__PURE__ */ jsxDEV13(Text9, {
1753
+ showScrollHint && totalLines > visibleLines && /* @__PURE__ */ jsxDEV14(Text10, {
1250
1754
  dimColor: true,
1251
1755
  children: [
1252
1756
  "j/k scroll • g top • G bottom ",
@@ -1258,29 +1762,29 @@ function ScrollableLog({
1258
1762
  }
1259
1763
 
1260
1764
  // 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";
1765
+ import { Box as Box12, Text as Text11, useInput as useInput7 } from "ink";
1766
+ import { jsxDEV as jsxDEV15 } from "react/jsx-dev-runtime";
1263
1767
  function PromptInput({
1264
1768
  question,
1265
1769
  options = ["y", "n"],
1266
1770
  onAnswer
1267
1771
  }) {
1268
- useInput6((input) => {
1772
+ useInput7((input) => {
1269
1773
  const lower = input.toLowerCase();
1270
1774
  if (options.includes(lower)) {
1271
1775
  onAnswer(lower);
1272
1776
  }
1273
1777
  });
1274
- return /* @__PURE__ */ jsxDEV14(Box11, {
1778
+ return /* @__PURE__ */ jsxDEV15(Box12, {
1275
1779
  marginTop: 1,
1276
1780
  borderStyle: "single",
1277
1781
  borderColor: colors.accent,
1278
1782
  paddingX: 1,
1279
- children: /* @__PURE__ */ jsxDEV14(Text10, {
1783
+ children: /* @__PURE__ */ jsxDEV15(Text11, {
1280
1784
  children: [
1281
1785
  question,
1282
1786
  " ",
1283
- /* @__PURE__ */ jsxDEV14(Text10, {
1787
+ /* @__PURE__ */ jsxDEV15(Text11, {
1284
1788
  color: colors.accent,
1285
1789
  children: [
1286
1790
  "[",
@@ -1288,7 +1792,7 @@ function PromptInput({
1288
1792
  "]"
1289
1793
  ]
1290
1794
  }, undefined, true, undefined, this),
1291
- /* @__PURE__ */ jsxDEV14(Text10, {
1795
+ /* @__PURE__ */ jsxDEV15(Text11, {
1292
1796
  dimColor: true,
1293
1797
  children: ": "
1294
1798
  }, undefined, false, undefined, this)
@@ -1297,11 +1801,147 @@ function PromptInput({
1297
1801
  }, undefined, false, undefined, this);
1298
1802
  }
1299
1803
 
1804
+ // src/components/OrphanTable.tsx
1805
+ import { useState as useState7 } from "react";
1806
+ import { Box as Box13, Text as Text12, useInput as useInput8 } from "ink";
1807
+ import { jsxDEV as jsxDEV16 } from "react/jsx-dev-runtime";
1808
+ function OrphanTable({ result, onAction, onDismiss }) {
1809
+ const [selectedIndex, setSelectedIndex] = useState7(0);
1810
+ const { orphans } = result;
1811
+ useInput8((input, key) => {
1812
+ if (orphans.length > 0) {
1813
+ if (input === "j" || key.downArrow) {
1814
+ setSelectedIndex((i) => Math.min(i + 1, orphans.length - 1));
1815
+ }
1816
+ if (input === "k" || key.upArrow) {
1817
+ setSelectedIndex((i) => Math.max(i - 1, 0));
1818
+ }
1819
+ if (input === "a") {
1820
+ onAction("add", orphans[selectedIndex]);
1821
+ }
1822
+ if (input === "x") {
1823
+ onAction("uninstall", orphans[selectedIndex]);
1824
+ }
1825
+ }
1826
+ if (key.escape || input === "h" || key.leftArrow) {
1827
+ onDismiss();
1828
+ }
1829
+ });
1830
+ const borderColor = orphans.length > 0 ? colors.warning : colors.success;
1831
+ return /* @__PURE__ */ jsxDEV16(Panel, {
1832
+ title: "Orphaned Packages",
1833
+ borderColor,
1834
+ children: [
1835
+ /* @__PURE__ */ jsxDEV16(Box13, {
1836
+ marginBottom: 1,
1837
+ children: /* @__PURE__ */ jsxDEV16(Text12, {
1838
+ dimColor: true,
1839
+ children: [
1840
+ "Config: ",
1841
+ result.configFormulas,
1842
+ " formulas, ",
1843
+ result.configCasks,
1844
+ " casks | Installed: ",
1845
+ result.installedLeaves,
1846
+ " leaves, ",
1847
+ result.installedCasks,
1848
+ " ",
1849
+ "casks"
1850
+ ]
1851
+ }, undefined, true, undefined, this)
1852
+ }, undefined, false, undefined, this),
1853
+ orphans.length === 0 ? /* @__PURE__ */ jsxDEV16(Box13, {
1854
+ flexDirection: "column",
1855
+ children: [
1856
+ /* @__PURE__ */ jsxDEV16(Text12, {
1857
+ color: colors.success,
1858
+ children: "No orphaned packages found!"
1859
+ }, undefined, false, undefined, this),
1860
+ /* @__PURE__ */ jsxDEV16(Text12, {
1861
+ dimColor: true,
1862
+ children: "All installed packages are in your config."
1863
+ }, undefined, false, undefined, this)
1864
+ ]
1865
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV16(Box13, {
1866
+ flexDirection: "column",
1867
+ children: [
1868
+ /* @__PURE__ */ jsxDEV16(Text12, {
1869
+ color: colors.warning,
1870
+ children: [
1871
+ "Found ",
1872
+ orphans.length,
1873
+ " orphaned package",
1874
+ orphans.length !== 1 ? "s" : "",
1875
+ ":"
1876
+ ]
1877
+ }, undefined, true, undefined, this),
1878
+ /* @__PURE__ */ jsxDEV16(Box13, {
1879
+ marginTop: 1,
1880
+ flexDirection: "column",
1881
+ children: [
1882
+ /* @__PURE__ */ jsxDEV16(Box13, {
1883
+ children: [
1884
+ /* @__PURE__ */ jsxDEV16(Text12, {
1885
+ bold: true,
1886
+ children: [
1887
+ " ",
1888
+ "Name"
1889
+ ]
1890
+ }, undefined, true, undefined, this),
1891
+ /* @__PURE__ */ jsxDEV16(Text12, {
1892
+ bold: true,
1893
+ children: [
1894
+ " ".repeat(28),
1895
+ "Type"
1896
+ ]
1897
+ }, undefined, true, undefined, this)
1898
+ ]
1899
+ }, undefined, true, undefined, this),
1900
+ /* @__PURE__ */ jsxDEV16(Text12, {
1901
+ color: colors.border,
1902
+ children: "─".repeat(50)
1903
+ }, undefined, false, undefined, this),
1904
+ orphans.map((pkg, i) => {
1905
+ const isSelected = i === selectedIndex;
1906
+ return /* @__PURE__ */ jsxDEV16(Box13, {
1907
+ children: [
1908
+ /* @__PURE__ */ jsxDEV16(Text12, {
1909
+ color: isSelected ? colors.primary : undefined,
1910
+ children: [
1911
+ isSelected ? "❯ " : " ",
1912
+ pkg.name.padEnd(30)
1913
+ ]
1914
+ }, undefined, true, undefined, this),
1915
+ /* @__PURE__ */ jsxDEV16(Text12, {
1916
+ color: pkg.type === "formula" ? colors.info : colors.accent,
1917
+ children: pkg.type
1918
+ }, undefined, false, undefined, this)
1919
+ ]
1920
+ }, `${pkg.type}-${pkg.name}`, true, undefined, this);
1921
+ })
1922
+ ]
1923
+ }, undefined, true, undefined, this)
1924
+ ]
1925
+ }, undefined, true, undefined, this),
1926
+ /* @__PURE__ */ jsxDEV16(Box13, {
1927
+ marginTop: 1,
1928
+ children: orphans.length > 0 ? /* @__PURE__ */ jsxDEV16(Text12, {
1929
+ dimColor: true,
1930
+ children: "j/k navigate | a add to config | x uninstall | esc/h back | q quit"
1931
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV16(Text12, {
1932
+ dimColor: true,
1933
+ children: "esc/h back | q quit"
1934
+ }, undefined, false, undefined, this)
1935
+ }, undefined, false, undefined, this)
1936
+ ]
1937
+ }, undefined, true, undefined, this);
1938
+ }
1939
+
1300
1940
  // src/cli/pkg-sync.ts
1301
1941
  import { parseArgs as parseArgs2 } from "util";
1302
1942
 
1303
1943
  // src/lib/config.ts
1304
- import { existsSync as existsSync3 } from "fs";
1944
+ import { existsSync as existsSync4 } from "fs";
1305
1945
  var DEFAULT_CONFIG = {
1306
1946
  config: {
1307
1947
  purge: false,
@@ -1316,7 +1956,7 @@ var DEFAULT_CONFIG = {
1316
1956
  async function loadPkgConfig(path) {
1317
1957
  await ensureConfigDir();
1318
1958
  const configPath = path || PKG_CONFIG_PATH;
1319
- if (!existsSync3(configPath)) {
1959
+ if (!existsSync4(configPath)) {
1320
1960
  await savePkgConfig(DEFAULT_CONFIG, configPath);
1321
1961
  return DEFAULT_CONFIG;
1322
1962
  }
@@ -1328,7 +1968,7 @@ async function savePkgConfig(config, path) {
1328
1968
  await writeFile(configPath, JSON.stringify(config, null, 2));
1329
1969
  }
1330
1970
  async function loadPkgLock() {
1331
- if (!existsSync3(PKG_LOCK_PATH)) {
1971
+ if (!existsSync4(PKG_LOCK_PATH)) {
1332
1972
  return null;
1333
1973
  }
1334
1974
  return readJson(PKG_LOCK_PATH);
@@ -2109,21 +2749,88 @@ if (isMainModule3) {
2109
2749
  main3().catch(console.error);
2110
2750
  }
2111
2751
 
2752
+ // src/lib/orphan-detector.ts
2753
+ function getPackageName(fullName) {
2754
+ const parts = fullName.split("/");
2755
+ return parts[parts.length - 1];
2756
+ }
2757
+ async function detectOrphanedPackages() {
2758
+ const config = await loadPkgConfig();
2759
+ const leavesResult = await exec(["brew", "leaves"]);
2760
+ const installedLeaves = leavesResult.success ? leavesResult.stdout.split(`
2761
+ `).filter(Boolean) : [];
2762
+ const casksResult = await exec(["brew", "list", "--cask"]);
2763
+ const installedCasks = casksResult.success ? casksResult.stdout.split(`
2764
+ `).filter(Boolean) : [];
2765
+ const orphans = [];
2766
+ const configPackages = config.packages.map((pkg) => ({
2767
+ full: pkg,
2768
+ short: getPackageName(pkg)
2769
+ }));
2770
+ for (const installed of installedLeaves) {
2771
+ const installedShort = getPackageName(installed);
2772
+ const isInConfig = configPackages.some((cfg) => installed === cfg.full || installed === cfg.short || installedShort === cfg.full || installedShort === cfg.short || installed.endsWith(`/${cfg.short}`));
2773
+ if (!isInConfig) {
2774
+ orphans.push({ name: installed, type: "formula" });
2775
+ }
2776
+ }
2777
+ const configCasksSet = new Set(config.casks);
2778
+ for (const cask of installedCasks) {
2779
+ if (!configCasksSet.has(cask)) {
2780
+ orphans.push({ name: cask, type: "cask" });
2781
+ }
2782
+ }
2783
+ orphans.sort((a, b) => {
2784
+ if (a.type !== b.type)
2785
+ return a.type === "formula" ? -1 : 1;
2786
+ return a.name.localeCompare(b.name);
2787
+ });
2788
+ return {
2789
+ orphans,
2790
+ configFormulas: config.packages.length,
2791
+ configCasks: config.casks.length,
2792
+ installedLeaves: installedLeaves.length,
2793
+ installedCasks: installedCasks.length
2794
+ };
2795
+ }
2796
+ async function addToConfig(pkg) {
2797
+ const config = await loadPkgConfig();
2798
+ if (pkg.type === "formula") {
2799
+ if (!config.packages.includes(pkg.name)) {
2800
+ config.packages.push(pkg.name);
2801
+ config.packages.sort();
2802
+ }
2803
+ } else {
2804
+ if (!config.casks.includes(pkg.name)) {
2805
+ config.casks.push(pkg.name);
2806
+ config.casks.sort();
2807
+ }
2808
+ }
2809
+ await savePkgConfig(config);
2810
+ }
2811
+ async function uninstallPackage(pkg) {
2812
+ const cmd = pkg.type === "cask" ? ["brew", "uninstall", "--cask", pkg.name] : ["brew", "uninstall", pkg.name];
2813
+ const result = await exec(cmd);
2814
+ return result.success;
2815
+ }
2816
+
2112
2817
  // src/components/menus/PackageMenu.tsx
2113
- import { jsxDEV as jsxDEV15 } from "react/jsx-dev-runtime";
2818
+ import { jsxDEV as jsxDEV17 } from "react/jsx-dev-runtime";
2114
2819
  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);
2820
+ const [state, setState] = useState8("menu");
2821
+ const [lines, setLines] = useState8([]);
2822
+ const [output, setOutput] = useState8("");
2823
+ const [isStreamingOp, setIsStreamingOp] = useState8(true);
2824
+ const [pendingPrompt, setPendingPrompt] = useState8(null);
2825
+ const [success, setSuccess] = useState8(true);
2826
+ const [orphanResult, setOrphanResult] = useState8(null);
2827
+ const [isOrphanView, setIsOrphanView] = useState8(false);
2121
2828
  const isRunningRef = useRef(false);
2122
- useInput7((input, key) => {
2829
+ useInput9((input, key) => {
2123
2830
  if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
2124
2831
  onBack();
2125
2832
  }
2126
- if (state === "result") {
2833
+ if (state === "result" && !isOrphanView) {
2127
2834
  setState("menu");
2128
2835
  setLines([]);
2129
2836
  }
@@ -2185,6 +2892,13 @@ function PackageMenu({ onBack }) {
2185
2892
  result = await runPkgLock(["status"]);
2186
2893
  setOutput(result.output);
2187
2894
  break;
2895
+ case "orphans":
2896
+ setIsStreamingOp(false);
2897
+ setIsOrphanView(true);
2898
+ const orphanData = await detectOrphanedPackages();
2899
+ setOrphanResult(orphanData);
2900
+ result = { output: "", success: true };
2901
+ break;
2188
2902
  default:
2189
2903
  setIsStreamingOp(false);
2190
2904
  result = { output: "Unknown action", success: false };
@@ -2196,17 +2910,17 @@ function PackageMenu({ onBack }) {
2196
2910
  };
2197
2911
  if (state === "running") {
2198
2912
  if (!isStreamingOp) {
2199
- return /* @__PURE__ */ jsxDEV15(LoadingPanel, {
2913
+ return /* @__PURE__ */ jsxDEV17(LoadingPanel, {
2200
2914
  title: "Package Sync"
2201
2915
  }, undefined, false, undefined, this);
2202
2916
  }
2203
- return /* @__PURE__ */ jsxDEV15(Panel, {
2917
+ return /* @__PURE__ */ jsxDEV17(Panel, {
2204
2918
  title: "Package Sync",
2205
2919
  children: [
2206
- /* @__PURE__ */ jsxDEV15(ScrollableLog, {
2920
+ /* @__PURE__ */ jsxDEV17(ScrollableLog, {
2207
2921
  lines
2208
2922
  }, undefined, false, undefined, this),
2209
- pendingPrompt && /* @__PURE__ */ jsxDEV15(PromptInput, {
2923
+ pendingPrompt && /* @__PURE__ */ jsxDEV17(PromptInput, {
2210
2924
  question: pendingPrompt.question,
2211
2925
  options: pendingPrompt.options,
2212
2926
  onAnswer: handlePromptAnswer
@@ -2215,39 +2929,58 @@ function PackageMenu({ onBack }) {
2215
2929
  }, undefined, true, undefined, this);
2216
2930
  }
2217
2931
  if (state === "result") {
2932
+ if (isOrphanView && orphanResult) {
2933
+ const handleOrphanAction = async (action, pkg) => {
2934
+ if (action === "add") {
2935
+ await addToConfig(pkg);
2936
+ } else {
2937
+ await uninstallPackage(pkg);
2938
+ }
2939
+ const updated = await detectOrphanedPackages();
2940
+ setOrphanResult(updated);
2941
+ };
2942
+ return /* @__PURE__ */ jsxDEV17(OrphanTable, {
2943
+ result: orphanResult,
2944
+ onAction: handleOrphanAction,
2945
+ onDismiss: () => {
2946
+ setIsOrphanView(false);
2947
+ setState("menu");
2948
+ }
2949
+ }, undefined, false, undefined, this);
2950
+ }
2218
2951
  if (!isStreamingOp) {
2219
- return /* @__PURE__ */ jsxDEV15(CommandOutput, {
2952
+ return /* @__PURE__ */ jsxDEV17(CommandOutput, {
2220
2953
  title: "Package Sync",
2221
2954
  output,
2222
2955
  success,
2223
2956
  onDismiss: () => setState("menu")
2224
2957
  }, undefined, false, undefined, this);
2225
2958
  }
2226
- return /* @__PURE__ */ jsxDEV15(Panel, {
2959
+ return /* @__PURE__ */ jsxDEV17(Panel, {
2227
2960
  title: "Package Sync",
2228
2961
  borderColor: success ? colors.success : colors.error,
2229
2962
  children: [
2230
- /* @__PURE__ */ jsxDEV15(ScrollableLog, {
2963
+ /* @__PURE__ */ jsxDEV17(ScrollableLog, {
2231
2964
  lines,
2232
2965
  autoScroll: false
2233
2966
  }, undefined, false, undefined, this),
2234
- /* @__PURE__ */ jsxDEV15(Box12, {
2967
+ /* @__PURE__ */ jsxDEV17(Box14, {
2235
2968
  marginTop: 1,
2236
- children: /* @__PURE__ */ jsxDEV15(Text11, {
2969
+ children: /* @__PURE__ */ jsxDEV17(Text13, {
2237
2970
  color: success ? colors.success : colors.error,
2238
2971
  children: success ? "Done" : "Failed"
2239
2972
  }, undefined, false, undefined, this)
2240
2973
  }, undefined, false, undefined, this),
2241
- /* @__PURE__ */ jsxDEV15(Text11, {
2974
+ /* @__PURE__ */ jsxDEV17(Text13, {
2242
2975
  dimColor: true,
2243
2976
  children: "Press any key to continue..."
2244
2977
  }, undefined, false, undefined, this)
2245
2978
  ]
2246
2979
  }, undefined, true, undefined, this);
2247
2980
  }
2248
- return /* @__PURE__ */ jsxDEV15(Panel, {
2981
+ return /* @__PURE__ */ jsxDEV17(Panel, {
2249
2982
  title: "Package Sync",
2250
- children: /* @__PURE__ */ jsxDEV15(VimSelect, {
2983
+ children: /* @__PURE__ */ jsxDEV17(VimSelect, {
2251
2984
  options: [
2252
2985
  { label: "Sync packages", value: "sync" },
2253
2986
  { label: "Sync with purge", value: "sync-purge" },
@@ -2255,6 +2988,7 @@ function PackageMenu({ onBack }) {
2255
2988
  { label: "Upgrade interactive", value: "upgrade-interactive" },
2256
2989
  { label: "Update lockfile", value: "lock-update" },
2257
2990
  { label: "Lockfile status", value: "lock-status" },
2991
+ { label: "Find orphaned packages", value: "orphans" },
2258
2992
  { label: "Back", value: "back" }
2259
2993
  ],
2260
2994
  onChange: handleAction
@@ -2263,14 +2997,14 @@ function PackageMenu({ onBack }) {
2263
2997
  }
2264
2998
 
2265
2999
  // 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";
3000
+ import { useState as useState10, useEffect as useEffect5, useMemo as useMemo4 } from "react";
3001
+ import { Box as Box16, Text as Text15 } from "ink";
3002
+ import { existsSync as existsSync7, readdirSync as readdirSync5 } from "fs";
3003
+ import { join as join6 } from "path";
2270
3004
 
2271
3005
  // src/components/ThemeCard.tsx
2272
- import { Box as Box13, Text as Text12 } from "ink";
2273
- import { jsxDEV as jsxDEV16 } from "react/jsx-dev-runtime";
3006
+ import { Box as Box15, Text as Text14 } from "ink";
3007
+ import { jsxDEV as jsxDEV18 } from "react/jsx-dev-runtime";
2274
3008
  function ThemeCard({ theme, isSelected, width }) {
2275
3009
  const borderColor = isSelected ? colors.accent : colors.border;
2276
3010
  const nameColor = isSelected ? colors.primary : colors.text;
@@ -2280,25 +3014,25 @@ function ThemeCard({ theme, isSelected, width }) {
2280
3014
  if (theme.isLightMode)
2281
3015
  indicators.push("light");
2282
3016
  const indicatorText = indicators.length > 0 ? ` [${indicators.join(" ")}]` : "";
2283
- return /* @__PURE__ */ jsxDEV16(Box13, {
3017
+ return /* @__PURE__ */ jsxDEV18(Box15, {
2284
3018
  flexDirection: "column",
2285
3019
  width,
2286
3020
  borderStyle: borderStyles.panel,
2287
3021
  borderColor,
2288
3022
  paddingX: 1,
2289
- children: /* @__PURE__ */ jsxDEV16(Box13, {
3023
+ children: /* @__PURE__ */ jsxDEV18(Box15, {
2290
3024
  children: [
2291
- /* @__PURE__ */ jsxDEV16(Text12, {
3025
+ /* @__PURE__ */ jsxDEV18(Text14, {
2292
3026
  color: isSelected ? colors.accent : colors.primaryDim,
2293
3027
  children: isSelected ? "● " : " "
2294
3028
  }, undefined, false, undefined, this),
2295
- /* @__PURE__ */ jsxDEV16(Text12, {
3029
+ /* @__PURE__ */ jsxDEV18(Text14, {
2296
3030
  color: nameColor,
2297
3031
  bold: true,
2298
3032
  wrap: "truncate",
2299
3033
  children: theme.name
2300
3034
  }, undefined, false, undefined, this),
2301
- /* @__PURE__ */ jsxDEV16(Text12, {
3035
+ /* @__PURE__ */ jsxDEV18(Text14, {
2302
3036
  color: colors.primaryDim,
2303
3037
  children: indicatorText
2304
3038
  }, undefined, false, undefined, this)
@@ -2308,8 +3042,8 @@ function ThemeCard({ theme, isSelected, width }) {
2308
3042
  }
2309
3043
 
2310
3044
  // src/hooks/useThemeGrid.ts
2311
- import { useState as useState7, useEffect as useEffect4 } from "react";
2312
- import { useInput as useInput8 } from "ink";
3045
+ import { useState as useState9, useEffect as useEffect4 } from "react";
3046
+ import { useInput as useInput10 } from "ink";
2313
3047
  function useThemeGrid({
2314
3048
  itemCount,
2315
3049
  cardHeight = 3,
@@ -2320,8 +3054,8 @@ function useThemeGrid({
2320
3054
  enabled = true
2321
3055
  }) {
2322
3056
  const { columns, rows } = useTerminalSize();
2323
- const [selectedIndex, setSelectedIndex] = useState7(0);
2324
- const [scrollOffset, setScrollOffset] = useState7(0);
3057
+ const [selectedIndex, setSelectedIndex] = useState9(0);
3058
+ const [scrollOffset, setScrollOffset] = useState9(0);
2325
3059
  const availableWidth = columns - 6;
2326
3060
  const cardsPerRow = Math.max(1, Math.floor(availableWidth / minCardWidth));
2327
3061
  const cardWidth = Math.floor(availableWidth / cardsPerRow);
@@ -2336,7 +3070,7 @@ function useThemeGrid({
2336
3070
  setScrollOffset(selectedRow - visibleRows + 1);
2337
3071
  }
2338
3072
  }, [selectedRow, scrollOffset, visibleRows]);
2339
- useInput8((input, key) => {
3073
+ useInput10((input, key) => {
2340
3074
  if (!enabled)
2341
3075
  return;
2342
3076
  if (key.escape && onBack) {
@@ -2387,8 +3121,8 @@ function useThemeGrid({
2387
3121
  }
2388
3122
 
2389
3123
  // src/lib/theme-parser.ts
2390
- import { existsSync as existsSync4, readdirSync as readdirSync3 } from "fs";
2391
- import { join as join3 } from "path";
3124
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
3125
+ import { join as join4 } from "path";
2392
3126
  function parseYaml(content) {
2393
3127
  const result = {};
2394
3128
  const lines = content.split(`
@@ -2419,8 +3153,8 @@ function parseYaml(content) {
2419
3153
  return result;
2420
3154
  }
2421
3155
  async function parseThemeMetadata(themePath) {
2422
- const yamlPath = join3(themePath, "theme.yaml");
2423
- if (!existsSync4(yamlPath)) {
3156
+ const yamlPath = join4(themePath, "theme.yaml");
3157
+ if (!existsSync5(yamlPath)) {
2424
3158
  return;
2425
3159
  }
2426
3160
  try {
@@ -2442,7 +3176,7 @@ function parseThemeFiles(themePath) {
2442
3176
  const entries = readdirSync3(themePath, { withFileTypes: true });
2443
3177
  return entries.filter((e) => e.isFile() && !e.name.startsWith(".") && e.name !== "theme.yaml" && e.name !== "light.mode").map((e) => ({
2444
3178
  name: e.name,
2445
- path: join3(themePath, e.name),
3179
+ path: join4(themePath, e.name),
2446
3180
  application: e.name.replace(/\.(conf|theme|lua|toml|css|json|ini)$/, "")
2447
3181
  }));
2448
3182
  }
@@ -2454,16 +3188,16 @@ async function parseTheme(themePath, themeName) {
2454
3188
  path: themePath,
2455
3189
  files,
2456
3190
  metadata,
2457
- hasBackgrounds: existsSync4(join3(themePath, "backgrounds")),
2458
- hasPreview: existsSync4(join3(themePath, "preview.png")),
2459
- isLightMode: existsSync4(join3(themePath, "light.mode"))
3191
+ hasBackgrounds: existsSync5(join4(themePath, "backgrounds")),
3192
+ hasPreview: existsSync5(join4(themePath, "preview.png")),
3193
+ isLightMode: existsSync5(join4(themePath, "light.mode"))
2460
3194
  };
2461
3195
  }
2462
3196
 
2463
3197
  // src/cli/set-theme.ts
2464
3198
  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";
3199
+ import { readdirSync as readdirSync4, existsSync as existsSync6, rmSync, symlinkSync, unlinkSync } from "fs";
3200
+ import { join as join5 } from "path";
2467
3201
  var colors5 = {
2468
3202
  red: "\x1B[0;31m",
2469
3203
  green: "\x1B[0;32m",
@@ -2475,14 +3209,14 @@ var colors5 = {
2475
3209
  };
2476
3210
  async function listThemes() {
2477
3211
  await ensureConfigDir();
2478
- if (!existsSync5(THEMES_DIR)) {
3212
+ if (!existsSync6(THEMES_DIR)) {
2479
3213
  return [];
2480
3214
  }
2481
3215
  const entries = readdirSync4(THEMES_DIR, { withFileTypes: true });
2482
3216
  const themes = [];
2483
3217
  for (const entry of entries) {
2484
3218
  if (entry.isDirectory()) {
2485
- const themePath = join4(THEMES_DIR, entry.name);
3219
+ const themePath = join5(THEMES_DIR, entry.name);
2486
3220
  const theme = await parseTheme(themePath, entry.name);
2487
3221
  themes.push(theme);
2488
3222
  }
@@ -2490,10 +3224,10 @@ async function listThemes() {
2490
3224
  return themes;
2491
3225
  }
2492
3226
  function clearDirectory(dir) {
2493
- if (existsSync5(dir)) {
3227
+ if (existsSync6(dir)) {
2494
3228
  const entries = readdirSync4(dir, { withFileTypes: true });
2495
3229
  for (const entry of entries) {
2496
- const fullPath = join4(dir, entry.name);
3230
+ const fullPath = join5(dir, entry.name);
2497
3231
  if (entry.isSymbolicLink() || entry.isFile()) {
2498
3232
  unlinkSync(fullPath);
2499
3233
  } else if (entry.isDirectory()) {
@@ -2503,33 +3237,33 @@ function clearDirectory(dir) {
2503
3237
  }
2504
3238
  }
2505
3239
  function createSymlink(source, target) {
2506
- if (existsSync5(target)) {
3240
+ if (existsSync6(target)) {
2507
3241
  unlinkSync(target);
2508
3242
  }
2509
3243
  symlinkSync(source, target);
2510
3244
  }
2511
3245
  async function applyTheme(themeName) {
2512
- const themeDir = join4(THEMES_DIR, themeName);
2513
- if (!existsSync5(themeDir)) {
3246
+ const themeDir = join5(THEMES_DIR, themeName);
3247
+ if (!existsSync6(themeDir)) {
2514
3248
  return { output: `Theme '${themeName}' not found`, success: false };
2515
3249
  }
2516
3250
  await ensureConfigDir();
2517
3251
  await ensureDir2(THEME_TARGET_DIR);
2518
3252
  const theme = await parseTheme(themeDir, themeName);
2519
3253
  clearDirectory(THEME_TARGET_DIR);
2520
- if (existsSync5(BACKGROUNDS_TARGET_DIR)) {
3254
+ if (existsSync6(BACKGROUNDS_TARGET_DIR)) {
2521
3255
  rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
2522
3256
  }
2523
3257
  const entries = readdirSync4(themeDir, { withFileTypes: true });
2524
3258
  for (const entry of entries) {
2525
- const source = join4(themeDir, entry.name);
3259
+ const source = join5(themeDir, entry.name);
2526
3260
  if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
2527
- const target = join4(THEME_TARGET_DIR, entry.name);
3261
+ const target = join5(THEME_TARGET_DIR, entry.name);
2528
3262
  createSymlink(source, target);
2529
3263
  }
2530
3264
  }
2531
3265
  if (theme.hasBackgrounds) {
2532
- const backgroundsSource = join4(themeDir, "backgrounds");
3266
+ const backgroundsSource = join5(themeDir, "backgrounds");
2533
3267
  createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
2534
3268
  }
2535
3269
  let output = `Theme '${theme.name}' applied successfully`;
@@ -2548,8 +3282,8 @@ Note: This is a light mode theme`;
2548
3282
  return { output, success: true };
2549
3283
  }
2550
3284
  async function showThemeInfo(themeName) {
2551
- const themeDir = join4(THEMES_DIR, themeName);
2552
- if (!existsSync5(themeDir)) {
3285
+ const themeDir = join5(THEMES_DIR, themeName);
3286
+ if (!existsSync6(themeDir)) {
2553
3287
  console.error(`${colors5.red}Error: Theme '${themeName}' not found${colors5.reset}`);
2554
3288
  process.exit(1);
2555
3289
  }
@@ -2631,10 +3365,10 @@ if (isMainModule4) {
2631
3365
  }
2632
3366
 
2633
3367
  // src/components/menus/ThemeMenu.tsx
2634
- import { jsxDEV as jsxDEV17 } from "react/jsx-dev-runtime";
3368
+ import { jsxDEV as jsxDEV19 } from "react/jsx-dev-runtime";
2635
3369
  function ThemeMenu({ onBack }) {
2636
- const [themes, setThemes] = useState8([]);
2637
- const [loading, setLoading] = useState8(true);
3370
+ const [themes, setThemes] = useState10([]);
3371
+ const [loading, setLoading] = useState10(true);
2638
3372
  const { state, output, success, isRunning, isResult, execute, reset } = useMenuAction();
2639
3373
  const grid = useThemeGrid({
2640
3374
  itemCount: themes.length,
@@ -2644,7 +3378,7 @@ function ThemeMenu({ onBack }) {
2644
3378
  });
2645
3379
  useEffect5(() => {
2646
3380
  async function loadThemes() {
2647
- if (!existsSync6(THEMES_DIR)) {
3381
+ if (!existsSync7(THEMES_DIR)) {
2648
3382
  setThemes([]);
2649
3383
  setLoading(false);
2650
3384
  return;
@@ -2653,7 +3387,7 @@ function ThemeMenu({ onBack }) {
2653
3387
  const loadedThemes = [];
2654
3388
  for (const entry of entries) {
2655
3389
  if (entry.isDirectory()) {
2656
- const themePath = join5(THEMES_DIR, entry.name);
3390
+ const themePath = join6(THEMES_DIR, entry.name);
2657
3391
  const theme = await parseTheme(themePath, entry.name);
2658
3392
  loadedThemes.push(theme);
2659
3393
  }
@@ -2671,13 +3405,13 @@ function ThemeMenu({ onBack }) {
2671
3405
  return themes.slice(grid.visibleStartIndex, grid.visibleEndIndex);
2672
3406
  }, [themes, grid.visibleStartIndex, grid.visibleEndIndex]);
2673
3407
  if (loading || isRunning) {
2674
- return /* @__PURE__ */ jsxDEV17(LoadingPanel, {
3408
+ return /* @__PURE__ */ jsxDEV19(LoadingPanel, {
2675
3409
  title: "Select Theme",
2676
3410
  label: loading ? "Loading themes..." : "Applying theme..."
2677
3411
  }, undefined, false, undefined, this);
2678
3412
  }
2679
3413
  if (isResult) {
2680
- return /* @__PURE__ */ jsxDEV17(CommandOutput, {
3414
+ return /* @__PURE__ */ jsxDEV19(CommandOutput, {
2681
3415
  title: "Select Theme",
2682
3416
  output,
2683
3417
  success,
@@ -2685,28 +3419,28 @@ function ThemeMenu({ onBack }) {
2685
3419
  }, undefined, false, undefined, this);
2686
3420
  }
2687
3421
  if (themes.length === 0) {
2688
- return /* @__PURE__ */ jsxDEV17(Panel, {
3422
+ return /* @__PURE__ */ jsxDEV19(Panel, {
2689
3423
  title: "Select Theme",
2690
3424
  children: [
2691
- /* @__PURE__ */ jsxDEV17(Box14, {
3425
+ /* @__PURE__ */ jsxDEV19(Box16, {
2692
3426
  flexDirection: "column",
2693
3427
  children: [
2694
- /* @__PURE__ */ jsxDEV17(Text13, {
3428
+ /* @__PURE__ */ jsxDEV19(Text15, {
2695
3429
  color: colors.warning,
2696
3430
  children: "No themes available."
2697
3431
  }, undefined, false, undefined, this),
2698
- /* @__PURE__ */ jsxDEV17(Text13, {
3432
+ /* @__PURE__ */ jsxDEV19(Text15, {
2699
3433
  children: "This system is compatible with omarchy themes."
2700
3434
  }, undefined, false, undefined, this),
2701
- /* @__PURE__ */ jsxDEV17(Text13, {
3435
+ /* @__PURE__ */ jsxDEV19(Text15, {
2702
3436
  dimColor: true,
2703
3437
  children: "Add themes to ~/.config/formalconf/themes/"
2704
3438
  }, undefined, false, undefined, this)
2705
3439
  ]
2706
3440
  }, undefined, true, undefined, this),
2707
- /* @__PURE__ */ jsxDEV17(Box14, {
3441
+ /* @__PURE__ */ jsxDEV19(Box16, {
2708
3442
  marginTop: 1,
2709
- children: /* @__PURE__ */ jsxDEV17(VimSelect, {
3443
+ children: /* @__PURE__ */ jsxDEV19(VimSelect, {
2710
3444
  options: [{ label: "Back", value: "back" }],
2711
3445
  onChange: () => onBack()
2712
3446
  }, undefined, false, undefined, this)
@@ -2714,10 +3448,10 @@ function ThemeMenu({ onBack }) {
2714
3448
  ]
2715
3449
  }, undefined, true, undefined, this);
2716
3450
  }
2717
- return /* @__PURE__ */ jsxDEV17(Panel, {
3451
+ return /* @__PURE__ */ jsxDEV19(Panel, {
2718
3452
  title: "Select Theme",
2719
3453
  children: [
2720
- grid.showScrollUp && /* @__PURE__ */ jsxDEV17(Text13, {
3454
+ grid.showScrollUp && /* @__PURE__ */ jsxDEV19(Text15, {
2721
3455
  dimColor: true,
2722
3456
  children: [
2723
3457
  " ",
@@ -2727,18 +3461,18 @@ function ThemeMenu({ onBack }) {
2727
3461
  grid.scrollOffset > 1 ? "s" : ""
2728
3462
  ]
2729
3463
  }, undefined, true, undefined, this),
2730
- /* @__PURE__ */ jsxDEV17(Box14, {
3464
+ /* @__PURE__ */ jsxDEV19(Box16, {
2731
3465
  flexDirection: "row",
2732
3466
  flexWrap: "wrap",
2733
3467
  height: grid.gridHeight,
2734
3468
  overflow: "hidden",
2735
- children: visibleThemes.map((theme, index) => /* @__PURE__ */ jsxDEV17(ThemeCard, {
3469
+ children: visibleThemes.map((theme, index) => /* @__PURE__ */ jsxDEV19(ThemeCard, {
2736
3470
  theme,
2737
3471
  isSelected: grid.visibleStartIndex + index === grid.selectedIndex,
2738
3472
  width: grid.cardWidth
2739
3473
  }, theme.path, false, undefined, this))
2740
3474
  }, undefined, false, undefined, this),
2741
- grid.showScrollDown && /* @__PURE__ */ jsxDEV17(Text13, {
3475
+ grid.showScrollDown && /* @__PURE__ */ jsxDEV19(Text15, {
2742
3476
  dimColor: true,
2743
3477
  children: [
2744
3478
  " ",
@@ -2748,9 +3482,9 @@ function ThemeMenu({ onBack }) {
2748
3482
  grid.totalRows - grid.scrollOffset - grid.visibleRows > 1 ? "s" : ""
2749
3483
  ]
2750
3484
  }, undefined, true, undefined, this),
2751
- /* @__PURE__ */ jsxDEV17(Box14, {
3485
+ /* @__PURE__ */ jsxDEV19(Box16, {
2752
3486
  marginTop: 1,
2753
- children: /* @__PURE__ */ jsxDEV17(Text13, {
3487
+ children: /* @__PURE__ */ jsxDEV19(Text15, {
2754
3488
  dimColor: true,
2755
3489
  children: "←→↑↓/hjkl navigate • Enter select • Esc back"
2756
3490
  }, undefined, false, undefined, this)
@@ -2760,7 +3494,7 @@ function ThemeMenu({ onBack }) {
2760
3494
  }
2761
3495
 
2762
3496
  // src/cli/formalconf.tsx
2763
- import { jsxDEV as jsxDEV18 } from "react/jsx-dev-runtime";
3497
+ import { jsxDEV as jsxDEV20 } from "react/jsx-dev-runtime";
2764
3498
  var BREADCRUMBS = {
2765
3499
  main: ["Main"],
2766
3500
  config: ["Main", "Config Manager"],
@@ -2768,61 +3502,71 @@ var BREADCRUMBS = {
2768
3502
  themes: ["Main", "Themes"]
2769
3503
  };
2770
3504
  function App() {
2771
- const [appState, setAppState] = useState9("loading");
2772
- const [missingDeps, setMissingDeps] = useState9([]);
2773
- const [screen, setScreen] = useState9("main");
3505
+ const [appState, setAppState] = useState11("loading");
3506
+ const [missingDeps, setMissingDeps] = useState11([]);
3507
+ const [screen, setScreen] = useState11("main");
2774
3508
  const { exit } = useApp2();
2775
- useInput9((input) => {
3509
+ useInput11((input) => {
2776
3510
  if (input === "q")
2777
3511
  exit();
2778
3512
  });
2779
3513
  useEffect6(() => {
2780
3514
  async function init() {
2781
- ensureConfigDir();
3515
+ await ensureConfigDir();
2782
3516
  const result = await checkPrerequisites();
2783
3517
  if (!result.ok) {
2784
3518
  setMissingDeps(result.missing);
2785
3519
  setAppState("error");
2786
- } else {
2787
- setAppState("ready");
3520
+ return;
2788
3521
  }
3522
+ const firstRun = await isFirstRun();
3523
+ if (firstRun) {
3524
+ setAppState("onboarding");
3525
+ return;
3526
+ }
3527
+ setAppState("ready");
2789
3528
  }
2790
3529
  init();
2791
3530
  }, []);
2792
3531
  if (appState === "loading") {
2793
- return /* @__PURE__ */ jsxDEV18(Layout, {
3532
+ return /* @__PURE__ */ jsxDEV20(Layout, {
2794
3533
  breadcrumb: ["Loading"],
2795
- children: /* @__PURE__ */ jsxDEV18(Panel, {
3534
+ children: /* @__PURE__ */ jsxDEV20(Panel, {
2796
3535
  title: "FormalConf",
2797
- children: /* @__PURE__ */ jsxDEV18(Spinner2, {
3536
+ children: /* @__PURE__ */ jsxDEV20(Spinner2, {
2798
3537
  label: "Checking prerequisites..."
2799
3538
  }, undefined, false, undefined, this)
2800
3539
  }, undefined, false, undefined, this)
2801
3540
  }, undefined, false, undefined, this);
2802
3541
  }
2803
3542
  if (appState === "error") {
2804
- return /* @__PURE__ */ jsxDEV18(PrerequisiteError, {
3543
+ return /* @__PURE__ */ jsxDEV20(PrerequisiteError, {
2805
3544
  missing: missingDeps,
2806
3545
  onExit: exit
2807
3546
  }, undefined, false, undefined, this);
2808
3547
  }
3548
+ if (appState === "onboarding") {
3549
+ return /* @__PURE__ */ jsxDEV20(Onboarding, {
3550
+ onComplete: () => setAppState("ready")
3551
+ }, undefined, false, undefined, this);
3552
+ }
2809
3553
  const goBack = () => setScreen("main");
2810
- return /* @__PURE__ */ jsxDEV18(Layout, {
3554
+ return /* @__PURE__ */ jsxDEV20(Layout, {
2811
3555
  breadcrumb: BREADCRUMBS[screen],
2812
3556
  children: [
2813
- screen === "main" && /* @__PURE__ */ jsxDEV18(MainMenu, {
3557
+ screen === "main" && /* @__PURE__ */ jsxDEV20(MainMenu, {
2814
3558
  onSelect: setScreen
2815
3559
  }, undefined, false, undefined, this),
2816
- screen === "config" && /* @__PURE__ */ jsxDEV18(ConfigMenu, {
3560
+ screen === "config" && /* @__PURE__ */ jsxDEV20(ConfigMenu, {
2817
3561
  onBack: goBack
2818
3562
  }, undefined, false, undefined, this),
2819
- screen === "packages" && /* @__PURE__ */ jsxDEV18(PackageMenu, {
3563
+ screen === "packages" && /* @__PURE__ */ jsxDEV20(PackageMenu, {
2820
3564
  onBack: goBack
2821
3565
  }, undefined, false, undefined, this),
2822
- screen === "themes" && /* @__PURE__ */ jsxDEV18(ThemeMenu, {
3566
+ screen === "themes" && /* @__PURE__ */ jsxDEV20(ThemeMenu, {
2823
3567
  onBack: goBack
2824
3568
  }, undefined, false, undefined, this)
2825
3569
  ]
2826
3570
  }, undefined, true, undefined, this);
2827
3571
  }
2828
- render(/* @__PURE__ */ jsxDEV18(App, {}, undefined, false, undefined, this));
3572
+ render(/* @__PURE__ */ jsxDEV20(App, {}, undefined, false, undefined, this));