ctheme 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -4
  2. package/bin/ctheme.js +114 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,8 +42,9 @@ Apply a bundled theme live:
42
42
 
43
43
  ```bash
44
44
  ctheme use solarized
45
- ctheme use solarized-dark
46
- ctheme use tide-light
45
+ ctheme use tide
46
+ ctheme use tide --dark
47
+ ctheme use solarized --light
47
48
  ctheme use harbor --default
48
49
  ctheme use velvet --font "Space Mono"
49
50
  ```
@@ -59,6 +60,7 @@ See available themes:
59
60
  ```bash
60
61
  ctheme list
61
62
  ctheme preview solarized
63
+ ctheme preview tide --dark
62
64
  ctheme status
63
65
  ```
64
66
 
@@ -107,8 +109,8 @@ Examples:
107
109
 
108
110
  ```bash
109
111
  ctheme term apple-terminal --theme noir --font "SF Mono"
110
- ctheme term ghostty --theme noir --font "JetBrains Mono" --font-size 15 --write
111
- ctheme term kitty --theme ember --font "Berkeley Mono" --font-size 14 --write
112
+ ctheme term ghostty --theme tide --dark --font "JetBrains Mono" --font-size 15 --write
113
+ ctheme term kitty --theme ember --light --font "Berkeley Mono" --font-size 14 --write
112
114
  ctheme term wezterm --theme paper --font "IBM Plex Mono" --font-size 14
113
115
  ```
114
116
 
@@ -161,6 +163,8 @@ Bundled themes intentionally vary their font pairings so they do not all feel th
161
163
  The npm install bootstrap preinstalls the curated font set those presets rely on, so the bundled themes render correctly immediately on macOS.
162
164
  The default code and terminal font choices are biased toward tighter, denser monospace faces rather than wide-spaced ones.
163
165
  Every bundled preset family now has both `-light` and `-dark` variants.
166
+ Using the family name alone, like `tide`, follows your system appearance automatically.
167
+ You can still override the mode manually with `--light` or `--dark`.
164
168
 
165
169
  ## Contributing
166
170
 
package/bin/ctheme.js CHANGED
@@ -496,7 +496,7 @@ async function main() {
496
496
  runUse(args[1], parseFlags(args.slice(2)));
497
497
  return;
498
498
  case "preview":
499
- runPreview(args[1]);
499
+ runPreview(args[1], parseFlags(args.slice(2)));
500
500
  return;
501
501
  case "make":
502
502
  await runMake(args[1], parseFlags(args.slice(2)));
@@ -538,7 +538,7 @@ Commands:
538
538
  status Show ctheme paths and saved terminal restore state
539
539
  reset [--target <name>] Restore the terminal UI back to its original state
540
540
  use <name> [flags] Apply a theme to the actual terminal immediately
541
- preview <name> Preview a theme palette in the terminal
541
+ preview <name> [flags] Preview a theme palette in the terminal
542
542
  make [name] [flags] Create a custom theme or launch the theme wizard
543
543
  wizard [name] [flags] Launch the interactive theme wizard directly
544
544
  init <name> [flags] Create an editable starter JSON file
@@ -549,17 +549,18 @@ Commands:
549
549
  Examples:
550
550
  ctheme reset
551
551
  ctheme use solarized
552
+ ctheme use tide --dark
552
553
  ctheme use harbor --default
553
554
  ctheme make
554
555
  ctheme wizard
555
556
  ctheme make arjun --from velvet --accent "#ff4d6d"
556
557
  ctheme init custom-light --preset harbor
557
558
  ctheme live neon
558
- ctheme live solarized --font "Space Mono"
559
+ ctheme live solarized --light --font "JetBrains Mono"
559
560
  ctheme font install "Manrope"
560
561
  ctheme font list
561
562
  ctheme term apple-terminal --theme noir --font "SF Mono"
562
- ctheme term ghostty --font "JetBrains Mono" --font-size 15
563
+ ctheme term ghostty --theme tide --dark --font "JetBrains Mono" --font-size 15
563
564
 
564
565
  Flags for make:
565
566
  --preset <name> Base preset name
@@ -589,8 +590,10 @@ Font subcommands:
589
590
 
590
591
  Flags for term:
591
592
  Targets: apple-terminal, current, ghostty, kitty, wezterm
593
+ --dark Force the dark variant for bundled theme families
592
594
  --font <family>
593
595
  --font-size <number>
596
+ --light Force the light variant for bundled theme families
594
597
  --theme <name>
595
598
  --opacity <number>
596
599
  --write Write snippet into your terminal config location when supported
@@ -598,10 +601,16 @@ Flags for term:
598
601
  --default Apple Terminal: set as default and startup profile
599
602
 
600
603
  Flags for use:
604
+ --dark Force the dark variant for bundled theme families
601
605
  --font <family>
602
606
  --font-size <number>
607
+ --light Force the light variant for bundled theme families
603
608
  --default Apple Terminal: set as default and startup profile
604
609
 
610
+ Flags for preview:
611
+ --dark Force the dark variant for bundled theme families
612
+ --light Force the light variant for bundled theme families
613
+
605
614
  Flags for reset:
606
615
  --target <name> current, apple-terminal, ghostty, kitty, wezterm
607
616
  `);
@@ -668,7 +677,8 @@ function derivePaletteVariant(palette, targetMode) {
668
677
 
669
678
  function runList() {
670
679
  const paths = getThemePaths();
671
- const custom = new Set(Object.keys(PRESETS));
680
+ const bundled = getBundledThemeFamilies();
681
+ const custom = new Set();
672
682
 
673
683
  if (fs.existsSync(paths.paletteDir)) {
674
684
  for (const entry of fs.readdirSync(paths.paletteDir)) {
@@ -678,9 +688,12 @@ function runList() {
678
688
  }
679
689
  }
680
690
 
691
+ for (const name of bundled) {
692
+ console.log(`${name} (bundled, auto light/dark)`);
693
+ }
694
+
681
695
  for (const name of [...custom].sort()) {
682
- const source = Object.hasOwn(PRESETS, name) ? "bundled-custom" : "custom";
683
- console.log(`${name} (${source})`);
696
+ console.log(`${name} (custom)`);
684
697
  }
685
698
  }
686
699
 
@@ -740,9 +753,9 @@ function runReset(flags) {
740
753
  fail(`Unsupported reset target: ${target}`);
741
754
  }
742
755
 
743
- function runPreview(themeName) {
744
- assertThemeName(themeName);
745
- const theme = buildTheme(themeName, getBasePalette(themeName));
756
+ function runPreview(themeName, flags) {
757
+ const resolvedThemeName = resolveThemeName(themeName, flags || {});
758
+ const theme = buildTheme(resolvedThemeName, getBasePalette(resolvedThemeName));
746
759
 
747
760
  const palette = [
748
761
  ["accent", theme.colors.accent],
@@ -767,7 +780,8 @@ async function runMake(name, flags) {
767
780
  return;
768
781
  }
769
782
 
770
- const preset = getBasePalette(flags.from || flags.preset || "noir");
783
+ const presetName = resolveBaseThemeName(flags.from || flags.preset || "noir", flags || {});
784
+ const preset = getBasePalette(presetName);
771
785
 
772
786
  const customized = {
773
787
  ...preset,
@@ -800,7 +814,8 @@ function runInit(name, flags) {
800
814
  fail(`Theme already exists: ${filePath}. Re-run with --force to overwrite.`);
801
815
  }
802
816
 
803
- const palette = getBasePalette(flags.from || flags.preset || "noir");
817
+ const presetName = resolveBaseThemeName(flags.from || flags.preset || "noir", flags || {});
818
+ const palette = getBasePalette(presetName);
804
819
  writeThemeAssets(paths, name, palette);
805
820
  console.log(`Created starter theme at ${filePath}`);
806
821
  console.log("Edit the JSON, then run: ctheme make " + name + " --from " + name);
@@ -813,7 +828,7 @@ async function runMakeWizard(initialName, flags) {
813
828
  });
814
829
 
815
830
  try {
816
- const availablePresets = Object.keys(PRESETS).sort();
831
+ const availablePresets = getBundledThemeFamilies();
817
832
  console.log("Theme wizard");
818
833
  console.log("Press Enter to keep the default shown in brackets.");
819
834
  console.log(`Presets: ${availablePresets.join(", ")}`);
@@ -826,7 +841,8 @@ async function runMakeWizard(initialName, flags) {
826
841
  "Base preset",
827
842
  flags.from || flags.preset || "noir"
828
843
  );
829
- const preset = getBasePalette(basePresetName);
844
+ const resolvedBasePresetName = resolveBaseThemeName(basePresetName, flags || {});
845
+ const preset = getBasePalette(resolvedBasePresetName);
830
846
  const defaultName = initialName || `${basePresetName}-custom`;
831
847
  const themeName = await promptWithDefault(rl, "Theme name", defaultName);
832
848
 
@@ -865,7 +881,7 @@ async function runMakeWizard(initialName, flags) {
865
881
  console.log("\nTheme summary:");
866
882
  printThemeSummary({
867
883
  name: themeName,
868
- preset: basePresetName,
884
+ preset: resolvedBasePresetName,
869
885
  accent,
870
886
  bg,
871
887
  surface,
@@ -973,10 +989,9 @@ function runTerminal(target, flags) {
973
989
  }
974
990
 
975
991
  const normalizedTarget = normalizeTerminalTarget(target);
976
- const themeName = flags.theme || "noir";
977
- assertThemeName(themeName);
978
- const theme = buildTheme(themeName, getBasePalette(themeName));
979
- const presetFonts = getThemeFonts(themeName);
992
+ const resolvedThemeName = resolveThemeName(flags.theme || "noir", flags || {});
993
+ const theme = buildTheme(resolvedThemeName, getBasePalette(resolvedThemeName));
994
+ const presetFonts = getThemeFonts(resolvedThemeName);
980
995
  const font = flags.font || presetFonts.terminal || "JetBrains Mono";
981
996
  const fontSize = Number(flags["font-size"] || 15);
982
997
  const opacity = flags.opacity ? Number(flags.opacity) : null;
@@ -1290,6 +1305,86 @@ function detectCurrentTerminalTarget() {
1290
1305
  return "";
1291
1306
  }
1292
1307
 
1308
+ function getBundledThemeFamilies() {
1309
+ return Object.keys(BASE_PRESETS)
1310
+ .map((name) => name.replace(/-(light|dark)$/, ""))
1311
+ .filter((name, index, list) => list.indexOf(name) === index)
1312
+ .sort();
1313
+ }
1314
+
1315
+ function resolveBaseThemeName(name, flags) {
1316
+ return resolveThemeName(name, flags);
1317
+ }
1318
+
1319
+ function resolveThemeName(themeName, flags) {
1320
+ assertThemeName(themeName);
1321
+
1322
+ const wantsDark = Boolean(flags.dark);
1323
+ const wantsLight = Boolean(flags.light);
1324
+ if (wantsDark && wantsLight) {
1325
+ fail("Choose either --dark or --light, not both.");
1326
+ }
1327
+
1328
+ if (themeExists(themeName) && /-(light|dark)$/.test(themeName)) {
1329
+ return themeName;
1330
+ }
1331
+
1332
+ const desiredMode = wantsDark ? "dark" : wantsLight ? "light" : detectSystemAppearanceMode();
1333
+ const desiredVariant = `${themeName}-${desiredMode}`;
1334
+ if (themeExists(desiredVariant)) {
1335
+ return desiredVariant;
1336
+ }
1337
+
1338
+ if (themeExists(themeName)) {
1339
+ return themeName;
1340
+ }
1341
+
1342
+ const fallbackVariant = `${themeName}-${desiredMode === "dark" ? "light" : "dark"}`;
1343
+ if (themeExists(fallbackVariant)) {
1344
+ return fallbackVariant;
1345
+ }
1346
+
1347
+ fail(`Theme not found: ${themeName}`);
1348
+ }
1349
+
1350
+ function themeExists(name) {
1351
+ if (PRESETS[name]) return true;
1352
+ const paths = getThemePaths();
1353
+ return fs.existsSync(path.join(paths.paletteDir, `${name}.json`));
1354
+ }
1355
+
1356
+ function detectSystemAppearanceMode() {
1357
+ const envMode = String(process.env.CTHEME_MODE || "").trim().toLowerCase();
1358
+ if (envMode === "dark" || envMode === "light") {
1359
+ return envMode;
1360
+ }
1361
+
1362
+ if (process.platform === "darwin") {
1363
+ try {
1364
+ const result = execFileSync("defaults", ["read", "-g", "AppleInterfaceStyle"], { encoding: "utf8" }).trim();
1365
+ return result.toLowerCase() === "dark" ? "dark" : "light";
1366
+ } catch (_error) {
1367
+ return "light";
1368
+ }
1369
+ }
1370
+
1371
+ if (process.platform === "win32") {
1372
+ try {
1373
+ const result = execFileSync("reg", [
1374
+ "query",
1375
+ "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
1376
+ "/v",
1377
+ "AppsUseLightTheme"
1378
+ ], { encoding: "utf8" });
1379
+ return /0x0\b/i.test(result) ? "dark" : "light";
1380
+ } catch (_error) {
1381
+ return "dark";
1382
+ }
1383
+ }
1384
+
1385
+ return "dark";
1386
+ }
1387
+
1293
1388
  function getThemeFonts(name) {
1294
1389
  if (PRESETS[name] && PRESETS[name].fonts) {
1295
1390
  return PRESETS[name].fonts;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctheme",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Theme manager for making terminals look better with colors, fonts, and live profile switching",
5
5
  "license": "MIT",
6
6
  "repository": {