formalconf 2.0.2 → 2.0.3

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 +669 -155
  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 useState10, useEffect as useEffect6 } from "react";
4
+ import { render, useApp as useApp2, useInput as useInput10 } 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.3",
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 useState7, useCallback as useCallback2, useMemo as useMemo2, useRef } from "react";
1678
+ import { Box as Box13, Text as Text12, useInput as useInput8 } 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)
@@ -1301,7 +1805,7 @@ function PromptInput({
1301
1805
  import { parseArgs as parseArgs2 } from "util";
1302
1806
 
1303
1807
  // src/lib/config.ts
1304
- import { existsSync as existsSync3 } from "fs";
1808
+ import { existsSync as existsSync4 } from "fs";
1305
1809
  var DEFAULT_CONFIG = {
1306
1810
  config: {
1307
1811
  purge: false,
@@ -1316,7 +1820,7 @@ var DEFAULT_CONFIG = {
1316
1820
  async function loadPkgConfig(path) {
1317
1821
  await ensureConfigDir();
1318
1822
  const configPath = path || PKG_CONFIG_PATH;
1319
- if (!existsSync3(configPath)) {
1823
+ if (!existsSync4(configPath)) {
1320
1824
  await savePkgConfig(DEFAULT_CONFIG, configPath);
1321
1825
  return DEFAULT_CONFIG;
1322
1826
  }
@@ -1328,7 +1832,7 @@ async function savePkgConfig(config, path) {
1328
1832
  await writeFile(configPath, JSON.stringify(config, null, 2));
1329
1833
  }
1330
1834
  async function loadPkgLock() {
1331
- if (!existsSync3(PKG_LOCK_PATH)) {
1835
+ if (!existsSync4(PKG_LOCK_PATH)) {
1332
1836
  return null;
1333
1837
  }
1334
1838
  return readJson(PKG_LOCK_PATH);
@@ -2110,16 +2614,16 @@ if (isMainModule3) {
2110
2614
  }
2111
2615
 
2112
2616
  // src/components/menus/PackageMenu.tsx
2113
- import { jsxDEV as jsxDEV15 } from "react/jsx-dev-runtime";
2617
+ import { jsxDEV as jsxDEV16 } from "react/jsx-dev-runtime";
2114
2618
  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);
2619
+ const [state, setState] = useState7("menu");
2620
+ const [lines, setLines] = useState7([]);
2621
+ const [output, setOutput] = useState7("");
2622
+ const [isStreamingOp, setIsStreamingOp] = useState7(true);
2623
+ const [pendingPrompt, setPendingPrompt] = useState7(null);
2624
+ const [success, setSuccess] = useState7(true);
2121
2625
  const isRunningRef = useRef(false);
2122
- useInput7((input, key) => {
2626
+ useInput8((input, key) => {
2123
2627
  if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
2124
2628
  onBack();
2125
2629
  }
@@ -2196,17 +2700,17 @@ function PackageMenu({ onBack }) {
2196
2700
  };
2197
2701
  if (state === "running") {
2198
2702
  if (!isStreamingOp) {
2199
- return /* @__PURE__ */ jsxDEV15(LoadingPanel, {
2703
+ return /* @__PURE__ */ jsxDEV16(LoadingPanel, {
2200
2704
  title: "Package Sync"
2201
2705
  }, undefined, false, undefined, this);
2202
2706
  }
2203
- return /* @__PURE__ */ jsxDEV15(Panel, {
2707
+ return /* @__PURE__ */ jsxDEV16(Panel, {
2204
2708
  title: "Package Sync",
2205
2709
  children: [
2206
- /* @__PURE__ */ jsxDEV15(ScrollableLog, {
2710
+ /* @__PURE__ */ jsxDEV16(ScrollableLog, {
2207
2711
  lines
2208
2712
  }, undefined, false, undefined, this),
2209
- pendingPrompt && /* @__PURE__ */ jsxDEV15(PromptInput, {
2713
+ pendingPrompt && /* @__PURE__ */ jsxDEV16(PromptInput, {
2210
2714
  question: pendingPrompt.question,
2211
2715
  options: pendingPrompt.options,
2212
2716
  onAnswer: handlePromptAnswer
@@ -2216,38 +2720,38 @@ function PackageMenu({ onBack }) {
2216
2720
  }
2217
2721
  if (state === "result") {
2218
2722
  if (!isStreamingOp) {
2219
- return /* @__PURE__ */ jsxDEV15(CommandOutput, {
2723
+ return /* @__PURE__ */ jsxDEV16(CommandOutput, {
2220
2724
  title: "Package Sync",
2221
2725
  output,
2222
2726
  success,
2223
2727
  onDismiss: () => setState("menu")
2224
2728
  }, undefined, false, undefined, this);
2225
2729
  }
2226
- return /* @__PURE__ */ jsxDEV15(Panel, {
2730
+ return /* @__PURE__ */ jsxDEV16(Panel, {
2227
2731
  title: "Package Sync",
2228
2732
  borderColor: success ? colors.success : colors.error,
2229
2733
  children: [
2230
- /* @__PURE__ */ jsxDEV15(ScrollableLog, {
2734
+ /* @__PURE__ */ jsxDEV16(ScrollableLog, {
2231
2735
  lines,
2232
2736
  autoScroll: false
2233
2737
  }, undefined, false, undefined, this),
2234
- /* @__PURE__ */ jsxDEV15(Box12, {
2738
+ /* @__PURE__ */ jsxDEV16(Box13, {
2235
2739
  marginTop: 1,
2236
- children: /* @__PURE__ */ jsxDEV15(Text11, {
2740
+ children: /* @__PURE__ */ jsxDEV16(Text12, {
2237
2741
  color: success ? colors.success : colors.error,
2238
2742
  children: success ? "Done" : "Failed"
2239
2743
  }, undefined, false, undefined, this)
2240
2744
  }, undefined, false, undefined, this),
2241
- /* @__PURE__ */ jsxDEV15(Text11, {
2745
+ /* @__PURE__ */ jsxDEV16(Text12, {
2242
2746
  dimColor: true,
2243
2747
  children: "Press any key to continue..."
2244
2748
  }, undefined, false, undefined, this)
2245
2749
  ]
2246
2750
  }, undefined, true, undefined, this);
2247
2751
  }
2248
- return /* @__PURE__ */ jsxDEV15(Panel, {
2752
+ return /* @__PURE__ */ jsxDEV16(Panel, {
2249
2753
  title: "Package Sync",
2250
- children: /* @__PURE__ */ jsxDEV15(VimSelect, {
2754
+ children: /* @__PURE__ */ jsxDEV16(VimSelect, {
2251
2755
  options: [
2252
2756
  { label: "Sync packages", value: "sync" },
2253
2757
  { label: "Sync with purge", value: "sync-purge" },
@@ -2263,14 +2767,14 @@ function PackageMenu({ onBack }) {
2263
2767
  }
2264
2768
 
2265
2769
  // 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";
2770
+ import { useState as useState9, useEffect as useEffect5, useMemo as useMemo4 } from "react";
2771
+ import { Box as Box15, Text as Text14 } from "ink";
2772
+ import { existsSync as existsSync7, readdirSync as readdirSync5 } from "fs";
2773
+ import { join as join6 } from "path";
2270
2774
 
2271
2775
  // src/components/ThemeCard.tsx
2272
- import { Box as Box13, Text as Text12 } from "ink";
2273
- import { jsxDEV as jsxDEV16 } from "react/jsx-dev-runtime";
2776
+ import { Box as Box14, Text as Text13 } from "ink";
2777
+ import { jsxDEV as jsxDEV17 } from "react/jsx-dev-runtime";
2274
2778
  function ThemeCard({ theme, isSelected, width }) {
2275
2779
  const borderColor = isSelected ? colors.accent : colors.border;
2276
2780
  const nameColor = isSelected ? colors.primary : colors.text;
@@ -2280,25 +2784,25 @@ function ThemeCard({ theme, isSelected, width }) {
2280
2784
  if (theme.isLightMode)
2281
2785
  indicators.push("light");
2282
2786
  const indicatorText = indicators.length > 0 ? ` [${indicators.join(" ")}]` : "";
2283
- return /* @__PURE__ */ jsxDEV16(Box13, {
2787
+ return /* @__PURE__ */ jsxDEV17(Box14, {
2284
2788
  flexDirection: "column",
2285
2789
  width,
2286
2790
  borderStyle: borderStyles.panel,
2287
2791
  borderColor,
2288
2792
  paddingX: 1,
2289
- children: /* @__PURE__ */ jsxDEV16(Box13, {
2793
+ children: /* @__PURE__ */ jsxDEV17(Box14, {
2290
2794
  children: [
2291
- /* @__PURE__ */ jsxDEV16(Text12, {
2795
+ /* @__PURE__ */ jsxDEV17(Text13, {
2292
2796
  color: isSelected ? colors.accent : colors.primaryDim,
2293
2797
  children: isSelected ? "● " : " "
2294
2798
  }, undefined, false, undefined, this),
2295
- /* @__PURE__ */ jsxDEV16(Text12, {
2799
+ /* @__PURE__ */ jsxDEV17(Text13, {
2296
2800
  color: nameColor,
2297
2801
  bold: true,
2298
2802
  wrap: "truncate",
2299
2803
  children: theme.name
2300
2804
  }, undefined, false, undefined, this),
2301
- /* @__PURE__ */ jsxDEV16(Text12, {
2805
+ /* @__PURE__ */ jsxDEV17(Text13, {
2302
2806
  color: colors.primaryDim,
2303
2807
  children: indicatorText
2304
2808
  }, undefined, false, undefined, this)
@@ -2308,8 +2812,8 @@ function ThemeCard({ theme, isSelected, width }) {
2308
2812
  }
2309
2813
 
2310
2814
  // src/hooks/useThemeGrid.ts
2311
- import { useState as useState7, useEffect as useEffect4 } from "react";
2312
- import { useInput as useInput8 } from "ink";
2815
+ import { useState as useState8, useEffect as useEffect4 } from "react";
2816
+ import { useInput as useInput9 } from "ink";
2313
2817
  function useThemeGrid({
2314
2818
  itemCount,
2315
2819
  cardHeight = 3,
@@ -2320,8 +2824,8 @@ function useThemeGrid({
2320
2824
  enabled = true
2321
2825
  }) {
2322
2826
  const { columns, rows } = useTerminalSize();
2323
- const [selectedIndex, setSelectedIndex] = useState7(0);
2324
- const [scrollOffset, setScrollOffset] = useState7(0);
2827
+ const [selectedIndex, setSelectedIndex] = useState8(0);
2828
+ const [scrollOffset, setScrollOffset] = useState8(0);
2325
2829
  const availableWidth = columns - 6;
2326
2830
  const cardsPerRow = Math.max(1, Math.floor(availableWidth / minCardWidth));
2327
2831
  const cardWidth = Math.floor(availableWidth / cardsPerRow);
@@ -2336,7 +2840,7 @@ function useThemeGrid({
2336
2840
  setScrollOffset(selectedRow - visibleRows + 1);
2337
2841
  }
2338
2842
  }, [selectedRow, scrollOffset, visibleRows]);
2339
- useInput8((input, key) => {
2843
+ useInput9((input, key) => {
2340
2844
  if (!enabled)
2341
2845
  return;
2342
2846
  if (key.escape && onBack) {
@@ -2387,8 +2891,8 @@ function useThemeGrid({
2387
2891
  }
2388
2892
 
2389
2893
  // src/lib/theme-parser.ts
2390
- import { existsSync as existsSync4, readdirSync as readdirSync3 } from "fs";
2391
- import { join as join3 } from "path";
2894
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
2895
+ import { join as join4 } from "path";
2392
2896
  function parseYaml(content) {
2393
2897
  const result = {};
2394
2898
  const lines = content.split(`
@@ -2419,8 +2923,8 @@ function parseYaml(content) {
2419
2923
  return result;
2420
2924
  }
2421
2925
  async function parseThemeMetadata(themePath) {
2422
- const yamlPath = join3(themePath, "theme.yaml");
2423
- if (!existsSync4(yamlPath)) {
2926
+ const yamlPath = join4(themePath, "theme.yaml");
2927
+ if (!existsSync5(yamlPath)) {
2424
2928
  return;
2425
2929
  }
2426
2930
  try {
@@ -2442,7 +2946,7 @@ function parseThemeFiles(themePath) {
2442
2946
  const entries = readdirSync3(themePath, { withFileTypes: true });
2443
2947
  return entries.filter((e) => e.isFile() && !e.name.startsWith(".") && e.name !== "theme.yaml" && e.name !== "light.mode").map((e) => ({
2444
2948
  name: e.name,
2445
- path: join3(themePath, e.name),
2949
+ path: join4(themePath, e.name),
2446
2950
  application: e.name.replace(/\.(conf|theme|lua|toml|css|json|ini)$/, "")
2447
2951
  }));
2448
2952
  }
@@ -2454,16 +2958,16 @@ async function parseTheme(themePath, themeName) {
2454
2958
  path: themePath,
2455
2959
  files,
2456
2960
  metadata,
2457
- hasBackgrounds: existsSync4(join3(themePath, "backgrounds")),
2458
- hasPreview: existsSync4(join3(themePath, "preview.png")),
2459
- isLightMode: existsSync4(join3(themePath, "light.mode"))
2961
+ hasBackgrounds: existsSync5(join4(themePath, "backgrounds")),
2962
+ hasPreview: existsSync5(join4(themePath, "preview.png")),
2963
+ isLightMode: existsSync5(join4(themePath, "light.mode"))
2460
2964
  };
2461
2965
  }
2462
2966
 
2463
2967
  // src/cli/set-theme.ts
2464
2968
  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";
2969
+ import { readdirSync as readdirSync4, existsSync as existsSync6, rmSync, symlinkSync, unlinkSync } from "fs";
2970
+ import { join as join5 } from "path";
2467
2971
  var colors5 = {
2468
2972
  red: "\x1B[0;31m",
2469
2973
  green: "\x1B[0;32m",
@@ -2475,14 +2979,14 @@ var colors5 = {
2475
2979
  };
2476
2980
  async function listThemes() {
2477
2981
  await ensureConfigDir();
2478
- if (!existsSync5(THEMES_DIR)) {
2982
+ if (!existsSync6(THEMES_DIR)) {
2479
2983
  return [];
2480
2984
  }
2481
2985
  const entries = readdirSync4(THEMES_DIR, { withFileTypes: true });
2482
2986
  const themes = [];
2483
2987
  for (const entry of entries) {
2484
2988
  if (entry.isDirectory()) {
2485
- const themePath = join4(THEMES_DIR, entry.name);
2989
+ const themePath = join5(THEMES_DIR, entry.name);
2486
2990
  const theme = await parseTheme(themePath, entry.name);
2487
2991
  themes.push(theme);
2488
2992
  }
@@ -2490,10 +2994,10 @@ async function listThemes() {
2490
2994
  return themes;
2491
2995
  }
2492
2996
  function clearDirectory(dir) {
2493
- if (existsSync5(dir)) {
2997
+ if (existsSync6(dir)) {
2494
2998
  const entries = readdirSync4(dir, { withFileTypes: true });
2495
2999
  for (const entry of entries) {
2496
- const fullPath = join4(dir, entry.name);
3000
+ const fullPath = join5(dir, entry.name);
2497
3001
  if (entry.isSymbolicLink() || entry.isFile()) {
2498
3002
  unlinkSync(fullPath);
2499
3003
  } else if (entry.isDirectory()) {
@@ -2503,33 +3007,33 @@ function clearDirectory(dir) {
2503
3007
  }
2504
3008
  }
2505
3009
  function createSymlink(source, target) {
2506
- if (existsSync5(target)) {
3010
+ if (existsSync6(target)) {
2507
3011
  unlinkSync(target);
2508
3012
  }
2509
3013
  symlinkSync(source, target);
2510
3014
  }
2511
3015
  async function applyTheme(themeName) {
2512
- const themeDir = join4(THEMES_DIR, themeName);
2513
- if (!existsSync5(themeDir)) {
3016
+ const themeDir = join5(THEMES_DIR, themeName);
3017
+ if (!existsSync6(themeDir)) {
2514
3018
  return { output: `Theme '${themeName}' not found`, success: false };
2515
3019
  }
2516
3020
  await ensureConfigDir();
2517
3021
  await ensureDir2(THEME_TARGET_DIR);
2518
3022
  const theme = await parseTheme(themeDir, themeName);
2519
3023
  clearDirectory(THEME_TARGET_DIR);
2520
- if (existsSync5(BACKGROUNDS_TARGET_DIR)) {
3024
+ if (existsSync6(BACKGROUNDS_TARGET_DIR)) {
2521
3025
  rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
2522
3026
  }
2523
3027
  const entries = readdirSync4(themeDir, { withFileTypes: true });
2524
3028
  for (const entry of entries) {
2525
- const source = join4(themeDir, entry.name);
3029
+ const source = join5(themeDir, entry.name);
2526
3030
  if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
2527
- const target = join4(THEME_TARGET_DIR, entry.name);
3031
+ const target = join5(THEME_TARGET_DIR, entry.name);
2528
3032
  createSymlink(source, target);
2529
3033
  }
2530
3034
  }
2531
3035
  if (theme.hasBackgrounds) {
2532
- const backgroundsSource = join4(themeDir, "backgrounds");
3036
+ const backgroundsSource = join5(themeDir, "backgrounds");
2533
3037
  createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
2534
3038
  }
2535
3039
  let output = `Theme '${theme.name}' applied successfully`;
@@ -2548,8 +3052,8 @@ Note: This is a light mode theme`;
2548
3052
  return { output, success: true };
2549
3053
  }
2550
3054
  async function showThemeInfo(themeName) {
2551
- const themeDir = join4(THEMES_DIR, themeName);
2552
- if (!existsSync5(themeDir)) {
3055
+ const themeDir = join5(THEMES_DIR, themeName);
3056
+ if (!existsSync6(themeDir)) {
2553
3057
  console.error(`${colors5.red}Error: Theme '${themeName}' not found${colors5.reset}`);
2554
3058
  process.exit(1);
2555
3059
  }
@@ -2631,10 +3135,10 @@ if (isMainModule4) {
2631
3135
  }
2632
3136
 
2633
3137
  // src/components/menus/ThemeMenu.tsx
2634
- import { jsxDEV as jsxDEV17 } from "react/jsx-dev-runtime";
3138
+ import { jsxDEV as jsxDEV18 } from "react/jsx-dev-runtime";
2635
3139
  function ThemeMenu({ onBack }) {
2636
- const [themes, setThemes] = useState8([]);
2637
- const [loading, setLoading] = useState8(true);
3140
+ const [themes, setThemes] = useState9([]);
3141
+ const [loading, setLoading] = useState9(true);
2638
3142
  const { state, output, success, isRunning, isResult, execute, reset } = useMenuAction();
2639
3143
  const grid = useThemeGrid({
2640
3144
  itemCount: themes.length,
@@ -2644,7 +3148,7 @@ function ThemeMenu({ onBack }) {
2644
3148
  });
2645
3149
  useEffect5(() => {
2646
3150
  async function loadThemes() {
2647
- if (!existsSync6(THEMES_DIR)) {
3151
+ if (!existsSync7(THEMES_DIR)) {
2648
3152
  setThemes([]);
2649
3153
  setLoading(false);
2650
3154
  return;
@@ -2653,7 +3157,7 @@ function ThemeMenu({ onBack }) {
2653
3157
  const loadedThemes = [];
2654
3158
  for (const entry of entries) {
2655
3159
  if (entry.isDirectory()) {
2656
- const themePath = join5(THEMES_DIR, entry.name);
3160
+ const themePath = join6(THEMES_DIR, entry.name);
2657
3161
  const theme = await parseTheme(themePath, entry.name);
2658
3162
  loadedThemes.push(theme);
2659
3163
  }
@@ -2671,13 +3175,13 @@ function ThemeMenu({ onBack }) {
2671
3175
  return themes.slice(grid.visibleStartIndex, grid.visibleEndIndex);
2672
3176
  }, [themes, grid.visibleStartIndex, grid.visibleEndIndex]);
2673
3177
  if (loading || isRunning) {
2674
- return /* @__PURE__ */ jsxDEV17(LoadingPanel, {
3178
+ return /* @__PURE__ */ jsxDEV18(LoadingPanel, {
2675
3179
  title: "Select Theme",
2676
3180
  label: loading ? "Loading themes..." : "Applying theme..."
2677
3181
  }, undefined, false, undefined, this);
2678
3182
  }
2679
3183
  if (isResult) {
2680
- return /* @__PURE__ */ jsxDEV17(CommandOutput, {
3184
+ return /* @__PURE__ */ jsxDEV18(CommandOutput, {
2681
3185
  title: "Select Theme",
2682
3186
  output,
2683
3187
  success,
@@ -2685,28 +3189,28 @@ function ThemeMenu({ onBack }) {
2685
3189
  }, undefined, false, undefined, this);
2686
3190
  }
2687
3191
  if (themes.length === 0) {
2688
- return /* @__PURE__ */ jsxDEV17(Panel, {
3192
+ return /* @__PURE__ */ jsxDEV18(Panel, {
2689
3193
  title: "Select Theme",
2690
3194
  children: [
2691
- /* @__PURE__ */ jsxDEV17(Box14, {
3195
+ /* @__PURE__ */ jsxDEV18(Box15, {
2692
3196
  flexDirection: "column",
2693
3197
  children: [
2694
- /* @__PURE__ */ jsxDEV17(Text13, {
3198
+ /* @__PURE__ */ jsxDEV18(Text14, {
2695
3199
  color: colors.warning,
2696
3200
  children: "No themes available."
2697
3201
  }, undefined, false, undefined, this),
2698
- /* @__PURE__ */ jsxDEV17(Text13, {
3202
+ /* @__PURE__ */ jsxDEV18(Text14, {
2699
3203
  children: "This system is compatible with omarchy themes."
2700
3204
  }, undefined, false, undefined, this),
2701
- /* @__PURE__ */ jsxDEV17(Text13, {
3205
+ /* @__PURE__ */ jsxDEV18(Text14, {
2702
3206
  dimColor: true,
2703
3207
  children: "Add themes to ~/.config/formalconf/themes/"
2704
3208
  }, undefined, false, undefined, this)
2705
3209
  ]
2706
3210
  }, undefined, true, undefined, this),
2707
- /* @__PURE__ */ jsxDEV17(Box14, {
3211
+ /* @__PURE__ */ jsxDEV18(Box15, {
2708
3212
  marginTop: 1,
2709
- children: /* @__PURE__ */ jsxDEV17(VimSelect, {
3213
+ children: /* @__PURE__ */ jsxDEV18(VimSelect, {
2710
3214
  options: [{ label: "Back", value: "back" }],
2711
3215
  onChange: () => onBack()
2712
3216
  }, undefined, false, undefined, this)
@@ -2714,10 +3218,10 @@ function ThemeMenu({ onBack }) {
2714
3218
  ]
2715
3219
  }, undefined, true, undefined, this);
2716
3220
  }
2717
- return /* @__PURE__ */ jsxDEV17(Panel, {
3221
+ return /* @__PURE__ */ jsxDEV18(Panel, {
2718
3222
  title: "Select Theme",
2719
3223
  children: [
2720
- grid.showScrollUp && /* @__PURE__ */ jsxDEV17(Text13, {
3224
+ grid.showScrollUp && /* @__PURE__ */ jsxDEV18(Text14, {
2721
3225
  dimColor: true,
2722
3226
  children: [
2723
3227
  " ",
@@ -2727,18 +3231,18 @@ function ThemeMenu({ onBack }) {
2727
3231
  grid.scrollOffset > 1 ? "s" : ""
2728
3232
  ]
2729
3233
  }, undefined, true, undefined, this),
2730
- /* @__PURE__ */ jsxDEV17(Box14, {
3234
+ /* @__PURE__ */ jsxDEV18(Box15, {
2731
3235
  flexDirection: "row",
2732
3236
  flexWrap: "wrap",
2733
3237
  height: grid.gridHeight,
2734
3238
  overflow: "hidden",
2735
- children: visibleThemes.map((theme, index) => /* @__PURE__ */ jsxDEV17(ThemeCard, {
3239
+ children: visibleThemes.map((theme, index) => /* @__PURE__ */ jsxDEV18(ThemeCard, {
2736
3240
  theme,
2737
3241
  isSelected: grid.visibleStartIndex + index === grid.selectedIndex,
2738
3242
  width: grid.cardWidth
2739
3243
  }, theme.path, false, undefined, this))
2740
3244
  }, undefined, false, undefined, this),
2741
- grid.showScrollDown && /* @__PURE__ */ jsxDEV17(Text13, {
3245
+ grid.showScrollDown && /* @__PURE__ */ jsxDEV18(Text14, {
2742
3246
  dimColor: true,
2743
3247
  children: [
2744
3248
  " ",
@@ -2748,9 +3252,9 @@ function ThemeMenu({ onBack }) {
2748
3252
  grid.totalRows - grid.scrollOffset - grid.visibleRows > 1 ? "s" : ""
2749
3253
  ]
2750
3254
  }, undefined, true, undefined, this),
2751
- /* @__PURE__ */ jsxDEV17(Box14, {
3255
+ /* @__PURE__ */ jsxDEV18(Box15, {
2752
3256
  marginTop: 1,
2753
- children: /* @__PURE__ */ jsxDEV17(Text13, {
3257
+ children: /* @__PURE__ */ jsxDEV18(Text14, {
2754
3258
  dimColor: true,
2755
3259
  children: "←→↑↓/hjkl navigate • Enter select • Esc back"
2756
3260
  }, undefined, false, undefined, this)
@@ -2760,7 +3264,7 @@ function ThemeMenu({ onBack }) {
2760
3264
  }
2761
3265
 
2762
3266
  // src/cli/formalconf.tsx
2763
- import { jsxDEV as jsxDEV18 } from "react/jsx-dev-runtime";
3267
+ import { jsxDEV as jsxDEV19 } from "react/jsx-dev-runtime";
2764
3268
  var BREADCRUMBS = {
2765
3269
  main: ["Main"],
2766
3270
  config: ["Main", "Config Manager"],
@@ -2768,61 +3272,71 @@ var BREADCRUMBS = {
2768
3272
  themes: ["Main", "Themes"]
2769
3273
  };
2770
3274
  function App() {
2771
- const [appState, setAppState] = useState9("loading");
2772
- const [missingDeps, setMissingDeps] = useState9([]);
2773
- const [screen, setScreen] = useState9("main");
3275
+ const [appState, setAppState] = useState10("loading");
3276
+ const [missingDeps, setMissingDeps] = useState10([]);
3277
+ const [screen, setScreen] = useState10("main");
2774
3278
  const { exit } = useApp2();
2775
- useInput9((input) => {
3279
+ useInput10((input) => {
2776
3280
  if (input === "q")
2777
3281
  exit();
2778
3282
  });
2779
3283
  useEffect6(() => {
2780
3284
  async function init() {
2781
- ensureConfigDir();
3285
+ await ensureConfigDir();
2782
3286
  const result = await checkPrerequisites();
2783
3287
  if (!result.ok) {
2784
3288
  setMissingDeps(result.missing);
2785
3289
  setAppState("error");
2786
- } else {
2787
- setAppState("ready");
3290
+ return;
2788
3291
  }
3292
+ const firstRun = await isFirstRun();
3293
+ if (firstRun) {
3294
+ setAppState("onboarding");
3295
+ return;
3296
+ }
3297
+ setAppState("ready");
2789
3298
  }
2790
3299
  init();
2791
3300
  }, []);
2792
3301
  if (appState === "loading") {
2793
- return /* @__PURE__ */ jsxDEV18(Layout, {
3302
+ return /* @__PURE__ */ jsxDEV19(Layout, {
2794
3303
  breadcrumb: ["Loading"],
2795
- children: /* @__PURE__ */ jsxDEV18(Panel, {
3304
+ children: /* @__PURE__ */ jsxDEV19(Panel, {
2796
3305
  title: "FormalConf",
2797
- children: /* @__PURE__ */ jsxDEV18(Spinner2, {
3306
+ children: /* @__PURE__ */ jsxDEV19(Spinner2, {
2798
3307
  label: "Checking prerequisites..."
2799
3308
  }, undefined, false, undefined, this)
2800
3309
  }, undefined, false, undefined, this)
2801
3310
  }, undefined, false, undefined, this);
2802
3311
  }
2803
3312
  if (appState === "error") {
2804
- return /* @__PURE__ */ jsxDEV18(PrerequisiteError, {
3313
+ return /* @__PURE__ */ jsxDEV19(PrerequisiteError, {
2805
3314
  missing: missingDeps,
2806
3315
  onExit: exit
2807
3316
  }, undefined, false, undefined, this);
2808
3317
  }
3318
+ if (appState === "onboarding") {
3319
+ return /* @__PURE__ */ jsxDEV19(Onboarding, {
3320
+ onComplete: () => setAppState("ready")
3321
+ }, undefined, false, undefined, this);
3322
+ }
2809
3323
  const goBack = () => setScreen("main");
2810
- return /* @__PURE__ */ jsxDEV18(Layout, {
3324
+ return /* @__PURE__ */ jsxDEV19(Layout, {
2811
3325
  breadcrumb: BREADCRUMBS[screen],
2812
3326
  children: [
2813
- screen === "main" && /* @__PURE__ */ jsxDEV18(MainMenu, {
3327
+ screen === "main" && /* @__PURE__ */ jsxDEV19(MainMenu, {
2814
3328
  onSelect: setScreen
2815
3329
  }, undefined, false, undefined, this),
2816
- screen === "config" && /* @__PURE__ */ jsxDEV18(ConfigMenu, {
3330
+ screen === "config" && /* @__PURE__ */ jsxDEV19(ConfigMenu, {
2817
3331
  onBack: goBack
2818
3332
  }, undefined, false, undefined, this),
2819
- screen === "packages" && /* @__PURE__ */ jsxDEV18(PackageMenu, {
3333
+ screen === "packages" && /* @__PURE__ */ jsxDEV19(PackageMenu, {
2820
3334
  onBack: goBack
2821
3335
  }, undefined, false, undefined, this),
2822
- screen === "themes" && /* @__PURE__ */ jsxDEV18(ThemeMenu, {
3336
+ screen === "themes" && /* @__PURE__ */ jsxDEV19(ThemeMenu, {
2823
3337
  onBack: goBack
2824
3338
  }, undefined, false, undefined, this)
2825
3339
  ]
2826
3340
  }, undefined, true, undefined, this);
2827
3341
  }
2828
- render(/* @__PURE__ */ jsxDEV18(App, {}, undefined, false, undefined, this));
3342
+ render(/* @__PURE__ */ jsxDEV19(App, {}, undefined, false, undefined, this));