@metacells/mcellui-cli 0.1.5 → 0.2.1

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/index.js +466 -227
  2. package/package.json +9 -1
package/dist/index.js CHANGED
@@ -2,10 +2,84 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command as Command9 } from "commander";
5
+ import chalk11 from "chalk";
6
+
7
+ // src/utils/errors.ts
8
+ import chalk from "chalk";
9
+ function handleError(error) {
10
+ process.stderr.write("\n");
11
+ process.stderr.write(chalk.red(`Error: ${error.message}
12
+ `));
13
+ if (error.hint) {
14
+ process.stderr.write("\n");
15
+ process.stderr.write(chalk.dim(error.hint + "\n"));
16
+ }
17
+ process.stderr.write("\n");
18
+ process.exit(error.exitCode ?? 1);
19
+ }
20
+ var errors = {
21
+ /**
22
+ * Error when no valid project is found
23
+ */
24
+ noProject() {
25
+ return handleError({
26
+ message: "Could not find a valid Expo/React Native project",
27
+ hint: "Run this command from a project directory containing package.json",
28
+ code: "NO_PROJECT",
29
+ exitCode: 1
30
+ });
31
+ },
32
+ /**
33
+ * Error when project is not initialized with mcellui
34
+ */
35
+ notInitialized() {
36
+ return handleError({
37
+ message: "Project not initialized",
38
+ hint: "Run: npx mcellui init",
39
+ code: "NOT_INITIALIZED",
40
+ exitCode: 1
41
+ });
42
+ },
43
+ /**
44
+ * Error when component registry fetch fails
45
+ */
46
+ registryFetch(cause) {
47
+ return handleError({
48
+ message: "Failed to fetch component registry",
49
+ hint: cause ? `${cause}
50
+
51
+ Check your internet connection and try again` : "Check your internet connection and try again",
52
+ code: "REGISTRY_FETCH_FAILED",
53
+ exitCode: 1
54
+ });
55
+ },
56
+ /**
57
+ * Error when component is not found in registry
58
+ */
59
+ componentNotFound(name) {
60
+ return handleError({
61
+ message: `Component "${name}" not found in registry`,
62
+ hint: "Run: npx mcellui list",
63
+ code: "COMPONENT_NOT_FOUND",
64
+ exitCode: 1
65
+ });
66
+ },
67
+ /**
68
+ * Error when configuration is invalid
69
+ */
70
+ configInvalid(detail) {
71
+ return handleError({
72
+ message: "Invalid configuration",
73
+ hint: detail || "Check mcellui.config.ts",
74
+ code: "CONFIG_INVALID",
75
+ exitCode: 1
76
+ });
77
+ }
78
+ };
5
79
 
6
80
  // src/commands/init.ts
7
81
  import { Command } from "commander";
8
- import chalk2 from "chalk";
82
+ import chalk3 from "chalk";
9
83
  import ora from "ora";
10
84
  import prompts from "prompts";
11
85
  import fs2 from "fs-extra";
@@ -16,7 +90,7 @@ import fs from "fs-extra";
16
90
  import path from "path";
17
91
  import { fileURLToPath } from "url";
18
92
  import createJiti from "jiti";
19
- import chalk from "chalk";
93
+ import chalk2 from "chalk";
20
94
 
21
95
  // src/utils/config-schema.ts
22
96
  import { z } from "zod";
@@ -115,11 +189,11 @@ function validateConfig(config) {
115
189
  if (result.success) {
116
190
  return { success: true, data: result.data };
117
191
  }
118
- const errors = result.error.issues.map((issue) => {
192
+ const errors2 = result.error.issues.map((issue) => {
119
193
  const path12 = issue.path.join(".");
120
194
  return path12 ? `${path12}: ${issue.message}` : issue.message;
121
195
  });
122
- return { success: false, errors };
196
+ return { success: false, errors: errors2 };
123
197
  }
124
198
  function validateConfigOrThrow(config, configPath) {
125
199
  const result = validateConfig(config);
@@ -129,7 +203,7 @@ function validateConfigOrThrow(config, configPath) {
129
203
  `Invalid configuration in ${configPath}:
130
204
  ${errorList}
131
205
 
132
- See https://nativeui.dev/docs/config for valid options.`
206
+ See https://mcellui.dev/docs/config for valid options.`
133
207
  );
134
208
  }
135
209
  return result.data;
@@ -238,7 +312,7 @@ async function getConfig(projectRoot) {
238
312
  return resolveConfig(validatedConfig);
239
313
  } catch (error) {
240
314
  if (error instanceof Error && error.message.includes("Invalid configuration")) {
241
- console.error(chalk.red(error.message));
315
+ console.error(chalk2.red(error.message));
242
316
  throw error;
243
317
  }
244
318
  throw error;
@@ -259,10 +333,10 @@ async function loadTsConfig(configPath) {
259
333
  return resolveConfig(validatedConfig);
260
334
  } catch (error) {
261
335
  if (error instanceof Error && error.message.includes("Invalid configuration")) {
262
- console.error(chalk.red(error.message));
336
+ console.error(chalk2.red(error.message));
263
337
  throw error;
264
338
  }
265
- console.error(chalk.red("Failed to load config:"), error);
339
+ console.error(chalk2.red("Failed to load config:"), error);
266
340
  return getDefaultConfig();
267
341
  }
268
342
  }
@@ -274,10 +348,10 @@ async function loadJsConfig(configPath) {
274
348
  return resolveConfig(validatedConfig);
275
349
  } catch (error) {
276
350
  if (error instanceof Error && error.message.includes("Invalid configuration")) {
277
- console.error(chalk.red(error.message));
351
+ console.error(chalk2.red(error.message));
278
352
  throw error;
279
353
  }
280
- console.error(chalk.red("Failed to load config:"), error);
354
+ console.error(chalk2.red("Failed to load config:"), error);
281
355
  return getDefaultConfig();
282
356
  }
283
357
  }
@@ -311,27 +385,25 @@ var initCommand = new Command().name("init").description("Initialize mcellui in
311
385
  const cwd = path2.resolve(options.cwd);
312
386
  const projectRoot = await getProjectRoot(cwd);
313
387
  if (!projectRoot) {
314
- console.log(chalk2.red("Could not find a valid Expo/React Native project."));
315
- console.log(chalk2.dim("Make sure you run this command in a project with package.json"));
316
- process.exit(1);
388
+ errors.noProject();
317
389
  }
318
390
  console.log();
319
- console.log(chalk2.bold("Welcome to mcellui!"));
320
- console.log(chalk2.dim("The copy-paste component library for Expo/React Native"));
391
+ console.log(chalk3.bold("Welcome to mcellui!"));
392
+ console.log(chalk3.dim("The copy-paste component library for Expo/React Native"));
321
393
  console.log();
322
394
  const projectType = await detectProjectType(projectRoot);
323
- console.log(chalk2.dim(`Detected: ${projectType}`));
395
+ console.log(chalk3.dim(`Detected: ${projectType}`));
324
396
  const configPath = path2.join(projectRoot, "mcellui.config.ts");
325
397
  const legacyConfigPath = path2.join(projectRoot, "nativeui.config.ts");
326
398
  if (await fs2.pathExists(configPath)) {
327
- console.log(chalk2.yellow("Project already initialized."));
328
- console.log(chalk2.dim(`Config found at: ${configPath}`));
399
+ console.log(chalk3.yellow("Project already initialized."));
400
+ console.log(chalk3.dim(`Config found at: ${configPath}`));
329
401
  return;
330
402
  }
331
403
  if (await fs2.pathExists(legacyConfigPath)) {
332
- console.log(chalk2.yellow("Project already initialized with legacy config."));
333
- console.log(chalk2.dim(`Config found at: ${legacyConfigPath}`));
334
- console.log(chalk2.dim("Consider renaming to mcellui.config.ts"));
404
+ console.log(chalk3.yellow("Project already initialized with legacy config."));
405
+ console.log(chalk3.dim(`Config found at: ${legacyConfigPath}`));
406
+ console.log(chalk3.dim("Consider renaming to mcellui.config.ts"));
335
407
  return;
336
408
  }
337
409
  let config = {
@@ -364,6 +436,9 @@ var initCommand = new Command().name("init").description("Initialize mcellui in
364
436
  ]
365
437
  }
366
438
  ]);
439
+ if (response.componentsPath === void 0) {
440
+ process.exit(0);
441
+ }
367
442
  config = { ...config, ...response };
368
443
  }
369
444
  spinner.start("Creating configuration...");
@@ -402,7 +477,7 @@ export default defineConfig({
402
477
  // },
403
478
 
404
479
  // ============================================
405
- // CLI Configuration (used by npx nativeui add)
480
+ // CLI Configuration (used by npx mcellui add)
406
481
  // ============================================
407
482
 
408
483
  // Path where components will be installed
@@ -472,25 +547,27 @@ export function cn(...inputs: StyleInput[]): Style {
472
547
  );
473
548
  spinner.succeed("Utilities installed");
474
549
  console.log();
475
- console.log(chalk2.green("Success!") + " mcellui initialized.");
550
+ console.log(chalk3.green("Success!") + " mcellui initialized.");
476
551
  console.log();
477
552
  console.log("Next steps:");
478
- console.log(chalk2.dim(" 1."), "Add your first component:");
479
- console.log(chalk2.cyan(" npx mcellui add button"));
553
+ console.log(chalk3.dim(" 1."), "Add your first component:");
554
+ console.log(chalk3.cyan(" npx mcellui add button"));
480
555
  console.log();
481
- console.log(chalk2.dim(" 2."), "Browse available components:");
482
- console.log(chalk2.cyan(" npx mcellui list"));
556
+ console.log(chalk3.dim(" 2."), "Browse available components:");
557
+ console.log(chalk3.cyan(" npx mcellui list"));
483
558
  console.log();
484
559
  } catch (error) {
485
560
  spinner.fail("Failed to initialize");
486
- console.error(error);
487
- process.exit(1);
561
+ handleError({
562
+ message: "Initialization failed",
563
+ hint: error instanceof Error ? error.message : "Check file permissions and try again"
564
+ });
488
565
  }
489
566
  });
490
567
 
491
568
  // src/commands/add.ts
492
569
  import { Command as Command2 } from "commander";
493
- import chalk3 from "chalk";
570
+ import chalk4 from "chalk";
494
571
  import ora2 from "ora";
495
572
  import prompts2 from "prompts";
496
573
  import fs5 from "fs-extra";
@@ -508,6 +585,54 @@ var REGISTRY_URL = hasEnvOverride ? process.env.MCELLUI_REGISTRY_URL || process.
508
585
  function getLocalRegistryPath() {
509
586
  return path3.resolve(__dirname2, "..", "..", "registry");
510
587
  }
588
+ function sleep(ms) {
589
+ return new Promise((resolve) => setTimeout(resolve, ms));
590
+ }
591
+ function isNetworkError(error) {
592
+ if (!error) return false;
593
+ if (error instanceof DOMException && error.name === "AbortError") {
594
+ return true;
595
+ }
596
+ const err = error;
597
+ const errorCodes = ["ECONNRESET", "ENOTFOUND", "ETIMEDOUT", "ECONNREFUSED", "ECONNABORTED"];
598
+ if (err.code && errorCodes.includes(err.code)) {
599
+ return true;
600
+ }
601
+ if (err.message && typeof err.message === "string") {
602
+ const message = err.message.toLowerCase();
603
+ if (errorCodes.some((code) => message.includes(code.toLowerCase()))) {
604
+ return true;
605
+ }
606
+ }
607
+ if (err.cause) {
608
+ return isNetworkError(err.cause);
609
+ }
610
+ return false;
611
+ }
612
+ async function fetchWithRetry(url, maxRetries = 3) {
613
+ let lastError;
614
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
615
+ try {
616
+ const controller = new AbortController();
617
+ const timeoutId = setTimeout(() => controller.abort(), 3e4);
618
+ const response = await fetch(url, {
619
+ signal: controller.signal
620
+ });
621
+ clearTimeout(timeoutId);
622
+ return response;
623
+ } catch (error) {
624
+ lastError = error;
625
+ if (!isNetworkError(error) || attempt === maxRetries) {
626
+ throw error;
627
+ }
628
+ const baseDelay = Math.min(1e3 * Math.pow(2, attempt - 1), 5e3);
629
+ const jitter = Math.random() * 200;
630
+ const delay = baseDelay + jitter;
631
+ await sleep(delay);
632
+ }
633
+ }
634
+ throw lastError;
635
+ }
511
636
  function isLocalMode() {
512
637
  if (REGISTRY_URL) return false;
513
638
  const localPath = getLocalRegistryPath();
@@ -525,18 +650,23 @@ async function getLocalRegistry() {
525
650
  const registry = await fs3.readJson(registryPath);
526
651
  return registry.components;
527
652
  } catch (error) {
528
- console.error("Failed to load local registry:", error);
529
- return [];
653
+ throw new Error(
654
+ "Failed to load local registry: " + (error instanceof Error ? error.message : String(error))
655
+ );
530
656
  }
531
657
  }
532
658
  async function getRemoteRegistry() {
533
659
  try {
534
- const response = await fetch(`${REGISTRY_URL}/registry.json`);
660
+ const response = await fetchWithRetry(`${REGISTRY_URL}/registry.json`);
661
+ if (!response.ok) {
662
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
663
+ }
535
664
  const registry = await response.json();
536
665
  return registry.components;
537
666
  } catch (error) {
538
- console.error("Failed to fetch remote registry:", error);
539
- return [];
667
+ throw new Error(
668
+ "Failed to fetch registry: " + (error instanceof Error ? error.message : String(error))
669
+ );
540
670
  }
541
671
  }
542
672
  async function fetchComponent(name) {
@@ -571,7 +701,12 @@ async function fetchLocalComponent(item) {
571
701
  async function fetchRemoteComponent(item) {
572
702
  const files = await Promise.all(
573
703
  item.files.map(async (filePath) => {
574
- const response = await fetch(`${REGISTRY_URL}/${filePath}`);
704
+ const response = await fetchWithRetry(`${REGISTRY_URL}/${filePath}`);
705
+ if (!response.ok) {
706
+ throw new Error(
707
+ `Failed to fetch ${filePath}: HTTP ${response.status} ${response.statusText}`
708
+ );
709
+ }
575
710
  const content = await response.text();
576
711
  const name = path3.basename(filePath);
577
712
  return { name, content };
@@ -748,41 +883,42 @@ function getInstalledNames(installedFiles) {
748
883
  }
749
884
 
750
885
  // src/commands/add.ts
751
- var addCommand = new Command2().name("add").description("Add a component to your project").argument("[components...]", "Components to add").option("-y, --yes", "Skip confirmation").option("-o, --overwrite", "Overwrite existing files").option("--cwd <path>", "Working directory", process.cwd()).action(async (components, options) => {
886
+ var addCommand = new Command2().name("add").description("Add a component to your project").argument("[components...]", "Components to add").option("-y, --yes", "Skip confirmation").option("-o, --overwrite", "Overwrite existing files").option("--no-barrel", "Skip index.ts barrel file generation").option("--cwd <path>", "Working directory", process.cwd()).action(async (components, options) => {
752
887
  const spinner = ora2();
753
888
  try {
754
889
  const cwd = path5.resolve(options.cwd);
755
890
  const projectRoot = await getProjectRoot(cwd);
756
891
  if (!projectRoot) {
757
- console.log(chalk3.red("Could not find a valid project."));
758
- console.log(chalk3.dim("Run `npx nativeui init` first."));
759
- process.exit(1);
892
+ errors.noProject();
760
893
  }
761
894
  const config = await getConfig(projectRoot);
762
895
  if (!config) {
763
- console.log(chalk3.red("Project not initialized."));
764
- console.log(chalk3.dim("Run `npx nativeui init` first."));
765
- process.exit(1);
896
+ errors.notInitialized();
766
897
  }
767
898
  if (!components.length) {
768
899
  const registry2 = await getRegistry();
769
900
  if (!registry2.length) {
770
- console.log(chalk3.red("No components found in registry."));
771
- process.exit(1);
901
+ handleError({
902
+ message: "No components found in registry",
903
+ hint: "Check your internet connection or try again later"
904
+ });
772
905
  }
773
906
  const { selected } = await prompts2({
774
907
  type: "multiselect",
775
908
  name: "selected",
776
909
  message: "Which components would you like to add?",
777
910
  choices: registry2.map((item) => ({
778
- title: `${item.name} ${chalk3.dim(`(${item.status})`)}`,
911
+ title: `${item.name} ${chalk4.dim(`(${item.status})`)}`,
779
912
  value: item.name,
780
913
  description: item.description
781
914
  })),
782
915
  hint: "- Space to select, Enter to confirm"
783
916
  });
917
+ if (selected === void 0) {
918
+ process.exit(0);
919
+ }
784
920
  if (!selected?.length) {
785
- console.log(chalk3.dim("No components selected."));
921
+ console.log(chalk4.dim("No components selected."));
786
922
  return;
787
923
  }
788
924
  components = selected;
@@ -792,8 +928,11 @@ var addCommand = new Command2().name("add").description("Add a component to your
792
928
  const { resolved, circular } = resolveDependencies(components, registry);
793
929
  if (circular) {
794
930
  spinner.fail("Circular dependency detected");
795
- console.log(chalk3.red(` ${formatCircularError(circular)}`));
796
- process.exit(1);
931
+ handleError({
932
+ message: `Circular dependency: ${formatCircularError(circular)}`,
933
+ hint: "Check component dependencies for cycles",
934
+ code: "CIRCULAR_DEPENDENCY"
935
+ });
797
936
  }
798
937
  spinner.stop();
799
938
  const componentsDir = path5.join(projectRoot, config.componentsPath);
@@ -801,19 +940,19 @@ var addCommand = new Command2().name("add").description("Add a component to your
801
940
  const installedNames = new Set(getInstalledNames(installedFiles));
802
941
  const toInstall = options.overwrite ? resolved : resolved.filter((name) => !installedNames.has(name));
803
942
  if (toInstall.length === 0) {
804
- console.log(chalk3.yellow("All components are already installed."));
805
- console.log(chalk3.dim("Use --overwrite to reinstall."));
943
+ console.log(chalk4.yellow("All components are already installed."));
944
+ console.log(chalk4.dim("Use --overwrite to reinstall."));
806
945
  return;
807
946
  }
808
947
  const requested = new Set(components);
809
948
  const dependencies = toInstall.filter((name) => !requested.has(name));
810
949
  console.log();
811
- console.log(chalk3.bold("Adding components:"));
950
+ console.log(chalk4.bold("Adding components:"));
812
951
  for (const name of toInstall) {
813
952
  if (requested.has(name)) {
814
- console.log(chalk3.dim(` - ${name}`));
953
+ console.log(chalk4.dim(` - ${name}`));
815
954
  } else {
816
- console.log(chalk3.dim(` - ${name} ${chalk3.cyan("(dependency)")}`));
955
+ console.log(chalk4.dim(` - ${name} ${chalk4.cyan("(dependency)")}`));
817
956
  }
818
957
  }
819
958
  console.log();
@@ -824,19 +963,25 @@ var addCommand = new Command2().name("add").description("Add a component to your
824
963
  message: `Add ${dependencies.length} additional dependencies?`,
825
964
  initial: true
826
965
  });
966
+ if (confirm === void 0) {
967
+ process.exit(0);
968
+ }
827
969
  if (!confirm) {
828
- console.log(chalk3.dim("Cancelled."));
970
+ console.log(chalk4.dim("Cancelled."));
829
971
  return;
830
972
  }
831
973
  }
832
974
  const allDependencies = [];
833
975
  const allDevDependencies = [];
976
+ let failCount = 0;
977
+ const registryMap = new Map(registry.map((item) => [item.name, item]));
834
978
  for (const componentName of toInstall) {
835
979
  spinner.start(`Fetching ${componentName}...`);
836
980
  try {
837
981
  const component = await fetchComponent(componentName);
838
982
  if (!component) {
839
983
  spinner.fail(`Component "${componentName}" not found`);
984
+ failCount++;
840
985
  continue;
841
986
  }
842
987
  const targetDir = path5.join(projectRoot, config.componentsPath);
@@ -851,6 +996,14 @@ var addCommand = new Command2().name("add").description("Add a component to your
851
996
  await fs5.writeFile(targetPath, transformedContent);
852
997
  }
853
998
  spinner.succeed(`Added ${componentName}`);
999
+ if (requested.has(componentName)) {
1000
+ const registryItem = registryMap.get(componentName);
1001
+ if (registryItem?.props?.length) {
1002
+ console.log(chalk4.dim(` Props: ${registryItem.props.join(", ")}`));
1003
+ }
1004
+ const pascalName = toPascalCase(componentName);
1005
+ console.log(chalk4.dim(` Import: import { ${pascalName} } from '${config.alias}/components/${componentName}';`));
1006
+ }
854
1007
  if (component.dependencies?.length) {
855
1008
  allDependencies.push(...component.dependencies);
856
1009
  }
@@ -859,36 +1012,70 @@ var addCommand = new Command2().name("add").description("Add a component to your
859
1012
  }
860
1013
  } catch (error) {
861
1014
  spinner.fail(`Failed to add ${componentName}`);
862
- console.error(chalk3.dim(String(error)));
1015
+ console.error(chalk4.dim(String(error)));
1016
+ failCount++;
863
1017
  }
864
1018
  }
1019
+ if (failCount > 0) {
1020
+ process.exit(1);
1021
+ }
1022
+ if (options.barrel !== false) {
1023
+ const targetDir = path5.join(projectRoot, config.componentsPath);
1024
+ await generateBarrelFile(targetDir);
1025
+ }
865
1026
  const uniqueDeps = [...new Set(allDependencies)];
866
1027
  const uniqueDevDeps = [...new Set(allDevDependencies)];
867
1028
  if (uniqueDeps.length || uniqueDevDeps.length) {
868
1029
  console.log();
869
- console.log(chalk3.bold("Install dependencies:"));
1030
+ console.log(chalk4.bold("Install dependencies:"));
870
1031
  if (uniqueDeps.length) {
871
- console.log(chalk3.cyan(` npx expo install ${uniqueDeps.join(" ")}`));
1032
+ console.log(chalk4.cyan(` npx expo install ${uniqueDeps.join(" ")}`));
872
1033
  }
873
1034
  if (uniqueDevDeps.length) {
874
- console.log(chalk3.cyan(` npm install -D ${uniqueDevDeps.join(" ")}`));
1035
+ console.log(chalk4.cyan(` npm install -D ${uniqueDevDeps.join(" ")}`));
875
1036
  }
876
1037
  }
877
1038
  console.log();
878
- console.log(chalk3.green("Done!"));
1039
+ console.log(chalk4.green("Done!"));
879
1040
  } catch (error) {
880
1041
  spinner.fail("Failed");
881
- console.error(error);
882
- process.exit(1);
1042
+ handleError({
1043
+ message: "Failed to add components",
1044
+ hint: error instanceof Error ? error.message : "Check your network connection and try again"
1045
+ });
883
1046
  }
884
1047
  });
1048
+ async function generateBarrelFile(componentsDir) {
1049
+ if (!await fs5.pathExists(componentsDir)) {
1050
+ return;
1051
+ }
1052
+ const files = await fs5.readdir(componentsDir);
1053
+ const componentFiles = files.filter((file) => (file.endsWith(".tsx") || file.endsWith(".ts")) && file !== "index.ts" && file !== "index.tsx").sort();
1054
+ if (componentFiles.length === 0) {
1055
+ return;
1056
+ }
1057
+ const exports = componentFiles.map((file) => {
1058
+ const name = file.replace(/\.tsx?$/, "");
1059
+ return `export * from './${name}';`;
1060
+ }).join("\n");
1061
+ const content = `// Auto-generated barrel file - do not edit manually
1062
+ // Re-run \`npx mcellui add\` to regenerate
1063
+
1064
+ ${exports}
1065
+ `;
1066
+ const indexPath = path5.join(componentsDir, "index.ts");
1067
+ await fs5.writeFile(indexPath, content);
1068
+ }
1069
+ function toPascalCase(str) {
1070
+ return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1071
+ }
885
1072
 
886
1073
  // src/commands/list.ts
887
1074
  import { Command as Command3 } from "commander";
888
- import chalk4 from "chalk";
1075
+ import chalk5 from "chalk";
889
1076
  import ora3 from "ora";
890
1077
  import path6 from "path";
891
- var listCommand = new Command3().name("list").description("List available or installed components").option("-c, --category <category>", "Filter by category").option("-i, --installed", "Show installed components and their sync status").option("--cwd <path>", "Working directory", process.cwd()).action(async (options) => {
1078
+ var listCommand = new Command3().name("list").description("List available or installed components").option("-c, --category <category>", "Filter by category").option("-i, --installed", "Show installed components and their sync status").option("--json", "Output as JSON").option("--cwd <path>", "Working directory", process.cwd()).action(async (options) => {
892
1079
  if (options.installed) {
893
1080
  await listInstalledComponents(options);
894
1081
  } else {
@@ -900,8 +1087,13 @@ async function listAvailableComponents(options) {
900
1087
  try {
901
1088
  const registry = await getRegistry();
902
1089
  spinner.stop();
1090
+ if (options.json) {
1091
+ const items = options.category ? registry.filter((item) => (item.category || "Other").toLowerCase() === options.category.toLowerCase()) : registry;
1092
+ console.log(JSON.stringify(items, null, 2));
1093
+ return;
1094
+ }
903
1095
  console.log();
904
- console.log(chalk4.bold("Available Components"));
1096
+ console.log(chalk5.bold("Available Components"));
905
1097
  console.log();
906
1098
  const categories = /* @__PURE__ */ new Map();
907
1099
  for (const item of registry) {
@@ -915,20 +1107,22 @@ async function listAvailableComponents(options) {
915
1107
  categories.get(category).push(item);
916
1108
  }
917
1109
  for (const [category, items] of categories) {
918
- console.log(chalk4.cyan.bold(`${category}`));
1110
+ console.log(chalk5.cyan.bold(`${category}`));
919
1111
  for (const item of items) {
920
- const status = item.status === "stable" ? "" : chalk4.yellow(` [${item.status}]`);
921
- console.log(` ${chalk4.white(item.name)}${status}`);
922
- console.log(chalk4.dim(` ${item.description}`));
1112
+ const status = item.status === "stable" ? "" : chalk5.yellow(` [${item.status}]`);
1113
+ console.log(` ${chalk5.white(item.name)}${status}`);
1114
+ console.log(chalk5.dim(` ${item.description}`));
923
1115
  }
924
1116
  console.log();
925
1117
  }
926
- console.log(chalk4.dim("Add a component: npx mcellui add <component>"));
1118
+ console.log(chalk5.dim("Add a component: npx mcellui add <component>"));
927
1119
  console.log();
928
1120
  } catch (error) {
929
1121
  spinner.fail("Failed to fetch components");
930
- console.error(error);
931
- process.exit(1);
1122
+ handleError({
1123
+ message: "Failed to fetch components",
1124
+ hint: error instanceof Error ? error.message : "Check your network connection and try again"
1125
+ });
932
1126
  }
933
1127
  }
934
1128
  async function listInstalledComponents(options) {
@@ -937,22 +1131,23 @@ async function listInstalledComponents(options) {
937
1131
  const cwd = path6.resolve(options.cwd || process.cwd());
938
1132
  const projectRoot = await getProjectRoot(cwd);
939
1133
  if (!projectRoot) {
940
- console.log(chalk4.red("Could not find a valid project."));
941
- console.log(chalk4.dim("Run `npx mcellui init` first."));
942
- process.exit(1);
1134
+ errors.noProject();
943
1135
  }
944
1136
  const config = await getConfig(projectRoot);
945
1137
  if (!config) {
946
- console.log(chalk4.red("Project not initialized."));
947
- console.log(chalk4.dim("Run `npx mcellui init` first."));
948
- process.exit(1);
1138
+ errors.notInitialized();
949
1139
  }
950
1140
  spinner.start("Scanning installed components...");
951
1141
  const componentsDir = path6.join(projectRoot, config.componentsPath);
952
1142
  const installedFiles = await getInstalledFiles(componentsDir);
953
1143
  if (installedFiles.length === 0) {
1144
+ spinner.stop();
1145
+ if (options.json) {
1146
+ console.log(JSON.stringify([], null, 2));
1147
+ return;
1148
+ }
954
1149
  spinner.info("No components installed yet.");
955
- console.log(chalk4.dim("\nAdd components with: npx mcellui add <component>"));
1150
+ console.log(chalk5.dim("\nAdd components with: npx mcellui add <component>"));
956
1151
  return;
957
1152
  }
958
1153
  spinner.text = "Fetching registry...";
@@ -960,13 +1155,17 @@ async function listInstalledComponents(options) {
960
1155
  spinner.text = "Comparing components...";
961
1156
  const installed = await getInstallStatus(installedFiles, registry, config);
962
1157
  spinner.stop();
1158
+ if (options.json) {
1159
+ console.log(JSON.stringify(installed, null, 2));
1160
+ return;
1161
+ }
963
1162
  const identical = installed.filter((c) => c.status === "identical");
964
1163
  const modified = installed.filter((c) => c.status === "modified");
965
1164
  const localOnly = installed.filter((c) => c.status === "local-only");
966
1165
  const installedNames = new Set(installed.map((c) => c.name));
967
1166
  const notInstalled = registry.filter((item) => !installedNames.has(item.name));
968
1167
  console.log();
969
- console.log(chalk4.bold(`Installed Components (${installed.length})`));
1168
+ console.log(chalk5.bold(`Installed Components (${installed.length})`));
970
1169
  console.log();
971
1170
  const categories = /* @__PURE__ */ new Map();
972
1171
  for (const comp of installed) {
@@ -978,56 +1177,58 @@ async function listInstalledComponents(options) {
978
1177
  categories.get(category).push(comp);
979
1178
  }
980
1179
  for (const [category, components] of categories) {
981
- console.log(chalk4.cyan.bold(category));
1180
+ console.log(chalk5.cyan.bold(category));
982
1181
  for (const comp of components) {
983
1182
  let statusIcon;
984
1183
  let statusText;
985
1184
  switch (comp.status) {
986
1185
  case "identical":
987
- statusIcon = chalk4.green("\u2713");
988
- statusText = chalk4.dim("(up to date)");
1186
+ statusIcon = chalk5.green("\u2713");
1187
+ statusText = chalk5.dim("(up to date)");
989
1188
  break;
990
1189
  case "modified":
991
- statusIcon = chalk4.yellow("\u26A0");
992
- statusText = chalk4.yellow("(modified locally)");
1190
+ statusIcon = chalk5.yellow("\u26A0");
1191
+ statusText = chalk5.yellow("(modified locally)");
993
1192
  break;
994
1193
  case "local-only":
995
- statusIcon = chalk4.blue("?");
996
- statusText = chalk4.dim("(custom component)");
1194
+ statusIcon = chalk5.blue("?");
1195
+ statusText = chalk5.dim("(custom component)");
997
1196
  break;
998
1197
  }
999
- console.log(` ${statusIcon} ${chalk4.white(comp.name)} ${statusText}`);
1198
+ console.log(` ${statusIcon} ${chalk5.white(comp.name)} ${statusText}`);
1000
1199
  }
1001
1200
  console.log();
1002
1201
  }
1003
1202
  if (notInstalled.length > 0) {
1004
- console.log(chalk4.dim("Not Installed"));
1203
+ console.log(chalk5.dim("Not Installed"));
1005
1204
  const notInstalledNames = notInstalled.map((c) => c.name).slice(0, 10);
1006
1205
  const remaining = notInstalled.length - 10;
1007
- console.log(chalk4.dim(` ${notInstalledNames.join(", ")}${remaining > 0 ? `, ... +${remaining} more` : ""}`));
1206
+ console.log(chalk5.dim(` ${notInstalledNames.join(", ")}${remaining > 0 ? `, ... +${remaining} more` : ""}`));
1008
1207
  console.log();
1009
1208
  }
1010
- console.log(chalk4.dim("\u2500".repeat(50)));
1209
+ console.log(chalk5.dim("\u2500".repeat(50)));
1011
1210
  const parts = [];
1012
- if (identical.length > 0) parts.push(chalk4.green(`${identical.length} up to date`));
1013
- if (modified.length > 0) parts.push(chalk4.yellow(`${modified.length} modified`));
1014
- if (localOnly.length > 0) parts.push(chalk4.blue(`${localOnly.length} custom`));
1211
+ if (identical.length > 0) parts.push(chalk5.green(`${identical.length} up to date`));
1212
+ if (modified.length > 0) parts.push(chalk5.yellow(`${modified.length} modified`));
1213
+ if (localOnly.length > 0) parts.push(chalk5.blue(`${localOnly.length} custom`));
1015
1214
  console.log(parts.join(" \u2022 "));
1016
1215
  if (modified.length > 0) {
1017
1216
  console.log();
1018
- console.log(chalk4.dim("Sync modified components:"));
1019
- console.log(chalk4.cyan(` npx mcellui diff`));
1217
+ console.log(chalk5.dim("Sync modified components:"));
1218
+ console.log(chalk5.cyan(` npx mcellui diff`));
1020
1219
  }
1021
1220
  } catch (error) {
1022
1221
  spinner.fail("Failed to list installed components");
1023
- console.error(error);
1024
- process.exit(1);
1222
+ handleError({
1223
+ message: "Failed to list installed components",
1224
+ hint: error instanceof Error ? error.message : "Check your network connection and try again"
1225
+ });
1025
1226
  }
1026
1227
  }
1027
1228
 
1028
1229
  // src/commands/doctor.ts
1029
1230
  import { Command as Command4 } from "commander";
1030
- import chalk5 from "chalk";
1231
+ import chalk6 from "chalk";
1031
1232
  import fs6 from "fs-extra";
1032
1233
  import path7 from "path";
1033
1234
  var REQUIRED_PEER_DEPS = [
@@ -1408,43 +1609,43 @@ async function checkExpoGo(projectRoot) {
1408
1609
  }
1409
1610
  function printReport(report) {
1410
1611
  console.log();
1411
- console.log(chalk5.bold("mcellui Doctor"));
1412
- console.log(chalk5.dim("Checking your project setup..."));
1612
+ console.log(chalk6.bold("mcellui Doctor"));
1613
+ console.log(chalk6.dim("Checking your project setup..."));
1413
1614
  console.log();
1414
- console.log(chalk5.dim("Project:"), report.projectRoot);
1415
- console.log(chalk5.dim("Type:"), report.projectType);
1416
- console.log(chalk5.dim("Package Manager:"), report.packageManager);
1615
+ console.log(chalk6.dim("Project:"), report.projectRoot);
1616
+ console.log(chalk6.dim("Type:"), report.projectType);
1617
+ console.log(chalk6.dim("Package Manager:"), report.packageManager);
1417
1618
  console.log();
1418
- console.log(chalk5.bold("Checks"));
1619
+ console.log(chalk6.bold("Checks"));
1419
1620
  console.log();
1420
1621
  for (const check of report.checks) {
1421
- const icon = check.status === "pass" ? chalk5.green("\u2713") : check.status === "warn" ? chalk5.yellow("!") : chalk5.red("\u2717");
1422
- const statusColor = check.status === "pass" ? chalk5.green : check.status === "warn" ? chalk5.yellow : chalk5.red;
1423
- console.log(` ${icon} ${chalk5.white(check.name)}`);
1622
+ const icon = check.status === "pass" ? chalk6.green("\u2713") : check.status === "warn" ? chalk6.yellow("!") : chalk6.red("\u2717");
1623
+ const statusColor = check.status === "pass" ? chalk6.green : check.status === "warn" ? chalk6.yellow : chalk6.red;
1624
+ console.log(` ${icon} ${chalk6.white(check.name)}`);
1424
1625
  console.log(` ${statusColor(check.message)}`);
1425
1626
  if (check.fix && check.status !== "pass") {
1426
- console.log(chalk5.dim(` Fix: ${check.fix}`));
1627
+ console.log(chalk6.dim(` Fix: ${check.fix}`));
1427
1628
  }
1428
1629
  }
1429
1630
  console.log();
1430
1631
  const summaryParts = [];
1431
1632
  if (report.passed > 0) {
1432
- summaryParts.push(chalk5.green(`${report.passed} passed`));
1633
+ summaryParts.push(chalk6.green(`${report.passed} passed`));
1433
1634
  }
1434
1635
  if (report.warnings > 0) {
1435
- summaryParts.push(chalk5.yellow(`${report.warnings} warnings`));
1636
+ summaryParts.push(chalk6.yellow(`${report.warnings} warnings`));
1436
1637
  }
1437
1638
  if (report.failed > 0) {
1438
- summaryParts.push(chalk5.red(`${report.failed} failed`));
1639
+ summaryParts.push(chalk6.red(`${report.failed} failed`));
1439
1640
  }
1440
- console.log(chalk5.bold("Summary:"), summaryParts.join(", "));
1641
+ console.log(chalk6.bold("Summary:"), summaryParts.join(", "));
1441
1642
  console.log();
1442
1643
  if (report.failed > 0) {
1443
- console.log(chalk5.red("Some checks failed. Please fix the issues above."));
1644
+ console.log(chalk6.red("Some checks failed. Please fix the issues above."));
1444
1645
  } else if (report.warnings > 0) {
1445
- console.log(chalk5.yellow("Your project has some warnings but should work."));
1646
+ console.log(chalk6.yellow("Your project has some warnings but should work."));
1446
1647
  } else {
1447
- console.log(chalk5.green("Your project is properly configured!"));
1648
+ console.log(chalk6.green("Your project is properly configured!"));
1448
1649
  }
1449
1650
  console.log();
1450
1651
  }
@@ -1453,9 +1654,7 @@ var doctorCommand = new Command4().name("doctor").description("Check project set
1453
1654
  const cwd = path7.resolve(options.cwd);
1454
1655
  const projectRoot = await getProjectRoot(cwd);
1455
1656
  if (!projectRoot) {
1456
- console.log(chalk5.red("Could not find a valid project."));
1457
- console.log(chalk5.dim("Make sure you run this command in a project directory."));
1458
- process.exit(1);
1657
+ errors.noProject();
1459
1658
  }
1460
1659
  const projectType = await detectProjectType(projectRoot);
1461
1660
  const packageManager = await detectPackageManager(projectRoot);
@@ -1493,14 +1692,16 @@ var doctorCommand = new Command4().name("doctor").description("Check project set
1493
1692
  process.exit(1);
1494
1693
  }
1495
1694
  } catch (error) {
1496
- console.error(chalk5.red("Doctor check failed:"), error);
1497
- process.exit(1);
1695
+ handleError({
1696
+ message: "Doctor check failed",
1697
+ hint: error instanceof Error ? error.message : "Run again with --json for details"
1698
+ });
1498
1699
  }
1499
1700
  });
1500
1701
 
1501
1702
  // src/commands/diff.ts
1502
1703
  import { Command as Command5 } from "commander";
1503
- import chalk6 from "chalk";
1704
+ import chalk7 from "chalk";
1504
1705
  import ora4 from "ora";
1505
1706
  import fs7 from "fs-extra";
1506
1707
  import path8 from "path";
@@ -1511,22 +1712,18 @@ var diffCommand = new Command5().name("diff").description("Compare locally insta
1511
1712
  const cwd = path8.resolve(options.cwd);
1512
1713
  const projectRoot = await getProjectRoot(cwd);
1513
1714
  if (!projectRoot) {
1514
- console.log(chalk6.red("Could not find a valid project."));
1515
- console.log(chalk6.dim("Run `npx mcellui init` first."));
1516
- process.exit(1);
1715
+ errors.noProject();
1517
1716
  }
1518
1717
  const config = await getConfig(projectRoot);
1519
1718
  if (!config) {
1520
- console.log(chalk6.red("Project not initialized."));
1521
- console.log(chalk6.dim("Run `npx mcellui init` first."));
1522
- process.exit(1);
1719
+ errors.notInitialized();
1523
1720
  }
1524
1721
  spinner.start("Scanning installed components...");
1525
1722
  const componentsDir = path8.join(projectRoot, config.componentsPath);
1526
1723
  const installedFiles = await getInstalledFiles(componentsDir);
1527
1724
  if (installedFiles.length === 0) {
1528
1725
  spinner.info("No components installed yet.");
1529
- console.log(chalk6.dim("\nAdd components with: npx mcellui add <component>"));
1726
+ console.log(chalk7.dim("\nAdd components with: npx mcellui add <component>"));
1530
1727
  return;
1531
1728
  }
1532
1729
  spinner.text = "Fetching registry...";
@@ -1548,8 +1745,11 @@ var diffCommand = new Command5().name("diff").description("Compare locally insta
1548
1745
  return componentFileNames.includes(fileName);
1549
1746
  });
1550
1747
  if (filesToCompare.length === 0) {
1551
- spinner.fail(`None of the specified components found: ${components.join(", ")}`);
1552
- process.exit(1);
1748
+ spinner.stop();
1749
+ handleError({
1750
+ message: "None of the specified components are installed",
1751
+ hint: "Check component names: npx mcellui list --installed"
1752
+ });
1553
1753
  }
1554
1754
  }
1555
1755
  spinner.text = "Comparing components...";
@@ -1633,35 +1833,37 @@ var diffCommand = new Command5().name("diff").description("Compare locally insta
1633
1833
  const hasChanges = results.some((r) => r.status === "modified");
1634
1834
  process.exit(hasChanges ? 1 : 0);
1635
1835
  } catch (error) {
1636
- spinner.fail("Failed to diff components");
1637
- console.error(error);
1638
- process.exit(1);
1836
+ spinner.stop();
1837
+ handleError({
1838
+ message: "Failed to diff components",
1839
+ hint: error instanceof Error ? error.message : "Check your network connection"
1840
+ });
1639
1841
  }
1640
1842
  });
1641
1843
  function printResults(results, listOnly) {
1642
1844
  const identical = results.filter((r) => r.status === "identical");
1643
1845
  const modified = results.filter((r) => r.status === "modified");
1644
1846
  const localOnly = results.filter((r) => r.status === "local-only");
1645
- const errors = results.filter((r) => r.status === "error");
1847
+ const errors2 = results.filter((r) => r.status === "error");
1646
1848
  console.log();
1647
- console.log(chalk6.bold("Comparing components..."));
1849
+ console.log(chalk7.bold("Comparing components..."));
1648
1850
  console.log();
1649
1851
  for (const result of results) {
1650
1852
  switch (result.status) {
1651
1853
  case "identical":
1652
- console.log(`${chalk6.green("\u2713")} ${result.fileName} ${chalk6.dim("(identical)")}`);
1854
+ console.log(`${chalk7.green("\u2713")} ${result.fileName} ${chalk7.dim("(identical)")}`);
1653
1855
  break;
1654
1856
  case "modified":
1655
- console.log(`${chalk6.red("\u2717")} ${result.fileName} ${chalk6.yellow("(modified)")}`);
1857
+ console.log(`${chalk7.red("\u2717")} ${result.fileName} ${chalk7.yellow("(modified)")}`);
1656
1858
  if (!listOnly && result.diff) {
1657
1859
  printColoredDiff(result.diff);
1658
1860
  }
1659
1861
  break;
1660
1862
  case "local-only":
1661
- console.log(`${chalk6.yellow("\u26A0")} ${result.fileName} ${chalk6.dim("(not in registry)")}`);
1863
+ console.log(`${chalk7.yellow("\u26A0")} ${result.fileName} ${chalk7.dim("(not in registry)")}`);
1662
1864
  break;
1663
1865
  case "error":
1664
- console.log(`${chalk6.red("!")} ${result.fileName} ${chalk6.red(`(error: ${result.error})`)}`);
1866
+ console.log(`${chalk7.red("!")} ${result.fileName} ${chalk7.red(`(error: ${result.error})`)}`);
1665
1867
  break;
1666
1868
  }
1667
1869
  }
@@ -1670,12 +1872,12 @@ function printResults(results, listOnly) {
1670
1872
  if (identical.length > 0) parts.push(`${identical.length} identical`);
1671
1873
  if (modified.length > 0) parts.push(`${modified.length} modified`);
1672
1874
  if (localOnly.length > 0) parts.push(`${localOnly.length} custom`);
1673
- if (errors.length > 0) parts.push(`${errors.length} errors`);
1674
- console.log(chalk6.dim(`Summary: ${parts.join(", ")}`));
1875
+ if (errors2.length > 0) parts.push(`${errors2.length} errors`);
1876
+ console.log(chalk7.dim(`Summary: ${parts.join(", ")}`));
1675
1877
  if (modified.length > 0) {
1676
1878
  console.log();
1677
- console.log(chalk6.dim("Update modified components with:"));
1678
- console.log(chalk6.cyan(` npx mcellui add ${modified.map((m) => m.name).join(" ")} --overwrite`));
1879
+ console.log(chalk7.dim("Update modified components with:"));
1880
+ console.log(chalk7.cyan(` npx mcellui add ${modified.map((m) => m.name).join(" ")} --overwrite`));
1679
1881
  }
1680
1882
  }
1681
1883
  function printColoredDiff(diffOutput) {
@@ -1683,20 +1885,20 @@ function printColoredDiff(diffOutput) {
1683
1885
  const contentLines = lines.slice(4);
1684
1886
  for (const line of contentLines) {
1685
1887
  if (line.startsWith("+") && !line.startsWith("+++")) {
1686
- console.log(chalk6.green(` ${line}`));
1888
+ console.log(chalk7.green(` ${line}`));
1687
1889
  } else if (line.startsWith("-") && !line.startsWith("---")) {
1688
- console.log(chalk6.red(` ${line}`));
1890
+ console.log(chalk7.red(` ${line}`));
1689
1891
  } else if (line.startsWith("@@")) {
1690
- console.log(chalk6.cyan(` ${line}`));
1892
+ console.log(chalk7.cyan(` ${line}`));
1691
1893
  } else if (line.trim()) {
1692
- console.log(chalk6.dim(` ${line}`));
1894
+ console.log(chalk7.dim(` ${line}`));
1693
1895
  }
1694
1896
  }
1695
1897
  }
1696
1898
 
1697
1899
  // src/commands/update.ts
1698
1900
  import { Command as Command6 } from "commander";
1699
- import chalk7 from "chalk";
1901
+ import chalk8 from "chalk";
1700
1902
  import ora5 from "ora";
1701
1903
  import prompts3 from "prompts";
1702
1904
  import fs8 from "fs-extra";
@@ -1707,22 +1909,18 @@ var updateCommand = new Command6().name("update").description("Update installed
1707
1909
  const cwd = path9.resolve(options.cwd);
1708
1910
  const projectRoot = await getProjectRoot(cwd);
1709
1911
  if (!projectRoot) {
1710
- console.log(chalk7.red("Could not find a valid project."));
1711
- console.log(chalk7.dim("Run `npx nativeui init` first."));
1712
- process.exit(1);
1912
+ errors.noProject();
1713
1913
  }
1714
1914
  const config = await getConfig(projectRoot);
1715
1915
  if (!config) {
1716
- console.log(chalk7.red("Project not initialized."));
1717
- console.log(chalk7.dim("Run `npx nativeui init` first."));
1718
- process.exit(1);
1916
+ errors.notInitialized();
1719
1917
  }
1720
1918
  spinner.start("Checking for updates...");
1721
1919
  const componentsDir = path9.join(projectRoot, config.componentsPath);
1722
1920
  const diffs = await getComponentDiffs(componentsDir, config);
1723
1921
  if (diffs.length === 0) {
1724
1922
  spinner.info("No components installed yet.");
1725
- console.log(chalk7.dim("\nAdd components with: npx nativeui add <component>"));
1923
+ console.log(chalk8.dim("\nAdd components with: npx mcellui add <component>"));
1726
1924
  return;
1727
1925
  }
1728
1926
  spinner.stop();
@@ -1731,7 +1929,7 @@ var updateCommand = new Command6().name("update").description("Update installed
1731
1929
  toUpdate = diffs.filter((d) => components.includes(d.name));
1732
1930
  const notFound = components.filter((c) => !diffs.some((d) => d.name === c));
1733
1931
  if (notFound.length > 0) {
1734
- console.log(chalk7.yellow(`Components not found: ${notFound.join(", ")}`));
1932
+ console.log(chalk8.yellow(`Components not found: ${notFound.join(", ")}`));
1735
1933
  }
1736
1934
  } else if (options.all) {
1737
1935
  toUpdate = diffs;
@@ -1739,19 +1937,19 @@ var updateCommand = new Command6().name("update").description("Update installed
1739
1937
  toUpdate = diffs.filter((d) => d.hasUpdate);
1740
1938
  }
1741
1939
  if (toUpdate.length === 0) {
1742
- console.log(chalk7.green("\n\u2713 All components are up to date!"));
1940
+ console.log(chalk8.green("\n\u2713 All components are up to date!"));
1743
1941
  return;
1744
1942
  }
1745
1943
  console.log();
1746
- console.log(chalk7.bold(`Components to update (${toUpdate.length}):`));
1944
+ console.log(chalk8.bold(`Components to update (${toUpdate.length}):`));
1747
1945
  for (const comp of toUpdate) {
1748
- const status = comp.hasUpdate ? chalk7.yellow("\u25CF") : chalk7.green("\u25CF");
1749
- const label = comp.hasUpdate ? chalk7.dim("(update available)") : chalk7.dim("(re-sync)");
1946
+ const status = comp.hasUpdate ? chalk8.yellow("\u25CF") : chalk8.green("\u25CF");
1947
+ const label = comp.hasUpdate ? chalk8.dim("(update available)") : chalk8.dim("(re-sync)");
1750
1948
  console.log(` ${status} ${comp.name} ${label}`);
1751
1949
  }
1752
1950
  console.log();
1753
1951
  if (options.dryRun) {
1754
- console.log(chalk7.dim("Dry run mode - no changes made."));
1952
+ console.log(chalk8.dim("Dry run mode - no changes made."));
1755
1953
  return;
1756
1954
  }
1757
1955
  if (!options.yes) {
@@ -1761,8 +1959,11 @@ var updateCommand = new Command6().name("update").description("Update installed
1761
1959
  message: `Update ${toUpdate.length} component${toUpdate.length !== 1 ? "s" : ""}?`,
1762
1960
  initial: true
1763
1961
  });
1962
+ if (confirm === void 0) {
1963
+ process.exit(0);
1964
+ }
1764
1965
  if (!confirm) {
1765
- console.log(chalk7.dim("Cancelled."));
1966
+ console.log(chalk8.dim("Cancelled."));
1766
1967
  return;
1767
1968
  }
1768
1969
  }
@@ -1802,27 +2003,32 @@ var updateCommand = new Command6().name("update").description("Update installed
1802
2003
  }
1803
2004
  console.log();
1804
2005
  if (successCount > 0) {
1805
- console.log(chalk7.green(`\u2713 Updated ${successCount} component${successCount !== 1 ? "s" : ""}`));
2006
+ console.log(chalk8.green(`\u2713 Updated ${successCount} component${successCount !== 1 ? "s" : ""}`));
1806
2007
  }
1807
2008
  if (failCount > 0) {
1808
- console.log(chalk7.red(`\u2717 Failed to update ${failCount} component${failCount !== 1 ? "s" : ""}`));
2009
+ console.log(chalk8.red(`\u2717 Failed to update ${failCount} component${failCount !== 1 ? "s" : ""}`));
1809
2010
  }
1810
2011
  const uniqueDeps = [...new Set(allDependencies)];
1811
2012
  const uniqueDevDeps = [...new Set(allDevDependencies)];
1812
2013
  if (uniqueDeps.length || uniqueDevDeps.length) {
1813
2014
  console.log();
1814
- console.log(chalk7.bold("Install/update dependencies:"));
2015
+ console.log(chalk8.bold("Install/update dependencies:"));
1815
2016
  if (uniqueDeps.length) {
1816
- console.log(chalk7.cyan(` npx expo install ${uniqueDeps.join(" ")}`));
2017
+ console.log(chalk8.cyan(` npx expo install ${uniqueDeps.join(" ")}`));
1817
2018
  }
1818
2019
  if (uniqueDevDeps.length) {
1819
- console.log(chalk7.cyan(` npm install -D ${uniqueDevDeps.join(" ")}`));
2020
+ console.log(chalk8.cyan(` npm install -D ${uniqueDevDeps.join(" ")}`));
1820
2021
  }
1821
2022
  }
2023
+ if (failCount > 0) {
2024
+ process.exit(1);
2025
+ }
1822
2026
  } catch (error) {
1823
2027
  spinner.fail("Failed to update");
1824
- console.error(error);
1825
- process.exit(1);
2028
+ handleError({
2029
+ message: "Failed to update components",
2030
+ hint: error instanceof Error ? error.message : "Check your network connection and try again"
2031
+ });
1826
2032
  }
1827
2033
  });
1828
2034
  async function getComponentDiffs(componentsDir, config) {
@@ -1878,7 +2084,7 @@ async function checkForUpdate(localFile, registryItem, config) {
1878
2084
 
1879
2085
  // src/commands/create.ts
1880
2086
  import { Command as Command7 } from "commander";
1881
- import chalk8 from "chalk";
2087
+ import chalk9 from "chalk";
1882
2088
  import ora6 from "ora";
1883
2089
  import prompts4 from "prompts";
1884
2090
  import fs9 from "fs-extra";
@@ -1889,33 +2095,31 @@ var createCommand = new Command7().name("create").description("Scaffold a new cu
1889
2095
  const cwd = path10.resolve(options.cwd);
1890
2096
  const projectRoot = await getProjectRoot(cwd);
1891
2097
  if (!projectRoot) {
1892
- console.log(chalk8.red("Could not find a valid project."));
1893
- console.log(chalk8.dim("Run `npx nativeui init` first."));
1894
- process.exit(1);
2098
+ errors.noProject();
1895
2099
  }
1896
2100
  const config = await getConfig(projectRoot);
1897
2101
  if (!config) {
1898
- console.log(chalk8.red("Project not initialized."));
1899
- console.log(chalk8.dim("Run `npx nativeui init` first."));
1900
- process.exit(1);
2102
+ errors.notInitialized();
1901
2103
  }
1902
- const componentName = toPascalCase(name);
2104
+ const componentName = toPascalCase2(name);
1903
2105
  const fileName = toKebabCase(name) + ".tsx";
1904
2106
  const targetDir = path10.join(projectRoot, config.componentsPath);
1905
2107
  const targetPath = path10.join(targetDir, fileName);
1906
2108
  if (await fs9.pathExists(targetPath)) {
1907
- console.log(chalk8.red(`Component already exists: ${fileName}`));
1908
- console.log(chalk8.dim(`Path: ${targetPath}`));
1909
- process.exit(1);
2109
+ handleError({
2110
+ message: `Component already exists: ${fileName}`,
2111
+ hint: "Choose a different name or delete the existing file",
2112
+ code: "COMPONENT_EXISTS"
2113
+ });
1910
2114
  }
1911
2115
  if (!options.yes) {
1912
2116
  console.log();
1913
- console.log(chalk8.bold("Create new component:"));
1914
- console.log(` Name: ${chalk8.cyan(componentName)}`);
1915
- console.log(` File: ${chalk8.dim(fileName)}`);
1916
- console.log(` Path: ${chalk8.dim(targetPath)}`);
1917
- console.log(` Template: ${chalk8.dim(options.template)}`);
1918
- console.log(` ForwardRef: ${chalk8.dim(options.forwardRef ? "Yes" : "No")}`);
2117
+ console.log(chalk9.bold("Create new component:"));
2118
+ console.log(` Name: ${chalk9.cyan(componentName)}`);
2119
+ console.log(` File: ${chalk9.dim(fileName)}`);
2120
+ console.log(` Path: ${chalk9.dim(targetPath)}`);
2121
+ console.log(` Template: ${chalk9.dim(options.template)}`);
2122
+ console.log(` ForwardRef: ${chalk9.dim(options.forwardRef ? "Yes" : "No")}`);
1919
2123
  console.log();
1920
2124
  const { confirm } = await prompts4({
1921
2125
  type: "confirm",
@@ -1923,8 +2127,11 @@ var createCommand = new Command7().name("create").description("Scaffold a new cu
1923
2127
  message: "Create this component?",
1924
2128
  initial: true
1925
2129
  });
2130
+ if (confirm === void 0) {
2131
+ process.exit(0);
2132
+ }
1926
2133
  if (!confirm) {
1927
- console.log(chalk8.dim("Cancelled."));
2134
+ console.log(chalk9.dim("Cancelled."));
1928
2135
  return;
1929
2136
  }
1930
2137
  }
@@ -1934,18 +2141,20 @@ var createCommand = new Command7().name("create").description("Scaffold a new cu
1934
2141
  await fs9.writeFile(targetPath, code);
1935
2142
  spinner.succeed(`Created ${fileName}`);
1936
2143
  console.log();
1937
- console.log(chalk8.bold("Next steps:"));
1938
- console.log(` 1. Edit your component: ${chalk8.cyan(targetPath)}`);
2144
+ console.log(chalk9.bold("Next steps:"));
2145
+ console.log(` 1. Edit your component: ${chalk9.cyan(targetPath)}`);
1939
2146
  console.log(` 2. Import it in your app:`);
1940
- console.log(chalk8.dim(` import { ${componentName} } from '${config.aliases?.components || "@/components"}/ui/${toKebabCase(name)}';`));
2147
+ console.log(chalk9.dim(` import { ${componentName} } from '${config.aliases?.components || "@/components"}/ui/${toKebabCase(name)}';`));
1941
2148
  console.log();
1942
2149
  } catch (error) {
1943
2150
  spinner.fail("Failed to create component");
1944
- console.error(error);
1945
- process.exit(1);
2151
+ handleError({
2152
+ message: "Failed to create component",
2153
+ hint: error instanceof Error ? error.message : "Check file permissions and try again"
2154
+ });
1946
2155
  }
1947
2156
  });
1948
- function toPascalCase(str) {
2157
+ function toPascalCase2(str) {
1949
2158
  return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
1950
2159
  }
1951
2160
  function toKebabCase(str) {
@@ -1976,7 +2185,7 @@ export interface ${name}Props extends ViewProps {
1976
2185
  /**
1977
2186
  * ${name}
1978
2187
  *
1979
- * A custom component created with nativeui create.
2188
+ * A custom component created with mcellui create.
1980
2189
  *
1981
2190
  * @example
1982
2191
  * \`\`\`tsx
@@ -2036,7 +2245,7 @@ export interface ${name}Props extends ViewProps {
2036
2245
  /**
2037
2246
  * ${name}
2038
2247
  *
2039
- * A custom component created with nativeui create.
2248
+ * A custom component created with mcellui create.
2040
2249
  *
2041
2250
  * @example
2042
2251
  * \`\`\`tsx
@@ -2103,7 +2312,7 @@ export interface ${name}Props extends ViewProps {
2103
2312
  /**
2104
2313
  * ${name}
2105
2314
  *
2106
- * An animated component created with nativeui create.
2315
+ * An animated component created with mcellui create.
2107
2316
  *
2108
2317
  * @example
2109
2318
  * \`\`\`tsx
@@ -2178,7 +2387,7 @@ export interface ${name}Props extends Omit<PressableProps, 'style'> {
2178
2387
  /**
2179
2388
  * ${name}
2180
2389
  *
2181
- * A pressable component created with nativeui create.
2390
+ * A pressable component created with mcellui create.
2182
2391
  *
2183
2392
  * @example
2184
2393
  * \`\`\`tsx
@@ -2271,7 +2480,7 @@ export interface ${name}Props extends Omit<PressableProps, 'style'> {
2271
2480
  /**
2272
2481
  * ${name}
2273
2482
  *
2274
- * A pressable component created with nativeui create.
2483
+ * A pressable component created with mcellui create.
2275
2484
  *
2276
2485
  * @example
2277
2486
  * \`\`\`tsx
@@ -2359,7 +2568,7 @@ export interface ${name}Props extends TextInputProps {
2359
2568
  /**
2360
2569
  * ${name}
2361
2570
  *
2362
- * A custom input component created with nativeui create.
2571
+ * A custom input component created with mcellui create.
2363
2572
  *
2364
2573
  * @example
2365
2574
  * \`\`\`tsx
@@ -2484,7 +2693,7 @@ export interface ${name}Props extends TextInputProps {
2484
2693
  /**
2485
2694
  * ${name}
2486
2695
  *
2487
- * A custom input component created with nativeui create.
2696
+ * A custom input component created with mcellui create.
2488
2697
  *
2489
2698
  * @example
2490
2699
  * \`\`\`tsx
@@ -2584,7 +2793,7 @@ const styles = StyleSheet.create({
2584
2793
 
2585
2794
  // src/commands/pick.ts
2586
2795
  import { Command as Command8 } from "commander";
2587
- import chalk9 from "chalk";
2796
+ import chalk10 from "chalk";
2588
2797
  import prompts5 from "prompts";
2589
2798
  import ora7 from "ora";
2590
2799
  import fs10 from "fs-extra";
@@ -2636,7 +2845,7 @@ function formatCategoryName(category) {
2636
2845
  }
2637
2846
  function formatComponentChoice(item, installed) {
2638
2847
  const isInstalled = installed.has(item.name);
2639
- const status = isInstalled ? chalk9.green(" \u2713") : "";
2848
+ const status = isInstalled ? chalk10.green(" \u2713") : "";
2640
2849
  const description = item.description || "";
2641
2850
  return {
2642
2851
  title: `${item.name}${status}`,
@@ -2646,24 +2855,24 @@ function formatComponentChoice(item, installed) {
2646
2855
  };
2647
2856
  }
2648
2857
  var pickCommand = new Command8().name("pick").description("Interactively browse and select components to add").option("--all", "Show all components without category selection").option("-o, --overwrite", "Overwrite existing files").option("--cwd <path>", "Working directory", process.cwd()).action(async (options) => {
2649
- console.log(chalk9.bold("\n\u{1F3A8} nativeui Component Picker\n"));
2858
+ console.log(chalk10.bold("\n\u{1F3A8} mcellui Component Picker\n"));
2650
2859
  const cwd = path11.resolve(options.cwd);
2651
2860
  const projectRoot = await getProjectRoot(cwd);
2652
2861
  if (!projectRoot) {
2653
- console.log(chalk9.red("Could not find a valid project."));
2654
- console.log(chalk9.dim("Run `npx nativeui init` first.\n"));
2655
- return;
2862
+ errors.noProject();
2656
2863
  }
2657
2864
  const config = await getConfig(projectRoot);
2658
2865
  if (!config) {
2659
- console.log(chalk9.yellow("No nativeui.config.ts found. Run `npx nativeui init` first.\n"));
2660
- return;
2866
+ errors.notInitialized();
2661
2867
  }
2662
2868
  const spinner = ora7("Loading component registry...").start();
2663
2869
  const registry = await getRegistry();
2664
2870
  if (!registry.length) {
2665
- spinner.fail("Could not load registry");
2666
- return;
2871
+ spinner.stop();
2872
+ handleError({
2873
+ message: "Could not load component registry",
2874
+ hint: "Check your internet connection and try again"
2875
+ });
2667
2876
  }
2668
2877
  spinner.succeed(`Loaded ${registry.length} components`);
2669
2878
  const componentsDir = path11.join(projectRoot, config.componentsPath);
@@ -2693,8 +2902,11 @@ var pickCommand = new Command8().name("pick").description("Interactively browse
2693
2902
  message: "Select a category",
2694
2903
  choices: categoryChoices
2695
2904
  });
2905
+ if (categoryResponse.category === void 0) {
2906
+ process.exit(0);
2907
+ }
2696
2908
  if (!categoryResponse.category) {
2697
- console.log(chalk9.yellow("\nCancelled.\n"));
2909
+ console.log(chalk10.yellow("\nCancelled.\n"));
2698
2910
  return;
2699
2911
  }
2700
2912
  selectedCategory = categoryResponse.category;
@@ -2707,10 +2919,10 @@ var pickCommand = new Command8().name("pick").description("Interactively browse
2707
2919
  );
2708
2920
  const availableCount = componentChoices.filter((c) => !c.disabled).length;
2709
2921
  if (availableCount === 0) {
2710
- console.log(chalk9.green("\n\u2713 All components in this category are already installed!\n"));
2922
+ console.log(chalk10.green("\n\u2713 All components in this category are already installed!\n"));
2711
2923
  return;
2712
2924
  }
2713
- console.log(chalk9.dim(`
2925
+ console.log(chalk10.dim(`
2714
2926
  ${installed.size} already installed, ${availableCount} available
2715
2927
  `));
2716
2928
  const componentResponse = await prompts5({
@@ -2721,14 +2933,17 @@ ${installed.size} already installed, ${availableCount} available
2721
2933
  hint: "- Space to select. Return to submit",
2722
2934
  instructions: false
2723
2935
  });
2936
+ if (componentResponse.components === void 0) {
2937
+ process.exit(0);
2938
+ }
2724
2939
  if (!componentResponse.components || componentResponse.components.length === 0) {
2725
- console.log(chalk9.yellow("\nNo components selected.\n"));
2940
+ console.log(chalk10.yellow("\nNo components selected.\n"));
2726
2941
  return;
2727
2942
  }
2728
2943
  const selectedComponents = componentResponse.components;
2729
- console.log(chalk9.bold("\nComponents to install:"));
2944
+ console.log(chalk10.bold("\nComponents to install:"));
2730
2945
  for (const name of selectedComponents) {
2731
- console.log(chalk9.cyan(` \u2022 ${name}`));
2946
+ console.log(chalk10.cyan(` \u2022 ${name}`));
2732
2947
  }
2733
2948
  const confirmResponse = await prompts5({
2734
2949
  type: "confirm",
@@ -2736,12 +2951,16 @@ ${installed.size} already installed, ${availableCount} available
2736
2951
  message: `Install ${selectedComponents.length} component(s)?`,
2737
2952
  initial: true
2738
2953
  });
2954
+ if (confirmResponse.proceed === void 0) {
2955
+ process.exit(0);
2956
+ }
2739
2957
  if (!confirmResponse.proceed) {
2740
- console.log(chalk9.yellow("\nCancelled.\n"));
2958
+ console.log(chalk10.yellow("\nCancelled.\n"));
2741
2959
  return;
2742
2960
  }
2743
2961
  console.log("");
2744
2962
  let successCount = 0;
2963
+ let failCount = 0;
2745
2964
  const allDependencies = [];
2746
2965
  const allDevDependencies = [];
2747
2966
  for (const name of selectedComponents) {
@@ -2750,6 +2969,7 @@ ${installed.size} already installed, ${availableCount} available
2750
2969
  const component = await fetchComponent(name);
2751
2970
  if (!component) {
2752
2971
  installSpinner.fail(`Component "${name}" not found`);
2972
+ failCount++;
2753
2973
  continue;
2754
2974
  }
2755
2975
  const targetDir = path11.join(projectRoot, config.componentsPath);
@@ -2763,7 +2983,7 @@ ${installed.size} already installed, ${availableCount} available
2763
2983
  const transformedContent = transformToInstalled(file.content, config);
2764
2984
  await fs10.writeFile(targetPath, transformedContent);
2765
2985
  }
2766
- installSpinner.succeed(`Installed ${chalk9.green(name)}`);
2986
+ installSpinner.succeed(`Installed ${chalk10.green(name)}`);
2767
2987
  successCount++;
2768
2988
  if (component.dependencies?.length) {
2769
2989
  allDependencies.push(...component.dependencies);
@@ -2772,35 +2992,39 @@ ${installed.size} already installed, ${availableCount} available
2772
2992
  allDevDependencies.push(...component.devDependencies);
2773
2993
  }
2774
2994
  if (component.registryDependencies?.length) {
2775
- console.log(chalk9.dim(` Requires: ${component.registryDependencies.join(", ")}`));
2995
+ console.log(chalk10.dim(` Requires: ${component.registryDependencies.join(", ")}`));
2776
2996
  }
2777
2997
  } catch (error) {
2778
2998
  installSpinner.fail(`Failed to install ${name}: ${error}`);
2999
+ failCount++;
2779
3000
  }
2780
3001
  }
3002
+ if (failCount > 0) {
3003
+ process.exit(1);
3004
+ }
2781
3005
  console.log(
2782
- chalk9.bold.green(`
3006
+ chalk10.bold.green(`
2783
3007
  \u2713 Successfully installed ${successCount}/${selectedComponents.length} components
2784
3008
  `)
2785
3009
  );
2786
3010
  const uniqueDeps = [...new Set(allDependencies)];
2787
3011
  const uniqueDevDeps = [...new Set(allDevDependencies)];
2788
3012
  if (uniqueDeps.length || uniqueDevDeps.length) {
2789
- console.log(chalk9.bold("Install dependencies:"));
3013
+ console.log(chalk10.bold("Install dependencies:"));
2790
3014
  if (uniqueDeps.length) {
2791
- console.log(chalk9.cyan(` npx expo install ${uniqueDeps.join(" ")}`));
3015
+ console.log(chalk10.cyan(` npx expo install ${uniqueDeps.join(" ")}`));
2792
3016
  }
2793
3017
  if (uniqueDevDeps.length) {
2794
- console.log(chalk9.cyan(` npm install -D ${uniqueDevDeps.join(" ")}`));
3018
+ console.log(chalk10.cyan(` npm install -D ${uniqueDevDeps.join(" ")}`));
2795
3019
  }
2796
3020
  console.log("");
2797
3021
  }
2798
3022
  if (successCount > 0) {
2799
- console.log(chalk9.dim("Import example:"));
3023
+ console.log(chalk10.dim("Import example:"));
2800
3024
  const firstComponent = selectedComponents[0];
2801
3025
  const pascalName = firstComponent.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
2802
3026
  const alias = config.aliases?.components || "@/components/ui";
2803
- console.log(chalk9.dim(` import { ${pascalName} } from '${alias}/${firstComponent}';
3027
+ console.log(chalk10.dim(` import { ${pascalName} } from '${alias}/${firstComponent}';
2804
3028
  `));
2805
3029
  }
2806
3030
  });
@@ -2808,6 +3032,11 @@ ${installed.size} already installed, ${availableCount} available
2808
3032
  // src/index.ts
2809
3033
  var program = new Command9();
2810
3034
  program.name("mcellui").description("Add beautiful UI components to your Expo/React Native project").version("0.1.4");
3035
+ program.configureOutput({
3036
+ writeOut: (str) => process.stdout.write(str),
3037
+ writeErr: (str) => process.stderr.write(str),
3038
+ outputError: (str, write) => write(chalk11.red(str))
3039
+ });
2811
3040
  program.addCommand(initCommand);
2812
3041
  program.addCommand(addCommand);
2813
3042
  program.addCommand(listCommand);
@@ -2816,4 +3045,14 @@ program.addCommand(diffCommand);
2816
3045
  program.addCommand(updateCommand);
2817
3046
  program.addCommand(createCommand);
2818
3047
  program.addCommand(pickCommand);
2819
- program.parse();
3048
+ async function main() {
3049
+ try {
3050
+ await program.parseAsync(process.argv);
3051
+ } catch (error) {
3052
+ handleError({
3053
+ message: error instanceof Error ? error.message : "An unexpected error occurred",
3054
+ hint: "If this persists, run: npx mcellui doctor"
3055
+ });
3056
+ }
3057
+ }
3058
+ main();