formalconf 2.0.15 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/formalconf.js +449 -207
  2. package/package.json +1 -1
@@ -336,6 +336,9 @@ var BUNDLED_TEMPLATES_DIR = join(ROOT_DIR, "templates");
336
336
  var BUNDLED_MANIFEST_PATH = join(BUNDLED_TEMPLATES_DIR, "templates.json");
337
337
  var GTK_DIR = join(CONFIG_DIR, "gtk");
338
338
  var COLLOID_DIR = join(GTK_DIR, "colloid-gtk-theme");
339
+ var QT_DIR = join(CONFIG_DIR, "qt");
340
+ var KVANTUM_CONFIG_DIR = join(HOME_DIR, ".config", "Kvantum");
341
+ var KVANTUM_THEME_DIR = join(KVANTUM_CONFIG_DIR, "FormalConf");
339
342
  async function ensureDir2(path) {
340
343
  await ensureDir(path);
341
344
  }
@@ -467,7 +470,7 @@ function StatusIndicator({
467
470
  // package.json
468
471
  var package_default = {
469
472
  name: "formalconf",
470
- version: "2.0.15",
473
+ version: "2.0.17",
471
474
  description: "Dotfiles management TUI for macOS and Linux - config management, package sync, and theme switching",
472
475
  type: "module",
473
476
  main: "./dist/formalconf.js",
@@ -4666,13 +4669,13 @@ function useThemeGrid({
4666
4669
  import { parseArgs as parseArgs4 } from "util";
4667
4670
  import {
4668
4671
  readdirSync as readdirSync9,
4669
- existsSync as existsSync13,
4672
+ existsSync as existsSync14,
4670
4673
  rmSync,
4671
4674
  symlinkSync,
4672
4675
  unlinkSync as unlinkSync2,
4673
- copyFileSync
4676
+ copyFileSync as copyFileSync2
4674
4677
  } from "fs";
4675
- import { join as join12, basename as basename4 } from "path";
4678
+ import { join as join13, basename as basename4 } from "path";
4676
4679
 
4677
4680
  // src/lib/theme-parser.ts
4678
4681
  init_runtime();
@@ -5121,9 +5124,304 @@ ${instructions}`
5121
5124
  themeName: installedThemeName
5122
5125
  };
5123
5126
  }
5127
+ // src/lib/qt/kvantum.ts
5128
+ import { existsSync as existsSync8, copyFileSync } from "fs";
5129
+ import { join as join7 } from "path";
5130
+ init_runtime();
5131
+
5132
+ // src/lib/qt/palette.ts
5133
+ function hexToRgb2(hex) {
5134
+ const clean = hex.replace("#", "");
5135
+ return {
5136
+ r: parseInt(clean.substring(0, 2), 16),
5137
+ g: parseInt(clean.substring(2, 4), 16),
5138
+ b: parseInt(clean.substring(4, 6), 16)
5139
+ };
5140
+ }
5141
+ function rgbToHex2(r, g, b) {
5142
+ const toHex = (n) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0");
5143
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
5144
+ }
5145
+ function darken2(hex, percent) {
5146
+ const { r, g, b } = hexToRgb2(hex);
5147
+ const factor = 1 - percent / 100;
5148
+ return rgbToHex2(r * factor, g * factor, b * factor);
5149
+ }
5150
+ function lighten2(hex, percent) {
5151
+ const { r, g, b } = hexToRgb2(hex);
5152
+ const factor = percent / 100;
5153
+ return rgbToHex2(r + (255 - r) * factor, g + (255 - g) * factor, b + (255 - b) * factor);
5154
+ }
5155
+ function createKvantumPalette(palette, mode) {
5156
+ const isDark = mode === "dark";
5157
+ const accent = palette.accent || palette.color4;
5158
+ const altBase = isDark ? lighten2(palette.background, 8) : darken2(palette.background, 5);
5159
+ const button = isDark ? lighten2(palette.background, 15) : darken2(palette.background, 10);
5160
+ const light = isDark ? lighten2(palette.background, 25) : palette.foreground;
5161
+ const dark = isDark ? darken2(palette.background, 15) : darken2(palette.background, 20);
5162
+ const highlightText = isDark ? palette.background : palette.foreground;
5163
+ return {
5164
+ window: palette.background,
5165
+ base: palette.background,
5166
+ altBase,
5167
+ button,
5168
+ light,
5169
+ dark,
5170
+ highlight: accent,
5171
+ text: palette.foreground,
5172
+ windowText: palette.foreground,
5173
+ buttonText: palette.foreground,
5174
+ highlightText,
5175
+ link: palette.color4,
5176
+ linkVisited: palette.color5
5177
+ };
5178
+ }
5179
+ function generateKvantumConfig(palette, mode, baseTheme) {
5180
+ const kv = createKvantumPalette(palette, mode);
5181
+ const inheritFrom = baseTheme || (mode === "dark" ? "KvFlat" : "KvFlatLight");
5182
+ const textDimmed = mode === "dark" ? `${kv.text}c8` : `${kv.text}b4`;
5183
+ const textMoreDimmed = mode === "dark" ? `${kv.text}8c` : `${kv.text}78`;
5184
+ return `[%General]
5185
+ author=FormalConf
5186
+ comment=Auto-generated theme from FormalConf
5187
+ inherits=${inheritFrom}
5188
+
5189
+ [GeneralColors]
5190
+ window.color=${kv.window}
5191
+ inactive.window.color=${kv.window}
5192
+ base.color=${kv.base}
5193
+ inactive.base.color=${kv.base}
5194
+ alt.base.color=${kv.altBase}
5195
+ inactive.alt.base.color=${kv.altBase}
5196
+ button.color=${kv.button}
5197
+ light.color=${kv.light}
5198
+ mid.light.color=${kv.light}
5199
+ dark.color=${kv.dark}
5200
+ mid.color=${kv.altBase}
5201
+ highlight.color=${kv.highlight}
5202
+ inactive.highlight.color=${kv.highlight}
5203
+ text.color=${kv.text}
5204
+ inactive.text.color=${textDimmed}
5205
+ window.text.color=${kv.windowText}
5206
+ inactive.window.text.color=${textMoreDimmed}
5207
+ button.text.color=${kv.buttonText}
5208
+ disabled.text.color=${textMoreDimmed}
5209
+ tooltip.text.color=${kv.text}
5210
+ highlight.text.color=${kv.highlightText}
5211
+ link.color=${kv.link}
5212
+ link.visited.color=${kv.linkVisited}
5213
+ progress.indicator.text.color=${kv.highlightText}
5214
+
5215
+ [Hacks]
5216
+ transparent_dolphin_view=false
5217
+ blur_translucent=true
5218
+ respect_darkness=true
5219
+
5220
+ [PanelButtonCommand]
5221
+ text.normal.color=${kv.text}
5222
+ text.normal.inactive.color=${textMoreDimmed}
5223
+ text.focus.color=${kv.text}
5224
+ text.press.color=${kv.text}
5225
+ text.toggle.color=${kv.text}
5226
+ text.toggle.inactive.color=${textMoreDimmed}
5227
+
5228
+ [Toolbar]
5229
+ text.normal.color=${kv.text}
5230
+ text.focus.color=${kv.text}
5231
+
5232
+ [MenuBar]
5233
+ text.normal.color=${kv.text}
5234
+
5235
+ [MenuBarItem]
5236
+ text.normal.color=${kv.text}
5237
+ text.focus.color=${kv.highlight}
5238
+
5239
+ [MenuItem]
5240
+ text.normal.color=${kv.text}
5241
+ text.focus.color=${kv.text}
5242
+
5243
+ [ItemView]
5244
+ text.normal.color=${kv.text}
5245
+ text.normal.inactive.color=${textDimmed}
5246
+ text.focus.color=${kv.text}
5247
+ text.press.color=${kv.text}
5248
+ text.toggle.color=${kv.text}
5249
+ text.toggle.inactive.color=${textDimmed}
5250
+
5251
+ [Tab]
5252
+ text.normal.color=${textMoreDimmed}
5253
+ text.normal.inactive.color=${textMoreDimmed}
5254
+ text.focus.color=${textDimmed}
5255
+ text.toggle.color=${kv.text}
5256
+
5257
+ [ToolboxTab]
5258
+ text.normal.color=${textDimmed}
5259
+ text.normal.inactive.color=${textMoreDimmed}
5260
+ text.press.color=${kv.text}
5261
+ text.focus.color=${kv.text}
5262
+
5263
+ [HeaderSection]
5264
+ text.normal.color=${textMoreDimmed}
5265
+ text.normal.inactive.color=${textMoreDimmed}
5266
+ text.focus.color=${textDimmed}
5267
+ text.toggle.color=${kv.text}
5268
+
5269
+ [Progressbar]
5270
+ text.normal.color=${textMoreDimmed}
5271
+ text.normal.inactive.color=${textMoreDimmed}
5272
+ text.focus.color=${kv.text}
5273
+ text.press.color=${kv.text}
5274
+ text.toggle.color=${kv.text}
5275
+
5276
+ [TitleBar]
5277
+ text.normal.color=${textMoreDimmed}
5278
+ text.focus.color=${kv.text}
5279
+
5280
+ [GroupBox]
5281
+ text.normal.color=${kv.text}
5282
+ text.press.color=${kv.text}
5283
+ text.focus.color=${kv.text}
5284
+
5285
+ [Dock]
5286
+ text.normal.color=${kv.text}
5287
+
5288
+ [DockTitle]
5289
+ text.normal.color=${kv.text}
5290
+ text.focus.color=${kv.text}
5291
+
5292
+ [RadioButton]
5293
+ text.normal.color=${kv.text}
5294
+ text.focus.color=${kv.text}
5295
+
5296
+ [CheckBox]
5297
+ text.normal.color=${kv.text}
5298
+ text.focus.color=${kv.text}
5299
+
5300
+ [LineEdit]
5301
+ text.normal.color=${kv.text}
5302
+
5303
+ [IndicatorSpinBox]
5304
+ text.normal.color=${kv.text}
5305
+
5306
+ [Menu]
5307
+ text.normal.color=${kv.text}
5308
+ `;
5309
+ }
5310
+
5311
+ // src/lib/qt/kvantum.ts
5312
+ var SYSTEM_KVANTUM_DIR = "/usr/share/Kvantum";
5313
+ var SETUP_SHOWN_FILE = join7(QT_DIR, ".setup-shown");
5314
+ async function checkQtDependencies() {
5315
+ const [kvantum, qt5ct, qt6ct] = await Promise.all([
5316
+ commandExists("kvantummanager"),
5317
+ commandExists("qt5ct"),
5318
+ commandExists("qt6ct")
5319
+ ]);
5320
+ const missing = [];
5321
+ if (!kvantum)
5322
+ missing.push("kvantum");
5323
+ return { kvantum, qt5ct, qt6ct, missing };
5324
+ }
5325
+ async function hasShownSetupReminder() {
5326
+ return existsSync8(SETUP_SHOWN_FILE);
5327
+ }
5328
+ async function markSetupReminderShown() {
5329
+ await ensureDir2(QT_DIR);
5330
+ await writeFile(SETUP_SHOWN_FILE, new Date().toISOString());
5331
+ }
5332
+ function getBaseTheme(mode) {
5333
+ return mode === "dark" ? "KvFlat" : "KvFlatLight";
5334
+ }
5335
+ function copyBaseThemeSvg(baseTheme) {
5336
+ const sourceSvg = join7(SYSTEM_KVANTUM_DIR, baseTheme, `${baseTheme}.svg`);
5337
+ const targetSvg = join7(KVANTUM_THEME_DIR, "FormalConf.svg");
5338
+ if (existsSync8(sourceSvg)) {
5339
+ copyFileSync(sourceSvg, targetSvg);
5340
+ return true;
5341
+ }
5342
+ return false;
5343
+ }
5344
+ async function writeKvantumTheme(theme, mode) {
5345
+ const palette = mode === "dark" ? theme.dark : theme.light;
5346
+ if (!palette) {
5347
+ throw new Error(`Theme does not have a ${mode} palette`);
5348
+ }
5349
+ await ensureDir2(KVANTUM_THEME_DIR);
5350
+ const baseTheme = getBaseTheme(mode);
5351
+ copyBaseThemeSvg(baseTheme);
5352
+ const config = generateKvantumConfig(palette, mode, baseTheme);
5353
+ const configPath = join7(KVANTUM_THEME_DIR, "FormalConf.kvconfig");
5354
+ await writeFile(configPath, config);
5355
+ }
5356
+ async function writeKvantumGlobalConfig() {
5357
+ await ensureDir2(KVANTUM_CONFIG_DIR);
5358
+ const globalConfig = `[General]
5359
+ theme=FormalConf
5360
+ `;
5361
+ const configPath = join7(KVANTUM_CONFIG_DIR, "kvantum.kvconfig");
5362
+ await writeFile(configPath, globalConfig);
5363
+ }
5364
+ async function applyQtTheme(theme, mode) {
5365
+ if (getOS() !== "linux") {
5366
+ return {
5367
+ success: true,
5368
+ themeName: "",
5369
+ skipped: true,
5370
+ skipReason: "QT theming only available on Linux"
5371
+ };
5372
+ }
5373
+ const palette = mode === "dark" ? theme.dark : theme.light;
5374
+ if (!palette) {
5375
+ return {
5376
+ success: false,
5377
+ themeName: "",
5378
+ error: `Theme does not have a ${mode} palette`
5379
+ };
5380
+ }
5381
+ const deps = await checkQtDependencies();
5382
+ if (!deps.kvantum) {
5383
+ return {
5384
+ success: true,
5385
+ themeName: "",
5386
+ skipped: true,
5387
+ skipReason: "Kvantum not installed (install kvantum for QT theming)"
5388
+ };
5389
+ }
5390
+ try {
5391
+ await writeKvantumTheme(theme, mode);
5392
+ await writeKvantumGlobalConfig();
5393
+ const shownBefore = await hasShownSetupReminder();
5394
+ return {
5395
+ success: true,
5396
+ themeName: "FormalConf (Kvantum)",
5397
+ ...!shownBefore && {
5398
+ error: undefined
5399
+ }
5400
+ };
5401
+ } catch (err) {
5402
+ const message = err instanceof Error ? err.message : String(err);
5403
+ return {
5404
+ success: false,
5405
+ themeName: "",
5406
+ error: `Failed to write Kvantum theme: ${message}`
5407
+ };
5408
+ }
5409
+ }
5410
+ function getQtSetupReminder() {
5411
+ return `Note: Add to your shell profile for QT apps to use Kvantum:
5412
+ export QT_QPA_PLATFORMTHEME=kvantum`;
5413
+ }
5414
+ async function getAndMarkSetupReminder() {
5415
+ const shown = await hasShownSetupReminder();
5416
+ if (shown) {
5417
+ return null;
5418
+ }
5419
+ await markSetupReminderShown();
5420
+ return getQtSetupReminder();
5421
+ }
5124
5422
  // src/lib/theme-config.ts
5125
5423
  import { hostname } from "os";
5126
- import { existsSync as existsSync8, readFileSync, writeFileSync } from "fs";
5424
+ import { existsSync as existsSync9, readFileSync, writeFileSync } from "fs";
5127
5425
  var DEFAULT_CONFIG = {
5128
5426
  version: 1,
5129
5427
  defaultTheme: null,
@@ -5133,7 +5431,7 @@ function getDeviceHostname() {
5133
5431
  return hostname();
5134
5432
  }
5135
5433
  function loadThemeConfig() {
5136
- if (!existsSync8(THEME_CONFIG_PATH)) {
5434
+ if (!existsSync9(THEME_CONFIG_PATH)) {
5137
5435
  return { ...DEFAULT_CONFIG, devices: {} };
5138
5436
  }
5139
5437
  try {
@@ -5199,8 +5497,8 @@ function getDefaultTheme() {
5199
5497
 
5200
5498
  // src/lib/theme-v2/loader.ts
5201
5499
  init_runtime();
5202
- import { existsSync as existsSync9, readdirSync as readdirSync5 } from "fs";
5203
- import { join as join7, basename as basename2 } from "path";
5500
+ import { existsSync as existsSync10, readdirSync as readdirSync5 } from "fs";
5501
+ import { join as join8, basename as basename2 } from "path";
5204
5502
 
5205
5503
  // src/lib/theme-v2/color.ts
5206
5504
  function isValidHex(hex) {
@@ -5218,7 +5516,7 @@ function normalizeHex(hex) {
5218
5516
  }
5219
5517
  return hex.toUpperCase();
5220
5518
  }
5221
- function hexToRgb2(hex) {
5519
+ function hexToRgb3(hex) {
5222
5520
  const normalized = normalizeHex(hex);
5223
5521
  if (!isValidHex(normalized)) {
5224
5522
  throw new Error(`Invalid hex color: ${hex}`);
@@ -5230,7 +5528,7 @@ function hexToRgb2(hex) {
5230
5528
  }
5231
5529
  function hexToColorVariable(hex) {
5232
5530
  const normalized = normalizeHex(hex);
5233
- const { r, g, b } = hexToRgb2(normalized);
5531
+ const { r, g, b } = hexToRgb3(normalized);
5234
5532
  return {
5235
5533
  hex: normalized,
5236
5534
  strip: normalized.slice(1),
@@ -5479,7 +5777,7 @@ ${validationErrors}`);
5479
5777
  }
5480
5778
  }
5481
5779
  async function loadThemeJson(themePath) {
5482
- if (!existsSync9(themePath)) {
5780
+ if (!existsSync10(themePath)) {
5483
5781
  throw new ThemeLoadError(themePath, "file not found");
5484
5782
  }
5485
5783
  let content;
@@ -5509,14 +5807,14 @@ function getAvailableModes(theme) {
5509
5807
  return modes;
5510
5808
  }
5511
5809
  async function listJsonThemes() {
5512
- if (!existsSync9(THEMES_DIR)) {
5810
+ if (!existsSync10(THEMES_DIR)) {
5513
5811
  return [];
5514
5812
  }
5515
5813
  const entries = readdirSync5(THEMES_DIR, { withFileTypes: true });
5516
5814
  const themes = [];
5517
5815
  for (const entry of entries) {
5518
5816
  if (entry.isFile() && entry.name.endsWith(".json")) {
5519
- const path = join7(THEMES_DIR, entry.name);
5817
+ const path = join8(THEMES_DIR, entry.name);
5520
5818
  try {
5521
5819
  const theme = await loadThemeJson(path);
5522
5820
  themes.push({
@@ -5535,7 +5833,7 @@ async function listJsonThemes() {
5535
5833
 
5536
5834
  // src/lib/template-engine/engine.ts
5537
5835
  init_runtime();
5538
- import { join as join9 } from "path";
5836
+ import { join as join10 } from "path";
5539
5837
 
5540
5838
  // src/lib/template-engine/modifiers.ts
5541
5839
  var VALID_MODIFIERS = [
@@ -5623,6 +5921,25 @@ var COLOR_VARIABLES = [
5623
5921
  function isColorVariable(name) {
5624
5922
  return COLOR_VARIABLES.includes(name);
5625
5923
  }
5924
+ function getNestedValue(obj, path) {
5925
+ return path.split(".").reduce((acc, key) => {
5926
+ if (acc && typeof acc === "object" && key in acc) {
5927
+ return acc[key];
5928
+ }
5929
+ return;
5930
+ }, obj);
5931
+ }
5932
+ function processConditionals(template, context) {
5933
+ template = template.replace(/\{\{#if\s+(\S+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, variable, content) => {
5934
+ const value = getNestedValue(context, variable);
5935
+ return value ? content : "";
5936
+ });
5937
+ template = template.replace(/\{\{#unless\s+(\S+)\}\}([\s\S]*?)\{\{\/unless\}\}/g, (_, variable, content) => {
5938
+ const value = getNestedValue(context, variable);
5939
+ return !value ? content : "";
5940
+ });
5941
+ return template;
5942
+ }
5626
5943
  function getContextValue(context, variableName, modifier) {
5627
5944
  if (variableName.startsWith("theme.")) {
5628
5945
  const key = variableName.slice(6);
@@ -5634,6 +5951,13 @@ function getContextValue(context, variableName, modifier) {
5634
5951
  const value = context.gtk[key];
5635
5952
  return value !== undefined ? String(value) : undefined;
5636
5953
  }
5954
+ if (variableName.startsWith("neovim.")) {
5955
+ if (!context.neovim)
5956
+ return;
5957
+ const key = variableName.slice(7);
5958
+ const value = context.neovim[key];
5959
+ return value !== undefined ? String(value) : undefined;
5960
+ }
5637
5961
  if (variableName === "mode") {
5638
5962
  return context.mode;
5639
5963
  }
@@ -5644,7 +5968,8 @@ function getContextValue(context, variableName, modifier) {
5644
5968
  return;
5645
5969
  }
5646
5970
  function renderTemplate(template, context) {
5647
- return template.replace(VARIABLE_REGEX, (match, variable) => {
5971
+ let result = processConditionals(template, context);
5972
+ result = result.replace(VARIABLE_REGEX, (match, variable) => {
5648
5973
  const { name, modifier } = parseVariableReference(variable);
5649
5974
  const value = getContextValue(context, name, modifier);
5650
5975
  if (value === undefined) {
@@ -5652,9 +5977,11 @@ function renderTemplate(template, context) {
5652
5977
  }
5653
5978
  return value;
5654
5979
  });
5980
+ return result;
5655
5981
  }
5656
5982
  function renderDualModeTemplate(template, contexts) {
5657
- let result = template.replace(/\{\{dark\.([a-zA-Z0-9_.]+)\}\}/g, (match, variable) => {
5983
+ let result = processConditionals(template, contexts);
5984
+ result = result.replace(/\{\{dark\.([a-zA-Z0-9_.]+)\}\}/g, (match, variable) => {
5658
5985
  const { name, modifier } = parseVariableReference(variable);
5659
5986
  const value = getContextValue(contexts.dark, name, modifier);
5660
5987
  return value ?? match;
@@ -5673,14 +6000,14 @@ function renderDualModeTemplate(template, contexts) {
5673
6000
 
5674
6001
  // src/lib/template-engine/versioning.ts
5675
6002
  init_runtime();
5676
- import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
5677
- import { join as join8 } from "path";
6003
+ import { existsSync as existsSync11, readdirSync as readdirSync6 } from "fs";
6004
+ import { join as join9 } from "path";
5678
6005
  var DEFAULT_MANIFEST = {
5679
6006
  version: 1,
5680
6007
  templates: {}
5681
6008
  };
5682
6009
  async function loadTemplatesManifest() {
5683
- if (!existsSync10(TEMPLATES_MANIFEST_PATH)) {
6010
+ if (!existsSync11(TEMPLATES_MANIFEST_PATH)) {
5684
6011
  return { ...DEFAULT_MANIFEST };
5685
6012
  }
5686
6013
  try {
@@ -5695,7 +6022,7 @@ async function saveTemplatesManifest(manifest) {
5695
6022
  await writeFile(TEMPLATES_MANIFEST_PATH, JSON.stringify(manifest, null, 2));
5696
6023
  }
5697
6024
  async function loadBundledManifest() {
5698
- if (!existsSync10(BUNDLED_MANIFEST_PATH)) {
6025
+ if (!existsSync11(BUNDLED_MANIFEST_PATH)) {
5699
6026
  return { version: 1, templates: {} };
5700
6027
  }
5701
6028
  try {
@@ -5746,13 +6073,13 @@ async function installTemplate(templateName) {
5746
6073
  if (!bundledMeta) {
5747
6074
  throw new Error(`Template '${templateName}' not found in bundled templates`);
5748
6075
  }
5749
- const sourcePath = join8(BUNDLED_TEMPLATES_DIR, templateName);
5750
- if (!existsSync10(sourcePath)) {
6076
+ const sourcePath = join9(BUNDLED_TEMPLATES_DIR, templateName);
6077
+ if (!existsSync11(sourcePath)) {
5751
6078
  throw new Error(`Template file not found: ${sourcePath}`);
5752
6079
  }
5753
6080
  await ensureDir2(TEMPLATES_DIR);
5754
6081
  const content = await readText(sourcePath);
5755
- const destPath = join8(TEMPLATES_DIR, templateName);
6082
+ const destPath = join9(TEMPLATES_DIR, templateName);
5756
6083
  await writeFile(destPath, content);
5757
6084
  const manifest = await loadTemplatesManifest();
5758
6085
  manifest.templates[templateName] = {
@@ -5768,10 +6095,11 @@ async function installAllTemplates() {
5768
6095
  await installTemplate(name);
5769
6096
  }
5770
6097
  }
5771
- function getTemplateType(filename) {
5772
- const dualModeTemplates = ["ghostty.conf.template", "neovim.lua.template", "lynk.css.template"];
5773
- if (dualModeTemplates.includes(filename)) {
5774
- return "dual";
6098
+ async function getTemplateType(filename) {
6099
+ const manifest = await loadBundledManifest();
6100
+ const meta = manifest.templates[filename];
6101
+ if (meta?.mode) {
6102
+ return meta.mode;
5775
6103
  }
5776
6104
  if (filename.includes("-dark.") || filename.includes("-light.")) {
5777
6105
  return "partial";
@@ -5785,36 +6113,21 @@ function getPartialMode(filename) {
5785
6113
  return "light";
5786
6114
  return;
5787
6115
  }
5788
- function getOutputFilename(templateName) {
5789
- let output = templateName.replace(/\.template$/, "");
5790
- if (templateName.startsWith("kitty-dark")) {
5791
- return "dark-theme.auto.conf";
5792
- }
5793
- if (templateName.startsWith("kitty-light")) {
5794
- return "light-theme.auto.conf";
5795
- }
5796
- if (templateName.startsWith("waybar-dark")) {
5797
- return "style-dark.css";
5798
- }
5799
- if (templateName.startsWith("waybar-light")) {
5800
- return "style-light.css";
5801
- }
5802
- if (templateName === "ghostty-dark.theme.template") {
5803
- return "formalconf-dark";
5804
- }
5805
- if (templateName === "ghostty-light.theme.template") {
5806
- return "formalconf-light";
6116
+ async function getOutputFilename(templateName) {
6117
+ const manifest = await loadBundledManifest();
6118
+ const meta = manifest.templates[templateName];
6119
+ if (meta?.output) {
6120
+ return meta.output;
5807
6121
  }
5808
- if (templateName.startsWith("btop-dark")) {
5809
- return "formalconf-dark.theme";
5810
- }
5811
- if (templateName.startsWith("btop-light")) {
5812
- return "formalconf-light.theme";
5813
- }
5814
- return output;
6122
+ return templateName.replace(/\.template$/, "");
6123
+ }
6124
+ async function getTemplateTargets(templateName) {
6125
+ const manifest = await loadBundledManifest();
6126
+ const meta = manifest.templates[templateName];
6127
+ return meta?.targets ?? [];
5815
6128
  }
5816
6129
  async function listInstalledTemplates() {
5817
- if (!existsSync10(TEMPLATES_DIR)) {
6130
+ if (!existsSync11(TEMPLATES_DIR)) {
5818
6131
  return [];
5819
6132
  }
5820
6133
  const entries = readdirSync6(TEMPLATES_DIR, { withFileTypes: true });
@@ -5823,98 +6136,17 @@ async function listInstalledTemplates() {
5823
6136
  if (entry.isFile() && entry.name.endsWith(".template")) {
5824
6137
  templates.push({
5825
6138
  name: entry.name,
5826
- path: join8(TEMPLATES_DIR, entry.name),
5827
- outputName: getOutputFilename(entry.name),
5828
- type: getTemplateType(entry.name),
5829
- partialMode: getPartialMode(entry.name)
6139
+ path: join9(TEMPLATES_DIR, entry.name),
6140
+ outputName: await getOutputFilename(entry.name),
6141
+ type: await getTemplateType(entry.name),
6142
+ partialMode: getPartialMode(entry.name),
6143
+ targets: await getTemplateTargets(entry.name)
5830
6144
  });
5831
6145
  }
5832
6146
  }
5833
6147
  return templates;
5834
6148
  }
5835
6149
 
5836
- // src/lib/neovim/generator.ts
5837
- function toLua(value, indent = 2) {
5838
- const spaces = " ".repeat(indent);
5839
- if (value === null || value === undefined) {
5840
- return "nil";
5841
- }
5842
- if (typeof value === "boolean") {
5843
- return value ? "true" : "false";
5844
- }
5845
- if (typeof value === "number") {
5846
- return String(value);
5847
- }
5848
- if (typeof value === "string") {
5849
- const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
5850
- return `"${escaped}"`;
5851
- }
5852
- if (Array.isArray(value)) {
5853
- if (value.length === 0) {
5854
- return "{}";
5855
- }
5856
- const items = value.map((v) => `${spaces}${toLua(v, indent + 2)}`);
5857
- return `{
5858
- ${items.join(`,
5859
- `)}
5860
- ${" ".repeat(indent - 2)}}`;
5861
- }
5862
- if (typeof value === "object") {
5863
- const entries = Object.entries(value);
5864
- if (entries.length === 0) {
5865
- return "{}";
5866
- }
5867
- const items = entries.map(([k, v]) => {
5868
- const key = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(k) ? k : `["${k}"]`;
5869
- return `${spaces}${key} = ${toLua(v, indent + 2)}`;
5870
- });
5871
- return `{
5872
- ${items.join(`,
5873
- `)}
5874
- ${" ".repeat(indent - 2)}}`;
5875
- }
5876
- return "nil";
5877
- }
5878
- function generateOptsSection(opts) {
5879
- if (!opts || Object.keys(opts).length === 0) {
5880
- return "";
5881
- }
5882
- return ` opts = ${toLua(opts, 6)},`;
5883
- }
5884
- function generateNeovimConfig(theme, mode) {
5885
- if (!theme.neovim) {
5886
- return "";
5887
- }
5888
- const { repo, colorscheme, light_colorscheme, opts } = theme.neovim;
5889
- const effectiveColorscheme = mode === "light" && light_colorscheme ? light_colorscheme : colorscheme;
5890
- const optsSection = generateOptsSection(opts);
5891
- const hasDualMode = light_colorscheme && light_colorscheme !== colorscheme;
5892
- const lines = [
5893
- `-- ${theme.title} (${mode}) - Generated by FormalConf`,
5894
- `-- Neovim colorscheme configuration for LazyVim`,
5895
- ``,
5896
- `return {`,
5897
- ` {`,
5898
- ` "${repo}",`,
5899
- ` name = "${theme.title.toLowerCase().replace(/\s+/g, "-")}",`,
5900
- ` priority = 1000,`
5901
- ];
5902
- if (optsSection) {
5903
- lines.push(optsSection);
5904
- }
5905
- lines.push(` },`, ` {`, ` "LazyVim/LazyVim",`, ` opts = {`, ` colorscheme = "${effectiveColorscheme}",`, ` },`, ` },`);
5906
- if (hasDualMode) {
5907
- lines.push(` {`, ` "f-person/auto-dark-mode.nvim",`, ` opts = {`, ` update_background = false,`, ` set_dark_mode = function()`, ` vim.cmd("colorscheme ${colorscheme}")`, ` end,`, ` set_light_mode = function()`, ` vim.cmd("colorscheme ${light_colorscheme}")`, ` end,`, ` },`, ` },`);
5908
- }
5909
- lines.push(`}`);
5910
- return lines.join(`
5911
- `) + `
5912
- `;
5913
- }
5914
- function hasNeovimConfig(theme) {
5915
- return !!theme.neovim?.repo && !!theme.neovim?.colorscheme;
5916
- }
5917
-
5918
6150
  // src/lib/template-engine/engine.ts
5919
6151
  function buildThemeMetadata(theme, mode) {
5920
6152
  return {
@@ -5934,7 +6166,7 @@ function buildGtkMetadata(theme, mode) {
5934
6166
  };
5935
6167
  }
5936
6168
  function buildTemplateContext(theme, palette, mode) {
5937
- return {
6169
+ const context = {
5938
6170
  color0: hexToColorVariable(palette.color0),
5939
6171
  color1: hexToColorVariable(palette.color1),
5940
6172
  color2: hexToColorVariable(palette.color2),
@@ -5962,6 +6194,15 @@ function buildTemplateContext(theme, palette, mode) {
5962
6194
  gtk: buildGtkMetadata(theme, mode),
5963
6195
  mode
5964
6196
  };
6197
+ if (theme.neovim) {
6198
+ context.neovim = {
6199
+ repo: theme.neovim.repo,
6200
+ colorscheme: theme.neovim.colorscheme,
6201
+ light_colorscheme: theme.neovim.light_colorscheme,
6202
+ opts: theme.neovim.opts
6203
+ };
6204
+ }
6205
+ return context;
5965
6206
  }
5966
6207
  function buildDualModeContext(theme) {
5967
6208
  if (!theme.dark || !theme.light) {
@@ -6005,7 +6246,7 @@ async function renderTemplateFile(templateFile, theme, mode) {
6005
6246
  return {
6006
6247
  template: templateFile,
6007
6248
  content,
6008
- outputPath: join9(GENERATED_DIR, templateFile.outputName)
6249
+ outputPath: join10(GENERATED_DIR, templateFile.outputName)
6009
6250
  };
6010
6251
  }
6011
6252
  async function renderAllTemplates(theme, mode) {
@@ -6034,38 +6275,16 @@ async function writeRenderedTemplates(results) {
6034
6275
  await writeFile(result.outputPath, result.content);
6035
6276
  }
6036
6277
  }
6037
- async function generateNeovimConfigFile(theme, mode) {
6038
- if (!hasNeovimConfig(theme)) {
6039
- return null;
6040
- }
6041
- const content = generateNeovimConfig(theme, mode);
6042
- const outputPath = join9(GENERATED_DIR, "neovim.lua");
6043
- await writeFile(outputPath, content);
6044
- return {
6045
- template: {
6046
- name: "neovim.lua",
6047
- path: "",
6048
- outputName: "neovim.lua",
6049
- type: "single"
6050
- },
6051
- content,
6052
- outputPath
6053
- };
6054
- }
6055
6278
  async function generateThemeConfigs(theme, mode) {
6056
6279
  const results = await renderAllTemplates(theme, mode);
6057
6280
  await writeRenderedTemplates(results);
6058
- const neovimResult = await generateNeovimConfigFile(theme, mode);
6059
- if (neovimResult) {
6060
- results.push(neovimResult);
6061
- }
6062
6281
  return results;
6063
6282
  }
6064
6283
 
6065
6284
  // src/lib/migration/extractor.ts
6066
6285
  init_runtime();
6067
- import { existsSync as existsSync11, readdirSync as readdirSync7 } from "fs";
6068
- import { join as join10 } from "path";
6286
+ import { existsSync as existsSync12, readdirSync as readdirSync7 } from "fs";
6287
+ import { join as join11 } from "path";
6069
6288
  function normalizeHex2(hex) {
6070
6289
  hex = hex.replace(/^(#|0x)/i, "");
6071
6290
  if (hex.length === 3) {
@@ -6206,7 +6425,7 @@ async function extractFromGhostty(path) {
6206
6425
  return { colors: colors5, source: "ghostty" };
6207
6426
  }
6208
6427
  async function extractColors(filePath) {
6209
- if (!existsSync11(filePath)) {
6428
+ if (!existsSync12(filePath)) {
6210
6429
  return null;
6211
6430
  }
6212
6431
  const filename = filePath.toLowerCase();
@@ -6228,7 +6447,7 @@ async function extractColors(filePath) {
6228
6447
  return null;
6229
6448
  }
6230
6449
  async function extractFromLegacyTheme(themePath) {
6231
- if (!existsSync11(themePath)) {
6450
+ if (!existsSync12(themePath)) {
6232
6451
  return null;
6233
6452
  }
6234
6453
  const files = readdirSync7(themePath, { withFileTypes: true });
@@ -6240,12 +6459,12 @@ async function extractFromLegacyTheme(themePath) {
6240
6459
  for (const preferred of preferredFiles) {
6241
6460
  const match = files.find((f) => f.name.toLowerCase() === preferred.toLowerCase());
6242
6461
  if (match) {
6243
- return extractColors(join10(themePath, match.name));
6462
+ return extractColors(join11(themePath, match.name));
6244
6463
  }
6245
6464
  }
6246
6465
  for (const file of files) {
6247
6466
  if (file.isFile() && (file.name.endsWith(".conf") || file.name.endsWith(".toml"))) {
6248
- const result = await extractColors(join10(themePath, file.name));
6467
+ const result = await extractColors(join11(themePath, file.name));
6249
6468
  if (result && Object.keys(result.colors).length > 0) {
6250
6469
  return result;
6251
6470
  }
@@ -6334,19 +6553,19 @@ function generateThemeJson(name, colors5, options = {}) {
6334
6553
  init_runtime();
6335
6554
 
6336
6555
  // src/lib/wallpaper.ts
6337
- import { existsSync as existsSync12, readdirSync as readdirSync8, unlinkSync } from "fs";
6338
- import { join as join11 } from "path";
6556
+ import { existsSync as existsSync13, readdirSync as readdirSync8, unlinkSync } from "fs";
6557
+ import { join as join12 } from "path";
6339
6558
  init_runtime();
6340
6559
  var DEFAULT_TIMEOUT_MS = 30000;
6341
6560
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
6342
6561
  function clearBackgroundsDir() {
6343
- if (!existsSync12(BACKGROUNDS_TARGET_DIR)) {
6562
+ if (!existsSync13(BACKGROUNDS_TARGET_DIR)) {
6344
6563
  return;
6345
6564
  }
6346
6565
  const entries = readdirSync8(BACKGROUNDS_TARGET_DIR, { withFileTypes: true });
6347
6566
  for (const entry of entries) {
6348
6567
  if (entry.isFile() || entry.isSymbolicLink()) {
6349
- unlinkSync(join11(BACKGROUNDS_TARGET_DIR, entry.name));
6568
+ unlinkSync(join12(BACKGROUNDS_TARGET_DIR, entry.name));
6350
6569
  }
6351
6570
  }
6352
6571
  }
@@ -6410,7 +6629,7 @@ async function downloadWallpaper(url, filename, timeoutMs = DEFAULT_TIMEOUT_MS)
6410
6629
  };
6411
6630
  }
6412
6631
  const ext = getExtension(url, contentType);
6413
- const outputPath = join11(BACKGROUNDS_TARGET_DIR, `${filename}.${ext}`);
6632
+ const outputPath = join12(BACKGROUNDS_TARGET_DIR, `${filename}.${ext}`);
6414
6633
  await writeBuffer(outputPath, arrayBuffer);
6415
6634
  return { success: true, path: outputPath };
6416
6635
  } catch (err) {
@@ -6467,11 +6686,11 @@ async function listAllThemes() {
6467
6686
  });
6468
6687
  }
6469
6688
  }
6470
- if (existsSync13(THEMES_DIR)) {
6689
+ if (existsSync14(THEMES_DIR)) {
6471
6690
  const entries = readdirSync9(THEMES_DIR, { withFileTypes: true });
6472
6691
  for (const entry of entries) {
6473
6692
  if (entry.isDirectory()) {
6474
- const themePath = join12(THEMES_DIR, entry.name);
6693
+ const themePath = join13(THEMES_DIR, entry.name);
6475
6694
  const theme = await parseTheme(themePath, entry.name);
6476
6695
  themes.push({
6477
6696
  displayName: theme.name,
@@ -6488,10 +6707,10 @@ async function listAllThemes() {
6488
6707
  return themes;
6489
6708
  }
6490
6709
  function clearDirectory(dir) {
6491
- if (existsSync13(dir)) {
6710
+ if (existsSync14(dir)) {
6492
6711
  const entries = readdirSync9(dir, { withFileTypes: true });
6493
6712
  for (const entry of entries) {
6494
- const fullPath = join12(dir, entry.name);
6713
+ const fullPath = join13(dir, entry.name);
6495
6714
  if (entry.isSymbolicLink() || entry.isFile()) {
6496
6715
  unlinkSync2(fullPath);
6497
6716
  } else if (entry.isDirectory()) {
@@ -6501,7 +6720,7 @@ function clearDirectory(dir) {
6501
6720
  }
6502
6721
  }
6503
6722
  function createSymlink(source, target) {
6504
- if (existsSync13(target)) {
6723
+ if (existsSync14(target)) {
6505
6724
  unlinkSync2(target);
6506
6725
  }
6507
6726
  symlinkSync(source, target);
@@ -6525,20 +6744,21 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
6525
6744
  await installAllTemplates();
6526
6745
  }
6527
6746
  clearDirectory(THEME_TARGET_DIR);
6528
- if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
6747
+ if (existsSync14(BACKGROUNDS_TARGET_DIR)) {
6529
6748
  rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
6530
6749
  }
6531
6750
  const results = await generateThemeConfigs(theme, mode);
6532
- const ghosttyThemesDir = join12(HOME_DIR, ".config", "ghostty", "themes");
6533
6751
  for (const result of results) {
6534
6752
  const filename = basename4(result.outputPath);
6535
- if (filename === "formalconf-dark" || filename === "formalconf-light") {
6536
- await ensureDir2(ghosttyThemesDir);
6537
- const targetPath2 = join12(ghosttyThemesDir, filename);
6538
- copyFileSync(result.outputPath, targetPath2);
6753
+ const templateTargets = result.template.targets ?? [];
6754
+ for (const target of templateTargets) {
6755
+ const expandedTarget = target.replace(/^~/, HOME_DIR);
6756
+ await ensureDir2(expandedTarget);
6757
+ const targetPath2 = join13(expandedTarget, filename);
6758
+ copyFileSync2(result.outputPath, targetPath2);
6539
6759
  }
6540
- const targetPath = join12(THEME_TARGET_DIR, filename);
6541
- copyFileSync(result.outputPath, targetPath);
6760
+ const targetPath = join13(THEME_TARGET_DIR, filename);
6761
+ copyFileSync2(result.outputPath, targetPath);
6542
6762
  }
6543
6763
  let wallpaperPaths = [];
6544
6764
  let wallpaperErrors = [];
@@ -6551,6 +6771,10 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
6551
6771
  if (getOS() === "linux") {
6552
6772
  gtkResult = await applyGtkTheme(theme, mode);
6553
6773
  }
6774
+ let qtResult = null;
6775
+ if (getOS() === "linux") {
6776
+ qtResult = await applyQtTheme(theme, mode);
6777
+ }
6554
6778
  if (saveMapping) {
6555
6779
  await setDeviceTheme(identifier);
6556
6780
  }
@@ -6586,6 +6810,21 @@ GTK theme: ${gtkResult.themeName}`;
6586
6810
  Warning: GTK theme failed - ${gtkResult.error}`;
6587
6811
  }
6588
6812
  }
6813
+ if (qtResult && !qtResult.skipped) {
6814
+ if (qtResult.success) {
6815
+ output += `
6816
+ QT theme: ${qtResult.themeName}`;
6817
+ const setupReminder = await getAndMarkSetupReminder();
6818
+ if (setupReminder) {
6819
+ output += `
6820
+
6821
+ ${setupReminder}`;
6822
+ }
6823
+ } else {
6824
+ output += `
6825
+ Warning: QT theme failed - ${qtResult.error}`;
6826
+ }
6827
+ }
6589
6828
  const hookEnv = {
6590
6829
  FORMALCONF_THEME: identifier,
6591
6830
  FORMALCONF_THEME_MODE: mode,
@@ -6597,6 +6836,9 @@ GTK theme: ${gtkResult.themeName}`;
6597
6836
  if (gtkResult?.success && gtkResult.themeName) {
6598
6837
  hookEnv.FORMALCONF_GTK_THEME = gtkResult.themeName;
6599
6838
  }
6839
+ if (qtResult?.success && qtResult.themeName) {
6840
+ hookEnv.FORMALCONF_QT_THEME = qtResult.themeName;
6841
+ }
6600
6842
  const hookSummary = await runHooks("theme-change", hookEnv);
6601
6843
  if (hookSummary.executed > 0) {
6602
6844
  output += `
@@ -6611,27 +6853,27 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
6611
6853
  return { output, success: true };
6612
6854
  }
6613
6855
  async function applyLegacyTheme(themeName, saveMapping) {
6614
- const themeDir = join12(THEMES_DIR, themeName);
6615
- if (!existsSync13(themeDir)) {
6856
+ const themeDir = join13(THEMES_DIR, themeName);
6857
+ if (!existsSync14(themeDir)) {
6616
6858
  return { output: `Theme '${themeName}' not found`, success: false };
6617
6859
  }
6618
6860
  await ensureConfigDir();
6619
6861
  await ensureDir2(THEME_TARGET_DIR);
6620
6862
  const theme = await parseTheme(themeDir, themeName);
6621
6863
  clearDirectory(THEME_TARGET_DIR);
6622
- if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
6864
+ if (existsSync14(BACKGROUNDS_TARGET_DIR)) {
6623
6865
  rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
6624
6866
  }
6625
6867
  const entries = readdirSync9(themeDir, { withFileTypes: true });
6626
6868
  for (const entry of entries) {
6627
- const source = join12(themeDir, entry.name);
6869
+ const source = join13(themeDir, entry.name);
6628
6870
  if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
6629
- const target = join12(THEME_TARGET_DIR, entry.name);
6871
+ const target = join13(THEME_TARGET_DIR, entry.name);
6630
6872
  createSymlink(source, target);
6631
6873
  }
6632
6874
  }
6633
6875
  if (theme.hasBackgrounds) {
6634
- const backgroundsSource = join12(themeDir, "backgrounds");
6876
+ const backgroundsSource = join13(themeDir, "backgrounds");
6635
6877
  createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
6636
6878
  }
6637
6879
  if (saveMapping) {
@@ -6671,12 +6913,12 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
6671
6913
  }
6672
6914
  async function applyTheme(themeIdentifier, saveMapping = false) {
6673
6915
  const { name, mode } = parseThemeIdentifier(themeIdentifier);
6674
- const jsonPath = join12(THEMES_DIR, `${name}.json`);
6675
- if (existsSync13(jsonPath) && mode) {
6916
+ const jsonPath = join13(THEMES_DIR, `${name}.json`);
6917
+ if (existsSync14(jsonPath) && mode) {
6676
6918
  return applyJsonTheme(jsonPath, mode, saveMapping, themeIdentifier);
6677
6919
  }
6678
- const legacyPath = join12(THEMES_DIR, name);
6679
- if (existsSync13(legacyPath)) {
6920
+ const legacyPath = join13(THEMES_DIR, name);
6921
+ if (existsSync14(legacyPath)) {
6680
6922
  return applyLegacyTheme(name, saveMapping);
6681
6923
  }
6682
6924
  const allThemes = await listAllThemes();
@@ -6695,8 +6937,8 @@ Did you mean:`;
6695
6937
  }
6696
6938
  async function showThemeInfo(themeIdentifier) {
6697
6939
  const { name, mode } = parseThemeIdentifier(themeIdentifier);
6698
- const jsonPath = join12(THEMES_DIR, `${name}.json`);
6699
- if (existsSync13(jsonPath)) {
6940
+ const jsonPath = join13(THEMES_DIR, `${name}.json`);
6941
+ if (existsSync14(jsonPath)) {
6700
6942
  const theme2 = await loadThemeJson(jsonPath);
6701
6943
  const modes = getAvailableModes(theme2);
6702
6944
  console.log(`
@@ -6736,8 +6978,8 @@ ${colors5.green}Wallpapers:${colors5.reset}`);
6736
6978
  }
6737
6979
  return;
6738
6980
  }
6739
- const themeDir = join12(THEMES_DIR, name);
6740
- if (!existsSync13(themeDir)) {
6981
+ const themeDir = join13(THEMES_DIR, name);
6982
+ if (!existsSync14(themeDir)) {
6741
6983
  console.error(`${colors5.red}Error: Theme '${themeIdentifier}' not found${colors5.reset}`);
6742
6984
  process.exit(1);
6743
6985
  }
@@ -6846,8 +7088,8 @@ To add themes:`);
6846
7088
  }
6847
7089
  }
6848
7090
  async function migrateTheme(themeName) {
6849
- const legacyPath = join12(THEMES_DIR, themeName);
6850
- if (!existsSync13(legacyPath)) {
7091
+ const legacyPath = join13(THEMES_DIR, themeName);
7092
+ if (!existsSync14(legacyPath)) {
6851
7093
  console.error(`${colors5.red}Error: Legacy theme '${themeName}' not found${colors5.reset}`);
6852
7094
  process.exit(1);
6853
7095
  }
@@ -6864,13 +7106,13 @@ async function migrateTheme(themeName) {
6864
7106
  console.log(`${colors5.yellow}Warning: Missing colors will be filled with defaults:${colors5.reset}`);
6865
7107
  console.log(` ${missing.join(", ")}`);
6866
7108
  }
6867
- const isLight = existsSync13(join12(legacyPath, "light.mode"));
7109
+ const isLight = existsSync14(join13(legacyPath, "light.mode"));
6868
7110
  const themeJson = generateThemeJson(themeName, result.colors, {
6869
7111
  description: `Migrated from legacy theme`,
6870
7112
  isLight
6871
7113
  });
6872
- const outputPath = join12(THEMES_DIR, `${themeName}.json`);
6873
- if (existsSync13(outputPath)) {
7114
+ const outputPath = join13(THEMES_DIR, `${themeName}.json`);
7115
+ if (existsSync14(outputPath)) {
6874
7116
  console.error(`${colors5.red}Error: JSON theme '${themeName}.json' already exists${colors5.reset}`);
6875
7117
  console.error(`Delete or rename it first, then try again.`);
6876
7118
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "formalconf",
3
- "version": "2.0.15",
3
+ "version": "2.0.17",
4
4
  "description": "Dotfiles management TUI for macOS and Linux - config management, package sync, and theme switching",
5
5
  "type": "module",
6
6
  "main": "./dist/formalconf.js",