@rokkit/cli 1.1.1 → 1.1.5

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.
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Build the default named-token starter config source used by `doctor --fix`.
3
+ * @returns {string}
4
+ */
5
+ export function defaultStarterSource(): string;
1
6
  /**
2
7
  * Run all doctor checks against the given filesystem adapter.
3
8
  * @param {{ exists: (p: string) => boolean, read: (p: string) => string, resolve: (p: string) => string }} fs
@@ -15,10 +20,41 @@ export function runChecks(fs: {
15
20
  fix: string;
16
21
  autoFix?: string;
17
22
  }>;
23
+ /**
24
+ * Scan files for legacy z-scale utilities and suggest named-token equivalents.
25
+ * Advisory only — z-scale still resolves via the preset's back-compat layer.
26
+ * @param {Array<{ path: string, content: string }>} files
27
+ * @returns {{ count: number, byFile: Array<{ path: string, hits: Array<{ token: string, suggestion: string }> }> }}
28
+ */
29
+ export function findLegacyZUtilities(files: Array<{
30
+ path: string;
31
+ content: string;
32
+ }>): {
33
+ count: number;
34
+ byFile: Array<{
35
+ path: string;
36
+ hits: Array<{
37
+ token: string;
38
+ suggestion: string;
39
+ }>;
40
+ }>;
41
+ };
42
+ /**
43
+ * Validate a parsed rokkit config for the named-token system. All advisory (warn).
44
+ * @param {Record<string, unknown> | null} config
45
+ * @returns {Array<{ id: string, label: string, status: 'warn', fixable: false, fix: string }>}
46
+ */
47
+ export function validateConfigShape(config: Record<string, unknown> | null): Array<{
48
+ id: string;
49
+ label: string;
50
+ status: "warn";
51
+ fixable: false;
52
+ fix: string;
53
+ }>;
18
54
  /**
19
55
  * Interactive doctor command — validates project setup.
20
56
  * @param {{ fix?: boolean }} [opts]
21
57
  */
22
58
  export function doctor(opts?: {
23
59
  fix?: boolean;
24
- }): void;
60
+ }): Promise<void>;
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Serialize a rokkit config object to a JS module string with a header comment.
3
+ * @param {Record<string, unknown>} config
4
+ * @returns {string}
5
+ */
6
+ export function serializeRokkitConfig(config: Record<string, unknown>): string;
1
7
  /**
2
8
  * Detect if the current directory is a SvelteKit project.
3
9
  * @param {string} cwd
@@ -22,25 +28,42 @@ export function generateChartConfig({ chartColors, chartShades }: {
22
28
  chartColors: string;
23
29
  chartShades: string;
24
30
  }): Record<string, unknown>;
31
+ /**
32
+ * Build the Zen-Sumi OKLCH starter (ink-on-paper, dual-palette dark mode).
33
+ * @param {{ themes?: string[], defaultTheme?: string, switcher?: string,
34
+ * includeChart?: boolean, chartColors?: string, chartShades?: string }} [opts]
35
+ * @returns {Record<string, unknown>}
36
+ */
37
+ export function generateZenSumiConfig(opts?: {
38
+ themes?: string[];
39
+ defaultTheme?: string;
40
+ switcher?: string;
41
+ includeChart?: boolean;
42
+ chartColors?: string;
43
+ chartShades?: string;
44
+ }): Record<string, unknown>;
45
+ /**
46
+ * Build a Rokkit config object from the user's init choices.
47
+ * Emits the named-token `skin` shape (skin + colorSpace + tokens), plus
48
+ * optional `icons` and `chart` sections.
49
+ * @param {{ palette: string, customColors?: Record<string, string>, icons?: string,
50
+ * iconPath?: string, iconStyle?: string, themes: string[], defaultTheme?: string,
51
+ * switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string }} opts
52
+ * @returns {Record<string, unknown>}
53
+ */
25
54
  export function generateConfig({ palette, customColors, icons, iconPath, iconStyle, themes, defaultTheme, switcher, includeChart, chartColors, chartShades }: {
26
- palette: any;
27
- customColors: any;
28
- icons: any;
29
- iconPath: any;
30
- iconStyle: any;
31
- themes: any;
32
- defaultTheme: any;
33
- switcher: any;
34
- includeChart: any;
35
- chartColors: any;
36
- chartShades: any;
37
- }): {
38
- colors: Record<string, unknown>;
39
- themes: any;
40
- defaultTheme: any;
41
- switcher: any;
42
- storageKey: string;
43
- };
55
+ palette: string;
56
+ customColors?: Record<string, string>;
57
+ icons?: string;
58
+ iconPath?: string;
59
+ iconStyle?: string;
60
+ themes: string[];
61
+ defaultTheme?: string;
62
+ switcher: string;
63
+ includeChart?: boolean;
64
+ chartColors?: string;
65
+ chartShades?: string;
66
+ }): Record<string, unknown>;
44
67
  /**
45
68
  * Generate the content for uno.config.js.
46
69
  * @returns {string}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.5",
4
4
  "description": "Command line utilities for icon bundling and conversion.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/doctor.js CHANGED
@@ -1,7 +1,15 @@
1
1
  /* eslint-disable no-console */
2
- import { existsSync, readFileSync, writeFileSync } from 'fs'
3
- import { resolve } from 'path'
4
- import { generateUnoConfig, generateAppCssImports, generateInitScript, generateChartConfig } from './init.js'
2
+ import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs'
3
+ import { resolve, extname } from 'path'
4
+ import { loadConfig as loadParsedConfig } from './config.js'
5
+ import {
6
+ generateUnoConfig,
7
+ generateAppCssImports,
8
+ generateInitScript,
9
+ generateChartConfig,
10
+ generateConfig,
11
+ serializeRokkitConfig
12
+ } from './init.js'
5
13
 
6
14
  const KNOWN_THEMES = [
7
15
  'rokkit',
@@ -11,6 +19,23 @@ const KNOWN_THEMES = [
11
19
  'zen-sumi'
12
20
  ]
13
21
 
22
+ /**
23
+ * Build the default named-token starter config source used by `doctor --fix`.
24
+ * @returns {string}
25
+ */
26
+ export function defaultStarterSource() {
27
+ const config = generateConfig({
28
+ palette: 'default',
29
+ icons: 'rokkit',
30
+ themes: ['rokkit'],
31
+ switcher: 'manual',
32
+ includeChart: true,
33
+ chartColors: 'default',
34
+ chartShades: 'standard'
35
+ })
36
+ return serializeRokkitConfig(config)
37
+ }
38
+
14
39
  /**
15
40
  * Check config file existence
16
41
  * @param {Object} fs
@@ -163,7 +188,7 @@ function createFsAdapter(cwd) {
163
188
  */
164
189
  function applyGenerateConfig(cwd, label) {
165
190
  const configPath = resolve(cwd, 'rokkit.config.js')
166
- writeFileSync(configPath, 'export default {}\n')
191
+ writeFileSync(configPath, defaultStarterSource())
167
192
  console.info(` Fixed: ${label}`)
168
193
  }
169
194
 
@@ -331,16 +356,159 @@ function handleResults(checks, cwd, failures, fix) {
331
356
  }
332
357
  }
333
358
 
359
+ // z-slot → named token (matches @rokkit/core Z_COLLAPSE maps).
360
+ const SURFACE_Z = {
361
+ z0: 'paper', z1: 'paper-soft', z2: 'paper-mute', z3: 'paper-mute', z4: 'paper-edge',
362
+ z5: 'ink-soft', z6: 'ink-soft', z7: 'ink-mute', z8: 'ink-mute', z9: 'ink', z10: 'ink'
363
+ }
364
+ const INK_Z = {
365
+ z0: 'ink', z1: 'ink-mute', z2: 'ink-mute', z3: 'ink-soft', z4: 'ink-soft',
366
+ z5: 'paper-edge', z6: 'paper-edge', z7: 'paper-mute', z8: 'paper-mute', z9: 'paper-soft', z10: 'paper'
367
+ }
368
+
369
+ /**
370
+ * Map a z-scale role+slot to its nearest named token.
371
+ * @param {string} role
372
+ * @param {string} z — e.g. 'z5'
373
+ * @returns {string}
374
+ */
375
+ function suggestNamedToken(role, z) {
376
+ if (role === 'surface') return SURFACE_Z[z]
377
+ if (role === 'ink') return INK_Z[z]
378
+ // accent + status roles collapse z0–z2 to their `-soft` tint (matches
379
+ // @rokkit/core theme.ts getZAliasesOther: zNum <= 2). primary has no `-soft`.
380
+ if (role !== 'primary' && Number(z.slice(1)) <= 2) return `${role}-soft`
381
+ return role
382
+ }
383
+
384
+ const Z_UTILITY_RE =
385
+ /\b(bg|text|border(?:-[tblr])?|fill|stroke|ring|outline|divide)-(surface|ink|primary|accent|success|warning|danger|error|info)-(z(?:10|[0-9]))\b/g
386
+
387
+ /**
388
+ * Scan files for legacy z-scale utilities and suggest named-token equivalents.
389
+ * Advisory only — z-scale still resolves via the preset's back-compat layer.
390
+ * @param {Array<{ path: string, content: string }>} files
391
+ * @returns {{ count: number, byFile: Array<{ path: string, hits: Array<{ token: string, suggestion: string }> }> }}
392
+ */
393
+ export function findLegacyZUtilities(files) {
394
+ const byFile = []
395
+ let count = 0
396
+ for (const { path: filePath, content } of files) {
397
+ const hits = []
398
+ for (const match of content.matchAll(Z_UTILITY_RE)) {
399
+ const [token, prefix, role, z] = match
400
+ hits.push({ token, suggestion: `${prefix}-${suggestNamedToken(role, z)}` })
401
+ }
402
+ if (hits.length > 0) {
403
+ byFile.push({ path: filePath, hits })
404
+ count += hits.length
405
+ }
406
+ }
407
+ return { count, byFile }
408
+ }
409
+
410
+ const SCAN_EXTS = new Set(['.svelte', '.css', '.ts', '.js'])
411
+
412
+ /**
413
+ * Recursively gather scannable source files under cwd/src.
414
+ * Skips node_modules, symlinks (avoids recursion loops), and uno.config.js
415
+ * (its safelist legitimately lists z-utilities).
416
+ * @param {string} cwd
417
+ * @returns {Array<{ path: string, content: string }>}
418
+ */
419
+ function gatherSrcFiles(cwd) {
420
+ const root = resolve(cwd, 'src')
421
+ if (!existsSync(root)) return []
422
+ const out = []
423
+ const walk = (dir) => {
424
+ for (const dirent of readdirSync(dir, { withFileTypes: true })) {
425
+ if (dirent.isSymbolicLink() || dirent.name === 'node_modules') continue
426
+ const full = resolve(dir, dirent.name)
427
+ if (dirent.isDirectory()) {
428
+ walk(full)
429
+ } else if (dirent.isFile() && SCAN_EXTS.has(extname(dirent.name)) && !full.endsWith('uno.config.js')) {
430
+ out.push({ path: full.slice(cwd.length + 1), content: readFileSync(full, 'utf-8') })
431
+ }
432
+ }
433
+ }
434
+ walk(root)
435
+ return out
436
+ }
437
+
438
+ /**
439
+ * Validate a parsed rokkit config for the named-token system. All advisory (warn).
440
+ * @param {Record<string, unknown> | null} config
441
+ * @returns {Array<{ id: string, label: string, status: 'warn', fixable: false, fix: string }>}
442
+ */
443
+ export function validateConfigShape(config) {
444
+ if (!config) return []
445
+ const checks = []
446
+ const usesColorsAlias =
447
+ typeof config.colors === 'object' && config.colors !== null && !Array.isArray(config.colors) && !config.skin
448
+ const colormap = config.skins?.default ?? config.skin ?? config.colors ?? {}
449
+
450
+ if (!('ink' in colormap)) {
451
+ checks.push({
452
+ id: 'skin-ink-role',
453
+ label: 'skin defines an `ink` role',
454
+ status: 'warn',
455
+ fixable: false,
456
+ fix: "Add `ink: '<palette>'` (reusing the surface palette is fine) — without it, ink-* text tokens fall back to the surface palette."
457
+ })
458
+ }
459
+ if (config.colorSpace === 'oklch' && Object.keys(config.palettes ?? {}).length === 0) {
460
+ checks.push({
461
+ id: 'oklch-needs-palettes',
462
+ label: 'colorSpace `oklch` has a `palettes` block',
463
+ status: 'warn',
464
+ fixable: false,
465
+ fix: 'oklch values need a `palettes` block of bare "L C H" components; Tailwind named colors are rgb.'
466
+ })
467
+ }
468
+ if (usesColorsAlias) {
469
+ checks.push({
470
+ id: 'colors-alias',
471
+ label: 'config uses `skin` (not the legacy `colors` alias)',
472
+ status: 'warn',
473
+ fixable: false,
474
+ fix: 'Rename `colors:` to `skin:` — `colors` is a back-compat alias.'
475
+ })
476
+ }
477
+ return checks
478
+ }
479
+
334
480
  /**
335
481
  * Interactive doctor command — validates project setup.
336
482
  * @param {{ fix?: boolean }} [opts]
337
483
  */
338
- export function doctor(opts = {}) {
484
+ export async function doctor(opts = {}) {
339
485
  const cwd = process.cwd()
340
486
  const checks = runChecks(createFsAdapter(cwd))
341
487
 
342
488
  console.info('Rokkit Doctor\n')
343
489
  const failures = printChecks(checks)
490
+
491
+ const parsed = await loadParsedConfig({ cwd })
492
+ const shapeChecks = validateConfigShape(parsed)
493
+ if (shapeChecks.length > 0) {
494
+ console.info('\nConfig shape:')
495
+ for (const c of shapeChecks) {
496
+ console.info(` WARN ${c.label}`)
497
+ console.info(` ${c.fix}`)
498
+ }
499
+ }
500
+
501
+ const { count, byFile } = findLegacyZUtilities(gatherSrcFiles(cwd))
502
+ if (count > 0) {
503
+ console.info(`\nLegacy z-scale utilities (advisory — ${count} in ${byFile.length} file(s)):`)
504
+ for (const { path: filePath, hits } of byFile.slice(0, 10)) {
505
+ const examples = [...new Map(hits.map((h) => [h.token, h.suggestion]))]
506
+ const shown = examples.slice(0, 3).map(([t, s]) => `${t} → ${s}`).join(', ')
507
+ console.info(` ${filePath}: ${shown}${examples.length > 3 ? ', …' : ''}`)
508
+ }
509
+ console.info(' Named tokens are preferred; z-scale still works via back-compat.')
510
+ }
511
+
344
512
  handleResults(checks, cwd, failures, opts.fix ?? false)
345
513
  console.info('')
346
514
  }
package/src/init.js CHANGED
@@ -8,6 +8,42 @@ import { detectPackageManager, buildInstallCommand } from './upgrade.js'
8
8
  const ROKKIT_PACKAGES = ['@rokkit/ui', '@rokkit/unocss', '@rokkit/themes', '@rokkit/icons']
9
9
  const LOCKFILES = ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json']
10
10
 
11
+ const NAMED_TOKEN_HEADER = `/**
12
+ * Rokkit token configuration — consumed by presetRokkit() in uno.config.js.
13
+ *
14
+ * Maps semantic roles (surface, ink, primary, accent, status…) to palettes.
15
+ * The preset emits the named-token vocabulary used throughout components:
16
+ *
17
+ * Surface bg-paper · bg-paper-soft · bg-paper-mute · border-paper-edge
18
+ * Text text-ink · text-ink-mute · text-ink-soft · text-ink-faint
19
+ * Accent bg-primary · text-on-primary · bg-accent · bg-accent-soft
20
+ * Status bg-success-soft · text-success (+ warning / danger / error / info)
21
+ *
22
+ * Tokens flip automatically under [data-mode="dark"]. The older z-scale
23
+ * utilities (bg-surface-z0, text-primary-z5) still resolve for back-compat,
24
+ * but new code should prefer the named tokens above.
25
+ *
26
+ * tokens: 'core' emits the named vocabulary; 'extended' also emits the full
27
+ * 11-shade palette ladder per role (for charts / data-viz).
28
+ */
29
+ `
30
+
31
+ const OKLCH_PALETTES_NOTE = `/**
32
+ * palettes: bare OKLCH "L C H" components (colorSpace: 'oklch'). surface/ink use
33
+ * a { light, dark } dual palette — kami (warm paper) in light, sumi (ink) in dark.
34
+ */
35
+ `
36
+
37
+ /**
38
+ * Serialize a rokkit config object to a JS module string with a header comment.
39
+ * @param {Record<string, unknown>} config
40
+ * @returns {string}
41
+ */
42
+ export function serializeRokkitConfig(config) {
43
+ const palettesNote = config.palettes ? OKLCH_PALETTES_NOTE : ''
44
+ return `${NAMED_TOKEN_HEADER}${palettesNote}export default ${JSON.stringify(config, null, 2)}\n`
45
+ }
46
+
11
47
  /**
12
48
  * Detect if the current directory is a SvelteKit project.
13
49
  * @param {string} cwd
@@ -61,12 +97,23 @@ const CHART_SHADE_PRESETS = {
61
97
  soft: { light: { fill: '400', stroke: '600' }, dark: { fill: '600', stroke: '300' } }
62
98
  }
63
99
 
100
+ // Base rgb skin — named-token roles only (no secondary/tertiary; ink defaults to surface).
101
+ const DEFAULT_SKIN_BASE = {
102
+ primary: 'orange',
103
+ accent: 'sky',
104
+ surface: 'slate',
105
+ success: 'green',
106
+ warning: 'yellow',
107
+ danger: 'red',
108
+ error: 'red',
109
+ info: 'cyan'
110
+ }
111
+
64
112
  const SKIN_PRESETS = {
65
- default: { primary: 'orange', secondary: 'pink', accent: 'sky', surface: 'slate' },
66
- vibrant: { primary: 'blue', secondary: 'purple', accent: 'sky', surface: 'slate' },
113
+ default: {},
114
+ vibrant: { primary: 'blue', accent: 'sky', surface: 'slate' },
67
115
  seaweed: {
68
116
  primary: 'sky',
69
- secondary: 'green',
70
117
  accent: 'blue',
71
118
  surface: 'zinc',
72
119
  danger: 'rose',
@@ -77,26 +124,66 @@ const SKIN_PRESETS = {
77
124
  }
78
125
  }
79
126
 
80
- const DEFAULT_COLORS = {
81
- primary: 'orange',
82
- secondary: 'pink',
83
- accent: 'sky',
84
- surface: 'slate',
85
- success: 'green',
86
- warning: 'yellow',
87
- danger: 'red',
88
- error: 'red',
89
- info: 'cyan'
90
- }
91
-
92
127
  /**
93
- * Generate a Rokkit config object from user choices.
94
- * @param {{ palette: string, customColors?: Record<string, string>, icons: string, iconPath?: string, themes: string[], switcher: string }} opts
95
- * @returns {Record<string, unknown>}
128
+ * Resolve a named-token rgb skin from a preset or custom colors.
129
+ * `ink` defaults to the `surface` palette. Only the named-token roles are
130
+ * emitted; `secondary`/`tertiary` are intentionally omitted (no named token
131
+ * reads them). A consumer who needs them can add them to their config's skin
132
+ * directly — @rokkit/unocss's loadConfig merges extra roles through.
133
+ * @param {string} palette
134
+ * @param {Record<string, string>} [customColors]
135
+ * @returns {Record<string, string>}
96
136
  */
97
- function resolveColors(palette, customColors) {
98
- if (palette === 'custom') return { ...DEFAULT_COLORS, ...customColors }
99
- return { ...DEFAULT_COLORS, ...(SKIN_PRESETS[palette] || {}) }
137
+ function resolveSkin(palette, customColors) {
138
+ const merged =
139
+ palette === 'custom'
140
+ ? { ...DEFAULT_SKIN_BASE, ...customColors }
141
+ : { ...DEFAULT_SKIN_BASE, ...(SKIN_PRESETS[palette] || {}) }
142
+ return {
143
+ surface: merged.surface,
144
+ ink: merged.ink ?? merged.surface,
145
+ primary: merged.primary,
146
+ accent: merged.accent,
147
+ success: merged.success,
148
+ warning: merged.warning,
149
+ danger: merged.danger,
150
+ error: merged.error,
151
+ info: merged.info
152
+ }
153
+ }
154
+
155
+ // Zen-Sumi OKLCH palettes — bare "L C H" components (colorSpace: 'oklch').
156
+ const ZEN_SUMI_PALETTES = {
157
+ kami: {
158
+ 50: '0.985 0.005 85', 100: '0.975 0.008 85', 200: '0.955 0.010 85',
159
+ 300: '0.920 0.012 85', 400: '0.850 0.010 70', 500: '0.750 0.008 50',
160
+ 600: '0.580 0.010 50', 700: '0.380 0.012 50', 800: '0.280 0.012 50',
161
+ 900: '0.220 0.012 50', 950: '0.170 0.010 50'
162
+ },
163
+ sumi: {
164
+ 50: '0.170 0.010 50', 100: '0.210 0.012 50', 200: '0.250 0.012 50',
165
+ 300: '0.320 0.012 50', 400: '0.420 0.010 50', 500: '0.570 0.010 50',
166
+ 600: '0.420 0.012 85', 700: '0.600 0.010 85', 800: '0.780 0.008 85',
167
+ 900: '0.940 0.008 85', 950: '0.975 0.008 85'
168
+ },
169
+ shu: {
170
+ 50: '0.970 0.020 35', 100: '0.940 0.040 35', 200: '0.880 0.070 35',
171
+ 300: '0.800 0.100 35', 400: '0.700 0.130 35', 500: '0.580 0.150 35',
172
+ 600: '0.500 0.140 35', 700: '0.420 0.120 35', 800: '0.350 0.100 35',
173
+ 900: '0.280 0.080 35', 950: '0.220 0.060 35'
174
+ },
175
+ hisui: {
176
+ 50: '0.970 0.015 160', 100: '0.940 0.030 160', 200: '0.880 0.050 160',
177
+ 300: '0.800 0.065 160', 400: '0.720 0.075 160', 500: '0.620 0.080 160',
178
+ 600: '0.540 0.075 160', 700: '0.460 0.065 160', 800: '0.380 0.055 160',
179
+ 900: '0.300 0.045 160', 950: '0.240 0.035 160'
180
+ },
181
+ kohaku: {
182
+ 50: '0.980 0.020 75', 100: '0.950 0.040 75', 200: '0.900 0.070 75',
183
+ 300: '0.850 0.095 75', 400: '0.790 0.110 75', 500: '0.720 0.120 75',
184
+ 600: '0.640 0.110 75', 700: '0.560 0.095 75', 800: '0.470 0.080 75',
185
+ 900: '0.380 0.065 75', 950: '0.300 0.050 75'
186
+ }
100
187
  }
101
188
 
102
189
  /**
@@ -111,6 +198,53 @@ export function generateChartConfig({ chartColors, chartShades }) {
111
198
  }
112
199
  }
113
200
 
201
+ /**
202
+ * Build the Zen-Sumi OKLCH starter (ink-on-paper, dual-palette dark mode).
203
+ * @param {{ themes?: string[], defaultTheme?: string, switcher?: string,
204
+ * includeChart?: boolean, chartColors?: string, chartShades?: string }} [opts]
205
+ * @returns {Record<string, unknown>}
206
+ */
207
+ export function generateZenSumiConfig(opts = {}) {
208
+ const { themes, defaultTheme, switcher, includeChart, chartColors, chartShades } = opts
209
+ const config = {
210
+ palettes: ZEN_SUMI_PALETTES,
211
+ colorSpace: 'oklch',
212
+ tokens: 'core',
213
+ skin: {
214
+ surface: { light: 'kami', dark: 'sumi' },
215
+ ink: { light: 'kami', dark: 'sumi' },
216
+ primary: 'shu',
217
+ accent: 'shu',
218
+ success: 'hisui',
219
+ warning: 'kohaku',
220
+ danger: 'shu',
221
+ error: 'shu',
222
+ info: 'kohaku'
223
+ },
224
+ shape: { radius: 'soft' },
225
+ typography: {
226
+ display: "'Fraunces', 'Iowan Old Style', Georgia, serif",
227
+ sans: "'Inter', system-ui, -apple-system, sans-serif",
228
+ mono: "'JetBrains Mono', 'SF Mono', Menlo, monospace"
229
+ },
230
+ themes: themes && themes.length ? themes : ['rokkit', 'zen-sumi'],
231
+ defaultTheme: defaultTheme || 'zen-sumi',
232
+ switcher: switcher || 'full',
233
+ storageKey: 'rokkit-theme'
234
+ }
235
+ if (includeChart) config.chart = generateChartConfig({ chartColors, chartShades })
236
+ return config
237
+ }
238
+
239
+ /**
240
+ * Build a Rokkit config object from the user's init choices.
241
+ * Emits the named-token `skin` shape (skin + colorSpace + tokens), plus
242
+ * optional `icons` and `chart` sections.
243
+ * @param {{ palette: string, customColors?: Record<string, string>, icons?: string,
244
+ * iconPath?: string, iconStyle?: string, themes: string[], defaultTheme?: string,
245
+ * switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string }} opts
246
+ * @returns {Record<string, unknown>}
247
+ */
114
248
  export function generateConfig({
115
249
  palette,
116
250
  customColors,
@@ -124,8 +258,14 @@ export function generateConfig({
124
258
  chartColors,
125
259
  chartShades
126
260
  }) {
261
+ if (palette === 'zen-sumi') {
262
+ return generateZenSumiConfig({ themes, defaultTheme, switcher, includeChart, chartColors, chartShades })
263
+ }
264
+
127
265
  const config = {
128
- colors: resolveColors(palette, customColors),
266
+ skin: resolveSkin(palette, customColors),
267
+ colorSpace: 'rgb',
268
+ tokens: 'core',
129
269
  themes,
130
270
  defaultTheme: defaultTheme || themes[0],
131
271
  switcher,
@@ -205,6 +345,7 @@ const PROMPTS_CONFIG = [
205
345
  { title: 'Default (orange/pink/sky)', value: 'default' },
206
346
  { title: 'Vibrant (blue/purple/sky)', value: 'vibrant' },
207
347
  { title: 'Seaweed (sky/green/blue)', value: 'seaweed' },
348
+ { title: 'Zen-Sumi (OKLCH ink-on-paper)', value: 'zen-sumi' },
208
349
  { title: 'Custom', value: 'custom' }
209
350
  ]
210
351
  },
@@ -327,7 +468,7 @@ function writeRokkitConfig(cwd, config) {
327
468
  console.warn(' rokkit.config.js already exists — skipping')
328
469
  return
329
470
  }
330
- writeFileSync(configPath, `export default ${JSON.stringify(config, null, 2)}\n`)
471
+ writeFileSync(configPath, serializeRokkitConfig(config))
331
472
  console.info(' Created rokkit.config.js')
332
473
  }
333
474