formalconf 2.0.15 → 2.0.16

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 +386 -66
  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.16",
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 = [
@@ -5673,14 +5971,14 @@ function renderDualModeTemplate(template, contexts) {
5673
5971
 
5674
5972
  // src/lib/template-engine/versioning.ts
5675
5973
  init_runtime();
5676
- import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
5677
- import { join as join8 } from "path";
5974
+ import { existsSync as existsSync11, readdirSync as readdirSync6 } from "fs";
5975
+ import { join as join9 } from "path";
5678
5976
  var DEFAULT_MANIFEST = {
5679
5977
  version: 1,
5680
5978
  templates: {}
5681
5979
  };
5682
5980
  async function loadTemplatesManifest() {
5683
- if (!existsSync10(TEMPLATES_MANIFEST_PATH)) {
5981
+ if (!existsSync11(TEMPLATES_MANIFEST_PATH)) {
5684
5982
  return { ...DEFAULT_MANIFEST };
5685
5983
  }
5686
5984
  try {
@@ -5695,7 +5993,7 @@ async function saveTemplatesManifest(manifest) {
5695
5993
  await writeFile(TEMPLATES_MANIFEST_PATH, JSON.stringify(manifest, null, 2));
5696
5994
  }
5697
5995
  async function loadBundledManifest() {
5698
- if (!existsSync10(BUNDLED_MANIFEST_PATH)) {
5996
+ if (!existsSync11(BUNDLED_MANIFEST_PATH)) {
5699
5997
  return { version: 1, templates: {} };
5700
5998
  }
5701
5999
  try {
@@ -5746,13 +6044,13 @@ async function installTemplate(templateName) {
5746
6044
  if (!bundledMeta) {
5747
6045
  throw new Error(`Template '${templateName}' not found in bundled templates`);
5748
6046
  }
5749
- const sourcePath = join8(BUNDLED_TEMPLATES_DIR, templateName);
5750
- if (!existsSync10(sourcePath)) {
6047
+ const sourcePath = join9(BUNDLED_TEMPLATES_DIR, templateName);
6048
+ if (!existsSync11(sourcePath)) {
5751
6049
  throw new Error(`Template file not found: ${sourcePath}`);
5752
6050
  }
5753
6051
  await ensureDir2(TEMPLATES_DIR);
5754
6052
  const content = await readText(sourcePath);
5755
- const destPath = join8(TEMPLATES_DIR, templateName);
6053
+ const destPath = join9(TEMPLATES_DIR, templateName);
5756
6054
  await writeFile(destPath, content);
5757
6055
  const manifest = await loadTemplatesManifest();
5758
6056
  manifest.templates[templateName] = {
@@ -5814,7 +6112,7 @@ function getOutputFilename(templateName) {
5814
6112
  return output;
5815
6113
  }
5816
6114
  async function listInstalledTemplates() {
5817
- if (!existsSync10(TEMPLATES_DIR)) {
6115
+ if (!existsSync11(TEMPLATES_DIR)) {
5818
6116
  return [];
5819
6117
  }
5820
6118
  const entries = readdirSync6(TEMPLATES_DIR, { withFileTypes: true });
@@ -5823,7 +6121,7 @@ async function listInstalledTemplates() {
5823
6121
  if (entry.isFile() && entry.name.endsWith(".template")) {
5824
6122
  templates.push({
5825
6123
  name: entry.name,
5826
- path: join8(TEMPLATES_DIR, entry.name),
6124
+ path: join9(TEMPLATES_DIR, entry.name),
5827
6125
  outputName: getOutputFilename(entry.name),
5828
6126
  type: getTemplateType(entry.name),
5829
6127
  partialMode: getPartialMode(entry.name)
@@ -6005,7 +6303,7 @@ async function renderTemplateFile(templateFile, theme, mode) {
6005
6303
  return {
6006
6304
  template: templateFile,
6007
6305
  content,
6008
- outputPath: join9(GENERATED_DIR, templateFile.outputName)
6306
+ outputPath: join10(GENERATED_DIR, templateFile.outputName)
6009
6307
  };
6010
6308
  }
6011
6309
  async function renderAllTemplates(theme, mode) {
@@ -6039,7 +6337,7 @@ async function generateNeovimConfigFile(theme, mode) {
6039
6337
  return null;
6040
6338
  }
6041
6339
  const content = generateNeovimConfig(theme, mode);
6042
- const outputPath = join9(GENERATED_DIR, "neovim.lua");
6340
+ const outputPath = join10(GENERATED_DIR, "neovim.lua");
6043
6341
  await writeFile(outputPath, content);
6044
6342
  return {
6045
6343
  template: {
@@ -6064,8 +6362,8 @@ async function generateThemeConfigs(theme, mode) {
6064
6362
 
6065
6363
  // src/lib/migration/extractor.ts
6066
6364
  init_runtime();
6067
- import { existsSync as existsSync11, readdirSync as readdirSync7 } from "fs";
6068
- import { join as join10 } from "path";
6365
+ import { existsSync as existsSync12, readdirSync as readdirSync7 } from "fs";
6366
+ import { join as join11 } from "path";
6069
6367
  function normalizeHex2(hex) {
6070
6368
  hex = hex.replace(/^(#|0x)/i, "");
6071
6369
  if (hex.length === 3) {
@@ -6206,7 +6504,7 @@ async function extractFromGhostty(path) {
6206
6504
  return { colors: colors5, source: "ghostty" };
6207
6505
  }
6208
6506
  async function extractColors(filePath) {
6209
- if (!existsSync11(filePath)) {
6507
+ if (!existsSync12(filePath)) {
6210
6508
  return null;
6211
6509
  }
6212
6510
  const filename = filePath.toLowerCase();
@@ -6228,7 +6526,7 @@ async function extractColors(filePath) {
6228
6526
  return null;
6229
6527
  }
6230
6528
  async function extractFromLegacyTheme(themePath) {
6231
- if (!existsSync11(themePath)) {
6529
+ if (!existsSync12(themePath)) {
6232
6530
  return null;
6233
6531
  }
6234
6532
  const files = readdirSync7(themePath, { withFileTypes: true });
@@ -6240,12 +6538,12 @@ async function extractFromLegacyTheme(themePath) {
6240
6538
  for (const preferred of preferredFiles) {
6241
6539
  const match = files.find((f) => f.name.toLowerCase() === preferred.toLowerCase());
6242
6540
  if (match) {
6243
- return extractColors(join10(themePath, match.name));
6541
+ return extractColors(join11(themePath, match.name));
6244
6542
  }
6245
6543
  }
6246
6544
  for (const file of files) {
6247
6545
  if (file.isFile() && (file.name.endsWith(".conf") || file.name.endsWith(".toml"))) {
6248
- const result = await extractColors(join10(themePath, file.name));
6546
+ const result = await extractColors(join11(themePath, file.name));
6249
6547
  if (result && Object.keys(result.colors).length > 0) {
6250
6548
  return result;
6251
6549
  }
@@ -6334,19 +6632,19 @@ function generateThemeJson(name, colors5, options = {}) {
6334
6632
  init_runtime();
6335
6633
 
6336
6634
  // src/lib/wallpaper.ts
6337
- import { existsSync as existsSync12, readdirSync as readdirSync8, unlinkSync } from "fs";
6338
- import { join as join11 } from "path";
6635
+ import { existsSync as existsSync13, readdirSync as readdirSync8, unlinkSync } from "fs";
6636
+ import { join as join12 } from "path";
6339
6637
  init_runtime();
6340
6638
  var DEFAULT_TIMEOUT_MS = 30000;
6341
6639
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
6342
6640
  function clearBackgroundsDir() {
6343
- if (!existsSync12(BACKGROUNDS_TARGET_DIR)) {
6641
+ if (!existsSync13(BACKGROUNDS_TARGET_DIR)) {
6344
6642
  return;
6345
6643
  }
6346
6644
  const entries = readdirSync8(BACKGROUNDS_TARGET_DIR, { withFileTypes: true });
6347
6645
  for (const entry of entries) {
6348
6646
  if (entry.isFile() || entry.isSymbolicLink()) {
6349
- unlinkSync(join11(BACKGROUNDS_TARGET_DIR, entry.name));
6647
+ unlinkSync(join12(BACKGROUNDS_TARGET_DIR, entry.name));
6350
6648
  }
6351
6649
  }
6352
6650
  }
@@ -6410,7 +6708,7 @@ async function downloadWallpaper(url, filename, timeoutMs = DEFAULT_TIMEOUT_MS)
6410
6708
  };
6411
6709
  }
6412
6710
  const ext = getExtension(url, contentType);
6413
- const outputPath = join11(BACKGROUNDS_TARGET_DIR, `${filename}.${ext}`);
6711
+ const outputPath = join12(BACKGROUNDS_TARGET_DIR, `${filename}.${ext}`);
6414
6712
  await writeBuffer(outputPath, arrayBuffer);
6415
6713
  return { success: true, path: outputPath };
6416
6714
  } catch (err) {
@@ -6467,11 +6765,11 @@ async function listAllThemes() {
6467
6765
  });
6468
6766
  }
6469
6767
  }
6470
- if (existsSync13(THEMES_DIR)) {
6768
+ if (existsSync14(THEMES_DIR)) {
6471
6769
  const entries = readdirSync9(THEMES_DIR, { withFileTypes: true });
6472
6770
  for (const entry of entries) {
6473
6771
  if (entry.isDirectory()) {
6474
- const themePath = join12(THEMES_DIR, entry.name);
6772
+ const themePath = join13(THEMES_DIR, entry.name);
6475
6773
  const theme = await parseTheme(themePath, entry.name);
6476
6774
  themes.push({
6477
6775
  displayName: theme.name,
@@ -6488,10 +6786,10 @@ async function listAllThemes() {
6488
6786
  return themes;
6489
6787
  }
6490
6788
  function clearDirectory(dir) {
6491
- if (existsSync13(dir)) {
6789
+ if (existsSync14(dir)) {
6492
6790
  const entries = readdirSync9(dir, { withFileTypes: true });
6493
6791
  for (const entry of entries) {
6494
- const fullPath = join12(dir, entry.name);
6792
+ const fullPath = join13(dir, entry.name);
6495
6793
  if (entry.isSymbolicLink() || entry.isFile()) {
6496
6794
  unlinkSync2(fullPath);
6497
6795
  } else if (entry.isDirectory()) {
@@ -6501,7 +6799,7 @@ function clearDirectory(dir) {
6501
6799
  }
6502
6800
  }
6503
6801
  function createSymlink(source, target) {
6504
- if (existsSync13(target)) {
6802
+ if (existsSync14(target)) {
6505
6803
  unlinkSync2(target);
6506
6804
  }
6507
6805
  symlinkSync(source, target);
@@ -6525,20 +6823,20 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
6525
6823
  await installAllTemplates();
6526
6824
  }
6527
6825
  clearDirectory(THEME_TARGET_DIR);
6528
- if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
6826
+ if (existsSync14(BACKGROUNDS_TARGET_DIR)) {
6529
6827
  rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
6530
6828
  }
6531
6829
  const results = await generateThemeConfigs(theme, mode);
6532
- const ghosttyThemesDir = join12(HOME_DIR, ".config", "ghostty", "themes");
6830
+ const ghosttyThemesDir = join13(HOME_DIR, ".config", "ghostty", "themes");
6533
6831
  for (const result of results) {
6534
6832
  const filename = basename4(result.outputPath);
6535
6833
  if (filename === "formalconf-dark" || filename === "formalconf-light") {
6536
6834
  await ensureDir2(ghosttyThemesDir);
6537
- const targetPath2 = join12(ghosttyThemesDir, filename);
6538
- copyFileSync(result.outputPath, targetPath2);
6835
+ const targetPath2 = join13(ghosttyThemesDir, filename);
6836
+ copyFileSync2(result.outputPath, targetPath2);
6539
6837
  }
6540
- const targetPath = join12(THEME_TARGET_DIR, filename);
6541
- copyFileSync(result.outputPath, targetPath);
6838
+ const targetPath = join13(THEME_TARGET_DIR, filename);
6839
+ copyFileSync2(result.outputPath, targetPath);
6542
6840
  }
6543
6841
  let wallpaperPaths = [];
6544
6842
  let wallpaperErrors = [];
@@ -6551,6 +6849,10 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
6551
6849
  if (getOS() === "linux") {
6552
6850
  gtkResult = await applyGtkTheme(theme, mode);
6553
6851
  }
6852
+ let qtResult = null;
6853
+ if (getOS() === "linux") {
6854
+ qtResult = await applyQtTheme(theme, mode);
6855
+ }
6554
6856
  if (saveMapping) {
6555
6857
  await setDeviceTheme(identifier);
6556
6858
  }
@@ -6586,6 +6888,21 @@ GTK theme: ${gtkResult.themeName}`;
6586
6888
  Warning: GTK theme failed - ${gtkResult.error}`;
6587
6889
  }
6588
6890
  }
6891
+ if (qtResult && !qtResult.skipped) {
6892
+ if (qtResult.success) {
6893
+ output += `
6894
+ QT theme: ${qtResult.themeName}`;
6895
+ const setupReminder = await getAndMarkSetupReminder();
6896
+ if (setupReminder) {
6897
+ output += `
6898
+
6899
+ ${setupReminder}`;
6900
+ }
6901
+ } else {
6902
+ output += `
6903
+ Warning: QT theme failed - ${qtResult.error}`;
6904
+ }
6905
+ }
6589
6906
  const hookEnv = {
6590
6907
  FORMALCONF_THEME: identifier,
6591
6908
  FORMALCONF_THEME_MODE: mode,
@@ -6597,6 +6914,9 @@ GTK theme: ${gtkResult.themeName}`;
6597
6914
  if (gtkResult?.success && gtkResult.themeName) {
6598
6915
  hookEnv.FORMALCONF_GTK_THEME = gtkResult.themeName;
6599
6916
  }
6917
+ if (qtResult?.success && qtResult.themeName) {
6918
+ hookEnv.FORMALCONF_QT_THEME = qtResult.themeName;
6919
+ }
6600
6920
  const hookSummary = await runHooks("theme-change", hookEnv);
6601
6921
  if (hookSummary.executed > 0) {
6602
6922
  output += `
@@ -6611,27 +6931,27 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
6611
6931
  return { output, success: true };
6612
6932
  }
6613
6933
  async function applyLegacyTheme(themeName, saveMapping) {
6614
- const themeDir = join12(THEMES_DIR, themeName);
6615
- if (!existsSync13(themeDir)) {
6934
+ const themeDir = join13(THEMES_DIR, themeName);
6935
+ if (!existsSync14(themeDir)) {
6616
6936
  return { output: `Theme '${themeName}' not found`, success: false };
6617
6937
  }
6618
6938
  await ensureConfigDir();
6619
6939
  await ensureDir2(THEME_TARGET_DIR);
6620
6940
  const theme = await parseTheme(themeDir, themeName);
6621
6941
  clearDirectory(THEME_TARGET_DIR);
6622
- if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
6942
+ if (existsSync14(BACKGROUNDS_TARGET_DIR)) {
6623
6943
  rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
6624
6944
  }
6625
6945
  const entries = readdirSync9(themeDir, { withFileTypes: true });
6626
6946
  for (const entry of entries) {
6627
- const source = join12(themeDir, entry.name);
6947
+ const source = join13(themeDir, entry.name);
6628
6948
  if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
6629
- const target = join12(THEME_TARGET_DIR, entry.name);
6949
+ const target = join13(THEME_TARGET_DIR, entry.name);
6630
6950
  createSymlink(source, target);
6631
6951
  }
6632
6952
  }
6633
6953
  if (theme.hasBackgrounds) {
6634
- const backgroundsSource = join12(themeDir, "backgrounds");
6954
+ const backgroundsSource = join13(themeDir, "backgrounds");
6635
6955
  createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
6636
6956
  }
6637
6957
  if (saveMapping) {
@@ -6671,12 +6991,12 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
6671
6991
  }
6672
6992
  async function applyTheme(themeIdentifier, saveMapping = false) {
6673
6993
  const { name, mode } = parseThemeIdentifier(themeIdentifier);
6674
- const jsonPath = join12(THEMES_DIR, `${name}.json`);
6675
- if (existsSync13(jsonPath) && mode) {
6994
+ const jsonPath = join13(THEMES_DIR, `${name}.json`);
6995
+ if (existsSync14(jsonPath) && mode) {
6676
6996
  return applyJsonTheme(jsonPath, mode, saveMapping, themeIdentifier);
6677
6997
  }
6678
- const legacyPath = join12(THEMES_DIR, name);
6679
- if (existsSync13(legacyPath)) {
6998
+ const legacyPath = join13(THEMES_DIR, name);
6999
+ if (existsSync14(legacyPath)) {
6680
7000
  return applyLegacyTheme(name, saveMapping);
6681
7001
  }
6682
7002
  const allThemes = await listAllThemes();
@@ -6695,8 +7015,8 @@ Did you mean:`;
6695
7015
  }
6696
7016
  async function showThemeInfo(themeIdentifier) {
6697
7017
  const { name, mode } = parseThemeIdentifier(themeIdentifier);
6698
- const jsonPath = join12(THEMES_DIR, `${name}.json`);
6699
- if (existsSync13(jsonPath)) {
7018
+ const jsonPath = join13(THEMES_DIR, `${name}.json`);
7019
+ if (existsSync14(jsonPath)) {
6700
7020
  const theme2 = await loadThemeJson(jsonPath);
6701
7021
  const modes = getAvailableModes(theme2);
6702
7022
  console.log(`
@@ -6736,8 +7056,8 @@ ${colors5.green}Wallpapers:${colors5.reset}`);
6736
7056
  }
6737
7057
  return;
6738
7058
  }
6739
- const themeDir = join12(THEMES_DIR, name);
6740
- if (!existsSync13(themeDir)) {
7059
+ const themeDir = join13(THEMES_DIR, name);
7060
+ if (!existsSync14(themeDir)) {
6741
7061
  console.error(`${colors5.red}Error: Theme '${themeIdentifier}' not found${colors5.reset}`);
6742
7062
  process.exit(1);
6743
7063
  }
@@ -6846,8 +7166,8 @@ To add themes:`);
6846
7166
  }
6847
7167
  }
6848
7168
  async function migrateTheme(themeName) {
6849
- const legacyPath = join12(THEMES_DIR, themeName);
6850
- if (!existsSync13(legacyPath)) {
7169
+ const legacyPath = join13(THEMES_DIR, themeName);
7170
+ if (!existsSync14(legacyPath)) {
6851
7171
  console.error(`${colors5.red}Error: Legacy theme '${themeName}' not found${colors5.reset}`);
6852
7172
  process.exit(1);
6853
7173
  }
@@ -6864,13 +7184,13 @@ async function migrateTheme(themeName) {
6864
7184
  console.log(`${colors5.yellow}Warning: Missing colors will be filled with defaults:${colors5.reset}`);
6865
7185
  console.log(` ${missing.join(", ")}`);
6866
7186
  }
6867
- const isLight = existsSync13(join12(legacyPath, "light.mode"));
7187
+ const isLight = existsSync14(join13(legacyPath, "light.mode"));
6868
7188
  const themeJson = generateThemeJson(themeName, result.colors, {
6869
7189
  description: `Migrated from legacy theme`,
6870
7190
  isLight
6871
7191
  });
6872
- const outputPath = join12(THEMES_DIR, `${themeName}.json`);
6873
- if (existsSync13(outputPath)) {
7192
+ const outputPath = join13(THEMES_DIR, `${themeName}.json`);
7193
+ if (existsSync14(outputPath)) {
6874
7194
  console.error(`${colors5.red}Error: JSON theme '${themeName}.json' already exists${colors5.reset}`);
6875
7195
  console.error(`Delete or rename it first, then try again.`);
6876
7196
  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.16",
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",