wensity 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +312 -72
  2. package/package.json +7 -1
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { dirname as dirname5, join as join6 } from "path";
6
6
 
7
7
  // src/constants.ts
8
8
  var CLI_NAME = "wensity";
9
- var CLI_VERSION = "0.1.3";
9
+ var CLI_VERSION = "0.1.5";
10
10
  var CONFIG_FILE_NAME = "wensity.json";
11
11
  var DEFAULT_API_ORIGIN = "https://ui.wensity.com";
12
12
  var DEFAULT_REGISTRY_PATH = "/api/registry";
@@ -396,6 +396,177 @@ function requiredHome(env) {
396
396
  throw new CliError("HOME is not set, so the CLI cannot locate ~/.config/wensity.");
397
397
  }
398
398
 
399
+ // src/output.ts
400
+ import boxen from "boxen";
401
+ import Table from "cli-table3";
402
+ import ora from "ora";
403
+ import pc from "picocolors";
404
+ var BRAND_RED = "#cd1c18";
405
+ var PRICING_BLUE = "#2563eb";
406
+ var LOGO = [
407
+ "\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
408
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D",
409
+ "\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D ",
410
+ "\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D ",
411
+ "\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
412
+ " \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D "
413
+ ];
414
+ function print(message = "") {
415
+ process.stdout.write(`${message}
416
+ `);
417
+ }
418
+ function warn(message) {
419
+ process.stderr.write(`${pc.yellow("Warning:")} ${message}
420
+ `);
421
+ }
422
+ function printJson(value) {
423
+ print(JSON.stringify(value, null, 2));
424
+ }
425
+ function formatList(items) {
426
+ return items.map((item) => ` ${pc.dim("\u2022")} ${item}`).join("\n");
427
+ }
428
+ function brandRed(message) {
429
+ return color(message, BRAND_RED);
430
+ }
431
+ function pricingBlue(message) {
432
+ return color(message, PRICING_BLUE);
433
+ }
434
+ function brandBanner(subtitle) {
435
+ const lines = [
436
+ "",
437
+ ...LOGO.map((line) => brandRed(centerLine(line))),
438
+ pc.bold(centerLine("Wensity CLI"))
439
+ ];
440
+ if (subtitle) lines.push(pc.dim(centerLine(subtitle)));
441
+ return lines.join("\n");
442
+ }
443
+ function printBrand(subtitle) {
444
+ print(brandBanner(subtitle));
445
+ }
446
+ function printTitle(title, subtitle) {
447
+ print(`${brandRed("\u25C6")} ${pc.bold(title)}${subtitle ? ` ${pc.dim(subtitle)}` : ""}`);
448
+ }
449
+ function success(message) {
450
+ return `${pc.green("\u2713")} ${message}`;
451
+ }
452
+ function failure(message) {
453
+ return `${brandRed("\u2715")} ${message}`;
454
+ }
455
+ function action(message) {
456
+ return `${brandRed("\u25C6")} ${message}`;
457
+ }
458
+ function badge(label, tone = "muted") {
459
+ const text = ` ${label.toUpperCase()} `;
460
+ if (tone === "pro") return bgBrandRed(pc.white(pc.bold(text)));
461
+ if (tone === "free") return pc.bgGreen(pc.black(pc.bold(text)));
462
+ return pc.inverse(text);
463
+ }
464
+ function panel(title, body) {
465
+ const content = Array.isArray(body) ? body.join("\n") : body;
466
+ return boxen(content, {
467
+ title: brandRed(pc.bold(title)),
468
+ titleAlignment: "left",
469
+ borderColor: "red",
470
+ borderStyle: "round",
471
+ padding: 1,
472
+ margin: { top: 1, bottom: 0 }
473
+ });
474
+ }
475
+ function nextSteps(steps) {
476
+ return panel(
477
+ "Next steps",
478
+ steps.map((step) => `${brandRed("\u203A")} ${pc.bold(step)}`)
479
+ );
480
+ }
481
+ function table(head, rows) {
482
+ const output = new Table({
483
+ head: head.map((item) => brandRed(pc.bold(item))),
484
+ chars: {
485
+ top: "\u2500",
486
+ "top-mid": "\u252C",
487
+ "top-left": "\u256D",
488
+ "top-right": "\u256E",
489
+ bottom: "\u2500",
490
+ "bottom-mid": "\u2534",
491
+ "bottom-left": "\u2570",
492
+ "bottom-right": "\u256F",
493
+ left: "\u2502",
494
+ "left-mid": "\u251C",
495
+ mid: "\u2500",
496
+ "mid-mid": "\u253C",
497
+ right: "\u2502",
498
+ "right-mid": "\u2524",
499
+ middle: "\u2502"
500
+ },
501
+ style: {
502
+ border: [],
503
+ head: []
504
+ },
505
+ wordWrap: true
506
+ });
507
+ output.push(...rows);
508
+ return output.toString();
509
+ }
510
+ function startSpinner(text) {
511
+ return ora({
512
+ text,
513
+ color: "red",
514
+ spinner: "dots",
515
+ isEnabled: process.stderr.isTTY && process.env.CI !== "true"
516
+ }).start();
517
+ }
518
+ async function withSpinner(text, task, successText = text) {
519
+ const spinner = startSpinner(text);
520
+ try {
521
+ const result = await task();
522
+ spinner.succeed(successText);
523
+ return result;
524
+ } catch (error) {
525
+ spinner.fail(text);
526
+ throw error;
527
+ }
528
+ }
529
+ function formatCliError(message) {
530
+ return `${failure(message)}
531
+
532
+ ${pc.dim("Need help?")} ${pricingBlue("https://ui.wensity.com/docs")}`;
533
+ }
534
+ function formatProRequiredError(resourceName, resourceType) {
535
+ return [
536
+ `${resourceName} is a Pro ${resourceType}.`,
537
+ brandRed("Create an API token from the Wensity dashboard, then run:"),
538
+ ` ${brandRed("npx wensity login --token <token>")}`,
539
+ `Pricing: ${pricingBlue("https://ui.wensity.com/pricing")}`
540
+ ].join("\n");
541
+ }
542
+ function color(message, hex) {
543
+ if (!shouldColor()) return message;
544
+ const [red, green, blue] = hexToRgb(hex);
545
+ return `\x1B[38;2;${red};${green};${blue}m${message}\x1B[39m`;
546
+ }
547
+ function bgBrandRed(message) {
548
+ if (!shouldColor()) return message;
549
+ const [red, green, blue] = hexToRgb(BRAND_RED);
550
+ return `\x1B[48;2;${red};${green};${blue}m${message}\x1B[49m`;
551
+ }
552
+ function shouldColor() {
553
+ if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") return true;
554
+ return process.env.NO_COLOR === void 0 && process.env.FORCE_COLOR !== "0";
555
+ }
556
+ function hexToRgb(hex) {
557
+ const value = hex.replace("#", "");
558
+ return [
559
+ Number.parseInt(value.slice(0, 2), 16),
560
+ Number.parseInt(value.slice(2, 4), 16),
561
+ Number.parseInt(value.slice(4, 6), 16)
562
+ ];
563
+ }
564
+ function centerLine(line) {
565
+ const columns = Math.min(process.stdout.columns || 80, 100);
566
+ const padding = Math.max(0, Math.floor((columns - line.length) / 2));
567
+ return `${" ".repeat(padding)}${line}`;
568
+ }
569
+
399
570
  // src/http.ts
400
571
  async function requestJson(url, options = {}) {
401
572
  const headers = new Headers(options.headers);
@@ -438,7 +609,7 @@ function formatErrorMessage(payload) {
438
609
  if (code === "pro_license_required") {
439
610
  const base = message ?? "This component requires Wensity Pro.";
440
611
  return `${base}
441
- Upgrade here: https://ui.wensity.com/pricing`;
612
+ Upgrade here: ${pricingBlue("https://ui.wensity.com/pricing")}`;
442
613
  }
443
614
  return message;
444
615
  }
@@ -447,28 +618,6 @@ Upgrade here: https://ui.wensity.com/pricing`;
447
618
  import { existsSync as existsSync2, readFileSync } from "fs";
448
619
  import { spawnSync } from "child_process";
449
620
  import { join as join4 } from "path";
450
-
451
- // src/output.ts
452
- function print(message = "") {
453
- process.stdout.write(`${message}
454
- `);
455
- }
456
- function warn(message) {
457
- process.stderr.write(`Warning: ${message}
458
- `);
459
- }
460
- function info(message) {
461
- process.stderr.write(`${message}
462
- `);
463
- }
464
- function printJson(value) {
465
- print(JSON.stringify(value, null, 2));
466
- }
467
- function formatList(items) {
468
- return items.map((item) => ` - ${item}`).join("\n");
469
- }
470
-
471
- // src/package-manager.ts
472
621
  function detectPackageManager(projectRoot) {
473
622
  if (existsSync2(join4(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
474
623
  if (existsSync2(join4(projectRoot, "bun.lockb")) || existsSync2(join4(projectRoot, "bun.lock"))) return "bun";
@@ -819,7 +968,7 @@ async function run(context) {
819
968
  return;
820
969
  }
821
970
  if (hasFlag(parsed, "help") || parsed.positionals.length === 0) {
822
- print(HELP_TEXT.trimEnd());
971
+ print(formatHelp());
823
972
  return;
824
973
  }
825
974
  const [command, ...positionals] = parsed.positionals;
@@ -865,9 +1014,10 @@ async function initCommand(context, parsed) {
865
1014
  const utilityRelativePath = toPosixPath(config.utils.endsWith(".ts") ? config.utils : `${config.utils}.ts`);
866
1015
  const dependencies = ["clsx", "tailwind-merge"];
867
1016
  if (dryRun) {
868
- print("Would write:");
1017
+ printTitle("Project initialization preview");
1018
+ print(action("Would write:"));
869
1019
  print(formatList([CONFIG_FILE_NAME, utilityRelativePath]));
870
- if (install) print(`Would install: ${dependencies.join(" ")}`);
1020
+ if (install) print(action(`Would install: ${dependencies.join(" ")}`));
871
1021
  return;
872
1022
  }
873
1023
  await writeConfigFile(configPath, config, force);
@@ -877,7 +1027,17 @@ async function initCommand(context, parsed) {
877
1027
  if (install) {
878
1028
  runInstall(context.cwd, missingDependencies(context.cwd, dependencies));
879
1029
  }
880
- print("Created wensity.json and local Wensity helper files.");
1030
+ printTitle("Project initialized");
1031
+ print(panel("Created", [
1032
+ success(CONFIG_FILE_NAME),
1033
+ success(utilityRelativePath),
1034
+ success(`registry: ${apiUrl}`)
1035
+ ]));
1036
+ print(nextSteps([
1037
+ "Browse available components: npx wensity list",
1038
+ "Browse premium blocks: npx wensity list --type block",
1039
+ "Install your chosen slug: npx wensity add <component-slug>"
1040
+ ]));
881
1041
  }
882
1042
  async function loginCommand(context, parsed) {
883
1043
  const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL ?? DEFAULT_API_ORIGIN;
@@ -896,12 +1056,23 @@ async function loginCommand(context, parsed) {
896
1056
  plan: whoami.plan,
897
1057
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
898
1058
  });
899
- print(`Logged in as ${whoami.user.email}.`);
900
- print(`Credentials saved to ${path}.`);
1059
+ printBrand("API token verified");
1060
+ print(formatAccountPanel(whoami, path));
1061
+ print(nextSteps(whoami.plan?.tier === "pro" ? [
1062
+ "Browse Pro components: npx wensity list",
1063
+ "Browse Pro blocks: npx wensity list --type block",
1064
+ "Install a component: npx wensity add <component-slug>",
1065
+ "Install a block: npx wensity add <block-slug> --type block"
1066
+ ] : [
1067
+ "Browse free components: npx wensity list",
1068
+ "Install any Free slug from the table: npx wensity add <slug>",
1069
+ `Upgrade to unlock Pro components and blocks: ${pricingBlue("https://ui.wensity.com/pricing")}`
1070
+ ]));
901
1071
  }
902
1072
  async function logoutCommand(context) {
903
1073
  const removed = await deleteCredentials(context.env);
904
- print(removed ? "Logged out of Wensity." : "No Wensity credentials were stored.");
1074
+ printTitle("Session");
1075
+ print(removed ? success("Logged out of Wensity.") : action("No Wensity credentials were stored."));
905
1076
  }
906
1077
  async function whoamiCommand(context, parsed) {
907
1078
  const credentials = await requireCredentials(context.env);
@@ -912,10 +1083,8 @@ async function whoamiCommand(context, parsed) {
912
1083
  printJson({ ...whoami, apiBaseUrl: credentials.apiBaseUrl, credentialsPath: credentialsPath(context.env) });
913
1084
  return;
914
1085
  }
915
- print(`Signed in as ${whoami.user.email}`);
916
- if (whoami.plan?.tier) {
917
- print(`Plan: ${whoami.plan.tier}${whoami.plan.status ? ` (${whoami.plan.status})` : ""}`);
918
- }
1086
+ printBrand("Account");
1087
+ print(formatAccountPanel(whoami, credentialsPath(context.env)));
919
1088
  }
920
1089
  async function listCommand(context, parsed) {
921
1090
  const resourceType = getResourceType(parsed);
@@ -930,11 +1099,17 @@ async function listCommand(context, parsed) {
930
1099
  return;
931
1100
  }
932
1101
  if (response2.blocks.length === 0) {
933
- print("No Wensity blocks found.");
1102
+ printBrand("Blocks");
1103
+ print(action("No Wensity blocks found."));
934
1104
  return;
935
1105
  }
936
- print("Available Wensity blocks:");
1106
+ printBrand("Premium blocks");
937
1107
  print(formatBlockGroup(response2.blocks));
1108
+ print(nextSteps([
1109
+ brandRed("Create an API token in the dashboard if you have Pro"),
1110
+ brandRed("Save the token: npx wensity login --token <token>"),
1111
+ "Install your chosen block: npx wensity add <block-slug> --type block"
1112
+ ]));
938
1113
  return;
939
1114
  }
940
1115
  const response = await requestJson(registry);
@@ -943,32 +1118,39 @@ async function listCommand(context, parsed) {
943
1118
  return;
944
1119
  }
945
1120
  if (response.components.length === 0) {
946
- print("No registry components found.");
1121
+ printBrand("Components");
1122
+ print(action("No registry components found."));
947
1123
  return;
948
1124
  }
949
- print("Available Wensity components:");
1125
+ printBrand("Components");
950
1126
  print(formatComponentGroup("Free", response.components.filter((component) => component.access !== "pro")));
951
1127
  print(formatComponentGroup("Pro", response.components.filter((component) => component.access === "pro")));
1128
+ print(nextSteps([
1129
+ "Choose any Free slug above: npx wensity add <component-slug>",
1130
+ brandRed("For Pro slugs, save an API token: npx wensity login --token <token>"),
1131
+ "Browse layout blocks: npx wensity list --type block"
1132
+ ]));
952
1133
  }
953
1134
  function formatComponentGroup(label, components) {
954
- const rows = components.map((component) => {
955
- const category = component.category ? ` ${component.category}` : "";
956
- return ` ${component.slug.padEnd(32)}${category}`;
957
- });
1135
+ const tone = label.toLowerCase() === "pro" ? "pro" : "free";
1136
+ const rows = components.map((component) => [
1137
+ component.slug,
1138
+ component.category ?? "uncategorized",
1139
+ badge(label, tone)
1140
+ ]);
958
1141
  return rows.length > 0 ? `
959
- ${label}
960
- ${rows.join("\n")}` : `
961
- ${label}
962
- No components`;
1142
+ ${badge(label, tone)}
1143
+ ${table(["Slug", "Category", "Access"], rows)}` : `
1144
+ ${badge(label, tone)}
1145
+ ${action("No components")}`;
963
1146
  }
964
1147
  function formatBlockGroup(blocks) {
965
- const rows = blocks.map((block) => {
966
- const category = block.category ? ` ${block.category}` : "";
967
- return ` ${block.slug.padEnd(32)}${category}`;
968
- });
969
- return rows.length > 0 ? `
970
- Pro
971
- ${rows.join("\n")}` : "\nPro\n No blocks";
1148
+ const rows = blocks.map((block) => [
1149
+ block.slug,
1150
+ block.category ?? "uncategorized",
1151
+ badge("Pro", "pro")
1152
+ ]);
1153
+ return rows.length > 0 ? `${table(["Slug", "Category", "Access"], rows)}` : action("No blocks");
972
1154
  }
973
1155
  async function addCommand(context, parsed, positionals, updateMode) {
974
1156
  const resourceType = getResourceType(parsed);
@@ -994,54 +1176,86 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
994
1176
  const plannedFiles = summarizeFiles(loaded.projectRoot, prepared.files);
995
1177
  const missing = missingDependencies(loaded.projectRoot, prepared.dependencies);
996
1178
  if (dryRun) {
997
- print("Would write:");
1179
+ printTitle(updateMode ? "Update preview" : "Install preview");
1180
+ print(action("Would write:"));
998
1181
  print(formatList(plannedFiles));
999
- if (install && missing.length > 0) print(`Would install: ${missing.join(" ")}`);
1182
+ if (install && missing.length > 0) print(action(`Would install: ${missing.join(" ")}`));
1000
1183
  return;
1001
1184
  }
1002
1185
  await writePlannedFiles(prepared.files);
1003
1186
  if (install) {
1004
1187
  runInstall(loaded.projectRoot, missing);
1005
1188
  }
1006
- print(`${updateMode ? "Updated" : "Added"} ${payload.name}.`);
1007
- print("Files written:");
1008
- print(formatList(plannedFiles));
1189
+ printTitle(updateMode ? "Component updated" : "Component installed");
1190
+ print(panel(payload.name, [
1191
+ success(`${updateMode ? "Updated" : "Added"} ${payload.slug}`),
1192
+ ...plannedFiles.map((file) => success(file))
1193
+ ]));
1009
1194
  if (!install && missing.length > 0) {
1010
1195
  warn(`Dependencies were not installed: ${missing.join(" ")}`);
1196
+ print(nextSteps([
1197
+ `Install skipped dependencies: npm install ${missing.join(" ")}`,
1198
+ "Run without --no-install next time to let Wensity install them automatically"
1199
+ ]));
1200
+ return;
1011
1201
  }
1202
+ print(nextSteps([
1203
+ "Import the installed file from your Wensity components folder",
1204
+ resourceType === "block" ? "Customize the block copy, colors, and sections" : "Customize props and styling for your app"
1205
+ ]));
1012
1206
  }
1013
1207
  async function fetchComponentPayload(input) {
1014
- info(`Fetching ${input.slug}...`);
1015
- const metadata = await requestJson(registryMetadataUrl(input.registry, input.slug));
1208
+ const metadata = await withSpinner(
1209
+ `Finding ${input.slug}`,
1210
+ () => requestJson(registryMetadataUrl(input.registry, input.slug)),
1211
+ `Found ${input.slug}`
1212
+ );
1016
1213
  if (input.credentials) {
1214
+ const credentials = input.credentials;
1017
1215
  const cliEndpoint = metadata.endpoints?.cli ?? `/api/cli/${input.slug}`;
1018
- const endpoint2 = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(input.credentials.apiBaseUrl, cliEndpoint);
1216
+ const endpoint2 = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(credentials.apiBaseUrl, cliEndpoint);
1019
1217
  try {
1020
- return await requestJson(endpoint2, { token: input.credentials.token });
1218
+ return await withSpinner(
1219
+ `Downloading ${metadata.name}`,
1220
+ () => requestJson(endpoint2, { token: credentials.token }),
1221
+ `Downloaded ${metadata.name}`
1222
+ );
1021
1223
  } catch (error) {
1022
1224
  if (metadata.access === "pro" || !isAuthFailure(error)) throw error;
1023
1225
  warn("Saved Wensity credentials were rejected; falling back to the public free registry payload.");
1024
1226
  }
1025
1227
  }
1026
1228
  if (metadata.access === "pro") {
1027
- throw new CliError(`${metadata.name} is a premium component. Run wensity login, then try again.`);
1229
+ throw new CliError(formatProRequiredError(metadata.name, "component"));
1028
1230
  }
1029
1231
  const publicSourceEndpoint = metadata.endpoints?.registry ?? registrySlugUrl(input.registry, input.slug);
1030
1232
  const endpoint = publicSourceEndpoint.startsWith("http") ? publicSourceEndpoint : joinUrl(registryOrigin(input.registry), publicSourceEndpoint);
1031
- return requestJson(endpoint);
1233
+ return withSpinner(
1234
+ `Downloading ${metadata.name}`,
1235
+ () => requestJson(endpoint),
1236
+ `Downloaded ${metadata.name}`
1237
+ );
1032
1238
  }
1033
1239
  function isAuthFailure(error) {
1034
1240
  return error instanceof CliError && /^(401|403)\b/.test(error.message);
1035
1241
  }
1036
1242
  async function fetchBlockPayload(input) {
1037
- info(`Fetching ${input.slug}...`);
1038
- const metadata = await requestJson(registryMetadataUrl(input.registry, input.slug));
1243
+ const metadata = await withSpinner(
1244
+ `Finding ${input.slug}`,
1245
+ () => requestJson(registryMetadataUrl(input.registry, input.slug)),
1246
+ `Found ${input.slug}`
1247
+ );
1039
1248
  if (!input.credentials) {
1040
- throw new CliError(`${metadata.name} is a premium block. Run wensity login, then try again.`);
1249
+ throw new CliError(formatProRequiredError(metadata.name, "block"));
1041
1250
  }
1251
+ const credentials = input.credentials;
1042
1252
  const cliEndpoint = metadata.endpoints?.cli ?? `/api/cli/blocks/${input.slug}`;
1043
- const endpoint = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(input.credentials.apiBaseUrl, cliEndpoint);
1044
- return requestJson(endpoint, { token: input.credentials.token });
1253
+ const endpoint = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(credentials.apiBaseUrl, cliEndpoint);
1254
+ return withSpinner(
1255
+ `Downloading ${metadata.name}`,
1256
+ () => requestJson(endpoint, { token: credentials.token }),
1257
+ `Downloaded ${metadata.name}`
1258
+ );
1045
1259
  }
1046
1260
  function getResourceType(parsed) {
1047
1261
  const value = getStringOption(parsed, "type") ?? "component";
@@ -1056,7 +1270,7 @@ function registryUrlForType(componentRegistry, resourceType, registryOverride) {
1056
1270
  async function requireCredentials(env) {
1057
1271
  const credentials = await readCredentials(env);
1058
1272
  if (!credentials) {
1059
- throw new CliError("Not logged in. Run wensity login with an API token from the Wensity dashboard.");
1273
+ throw new CliError(`Not logged in. ${brandRed("Run wensity login with an API token from the Wensity dashboard.")}`);
1060
1274
  }
1061
1275
  return credentials;
1062
1276
  }
@@ -1072,6 +1286,32 @@ function registryOrigin(registry) {
1072
1286
  const url = new URL(registry);
1073
1287
  return `${url.protocol}//${url.host}`;
1074
1288
  }
1289
+ function formatHelp() {
1290
+ return [
1291
+ brandBanner("Own your component source."),
1292
+ "",
1293
+ HELP_TEXT.trimEnd(),
1294
+ "",
1295
+ nextSteps([
1296
+ "npx wensity init",
1297
+ "npx wensity list",
1298
+ "npx wensity add <component-slug>"
1299
+ ])
1300
+ ].join("\n");
1301
+ }
1302
+ function formatAccountPanel(whoami, savedPath) {
1303
+ const isPro = whoami.plan?.tier === "pro";
1304
+ const lines = [
1305
+ `${badge(isPro ? "Pro" : "Free", isPro ? "pro" : "free")} ${whoami.user.email}`,
1306
+ whoami.plan?.status ? `Status: ${whoami.plan.status}` : "Status: active",
1307
+ `Credentials: ${savedPath}`,
1308
+ "",
1309
+ isPro ? success("Pro components unlocked") : success("Free components unlocked"),
1310
+ isPro ? success("Pro blocks unlocked") : action("Pro blocks locked until upgrade"),
1311
+ isPro ? success("API token verified") : action("Upgrade for premium source access")
1312
+ ];
1313
+ return panel(isPro ? "Wensity Pro" : "Wensity Free", lines);
1314
+ }
1075
1315
  async function writeTextFile(path, content) {
1076
1316
  await mkdir4(dirname5(path), { recursive: true });
1077
1317
  await writeFile4(path, content, "utf8");
@@ -1084,7 +1324,7 @@ run({
1084
1324
  env: process.env
1085
1325
  }).catch((error) => {
1086
1326
  if (error instanceof CliError) {
1087
- process.stderr.write(`${error.message}
1327
+ process.stderr.write(`${formatCliError(error.message)}
1088
1328
  `);
1089
1329
  process.exitCode = error.exitCode;
1090
1330
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wensity",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Wensity component registry CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,5 +28,11 @@
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"
31
+ },
32
+ "dependencies": {
33
+ "boxen": "^8.0.1",
34
+ "cli-table3": "^0.6.5",
35
+ "ora": "^9.4.0",
36
+ "picocolors": "^1.1.1"
31
37
  }
32
38
  }