@ansh-ui/cli 0.2.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ansh contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli.js ADDED
@@ -0,0 +1,680 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'util';
3
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
4
+ import { join, dirname, resolve } from 'path';
5
+ import { parseColor, generatePalette, rgbToOklch, toOklchString, toHex, createGradient, createScale, checkContrast, harmony, effects, behaviours } from '@ansh-ui/core';
6
+
7
+ function findRoot(cwd) {
8
+ let current = resolve(cwd);
9
+ for (let depth = 0; depth < 10; depth++) {
10
+ if (existsSync(join(current, "package.json"))) return current;
11
+ const parent = resolve(current, "..");
12
+ if (parent === current) break;
13
+ current = parent;
14
+ }
15
+ return resolve(cwd);
16
+ }
17
+ function readJson(path) {
18
+ try {
19
+ return JSON.parse(readFileSync(path, "utf8"));
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+ function detectProject(cwd = process.cwd()) {
25
+ const root = findRoot(cwd);
26
+ const pkg = readJson(join(root, "package.json")) ?? {};
27
+ const deps = {
28
+ ...pkg.dependencies ?? {},
29
+ ...pkg.devDependencies ?? {}
30
+ };
31
+ const framework = deps.nuxt ? "nuxt" : deps.next ? "next" : deps.vue ? "vue" : deps.react ? "react" : "unknown";
32
+ const typescript = Boolean(deps.typescript) || existsSync(join(root, "tsconfig.json"));
33
+ const packageManager = existsSync(join(root, "pnpm-lock.yaml")) ? "pnpm" : existsSync(join(root, "bun.lockb")) || existsSync(join(root, "bun.lock")) ? "bun" : existsSync(join(root, "yarn.lock")) ? "yarn" : "npm";
34
+ return {
35
+ root,
36
+ framework,
37
+ typescript,
38
+ componentsDir: detectComponentsDir(root, framework),
39
+ alias: detectAlias(root),
40
+ packageManager,
41
+ installed: Boolean(
42
+ deps["@ansh-ui/react"] || deps["@ansh-ui/vue"] || deps["@ansh-ui/nuxt"] || deps["@ansh-ui/core"]
43
+ )
44
+ };
45
+ }
46
+ function detectComponentsDir(root, framework) {
47
+ const candidates = framework === "nuxt" ? ["components", "app/components", "src/components"] : ["src/components", "components", "app/components", "src/lib/components", "app/ui", "src/ui"];
48
+ for (const candidate of candidates) {
49
+ if (existsSync(join(root, candidate))) return candidate;
50
+ }
51
+ if (existsSync(join(root, "src"))) return "src/components";
52
+ if (framework === "nuxt") return "components";
53
+ return "components";
54
+ }
55
+ function detectAlias(root) {
56
+ for (const file of ["tsconfig.json", "jsconfig.json"]) {
57
+ const config = readJson(join(root, file));
58
+ if (!config) continue;
59
+ const compilerOptions = config.compilerOptions;
60
+ const paths = compilerOptions?.paths;
61
+ if (!paths) continue;
62
+ for (const pattern of Object.keys(paths)) {
63
+ if (pattern.endsWith("/*")) return pattern.slice(0, -1);
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ function installCommand(info, packages) {
69
+ const list = packages.join(" ");
70
+ switch (info.packageManager) {
71
+ case "pnpm":
72
+ return `pnpm add ${list}`;
73
+ case "yarn":
74
+ return `yarn add ${list}`;
75
+ case "bun":
76
+ return `bun add ${list}`;
77
+ default:
78
+ return `npm install ${list}`;
79
+ }
80
+ }
81
+ var COMPONENT_NAMES = {
82
+ aurora: "AuroraBackground",
83
+ "mesh-gradient": "MeshGradient",
84
+ "animated-gradient": "AnimatedGradient",
85
+ "particle-field": "ParticleField",
86
+ constellation: "Constellation",
87
+ meteors: "MeteorShower",
88
+ rain: "Rain",
89
+ snow: "Snow",
90
+ fireflies: "Fireflies",
91
+ waves: "Waves",
92
+ blob: "LiquidBlob",
93
+ "noise-layer": "NoiseLayer",
94
+ spotlight: "Spotlight",
95
+ magnetic: "MagneticButton",
96
+ tilt: "TiltCard",
97
+ ripple: "Ripple",
98
+ marquee: "Marquee",
99
+ "scroll-reveal": "ScrollReveal",
100
+ parallax: "Parallax",
101
+ "split-text": "SplitText"
102
+ };
103
+ function componentNameFor(name) {
104
+ return COMPONENT_NAMES[name] ?? name.replace(/(^|-)([a-z])/g, (_, __, letter) => letter.toUpperCase());
105
+ }
106
+ function listCatalogue(filter) {
107
+ const entries = [];
108
+ if (filter !== "behaviour") {
109
+ for (const [name, definition] of Object.entries(effects)) {
110
+ entries.push({
111
+ name,
112
+ kind: "effect",
113
+ component: componentNameFor(name),
114
+ options: describeOptions(definition.defaults)
115
+ });
116
+ }
117
+ }
118
+ if (filter !== "effect") {
119
+ for (const [name, definition] of Object.entries(behaviours)) {
120
+ entries.push({
121
+ name,
122
+ kind: "behaviour",
123
+ component: componentNameFor(name),
124
+ options: describeOptions(definition.defaults)
125
+ });
126
+ }
127
+ }
128
+ return entries.sort((a, b) => a.name.localeCompare(b.name));
129
+ }
130
+ function describeOptions(defaults) {
131
+ return Object.entries(defaults).map(([name, value]) => ({ name, default: value })).sort((a, b) => a.name.localeCompare(b.name));
132
+ }
133
+ function findEntry(query) {
134
+ const needle = query.toLowerCase();
135
+ return listCatalogue().find(
136
+ (entry) => entry.name.toLowerCase() === needle || entry.component.toLowerCase() === needle
137
+ );
138
+ }
139
+
140
+ // src/commands/add.ts
141
+ function addComponent(options) {
142
+ const project = detectProject(options.cwd);
143
+ const entry = findEntry(options.name);
144
+ if (!entry) {
145
+ throw new Error(
146
+ `Unknown component "${options.name}". Run \`ansh list\` to see what's available.`
147
+ );
148
+ }
149
+ if (project.framework === "unknown") {
150
+ throw new Error(
151
+ "Could not detect a React, Next, Vue or Nuxt project. Run this from your project root, or install the framework first."
152
+ );
153
+ }
154
+ const isVue = project.framework === "vue" || project.framework === "nuxt";
155
+ const componentName = options.as ?? entry.component;
156
+ const extension = isVue ? "vue" : project.typescript ? "tsx" : "jsx";
157
+ const directory = options.dir ?? project.componentsDir;
158
+ const path = join(project.root, directory, `${componentName}.${extension}`);
159
+ const contents = isVue ? renderVue(componentName, entry.component, entry.options, project) : renderReact(componentName, entry.component, entry.options, project);
160
+ const missingDependencies = missingFor(project);
161
+ if (options.dryRun) {
162
+ return { path, contents, written: false, missingDependencies };
163
+ }
164
+ if (existsSync(path) && !options.force) {
165
+ throw new Error(`${path} already exists. Pass --force to overwrite.`);
166
+ }
167
+ mkdirSync(dirname(path), { recursive: true });
168
+ writeFileSync(path, contents, "utf8");
169
+ return { path, contents, written: true, missingDependencies };
170
+ }
171
+ function missingFor(project) {
172
+ if (project.installed) return [];
173
+ switch (project.framework) {
174
+ case "nuxt":
175
+ return ["@ansh-ui/nuxt"];
176
+ case "vue":
177
+ return ["@ansh-ui/vue", "@ansh-ui/tokens"];
178
+ default:
179
+ return ["@ansh-ui/react", "@ansh-ui/tokens"];
180
+ }
181
+ }
182
+ function literal(value) {
183
+ if (typeof value === "string") return JSON.stringify(value);
184
+ if (Array.isArray(value)) return `[${value.map(literal).join(", ")}]`;
185
+ if (value === null) return "null";
186
+ if (typeof value === "object") return JSON.stringify(value);
187
+ return String(value);
188
+ }
189
+ var INTERESTING_OPTIONS = /* @__PURE__ */ new Set([
190
+ "colors",
191
+ "speed",
192
+ "intensity",
193
+ "opacity",
194
+ "blur",
195
+ "seed",
196
+ "strength",
197
+ "radius",
198
+ "max",
199
+ "distance",
200
+ "amount",
201
+ "count"
202
+ ]);
203
+ function pickDefaults(options) {
204
+ return options.filter((option) => INTERESTING_OPTIONS.has(option.name));
205
+ }
206
+ function renderReact(componentName, sourceComponent, options, project) {
207
+ const ts = project.typescript;
208
+ const defaults = pickDefaults(options);
209
+ const defaultLines = defaults.map((option) => ` ${option.name}: ${literal(option.default)},`).join("\n");
210
+ const propsType = ts ? `
211
+ type ${componentName}Props = ComponentProps<typeof ${sourceComponent}>
212
+ ` : "";
213
+ const signature = ts ? `export function ${componentName}({ children, ...props }: ${componentName}Props) {` : `export function ${componentName}({ children, ...props }) {`;
214
+ const typeImport = ts ? `import type { ComponentProps } from 'react'
215
+ ` : "";
216
+ return `${typeImport}import { ${sourceComponent} } from '@ansh-ui/react'
217
+
218
+ /**
219
+ * ${componentName} \u2014 a project-local wrapper around Ansh's ${sourceComponent}.
220
+ *
221
+ * Edit the defaults below to match your design system. Every option remains overridable per
222
+ * instance, because props spread after the defaults.
223
+ *
224
+ * Generated by \`ansh add\`. Safe to edit \u2014 it will not be regenerated unless you ask.
225
+ */
226
+ const defaults = {
227
+ ${defaultLines}
228
+ }
229
+
230
+ ${propsType}
231
+ ${signature}
232
+ return (
233
+ <${sourceComponent} {...defaults} {...props}>
234
+ {children}
235
+ </${sourceComponent}>
236
+ )
237
+ }
238
+ `;
239
+ }
240
+ function renderVue(componentName, sourceComponent, options, project) {
241
+ const defaults = pickDefaults(options);
242
+ const defaultLines = defaults.map((option) => ` ${option.name}: ${literal(option.default)},`).join("\n");
243
+ const lang = project.typescript ? ' lang="ts"' : "";
244
+ const importLine = project.framework === "nuxt" ? `// ${sourceComponent} is auto-imported by @ansh-ui/nuxt.` : `import { ${sourceComponent} } from '@ansh-ui/vue'`;
245
+ return `<script setup${lang}>
246
+ ${importLine}
247
+
248
+ /**
249
+ * ${componentName} \u2014 a project-local wrapper around Ansh's ${sourceComponent}.
250
+ *
251
+ * Edit the defaults below to match your design system. Every option remains overridable per
252
+ * instance, because \`$attrs\` is bound after the defaults.
253
+ *
254
+ * Generated by \`ansh add\`. Safe to edit \u2014 it will not be regenerated unless you ask.
255
+ */
256
+ const defaults = {
257
+ ${defaultLines}
258
+ }
259
+ </script>
260
+
261
+ <template>
262
+ <${sourceComponent} v-bind="{ ...defaults, ...$attrs }">
263
+ <slot />
264
+ </${sourceComponent}>
265
+ </template>
266
+ `;
267
+ }
268
+ function generatePaletteReport(options = {}) {
269
+ const parsed = options.colors && options.colors.length > 0 ? options.colors.map((color) => parseColor(color)) : generatePalette({
270
+ count: options.count ?? 5,
271
+ seed: options.seed ?? 1,
272
+ lightness: options.lightness ?? 0.65,
273
+ saturation: options.saturation ?? 0.85,
274
+ spread: options.spread ?? 120
275
+ });
276
+ const colors = parsed.map((color) => {
277
+ const lch = rgbToOklch(color);
278
+ return {
279
+ hex: toHex(color),
280
+ oklch: toOklchString(color),
281
+ lightness: Number(lch.l.toFixed(4)),
282
+ chroma: Number(lch.c.toFixed(4)),
283
+ hue: Number(lch.h.toFixed(2))
284
+ };
285
+ });
286
+ const gradientSamples = options.gradient === false || parsed.length < 2 ? [] : createGradient(parsed, { space: "oklch" }).sample(9).map((color) => toHex(color));
287
+ const snippet = `colors={[${colors.map((c) => `'${c.hex}'`).join(", ")}]}`;
288
+ return { colors, gradientSamples, snippet };
289
+ }
290
+ var TAILWIND_KEYS = ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900", "950"];
291
+ function generateTheme(options) {
292
+ const base = parseColor(options.color);
293
+ const baseLightness = rgbToOklch(base).l;
294
+ const scale = createScale(base, {
295
+ steps: 11,
296
+ hueShift: options.hueShift ?? 0,
297
+ chromaFalloff: options.chromaFalloff ?? 0.35
298
+ });
299
+ const unsafeOnLight = [];
300
+ const unsafeOnDark = [];
301
+ const lines = [];
302
+ let nearestStep = "500";
303
+ let nearestDistance = Infinity;
304
+ scale.colors.forEach((color, index) => {
305
+ const key = TAILWIND_KEYS[index];
306
+ const onLight = checkContrast(color, "#ffffff");
307
+ const onDark = checkContrast(color, "#0b0b0f");
308
+ if (!onLight.passes) unsafeOnLight.push(key);
309
+ if (!onDark.passes) unsafeOnDark.push(key);
310
+ const distance = Math.abs(rgbToOklch(color).l - baseLightness);
311
+ if (distance < nearestDistance) {
312
+ nearestDistance = distance;
313
+ nearestStep = key;
314
+ }
315
+ lines.push(
316
+ ` --ansh-brand-${key}: ${toOklchString(color)}; /* ${toHex(color)} on-white ${onLight.ratio.toFixed(2)}:1 on-black ${onDark.ratio.toFixed(2)}:1 */`
317
+ );
318
+ });
319
+ let accentLines = "";
320
+ if (options.withAccent) {
321
+ const [, accent] = harmony(base, "splitComplementary");
322
+ const accentScale = createScale(accent, { steps: 11 });
323
+ accentLines = "\n /* Accent \u2014 split-complementary partner to the brand hue. */\n" + accentScale.colors.map((color, index) => ` --ansh-accent-${TAILWIND_KEYS[index]}: ${toOklchString(color)};`).join("\n") + "\n";
324
+ }
325
+ const css = `/**
326
+ * Ansh theme override.
327
+ *
328
+ * Generated by \`ansh theme ${options.color}\`.
329
+ * Import this *after* \`@ansh-ui/tokens/ansh.css\` so it wins in the cascade.
330
+ *
331
+ * Contrast ratios are annotated per step. Steps marked unsafe cannot legally hold body text on the
332
+ * corresponding surface \u2014 use them for fills, borders and large display type instead.
333
+ */
334
+
335
+ :root {
336
+ ${lines.join("\n")}
337
+ ${accentLines}
338
+ /* Wire the semantic accent tokens to the generated scale. */
339
+ --ansh-accent-subtle: var(--ansh-brand-50);
340
+ --ansh-accent-muted: var(--ansh-brand-100);
341
+ --ansh-accent-default: var(--ansh-brand-600);
342
+ --ansh-accent-hover: var(--ansh-brand-700);
343
+ --ansh-accent-active: var(--ansh-brand-800);
344
+ --ansh-accent-text: var(--ansh-brand-700);
345
+ --ansh-border-focus: var(--ansh-brand-600);
346
+ }
347
+
348
+ /**
349
+ * Dark theme.
350
+ *
351
+ * Not an inversion. Accents step *lighter* rather than mirroring their light-mode index, because
352
+ * saturated colour reads as less vivid against a dark background (Helmholtz\u2013Kohlrausch).
353
+ */
354
+ [data-theme='dark'] {
355
+ --ansh-accent-subtle: color-mix(in oklab, var(--ansh-brand-950) 40%, transparent);
356
+ --ansh-accent-muted: var(--ansh-brand-950);
357
+ --ansh-accent-default: var(--ansh-brand-500);
358
+ --ansh-accent-hover: var(--ansh-brand-400);
359
+ --ansh-accent-active: var(--ansh-brand-300);
360
+ --ansh-accent-text: var(--ansh-brand-300);
361
+ --ansh-border-focus: var(--ansh-brand-400);
362
+ }
363
+
364
+ @media (prefers-color-scheme: dark) {
365
+ :root:not([data-theme]) {
366
+ --ansh-accent-subtle: color-mix(in oklab, var(--ansh-brand-950) 40%, transparent);
367
+ --ansh-accent-muted: var(--ansh-brand-950);
368
+ --ansh-accent-default: var(--ansh-brand-500);
369
+ --ansh-accent-hover: var(--ansh-brand-400);
370
+ --ansh-accent-active: var(--ansh-brand-300);
371
+ --ansh-accent-text: var(--ansh-brand-300);
372
+ --ansh-border-focus: var(--ansh-brand-400);
373
+ }
374
+ }
375
+ `;
376
+ if (options.out) {
377
+ mkdirSync(dirname(options.out), { recursive: true });
378
+ writeFileSync(options.out, css, "utf8");
379
+ }
380
+ return { css, unsafeOnLight, unsafeOnDark, nearestStep };
381
+ }
382
+
383
+ // src/cli.ts
384
+ var useColor = process.stdout.isTTY && !process.env.NO_COLOR;
385
+ var paint = (code, text) => useColor ? `\x1B[${code}m${text}\x1B[0m` : text;
386
+ var bold = (text) => paint("1", text);
387
+ var dim = (text) => paint("2", text);
388
+ var cyan = (text) => paint("36", text);
389
+ var green = (text) => paint("32", text);
390
+ var yellow = (text) => paint("33", text);
391
+ var red = (text) => paint("31", text);
392
+ var VERSION = "0.1.0";
393
+ function help() {
394
+ console.log(`
395
+ ${bold("ansh")} ${dim(`v${VERSION}`)} \u2014 Motion, refined.
396
+
397
+ ${bold("USAGE")}
398
+ ansh <command> [options]
399
+
400
+ ${bold("COMMANDS")}
401
+ ${cyan("list")} [--effects|--behaviours] List the component catalogue
402
+ ${cyan("info")} <name> Show a component's full option surface
403
+ ${cyan("add")} <name> [--as <Name>] Generate a local wrapper component
404
+ ${cyan("theme")} <color> [--out <file>] Generate a token override stylesheet
405
+ ${cyan("palette")} [colors...] Generate or inspect a palette
406
+ ${cyan("doctor")} Report the detected project setup
407
+
408
+ ${bold("EXAMPLES")}
409
+ ${dim("# See what is available")}
410
+ ansh list
411
+
412
+ ${dim("# Generate a HeroBackground wrapper around the aurora effect")}
413
+ ansh add aurora --as HeroBackground
414
+
415
+ ${dim("# Build a theme from a brand colour, with contrast annotations")}
416
+ ansh theme '#0275f4' --out src/styles/theme.css --with-accent
417
+
418
+ ${dim("# Inspect how a palette interpolates")}
419
+ ansh palette '#0275f4' '#9f0550'
420
+
421
+ ${dim("# Reproducible generated palette")}
422
+ ansh palette --count 5 --seed hero-section
423
+ `);
424
+ }
425
+ function fail(message, hint) {
426
+ console.error(`${red("error")} ${message}`);
427
+ if (hint) console.error(`${dim("hint")} ${hint}`);
428
+ process.exit(1);
429
+ }
430
+ function commandList(argv) {
431
+ const { values } = parseArgs({
432
+ args: argv,
433
+ options: {
434
+ effects: { type: "boolean", default: false },
435
+ behaviours: { type: "boolean", default: false },
436
+ json: { type: "boolean", default: false }
437
+ },
438
+ allowPositionals: true
439
+ });
440
+ const filter = values.effects ? "effect" : values.behaviours ? "behaviour" : void 0;
441
+ const entries = listCatalogue(filter);
442
+ if (values.json) {
443
+ console.log(JSON.stringify(entries, null, 2));
444
+ return;
445
+ }
446
+ const effects2 = entries.filter((entry) => entry.kind === "effect");
447
+ const behaviours2 = entries.filter((entry) => entry.kind === "behaviour");
448
+ const print = (title, list) => {
449
+ if (list.length === 0) return;
450
+ console.log(`
451
+ ${bold(title)}`);
452
+ for (const entry of list) {
453
+ console.log(
454
+ ` ${cyan(entry.name.padEnd(20))} ${entry.component.padEnd(20)} ` + dim(`${entry.options.length} options`)
455
+ );
456
+ }
457
+ };
458
+ print(`Effects ${dim("(canvas)")}`, effects2);
459
+ print(`Behaviours ${dim("(DOM)")}`, behaviours2);
460
+ console.log(`
461
+ ${dim(`${entries.length} total. Run \`ansh info <name>\` for details.`)}
462
+ `);
463
+ }
464
+ function commandInfo(argv) {
465
+ const name = argv[0];
466
+ if (!name) fail("Missing component name.", "ansh info aurora");
467
+ const entry = findEntry(name);
468
+ if (!entry) fail(`Unknown component "${name}".`, "Run `ansh list` to see what is available.");
469
+ console.log(`
470
+ ${bold(entry.component)} ${dim(`(${entry.kind} "${entry.name}")`)}
471
+ `);
472
+ console.log(bold(" OPTIONS"));
473
+ const width = Math.max(...entry.options.map((option) => option.name.length));
474
+ for (const option of entry.options) {
475
+ const value = typeof option.default === "string" ? `'${option.default}'` : Array.isArray(option.default) ? `[${option.default.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(", ")}]` : String(option.default);
476
+ console.log(` ${option.name.padEnd(width)} ${dim("=")} ${green(value)}`);
477
+ }
478
+ console.log(
479
+ `
480
+ ${dim(` Docs: https://ansh.dev/docs/components/${entry.name}`)}
481
+ ${dim(` Add: ansh add ${entry.name}`)}
482
+ `
483
+ );
484
+ }
485
+ function commandAdd(argv) {
486
+ const { values, positionals } = parseArgs({
487
+ args: argv,
488
+ options: {
489
+ as: { type: "string" },
490
+ dir: { type: "string" },
491
+ force: { type: "boolean", default: false },
492
+ "dry-run": { type: "boolean", default: false }
493
+ },
494
+ allowPositionals: true
495
+ });
496
+ const name = positionals[0];
497
+ if (!name) fail("Missing component name.", "ansh add aurora --as HeroBackground");
498
+ try {
499
+ const result = addComponent({
500
+ name,
501
+ as: values.as,
502
+ dir: values.dir,
503
+ force: values.force,
504
+ dryRun: values["dry-run"]
505
+ });
506
+ if (!result.written) {
507
+ console.log(`${dim(`--- ${result.path} ---`)}
508
+ `);
509
+ console.log(result.contents);
510
+ return;
511
+ }
512
+ console.log(`${green("created")} ${result.path}`);
513
+ if (result.missingDependencies.length > 0) {
514
+ const project = detectProject();
515
+ console.log(
516
+ `
517
+ ${yellow("next")} Install the runtime packages:
518
+ ${installCommand(project, result.missingDependencies)}`
519
+ );
520
+ }
521
+ } catch (error) {
522
+ fail(error instanceof Error ? error.message : String(error));
523
+ }
524
+ }
525
+ function commandTheme(argv) {
526
+ const { values, positionals } = parseArgs({
527
+ args: argv,
528
+ options: {
529
+ out: { type: "string" },
530
+ "hue-shift": { type: "string" },
531
+ "chroma-falloff": { type: "string" },
532
+ "with-accent": { type: "boolean", default: false }
533
+ },
534
+ allowPositionals: true
535
+ });
536
+ const color = positionals[0];
537
+ if (!color) fail("Missing brand colour.", "ansh theme '#0275f4' --out theme.css");
538
+ try {
539
+ const report = generateTheme({
540
+ color,
541
+ out: values.out,
542
+ hueShift: values["hue-shift"] ? Number(values["hue-shift"]) : void 0,
543
+ chromaFalloff: values["chroma-falloff"] ? Number(values["chroma-falloff"]) : void 0,
544
+ withAccent: values["with-accent"]
545
+ });
546
+ if (!values.out) {
547
+ console.log(report.css);
548
+ return;
549
+ }
550
+ console.log(`${green("created")} ${values.out}`);
551
+ console.log(`${dim("nearest")} your input is closest to step ${report.nearestStep}`);
552
+ if (report.unsafeOnLight.length > 0) {
553
+ console.log(
554
+ `${yellow("warn")} steps ${report.unsafeOnLight.join(", ")} are below 4.5:1 on white \u2014 use them for fills and borders, not body text`
555
+ );
556
+ }
557
+ if (report.unsafeOnDark.length > 0) {
558
+ console.log(
559
+ `${yellow("warn")} steps ${report.unsafeOnDark.join(", ")} are below 4.5:1 on near-black`
560
+ );
561
+ }
562
+ } catch (error) {
563
+ fail(error instanceof Error ? error.message : String(error));
564
+ }
565
+ }
566
+ function commandPalette(argv) {
567
+ const { values, positionals } = parseArgs({
568
+ args: argv,
569
+ options: {
570
+ count: { type: "string" },
571
+ seed: { type: "string" },
572
+ lightness: { type: "string" },
573
+ saturation: { type: "string" },
574
+ spread: { type: "string" },
575
+ json: { type: "boolean", default: false }
576
+ },
577
+ allowPositionals: true
578
+ });
579
+ try {
580
+ const report = generatePaletteReport({
581
+ colors: positionals.length > 0 ? positionals : void 0,
582
+ count: values.count ? Number(values.count) : void 0,
583
+ seed: values.seed,
584
+ lightness: values.lightness ? Number(values.lightness) : void 0,
585
+ saturation: values.saturation ? Number(values.saturation) : void 0,
586
+ spread: values.spread ? Number(values.spread) : void 0
587
+ });
588
+ if (values.json) {
589
+ console.log(JSON.stringify(report, null, 2));
590
+ return;
591
+ }
592
+ console.log(`
593
+ ${bold("PALETTE")}
594
+ `);
595
+ for (const color of report.colors) {
596
+ console.log(
597
+ ` ${swatch(color.hex)} ${color.hex.padEnd(9)} ${dim(color.oklch.padEnd(28))} ` + dim(
598
+ `L ${color.lightness.toFixed(3)} C ${color.chroma.toFixed(3)} H ${color.hue.toFixed(1)}`
599
+ )
600
+ );
601
+ }
602
+ if (report.gradientSamples.length > 0) {
603
+ console.log(`
604
+ ${bold("GRADIENT")} ${dim("(OKLCH interpolation)")}
605
+ `);
606
+ console.log(" " + report.gradientSamples.map((hex) => swatch(hex)).join(""));
607
+ console.log(" " + dim(report.gradientSamples.join(" ")));
608
+ }
609
+ console.log(`
610
+ ${bold("SNIPPET")}
611
+
612
+ ${green(report.snippet)}
613
+ `);
614
+ } catch (error) {
615
+ fail(error instanceof Error ? error.message : String(error));
616
+ }
617
+ }
618
+ function commandDoctor() {
619
+ const project = detectProject();
620
+ console.log(`
621
+ ${bold("PROJECT")}
622
+ `);
623
+ console.log(` root ${project.root}`);
624
+ console.log(
625
+ ` framework ${project.framework === "unknown" ? yellow("not detected") : green(project.framework)}`
626
+ );
627
+ console.log(` typescript ${project.typescript ? green("yes") : dim("no")}`);
628
+ console.log(` package manager ${project.packageManager}`);
629
+ console.log(` components dir ${project.componentsDir}`);
630
+ console.log(` import alias ${project.alias ?? dim("none")}`);
631
+ console.log(` ansh ${project.installed ? green("installed") : yellow("not installed")}`);
632
+ if (!project.installed) {
633
+ const packages = project.framework === "nuxt" ? ["@ansh-ui/nuxt"] : project.framework === "vue" ? ["@ansh-ui/vue", "@ansh-ui/tokens"] : ["@ansh-ui/react", "@ansh-ui/tokens"];
634
+ console.log(`
635
+ ${yellow("next")} ${installCommand(project, packages)}
636
+ `);
637
+ } else {
638
+ console.log("");
639
+ }
640
+ }
641
+ function swatch(hex) {
642
+ if (!useColor) return "\u25A0";
643
+ const r = parseInt(hex.slice(1, 3), 16);
644
+ const g = parseInt(hex.slice(3, 5), 16);
645
+ const b = parseInt(hex.slice(5, 7), 16);
646
+ return `\x1B[48;2;${r};${g};${b}m \x1B[0m`;
647
+ }
648
+ function main() {
649
+ const [command, ...rest] = process.argv.slice(2);
650
+ switch (command) {
651
+ case "list":
652
+ case "ls":
653
+ return commandList(rest);
654
+ case "info":
655
+ case "show":
656
+ return commandInfo(rest);
657
+ case "add":
658
+ return commandAdd(rest);
659
+ case "theme":
660
+ return commandTheme(rest);
661
+ case "palette":
662
+ return commandPalette(rest);
663
+ case "doctor":
664
+ return commandDoctor();
665
+ case "--version":
666
+ case "-v":
667
+ console.log(VERSION);
668
+ return;
669
+ case void 0:
670
+ case "help":
671
+ case "--help":
672
+ case "-h":
673
+ return help();
674
+ default:
675
+ fail(`Unknown command "${command}".`, "Run `ansh help` to see available commands.");
676
+ }
677
+ }
678
+ main();
679
+ //# sourceMappingURL=cli.js.map
680
+ //# sourceMappingURL=cli.js.map