formalconf 2.0.14 → 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 +399 -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.14",
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 = [
@@ -5629,6 +5927,11 @@ function getContextValue(context, variableName, modifier) {
5629
5927
  const value = context.theme[key];
5630
5928
  return value !== undefined ? String(value) : undefined;
5631
5929
  }
5930
+ if (variableName.startsWith("gtk.")) {
5931
+ const key = variableName.slice(4);
5932
+ const value = context.gtk[key];
5933
+ return value !== undefined ? String(value) : undefined;
5934
+ }
5632
5935
  if (variableName === "mode") {
5633
5936
  return context.mode;
5634
5937
  }
@@ -5668,14 +5971,14 @@ function renderDualModeTemplate(template, contexts) {
5668
5971
 
5669
5972
  // src/lib/template-engine/versioning.ts
5670
5973
  init_runtime();
5671
- import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
5672
- import { join as join8 } from "path";
5974
+ import { existsSync as existsSync11, readdirSync as readdirSync6 } from "fs";
5975
+ import { join as join9 } from "path";
5673
5976
  var DEFAULT_MANIFEST = {
5674
5977
  version: 1,
5675
5978
  templates: {}
5676
5979
  };
5677
5980
  async function loadTemplatesManifest() {
5678
- if (!existsSync10(TEMPLATES_MANIFEST_PATH)) {
5981
+ if (!existsSync11(TEMPLATES_MANIFEST_PATH)) {
5679
5982
  return { ...DEFAULT_MANIFEST };
5680
5983
  }
5681
5984
  try {
@@ -5690,7 +5993,7 @@ async function saveTemplatesManifest(manifest) {
5690
5993
  await writeFile(TEMPLATES_MANIFEST_PATH, JSON.stringify(manifest, null, 2));
5691
5994
  }
5692
5995
  async function loadBundledManifest() {
5693
- if (!existsSync10(BUNDLED_MANIFEST_PATH)) {
5996
+ if (!existsSync11(BUNDLED_MANIFEST_PATH)) {
5694
5997
  return { version: 1, templates: {} };
5695
5998
  }
5696
5999
  try {
@@ -5741,13 +6044,13 @@ async function installTemplate(templateName) {
5741
6044
  if (!bundledMeta) {
5742
6045
  throw new Error(`Template '${templateName}' not found in bundled templates`);
5743
6046
  }
5744
- const sourcePath = join8(BUNDLED_TEMPLATES_DIR, templateName);
5745
- if (!existsSync10(sourcePath)) {
6047
+ const sourcePath = join9(BUNDLED_TEMPLATES_DIR, templateName);
6048
+ if (!existsSync11(sourcePath)) {
5746
6049
  throw new Error(`Template file not found: ${sourcePath}`);
5747
6050
  }
5748
6051
  await ensureDir2(TEMPLATES_DIR);
5749
6052
  const content = await readText(sourcePath);
5750
- const destPath = join8(TEMPLATES_DIR, templateName);
6053
+ const destPath = join9(TEMPLATES_DIR, templateName);
5751
6054
  await writeFile(destPath, content);
5752
6055
  const manifest = await loadTemplatesManifest();
5753
6056
  manifest.templates[templateName] = {
@@ -5809,7 +6112,7 @@ function getOutputFilename(templateName) {
5809
6112
  return output;
5810
6113
  }
5811
6114
  async function listInstalledTemplates() {
5812
- if (!existsSync10(TEMPLATES_DIR)) {
6115
+ if (!existsSync11(TEMPLATES_DIR)) {
5813
6116
  return [];
5814
6117
  }
5815
6118
  const entries = readdirSync6(TEMPLATES_DIR, { withFileTypes: true });
@@ -5818,7 +6121,7 @@ async function listInstalledTemplates() {
5818
6121
  if (entry.isFile() && entry.name.endsWith(".template")) {
5819
6122
  templates.push({
5820
6123
  name: entry.name,
5821
- path: join8(TEMPLATES_DIR, entry.name),
6124
+ path: join9(TEMPLATES_DIR, entry.name),
5822
6125
  outputName: getOutputFilename(entry.name),
5823
6126
  type: getTemplateType(entry.name),
5824
6127
  partialMode: getPartialMode(entry.name)
@@ -5921,6 +6224,13 @@ function buildThemeMetadata(theme, mode) {
5921
6224
  mode
5922
6225
  };
5923
6226
  }
6227
+ function buildGtkMetadata(theme, mode) {
6228
+ const themeName = theme.title.toLowerCase().replace(/\s+/g, "-");
6229
+ const modeCapitalized = mode === "dark" ? "Dark" : "Light";
6230
+ return {
6231
+ theme: `formalconf-${themeName}-${modeCapitalized}`
6232
+ };
6233
+ }
5924
6234
  function buildTemplateContext(theme, palette, mode) {
5925
6235
  return {
5926
6236
  color0: hexToColorVariable(palette.color0),
@@ -5947,6 +6257,7 @@ function buildTemplateContext(theme, palette, mode) {
5947
6257
  accent: hexToColorVariableOrDefault(palette.accent, palette.color4),
5948
6258
  border: hexToColorVariableOrDefault(palette.border, palette.color0),
5949
6259
  theme: buildThemeMetadata(theme, mode),
6260
+ gtk: buildGtkMetadata(theme, mode),
5950
6261
  mode
5951
6262
  };
5952
6263
  }
@@ -5992,7 +6303,7 @@ async function renderTemplateFile(templateFile, theme, mode) {
5992
6303
  return {
5993
6304
  template: templateFile,
5994
6305
  content,
5995
- outputPath: join9(GENERATED_DIR, templateFile.outputName)
6306
+ outputPath: join10(GENERATED_DIR, templateFile.outputName)
5996
6307
  };
5997
6308
  }
5998
6309
  async function renderAllTemplates(theme, mode) {
@@ -6026,7 +6337,7 @@ async function generateNeovimConfigFile(theme, mode) {
6026
6337
  return null;
6027
6338
  }
6028
6339
  const content = generateNeovimConfig(theme, mode);
6029
- const outputPath = join9(GENERATED_DIR, "neovim.lua");
6340
+ const outputPath = join10(GENERATED_DIR, "neovim.lua");
6030
6341
  await writeFile(outputPath, content);
6031
6342
  return {
6032
6343
  template: {
@@ -6051,8 +6362,8 @@ async function generateThemeConfigs(theme, mode) {
6051
6362
 
6052
6363
  // src/lib/migration/extractor.ts
6053
6364
  init_runtime();
6054
- import { existsSync as existsSync11, readdirSync as readdirSync7 } from "fs";
6055
- import { join as join10 } from "path";
6365
+ import { existsSync as existsSync12, readdirSync as readdirSync7 } from "fs";
6366
+ import { join as join11 } from "path";
6056
6367
  function normalizeHex2(hex) {
6057
6368
  hex = hex.replace(/^(#|0x)/i, "");
6058
6369
  if (hex.length === 3) {
@@ -6193,7 +6504,7 @@ async function extractFromGhostty(path) {
6193
6504
  return { colors: colors5, source: "ghostty" };
6194
6505
  }
6195
6506
  async function extractColors(filePath) {
6196
- if (!existsSync11(filePath)) {
6507
+ if (!existsSync12(filePath)) {
6197
6508
  return null;
6198
6509
  }
6199
6510
  const filename = filePath.toLowerCase();
@@ -6215,7 +6526,7 @@ async function extractColors(filePath) {
6215
6526
  return null;
6216
6527
  }
6217
6528
  async function extractFromLegacyTheme(themePath) {
6218
- if (!existsSync11(themePath)) {
6529
+ if (!existsSync12(themePath)) {
6219
6530
  return null;
6220
6531
  }
6221
6532
  const files = readdirSync7(themePath, { withFileTypes: true });
@@ -6227,12 +6538,12 @@ async function extractFromLegacyTheme(themePath) {
6227
6538
  for (const preferred of preferredFiles) {
6228
6539
  const match = files.find((f) => f.name.toLowerCase() === preferred.toLowerCase());
6229
6540
  if (match) {
6230
- return extractColors(join10(themePath, match.name));
6541
+ return extractColors(join11(themePath, match.name));
6231
6542
  }
6232
6543
  }
6233
6544
  for (const file of files) {
6234
6545
  if (file.isFile() && (file.name.endsWith(".conf") || file.name.endsWith(".toml"))) {
6235
- const result = await extractColors(join10(themePath, file.name));
6546
+ const result = await extractColors(join11(themePath, file.name));
6236
6547
  if (result && Object.keys(result.colors).length > 0) {
6237
6548
  return result;
6238
6549
  }
@@ -6321,19 +6632,19 @@ function generateThemeJson(name, colors5, options = {}) {
6321
6632
  init_runtime();
6322
6633
 
6323
6634
  // src/lib/wallpaper.ts
6324
- import { existsSync as existsSync12, readdirSync as readdirSync8, unlinkSync } from "fs";
6325
- import { join as join11 } from "path";
6635
+ import { existsSync as existsSync13, readdirSync as readdirSync8, unlinkSync } from "fs";
6636
+ import { join as join12 } from "path";
6326
6637
  init_runtime();
6327
6638
  var DEFAULT_TIMEOUT_MS = 30000;
6328
6639
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
6329
6640
  function clearBackgroundsDir() {
6330
- if (!existsSync12(BACKGROUNDS_TARGET_DIR)) {
6641
+ if (!existsSync13(BACKGROUNDS_TARGET_DIR)) {
6331
6642
  return;
6332
6643
  }
6333
6644
  const entries = readdirSync8(BACKGROUNDS_TARGET_DIR, { withFileTypes: true });
6334
6645
  for (const entry of entries) {
6335
6646
  if (entry.isFile() || entry.isSymbolicLink()) {
6336
- unlinkSync(join11(BACKGROUNDS_TARGET_DIR, entry.name));
6647
+ unlinkSync(join12(BACKGROUNDS_TARGET_DIR, entry.name));
6337
6648
  }
6338
6649
  }
6339
6650
  }
@@ -6397,7 +6708,7 @@ async function downloadWallpaper(url, filename, timeoutMs = DEFAULT_TIMEOUT_MS)
6397
6708
  };
6398
6709
  }
6399
6710
  const ext = getExtension(url, contentType);
6400
- const outputPath = join11(BACKGROUNDS_TARGET_DIR, `${filename}.${ext}`);
6711
+ const outputPath = join12(BACKGROUNDS_TARGET_DIR, `${filename}.${ext}`);
6401
6712
  await writeBuffer(outputPath, arrayBuffer);
6402
6713
  return { success: true, path: outputPath };
6403
6714
  } catch (err) {
@@ -6454,11 +6765,11 @@ async function listAllThemes() {
6454
6765
  });
6455
6766
  }
6456
6767
  }
6457
- if (existsSync13(THEMES_DIR)) {
6768
+ if (existsSync14(THEMES_DIR)) {
6458
6769
  const entries = readdirSync9(THEMES_DIR, { withFileTypes: true });
6459
6770
  for (const entry of entries) {
6460
6771
  if (entry.isDirectory()) {
6461
- const themePath = join12(THEMES_DIR, entry.name);
6772
+ const themePath = join13(THEMES_DIR, entry.name);
6462
6773
  const theme = await parseTheme(themePath, entry.name);
6463
6774
  themes.push({
6464
6775
  displayName: theme.name,
@@ -6475,10 +6786,10 @@ async function listAllThemes() {
6475
6786
  return themes;
6476
6787
  }
6477
6788
  function clearDirectory(dir) {
6478
- if (existsSync13(dir)) {
6789
+ if (existsSync14(dir)) {
6479
6790
  const entries = readdirSync9(dir, { withFileTypes: true });
6480
6791
  for (const entry of entries) {
6481
- const fullPath = join12(dir, entry.name);
6792
+ const fullPath = join13(dir, entry.name);
6482
6793
  if (entry.isSymbolicLink() || entry.isFile()) {
6483
6794
  unlinkSync2(fullPath);
6484
6795
  } else if (entry.isDirectory()) {
@@ -6488,7 +6799,7 @@ function clearDirectory(dir) {
6488
6799
  }
6489
6800
  }
6490
6801
  function createSymlink(source, target) {
6491
- if (existsSync13(target)) {
6802
+ if (existsSync14(target)) {
6492
6803
  unlinkSync2(target);
6493
6804
  }
6494
6805
  symlinkSync(source, target);
@@ -6512,20 +6823,20 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
6512
6823
  await installAllTemplates();
6513
6824
  }
6514
6825
  clearDirectory(THEME_TARGET_DIR);
6515
- if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
6826
+ if (existsSync14(BACKGROUNDS_TARGET_DIR)) {
6516
6827
  rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
6517
6828
  }
6518
6829
  const results = await generateThemeConfigs(theme, mode);
6519
- const ghosttyThemesDir = join12(HOME_DIR, ".config", "ghostty", "themes");
6830
+ const ghosttyThemesDir = join13(HOME_DIR, ".config", "ghostty", "themes");
6520
6831
  for (const result of results) {
6521
6832
  const filename = basename4(result.outputPath);
6522
6833
  if (filename === "formalconf-dark" || filename === "formalconf-light") {
6523
6834
  await ensureDir2(ghosttyThemesDir);
6524
- const targetPath2 = join12(ghosttyThemesDir, filename);
6525
- copyFileSync(result.outputPath, targetPath2);
6835
+ const targetPath2 = join13(ghosttyThemesDir, filename);
6836
+ copyFileSync2(result.outputPath, targetPath2);
6526
6837
  }
6527
- const targetPath = join12(THEME_TARGET_DIR, filename);
6528
- copyFileSync(result.outputPath, targetPath);
6838
+ const targetPath = join13(THEME_TARGET_DIR, filename);
6839
+ copyFileSync2(result.outputPath, targetPath);
6529
6840
  }
6530
6841
  let wallpaperPaths = [];
6531
6842
  let wallpaperErrors = [];
@@ -6538,6 +6849,10 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
6538
6849
  if (getOS() === "linux") {
6539
6850
  gtkResult = await applyGtkTheme(theme, mode);
6540
6851
  }
6852
+ let qtResult = null;
6853
+ if (getOS() === "linux") {
6854
+ qtResult = await applyQtTheme(theme, mode);
6855
+ }
6541
6856
  if (saveMapping) {
6542
6857
  await setDeviceTheme(identifier);
6543
6858
  }
@@ -6573,6 +6888,21 @@ GTK theme: ${gtkResult.themeName}`;
6573
6888
  Warning: GTK theme failed - ${gtkResult.error}`;
6574
6889
  }
6575
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
+ }
6576
6906
  const hookEnv = {
6577
6907
  FORMALCONF_THEME: identifier,
6578
6908
  FORMALCONF_THEME_MODE: mode,
@@ -6584,6 +6914,9 @@ GTK theme: ${gtkResult.themeName}`;
6584
6914
  if (gtkResult?.success && gtkResult.themeName) {
6585
6915
  hookEnv.FORMALCONF_GTK_THEME = gtkResult.themeName;
6586
6916
  }
6917
+ if (qtResult?.success && qtResult.themeName) {
6918
+ hookEnv.FORMALCONF_QT_THEME = qtResult.themeName;
6919
+ }
6587
6920
  const hookSummary = await runHooks("theme-change", hookEnv);
6588
6921
  if (hookSummary.executed > 0) {
6589
6922
  output += `
@@ -6598,27 +6931,27 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
6598
6931
  return { output, success: true };
6599
6932
  }
6600
6933
  async function applyLegacyTheme(themeName, saveMapping) {
6601
- const themeDir = join12(THEMES_DIR, themeName);
6602
- if (!existsSync13(themeDir)) {
6934
+ const themeDir = join13(THEMES_DIR, themeName);
6935
+ if (!existsSync14(themeDir)) {
6603
6936
  return { output: `Theme '${themeName}' not found`, success: false };
6604
6937
  }
6605
6938
  await ensureConfigDir();
6606
6939
  await ensureDir2(THEME_TARGET_DIR);
6607
6940
  const theme = await parseTheme(themeDir, themeName);
6608
6941
  clearDirectory(THEME_TARGET_DIR);
6609
- if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
6942
+ if (existsSync14(BACKGROUNDS_TARGET_DIR)) {
6610
6943
  rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
6611
6944
  }
6612
6945
  const entries = readdirSync9(themeDir, { withFileTypes: true });
6613
6946
  for (const entry of entries) {
6614
- const source = join12(themeDir, entry.name);
6947
+ const source = join13(themeDir, entry.name);
6615
6948
  if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
6616
- const target = join12(THEME_TARGET_DIR, entry.name);
6949
+ const target = join13(THEME_TARGET_DIR, entry.name);
6617
6950
  createSymlink(source, target);
6618
6951
  }
6619
6952
  }
6620
6953
  if (theme.hasBackgrounds) {
6621
- const backgroundsSource = join12(themeDir, "backgrounds");
6954
+ const backgroundsSource = join13(themeDir, "backgrounds");
6622
6955
  createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
6623
6956
  }
6624
6957
  if (saveMapping) {
@@ -6658,12 +6991,12 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
6658
6991
  }
6659
6992
  async function applyTheme(themeIdentifier, saveMapping = false) {
6660
6993
  const { name, mode } = parseThemeIdentifier(themeIdentifier);
6661
- const jsonPath = join12(THEMES_DIR, `${name}.json`);
6662
- if (existsSync13(jsonPath) && mode) {
6994
+ const jsonPath = join13(THEMES_DIR, `${name}.json`);
6995
+ if (existsSync14(jsonPath) && mode) {
6663
6996
  return applyJsonTheme(jsonPath, mode, saveMapping, themeIdentifier);
6664
6997
  }
6665
- const legacyPath = join12(THEMES_DIR, name);
6666
- if (existsSync13(legacyPath)) {
6998
+ const legacyPath = join13(THEMES_DIR, name);
6999
+ if (existsSync14(legacyPath)) {
6667
7000
  return applyLegacyTheme(name, saveMapping);
6668
7001
  }
6669
7002
  const allThemes = await listAllThemes();
@@ -6682,8 +7015,8 @@ Did you mean:`;
6682
7015
  }
6683
7016
  async function showThemeInfo(themeIdentifier) {
6684
7017
  const { name, mode } = parseThemeIdentifier(themeIdentifier);
6685
- const jsonPath = join12(THEMES_DIR, `${name}.json`);
6686
- if (existsSync13(jsonPath)) {
7018
+ const jsonPath = join13(THEMES_DIR, `${name}.json`);
7019
+ if (existsSync14(jsonPath)) {
6687
7020
  const theme2 = await loadThemeJson(jsonPath);
6688
7021
  const modes = getAvailableModes(theme2);
6689
7022
  console.log(`
@@ -6723,8 +7056,8 @@ ${colors5.green}Wallpapers:${colors5.reset}`);
6723
7056
  }
6724
7057
  return;
6725
7058
  }
6726
- const themeDir = join12(THEMES_DIR, name);
6727
- if (!existsSync13(themeDir)) {
7059
+ const themeDir = join13(THEMES_DIR, name);
7060
+ if (!existsSync14(themeDir)) {
6728
7061
  console.error(`${colors5.red}Error: Theme '${themeIdentifier}' not found${colors5.reset}`);
6729
7062
  process.exit(1);
6730
7063
  }
@@ -6833,8 +7166,8 @@ To add themes:`);
6833
7166
  }
6834
7167
  }
6835
7168
  async function migrateTheme(themeName) {
6836
- const legacyPath = join12(THEMES_DIR, themeName);
6837
- if (!existsSync13(legacyPath)) {
7169
+ const legacyPath = join13(THEMES_DIR, themeName);
7170
+ if (!existsSync14(legacyPath)) {
6838
7171
  console.error(`${colors5.red}Error: Legacy theme '${themeName}' not found${colors5.reset}`);
6839
7172
  process.exit(1);
6840
7173
  }
@@ -6851,13 +7184,13 @@ async function migrateTheme(themeName) {
6851
7184
  console.log(`${colors5.yellow}Warning: Missing colors will be filled with defaults:${colors5.reset}`);
6852
7185
  console.log(` ${missing.join(", ")}`);
6853
7186
  }
6854
- const isLight = existsSync13(join12(legacyPath, "light.mode"));
7187
+ const isLight = existsSync14(join13(legacyPath, "light.mode"));
6855
7188
  const themeJson = generateThemeJson(themeName, result.colors, {
6856
7189
  description: `Migrated from legacy theme`,
6857
7190
  isLight
6858
7191
  });
6859
- const outputPath = join12(THEMES_DIR, `${themeName}.json`);
6860
- if (existsSync13(outputPath)) {
7192
+ const outputPath = join13(THEMES_DIR, `${themeName}.json`);
7193
+ if (existsSync14(outputPath)) {
6861
7194
  console.error(`${colors5.red}Error: JSON theme '${themeName}.json' already exists${colors5.reset}`);
6862
7195
  console.error(`Delete or rename it first, then try again.`);
6863
7196
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "formalconf",
3
- "version": "2.0.14",
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",