@nikala-ui/cli 0.0.0-nightly.bb5956a

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 (42) hide show
  1. package/README.md +39 -0
  2. package/dist/commands/add.d.ts +9 -0
  3. package/dist/commands/add.js +89 -0
  4. package/dist/commands/diff.d.ts +7 -0
  5. package/dist/commands/diff.js +87 -0
  6. package/dist/commands/init.d.ts +8 -0
  7. package/dist/commands/init.js +116 -0
  8. package/dist/commands/theme.d.ts +5 -0
  9. package/dist/commands/theme.js +64 -0
  10. package/dist/commands/validate.d.ts +5 -0
  11. package/dist/commands/validate.js +38 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +49 -0
  14. package/dist/types/registry.d.ts +23 -0
  15. package/dist/types/registry.js +1 -0
  16. package/dist/utils/add/write-component-files.d.ts +10 -0
  17. package/dist/utils/add/write-component-files.js +31 -0
  18. package/dist/utils/cn.d.ts +5 -0
  19. package/dist/utils/cn.js +18 -0
  20. package/dist/utils/diff/compare-lines.d.ts +12 -0
  21. package/dist/utils/diff/compare-lines.js +31 -0
  22. package/dist/utils/diff/format-diff.d.ts +8 -0
  23. package/dist/utils/diff/format-diff.js +27 -0
  24. package/dist/utils/file.d.ts +4 -0
  25. package/dist/utils/file.js +23 -0
  26. package/dist/utils/init/configure-alias.d.ts +4 -0
  27. package/dist/utils/init/configure-alias.js +52 -0
  28. package/dist/utils/init/setup-css.d.ts +4 -0
  29. package/dist/utils/init/setup-css.js +57 -0
  30. package/dist/utils/pkg.d.ts +15 -0
  31. package/dist/utils/pkg.js +102 -0
  32. package/dist/utils/registry.d.ts +25 -0
  33. package/dist/utils/registry.js +123 -0
  34. package/dist/utils/theme.d.ts +26 -0
  35. package/dist/utils/theme.js +173 -0
  36. package/dist/utils/validate/check-config.d.ts +11 -0
  37. package/dist/utils/validate/check-config.js +47 -0
  38. package/dist/utils/validate/check-css.d.ts +7 -0
  39. package/dist/utils/validate/check-css.js +50 -0
  40. package/dist/utils/validate/check-deps.d.ts +7 -0
  41. package/dist/utils/validate/check-deps.js +52 -0
  42. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @nikala-ui/cli
2
+
3
+ Command Line Interface (CLI) for **Nikala UI** — a copy-paste component system for **SolidJS** built natively for **Tailwind CSS v4**.
4
+
5
+ Honoring the iconic Georgian painter **Niko Pirosmani (Nikala)**.
6
+
7
+ Official Documentation & Interactive Demos: [nikala.magradze.dev](https://nikala.magradze.dev)
8
+
9
+ ---
10
+
11
+ ## Installation & Usage
12
+
13
+ Execute directly using your preferred package manager:
14
+
15
+ ```bash
16
+ bunx @nikala-ui/cli init
17
+ # or
18
+ npx @nikala-ui/cli init
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Available Commands
24
+
25
+ - `nikala init` — Initializes configuration, `@` path aliases, and Tailwind CSS v4 variables.
26
+ - `nikala add` — Interactive searchable multiselect menu for component installation.
27
+ - `nikala validate` (or `nikala doctor`) — Runs health diagnostics on workspace configuration, packages, and CSS design tokens.
28
+ - `nikala diff` — Inspects line-by-line code differences between local components and upstream registry manifests.
29
+ - `nikala theme set [primary] [base]` — Customizes primary brand accent colors and base gray palettes directly from the terminal.
30
+
31
+ ---
32
+
33
+ ## Documentation
34
+
35
+ For full documentation, component lists, and theming guides, visit the official repository at [github.com/nikala-ui/ui](https://github.com/nikala-ui/ui).
36
+
37
+ ## License
38
+
39
+ [MIT](https://github.com/nikala-ui/ui/blob/main/LICENSE)
@@ -0,0 +1,9 @@
1
+ interface AddOptions {
2
+ overwrite?: boolean;
3
+ all?: boolean;
4
+ }
5
+ /**
6
+ * Command handler to fetch and install Nikala UI components from GitHub remote registry or custom URLs.
7
+ */
8
+ export declare function add(components?: string[], options?: AddOptions): Promise<void>;
9
+ export {};
@@ -0,0 +1,89 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import prompts from "prompts";
4
+ import pc from "picocolors";
5
+ import { readConfig } from "../utils/file.js";
6
+ import { installDependencies } from "../utils/pkg.js";
7
+ import { getRegistryIndex, getRegistryItem, resolveRegistryDependencies, } from "../utils/registry.js";
8
+ import { writeComponentFiles } from "../utils/add/write-component-files.js";
9
+ /**
10
+ * Command handler to fetch and install Nikala UI components from GitHub remote registry or custom URLs.
11
+ */
12
+ export async function add(components = [], options = {}) {
13
+ const cwd = process.cwd();
14
+ const config = await readConfig(cwd);
15
+ if (!config) {
16
+ console.log(pc.red("❌ nikala.config.json not found! Run `nikala init` first."));
17
+ process.exit(1);
18
+ }
19
+ const registryIndex = await getRegistryIndex();
20
+ if (!registryIndex) {
21
+ console.log(pc.red("❌ Failed to load registry index. Ensure network connection or build registry."));
22
+ process.exit(1);
23
+ }
24
+ const isAllRequested = options.all || components.includes("all");
25
+ let requestedComponents = components.filter((c) => c !== "all");
26
+ if (isAllRequested) {
27
+ requestedComponents = registryIndex.map((item) => item.name);
28
+ }
29
+ else if (requestedComponents.length === 0) {
30
+ const response = await prompts({
31
+ type: "autocompleteMultiselect",
32
+ name: "selectedComponents",
33
+ message: "Select components to install (Space to toggle, Enter to confirm)",
34
+ choices: registryIndex.map((item) => ({
35
+ title: item.title,
36
+ description: item.description,
37
+ value: item.name,
38
+ })),
39
+ hint: "- Space to select. Return to submit",
40
+ });
41
+ if (!response.selectedComponents || response.selectedComponents.length === 0) {
42
+ console.log(pc.yellow("\n❌ Installation cancelled. No components selected."));
43
+ return;
44
+ }
45
+ requestedComponents = response.selectedComponents;
46
+ }
47
+ const resolvedTargets = await resolveRegistryDependencies(requestedComponents);
48
+ const componentsDir = path.resolve(cwd, config.alias.components);
49
+ console.log(pc.cyan(`\n🎨 Adding components to project...\n`));
50
+ const requiredNpmDeps = new Set();
51
+ for (const target of resolvedTargets) {
52
+ const item = await getRegistryItem(target);
53
+ if (!item) {
54
+ const availableStr = registryIndex ? registryIndex.map((i) => i.name).join(", ") : "none";
55
+ console.log(pc.red(`❌ "${target}" not found in registry. Available: ${availableStr}`));
56
+ continue;
57
+ }
58
+ if (item.dependencies && item.dependencies.length > 0) {
59
+ for (const dep of item.dependencies) {
60
+ requiredNpmDeps.add(dep);
61
+ }
62
+ }
63
+ await writeComponentFiles(cwd, item, componentsDir, options.overwrite);
64
+ }
65
+ // Inspect user package.json and install missing NPM packages
66
+ const userPkgPath = path.join(cwd, "package.json");
67
+ const missingNpmDeps = [];
68
+ if (await fs.pathExists(userPkgPath)) {
69
+ try {
70
+ const userPkg = await fs.readJson(userPkgPath);
71
+ const installedDeps = { ...userPkg.dependencies, ...userPkg.devDependencies };
72
+ for (const dep of requiredNpmDeps) {
73
+ if (!installedDeps[dep]) {
74
+ missingNpmDeps.push(dep);
75
+ }
76
+ }
77
+ }
78
+ catch {
79
+ missingNpmDeps.push(...Array.from(requiredNpmDeps));
80
+ }
81
+ }
82
+ else {
83
+ missingNpmDeps.push(...Array.from(requiredNpmDeps));
84
+ }
85
+ if (missingNpmDeps.length > 0) {
86
+ await installDependencies(missingNpmDeps, cwd);
87
+ }
88
+ console.log(pc.cyan("\n✅ Components successfully added!"));
89
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Command handler for `nikala diff [component]`.
3
+ * Compares local installed component files against latest registry manifests and displays line differences.
4
+ *
5
+ * @param componentName - Optional specific component name to diff (e.g., "button")
6
+ */
7
+ export declare function diffCommand(componentName?: string): Promise<void>;
@@ -0,0 +1,87 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import prompts from "prompts";
4
+ import pc from "picocolors";
5
+ import { readConfig } from "../utils/file.js";
6
+ import { getRegistryItem } from "../utils/registry.js";
7
+ import { compareLines } from "../utils/diff/compare-lines.js";
8
+ import { printFormattedDiff } from "../utils/diff/format-diff.js";
9
+ /**
10
+ * Command handler for `nikala diff [component]`.
11
+ * Compares local installed component files against latest registry manifests and displays line differences.
12
+ *
13
+ * @param componentName - Optional specific component name to diff (e.g., "button")
14
+ */
15
+ export async function diffCommand(componentName) {
16
+ const cwd = process.cwd();
17
+ const config = await readConfig(cwd);
18
+ if (!config) {
19
+ console.log(pc.red("❌ nikala.config.json not found! Run `nikala init` first."));
20
+ process.exit(1);
21
+ }
22
+ const componentsDir = path.resolve(cwd, config.alias.components);
23
+ if (!(await fs.pathExists(componentsDir))) {
24
+ console.log(pc.yellow(`⚠️ Components directory not found: ${config.alias.components}`));
25
+ return;
26
+ }
27
+ let targets = [];
28
+ if (componentName) {
29
+ targets = [componentName];
30
+ }
31
+ else {
32
+ // Scan all installed component .tsx files in local components directory
33
+ const files = await fs.readdir(componentsDir);
34
+ targets = files
35
+ .filter((f) => f.endsWith(".tsx"))
36
+ .map((f) => path.basename(f, ".tsx"));
37
+ }
38
+ if (targets.length === 0) {
39
+ console.log(pc.yellow("No local components found to compare."));
40
+ return;
41
+ }
42
+ console.log(pc.cyan("\nComparing local components with latest registry manifests...\n"));
43
+ let totalDiffsFound = 0;
44
+ for (const name of targets) {
45
+ const registryItem = await getRegistryItem(name);
46
+ if (!registryItem) {
47
+ continue;
48
+ }
49
+ const targetFile = registryItem.files.find((f) => path.basename(f.path, ".tsx") === name);
50
+ if (!targetFile)
51
+ continue;
52
+ const localFilePath = path.join(componentsDir, `${name}.tsx`);
53
+ if (!(await fs.pathExists(localFilePath)))
54
+ continue;
55
+ const localContent = await fs.readFile(localFilePath, "utf-8");
56
+ const diffs = compareLines(localContent, targetFile.content);
57
+ const hasChanges = diffs.some((d) => d.type !== "same");
58
+ if (hasChanges) {
59
+ totalDiffsFound++;
60
+ console.log(pc.bold(pc.yellow(`Differences found in ${name}.tsx:`)));
61
+ printFormattedDiff(diffs);
62
+ const response = await prompts({
63
+ type: "select",
64
+ name: "action",
65
+ message: `Action for ${name}.tsx:`,
66
+ choices: [
67
+ { title: "Keep local version (skip)", value: "skip" },
68
+ { title: "Overwrite with latest registry version", value: "overwrite" },
69
+ ],
70
+ initial: 0,
71
+ });
72
+ if (response.action === "overwrite") {
73
+ await fs.writeFile(localFilePath, targetFile.content, "utf-8");
74
+ console.log(pc.green(` ✓ Overwrote ${name}.tsx with latest registry version.\n`));
75
+ }
76
+ else {
77
+ console.log(pc.dim(` Kept local version of ${name}.tsx.\n`));
78
+ }
79
+ }
80
+ else {
81
+ console.log(` ✓ ${pc.bold(name)}.tsx is up-to-date with registry`);
82
+ }
83
+ }
84
+ if (totalDiffsFound === 0) {
85
+ console.log(pc.green("\nAll local components are up-to-date with the latest registry manifests!"));
86
+ }
87
+ }
@@ -0,0 +1,8 @@
1
+ interface InitOptions {
2
+ defaults?: boolean;
3
+ }
4
+ /**
5
+ * Initializes Nikala UI workspace configuration and sets up design tokens.
6
+ */
7
+ export declare function init(options: InitOptions): Promise<void>;
8
+ export {};
@@ -0,0 +1,116 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import prompts from "prompts";
4
+ import pc from "picocolors";
5
+ import { cnTemplate } from "../utils/cn.js";
6
+ import { writeConfig } from "../utils/file.js";
7
+ import { installDependencies } from "../utils/pkg.js";
8
+ import { configureAliases } from "../utils/init/configure-alias.js";
9
+ import { setupCssTheme } from "../utils/init/setup-css.js";
10
+ /**
11
+ * Initializes Nikala UI workspace configuration and sets up design tokens.
12
+ */
13
+ export async function init(options) {
14
+ const cwd = process.cwd();
15
+ console.log(pc.cyan("🎨 Initializing Nikala UI...\n"));
16
+ const config = options.defaults
17
+ ? {
18
+ componentsDir: "src/components/ui",
19
+ utilsDir: "src/lib",
20
+ baseColor: "zinc",
21
+ primaryColor: "wine",
22
+ }
23
+ : await prompts([
24
+ {
25
+ type: "text",
26
+ name: "componentsDir",
27
+ message: "Components directory path",
28
+ initial: "src/components/ui",
29
+ },
30
+ {
31
+ type: "text",
32
+ name: "utilsDir",
33
+ message: "Utility functions directory path",
34
+ initial: "src/lib",
35
+ },
36
+ {
37
+ type: "select",
38
+ name: "baseColor",
39
+ message: "Select base gray palette",
40
+ choices: [
41
+ { title: "Zinc (Modern cool gray)", value: "zinc" },
42
+ { title: "Slate (Slightly blue gray)", value: "slate" },
43
+ { title: "Gray (Neutral gray)", value: "gray" },
44
+ { title: "Neutral (Warm gray)", value: "neutral" },
45
+ { title: "Stone (Earth gray)", value: "stone" },
46
+ ],
47
+ initial: 0,
48
+ },
49
+ {
50
+ type: "select",
51
+ name: "primaryColor",
52
+ message: "Select primary brand accent color",
53
+ choices: [
54
+ { title: "Wine (Pirosmani Red)", value: "wine" },
55
+ { title: "Violet (Deep Purple)", value: "violet" },
56
+ { title: "Sky (Vibrant Blue)", value: "sky" },
57
+ { title: "Emerald (Rich Green)", value: "emerald" },
58
+ { title: "Rose (Vivid Pink)", value: "rose" },
59
+ { title: "Amber (Warm Gold)", value: "amber" },
60
+ { title: "Zinc (Monochrome)", value: "zinc" },
61
+ ],
62
+ initial: 0,
63
+ },
64
+ ]);
65
+ if (!config.componentsDir || !config.utilsDir) {
66
+ console.log(pc.yellow("\n❌ Initialization cancelled."));
67
+ process.exit(0);
68
+ }
69
+ const componentsPath = path.resolve(cwd, config.componentsDir);
70
+ const utilsPath = path.resolve(cwd, config.utilsDir);
71
+ await fs.ensureDir(componentsPath);
72
+ await fs.ensureDir(utilsPath);
73
+ // 1. Install required packages
74
+ const userPkgPath = path.join(cwd, "package.json");
75
+ const requiredDeps = ["clsx", "tailwind-merge", "class-variance-authority"];
76
+ if (await fs.pathExists(userPkgPath)) {
77
+ try {
78
+ const userPkg = await fs.readJson(userPkgPath);
79
+ const installed = { ...userPkg.dependencies, ...userPkg.devDependencies };
80
+ if (!installed["tailwindcss"])
81
+ requiredDeps.push("tailwindcss");
82
+ if (!installed["@tailwindcss/vite"])
83
+ requiredDeps.push("@tailwindcss/vite");
84
+ }
85
+ catch {
86
+ requiredDeps.push("tailwindcss", "@tailwindcss/vite");
87
+ }
88
+ }
89
+ else {
90
+ requiredDeps.push("tailwindcss", "@tailwindcss/vite");
91
+ }
92
+ console.log(pc.yellow("\n📦 Installing required runtime & Tailwind CSS dependencies..."));
93
+ await installDependencies(requiredDeps, cwd);
94
+ // 2. Configure path aliases
95
+ await configureAliases(cwd);
96
+ // 3. Generate cn.ts helper
97
+ const cnFilePath = path.join(utilsPath, "cn.ts");
98
+ await fs.writeFile(cnFilePath, cnTemplate, "utf-8");
99
+ console.log(pc.green(`✓ Created ${config.utilsDir}/cn.ts`));
100
+ // 4. Setup CSS theme and entry imports
101
+ const cssPathRelative = await setupCssTheme(cwd, config.baseColor, config.primaryColor);
102
+ // 5. Generate nikala.config.json
103
+ await writeConfig(cwd, {
104
+ $schema: "https://nikala.dev/schema.json",
105
+ style: "default",
106
+ baseColor: config.baseColor || "zinc",
107
+ primaryColor: config.primaryColor || "wine",
108
+ css: cssPathRelative,
109
+ alias: {
110
+ components: config.componentsDir,
111
+ utils: config.utilsDir,
112
+ },
113
+ });
114
+ console.log(pc.green("✓ Created nikala.config.json"));
115
+ console.log(pc.green("\n✅ Nikala UI initialized successfully with custom theme!"));
116
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Command handler for `nikala theme` and `nikala theme set`.
3
+ * Supports both interactive prompts and positional arguments (e.g. `nikala theme set sky slate`).
4
+ */
5
+ export declare function themeCommand(primaryArg?: string, baseArg?: string): Promise<void>;
@@ -0,0 +1,64 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import prompts from "prompts";
4
+ import pc from "picocolors";
5
+ import { readConfig, writeConfig } from "../utils/file.js";
6
+ import { generateThemeCss, BASE_PALETTES, PRIMARY_COLORS } from "../utils/theme.js";
7
+ /**
8
+ * Command handler for `nikala theme` and `nikala theme set`.
9
+ * Supports both interactive prompts and positional arguments (e.g. `nikala theme set sky slate`).
10
+ */
11
+ export async function themeCommand(primaryArg, baseArg) {
12
+ const cwd = process.cwd();
13
+ const config = await readConfig(cwd);
14
+ if (!config) {
15
+ console.log(pc.red("❌ nikala.config.json not found! Run `nikala init` first."));
16
+ process.exit(1);
17
+ }
18
+ let selectedPrimary = primaryArg || config.primaryColor || "wine";
19
+ let selectedBase = baseArg || config.baseColor || "zinc";
20
+ // If no arguments provided, launch interactive prompts
21
+ if (!primaryArg) {
22
+ console.log(pc.cyan("🎨 Customize Nikala UI Theme\n"));
23
+ const response = await prompts([
24
+ {
25
+ type: "select",
26
+ name: "primaryColor",
27
+ message: "Select primary brand accent color",
28
+ choices: Object.keys(PRIMARY_COLORS).map((key) => ({
29
+ title: key.charAt(0).toUpperCase() + key.slice(1),
30
+ value: key,
31
+ })),
32
+ initial: Object.keys(PRIMARY_COLORS).indexOf(selectedPrimary),
33
+ },
34
+ {
35
+ type: "select",
36
+ name: "baseColor",
37
+ message: "Select base gray palette",
38
+ choices: Object.keys(BASE_PALETTES).map((key) => ({
39
+ title: key.charAt(0).toUpperCase() + key.slice(1),
40
+ value: key,
41
+ })),
42
+ initial: Object.keys(BASE_PALETTES).indexOf(selectedBase),
43
+ },
44
+ ]);
45
+ if (!response.primaryColor || !response.baseColor) {
46
+ console.log(pc.yellow("\n❌ Theme update cancelled."));
47
+ return;
48
+ }
49
+ selectedPrimary = response.primaryColor;
50
+ selectedBase = response.baseColor;
51
+ }
52
+ const cssPathRelative = config.css || "src/index.css";
53
+ const cssPath = path.resolve(cwd, cssPathRelative);
54
+ const generatedCss = generateThemeCss(selectedBase, selectedPrimary);
55
+ await fs.ensureDir(path.dirname(cssPath));
56
+ await fs.writeFile(cssPath, generatedCss, "utf-8");
57
+ await writeConfig(cwd, {
58
+ ...config,
59
+ baseColor: selectedBase,
60
+ primaryColor: selectedPrimary,
61
+ });
62
+ console.log(pc.green(`\n✅ Theme updated successfully! Primary: ${pc.bold(selectedPrimary)}, Base: ${pc.bold(selectedBase)}`));
63
+ console.log(pc.white(`Updated ${cssPathRelative}`));
64
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Command handler for `nikala validate` (or `nikala doctor`).
3
+ * Executes diagnostic checks verifying configuration, installed dependencies, and CSS theme tokens.
4
+ */
5
+ export declare function validateCommand(): Promise<void>;
@@ -0,0 +1,38 @@
1
+ import pc from "picocolors";
2
+ import { checkConfig } from "../utils/validate/check-config.js";
3
+ import { checkDeps } from "../utils/validate/check-deps.js";
4
+ import { checkCss } from "../utils/validate/check-css.js";
5
+ /**
6
+ * Command handler for `nikala validate` (or `nikala doctor`).
7
+ * Executes diagnostic checks verifying configuration, installed dependencies, and CSS theme tokens.
8
+ */
9
+ export async function validateCommand() {
10
+ const cwd = process.cwd();
11
+ console.log(pc.cyan("\nDiagnostic Health Check — Nikala UI Workspace\n"));
12
+ const checks = [
13
+ { name: "Project Configuration", fn: () => checkConfig(cwd) },
14
+ { name: "Dependencies & Packages", fn: () => checkDeps(cwd) },
15
+ { name: "CSS Setup & Theme Tokens", fn: () => checkCss(cwd) },
16
+ ];
17
+ let totalPassed = 0;
18
+ for (const check of checks) {
19
+ const result = await check.fn();
20
+ if (result.passed) {
21
+ totalPassed++;
22
+ console.log(` ✓ ${pc.bold(check.name)}: ${result.message}`);
23
+ }
24
+ else {
25
+ console.log(` ✗ ${pc.bold(check.name)}: ${result.message}`);
26
+ if (result.details) {
27
+ console.log(` ↳ ${pc.yellow(result.details)}`);
28
+ }
29
+ }
30
+ }
31
+ console.log("");
32
+ if (totalPassed === checks.length) {
33
+ console.log(pc.green(`Workspace health check passed (${totalPassed}/${checks.length} checks valid).\n`));
34
+ }
35
+ else {
36
+ console.log(pc.yellow(`Workspace health check finished with warnings (${totalPassed}/${checks.length} passed).\n`));
37
+ }
38
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import pc from "picocolors";
4
+ import { init } from "./commands/init.js";
5
+ import { add } from "./commands/add.js";
6
+ import { themeCommand } from "./commands/theme.js";
7
+ import { validateCommand } from "./commands/validate.js";
8
+ import { diffCommand } from "./commands/diff.js";
9
+ console.log(`\n🎨 ${pc.bold(pc.red("Nikala UI"))} ${pc.dim("v0.8.0")} — SolidJS + Tailwind v4 components`);
10
+ console.log(` ${pc.italic(pc.dim("Honoring Niko Pirosmani (Nikala)"))}\n`);
11
+ console.log(` ${pc.dim("Docs:")} ${pc.underline(pc.cyan("https://nikala.magradze.dev"))}\n`);
12
+ const program = new Command();
13
+ program
14
+ .name("nikala")
15
+ .description("Nikala UI — SolidJS + Tailwind v4 components")
16
+ .version("0.8.0");
17
+ program
18
+ .command("init")
19
+ .description("Initialize Nikala UI in your project")
20
+ .option("-d, --defaults", "Skip prompts and use defaults")
21
+ .action(init);
22
+ program
23
+ .command("add [components...]")
24
+ .description("Add components to your project")
25
+ .option("-o, --overwrite", "Overwrite existing files")
26
+ .option("--all", "Add all available components")
27
+ .action(add);
28
+ // Parent theme command
29
+ const themeProg = program
30
+ .command("theme")
31
+ .description("Customize project theme colors and design tokens")
32
+ .action(() => themeCommand());
33
+ // Sub-command: nikala theme set [primary] [base]
34
+ themeProg
35
+ .command("set [primary] [base]")
36
+ .description("Set project primary accent color and base palette")
37
+ .action((primary, base) => themeCommand(primary, base));
38
+ // Diagnostic command
39
+ program
40
+ .command("validate")
41
+ .alias("doctor")
42
+ .description("Run health diagnostics on Nikala UI configuration, packages, and CSS tokens")
43
+ .action(validateCommand);
44
+ // Diff command
45
+ program
46
+ .command("diff [component]")
47
+ .description("Compare local component files against latest registry manifests and view differences")
48
+ .action((component) => diffCommand(component));
49
+ program.parse();
@@ -0,0 +1,23 @@
1
+ export interface RegistryFile {
2
+ path: string;
3
+ content: string;
4
+ type: "registry:ui" | "registry:util" | "registry:hook";
5
+ }
6
+ export interface RegistryItem {
7
+ name: string;
8
+ title: string;
9
+ description: string;
10
+ type: "registry:ui";
11
+ dependencies?: string[];
12
+ registryDependencies?: string[];
13
+ files: RegistryFile[];
14
+ }
15
+ export interface RegistryIndexItem {
16
+ name: string;
17
+ title: string;
18
+ description: string;
19
+ type: "registry:ui";
20
+ dependencies?: string[];
21
+ registryDependencies?: string[];
22
+ }
23
+ export type RegistryIndex = RegistryIndexItem[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { RegistryItem } from "../../types/registry.js";
2
+ /**
3
+ * Writes registry component files to target workspace directories (ui vs providers).
4
+ *
5
+ * @param cwd - Working directory path of the target project
6
+ * @param item - Registry item manifest containing files
7
+ * @param componentsDir - Target components directory path (src/components/ui)
8
+ * @param overwrite - Whether to overwrite existing files
9
+ */
10
+ export declare function writeComponentFiles(cwd: string, item: RegistryItem, componentsDir: string, overwrite?: boolean): Promise<void>;
@@ -0,0 +1,31 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import pc from "picocolors";
4
+ /**
5
+ * Writes registry component files to target workspace directories (ui vs providers).
6
+ *
7
+ * @param cwd - Working directory path of the target project
8
+ * @param item - Registry item manifest containing files
9
+ * @param componentsDir - Target components directory path (src/components/ui)
10
+ * @param overwrite - Whether to overwrite existing files
11
+ */
12
+ export async function writeComponentFiles(cwd, item, componentsDir, overwrite = false) {
13
+ for (const file of item.files) {
14
+ let targetFilePath;
15
+ if (file.path.startsWith("ui/")) {
16
+ const relativePath = file.path.replace(/^ui\//, "");
17
+ targetFilePath = path.join(componentsDir, relativePath);
18
+ }
19
+ else {
20
+ targetFilePath = path.resolve(cwd, "src", file.path);
21
+ }
22
+ const displayPath = path.relative(cwd, targetFilePath);
23
+ if ((await fs.pathExists(targetFilePath)) && !overwrite) {
24
+ console.log(pc.yellow(` ⚠️ ${displayPath} already exists. Use --overwrite to replace.`));
25
+ continue;
26
+ }
27
+ await fs.ensureDir(path.dirname(targetFilePath));
28
+ await fs.writeFile(targetFilePath, file.content, "utf-8");
29
+ console.log(pc.green(` ✓ Added ${displayPath}`));
30
+ }
31
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Raw string template used by `nikala init` to generate the `cn.ts` helper utility
3
+ * in the user's project (`src/lib/cn.ts`).
4
+ */
5
+ export declare const cnTemplate = "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Utility function to merge Tailwind CSS class names without conflicts.\n * Combines clsx for conditional classes and tailwind-merge to resolve Tailwind class collisions.\n *\n * @param inputs - Class names, objects, or arrays\n * @returns Merged class string\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Raw string template used by `nikala init` to generate the `cn.ts` helper utility
3
+ * in the user's project (`src/lib/cn.ts`).
4
+ */
5
+ export const cnTemplate = `import { clsx, type ClassValue } from "clsx";
6
+ import { twMerge } from "tailwind-merge";
7
+
8
+ /**
9
+ * Utility function to merge Tailwind CSS class names without conflicts.
10
+ * Combines clsx for conditional classes and tailwind-merge to resolve Tailwind class collisions.
11
+ *
12
+ * @param inputs - Class names, objects, or arrays
13
+ * @returns Merged class string
14
+ */
15
+ export function cn(...inputs: ClassValue[]) {
16
+ return twMerge(clsx(inputs));
17
+ }
18
+ `;
@@ -0,0 +1,12 @@
1
+ export interface DiffLine {
2
+ type: "add" | "delete" | "same";
3
+ value: string;
4
+ }
5
+ /**
6
+ * Compares two text contents line-by-line and returns an array of structured diff entries.
7
+ * Uses a lightweight matching algorithm to identify added, removed, and identical lines.
8
+ *
9
+ * @param oldText - Original local component file content
10
+ * @param newText - Latest registry component file content
11
+ */
12
+ export declare function compareLines(oldText: string, newText: string): DiffLine[];