wensity 0.1.2 → 0.1.4

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 +250 -54
  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.1";
9
+ var CLI_VERSION = "0.1.4";
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";
@@ -449,23 +449,124 @@ import { spawnSync } from "child_process";
449
449
  import { join as join4 } from "path";
450
450
 
451
451
  // src/output.ts
452
+ import boxen from "boxen";
453
+ import Table from "cli-table3";
454
+ import ora from "ora";
455
+ import pc from "picocolors";
456
+ var LOGO = [
457
+ "\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",
458
+ "\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",
459
+ "\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 ",
460
+ "\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 ",
461
+ "\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 ",
462
+ " \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 "
463
+ ];
452
464
  function print(message = "") {
453
465
  process.stdout.write(`${message}
454
466
  `);
455
467
  }
456
468
  function warn(message) {
457
- process.stderr.write(`Warning: ${message}
458
- `);
459
- }
460
- function info(message) {
461
- process.stderr.write(`${message}
469
+ process.stderr.write(`${pc.yellow("Warning:")} ${message}
462
470
  `);
463
471
  }
464
472
  function printJson(value) {
465
473
  print(JSON.stringify(value, null, 2));
466
474
  }
467
475
  function formatList(items) {
468
- return items.map((item) => ` - ${item}`).join("\n");
476
+ return items.map((item) => ` ${pc.dim("\u2022")} ${item}`).join("\n");
477
+ }
478
+ function brandBanner(subtitle) {
479
+ const lines = [pc.red(LOGO.join("\n")), pc.bold("Wensity CLI")];
480
+ if (subtitle) lines.push(pc.dim(subtitle));
481
+ return lines.join("\n");
482
+ }
483
+ function printBrand(subtitle) {
484
+ print(brandBanner(subtitle));
485
+ }
486
+ function success(message) {
487
+ return `${pc.green("\u2713")} ${message}`;
488
+ }
489
+ function failure(message) {
490
+ return `${pc.red("\u2715")} ${message}`;
491
+ }
492
+ function action(message) {
493
+ return `${pc.red("\u25C6")} ${message}`;
494
+ }
495
+ function badge(label, tone = "muted") {
496
+ const text = ` ${label.toUpperCase()} `;
497
+ if (tone === "pro") return pc.bgRed(pc.white(pc.bold(text)));
498
+ if (tone === "free") return pc.bgGreen(pc.black(pc.bold(text)));
499
+ return pc.inverse(text);
500
+ }
501
+ function panel(title, body) {
502
+ const content = Array.isArray(body) ? body.join("\n") : body;
503
+ return boxen(content, {
504
+ title: pc.red(pc.bold(title)),
505
+ titleAlignment: "left",
506
+ borderColor: "red",
507
+ borderStyle: "round",
508
+ padding: 1,
509
+ margin: { top: 1, bottom: 0 }
510
+ });
511
+ }
512
+ function nextSteps(steps) {
513
+ return panel(
514
+ "Next steps",
515
+ steps.map((step) => `${pc.red("\u203A")} ${pc.bold(step)}`)
516
+ );
517
+ }
518
+ function table(head, rows) {
519
+ const output = new Table({
520
+ head: head.map((item) => pc.red(pc.bold(item))),
521
+ chars: {
522
+ top: "\u2500",
523
+ "top-mid": "\u252C",
524
+ "top-left": "\u256D",
525
+ "top-right": "\u256E",
526
+ bottom: "\u2500",
527
+ "bottom-mid": "\u2534",
528
+ "bottom-left": "\u2570",
529
+ "bottom-right": "\u256F",
530
+ left: "\u2502",
531
+ "left-mid": "\u251C",
532
+ mid: "\u2500",
533
+ "mid-mid": "\u253C",
534
+ right: "\u2502",
535
+ "right-mid": "\u2524",
536
+ middle: "\u2502"
537
+ },
538
+ style: {
539
+ border: [],
540
+ head: []
541
+ },
542
+ wordWrap: true
543
+ });
544
+ output.push(...rows);
545
+ return output.toString();
546
+ }
547
+ function startSpinner(text) {
548
+ return ora({
549
+ text,
550
+ color: "red",
551
+ spinner: "dots",
552
+ isEnabled: process.stderr.isTTY && process.env.CI !== "true"
553
+ }).start();
554
+ }
555
+ async function withSpinner(text, task, successText = text) {
556
+ const spinner = startSpinner(text);
557
+ try {
558
+ const result = await task();
559
+ spinner.succeed(successText);
560
+ return result;
561
+ } catch (error) {
562
+ spinner.fail(text);
563
+ throw error;
564
+ }
565
+ }
566
+ function formatCliError(message) {
567
+ return `${failure(message)}
568
+
569
+ ${pc.dim("Need help?")} ${pc.red("https://ui.wensity.com/docs")}`;
469
570
  }
470
571
 
471
572
  // src/package-manager.ts
@@ -819,7 +920,7 @@ async function run(context) {
819
920
  return;
820
921
  }
821
922
  if (hasFlag(parsed, "help") || parsed.positionals.length === 0) {
822
- print(HELP_TEXT.trimEnd());
923
+ print(formatHelp());
823
924
  return;
824
925
  }
825
926
  const [command, ...positionals] = parsed.positionals;
@@ -865,9 +966,10 @@ async function initCommand(context, parsed) {
865
966
  const utilityRelativePath = toPosixPath(config.utils.endsWith(".ts") ? config.utils : `${config.utils}.ts`);
866
967
  const dependencies = ["clsx", "tailwind-merge"];
867
968
  if (dryRun) {
868
- print("Would write:");
969
+ printBrand("Project initialization preview");
970
+ print(action("Would write:"));
869
971
  print(formatList([CONFIG_FILE_NAME, utilityRelativePath]));
870
- if (install) print(`Would install: ${dependencies.join(" ")}`);
972
+ if (install) print(action(`Would install: ${dependencies.join(" ")}`));
871
973
  return;
872
974
  }
873
975
  await writeConfigFile(configPath, config, force);
@@ -877,7 +979,17 @@ async function initCommand(context, parsed) {
877
979
  if (install) {
878
980
  runInstall(context.cwd, missingDependencies(context.cwd, dependencies));
879
981
  }
880
- print("Created wensity.json and local Wensity helper files.");
982
+ printBrand("Project initialized");
983
+ print(panel("Created", [
984
+ success(CONFIG_FILE_NAME),
985
+ success(utilityRelativePath),
986
+ success(`registry: ${apiUrl}`)
987
+ ]));
988
+ print(nextSteps([
989
+ "npx wensity list",
990
+ "npx wensity add liquid-multimodal-input",
991
+ "npx wensity login --token <token>"
992
+ ]));
881
993
  }
882
994
  async function loginCommand(context, parsed) {
883
995
  const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL ?? DEFAULT_API_ORIGIN;
@@ -896,12 +1008,21 @@ async function loginCommand(context, parsed) {
896
1008
  plan: whoami.plan,
897
1009
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
898
1010
  });
899
- print(`Logged in as ${whoami.user.email}.`);
900
- print(`Credentials saved to ${path}.`);
1011
+ printBrand("API token verified");
1012
+ print(formatAccountPanel(whoami, path));
1013
+ print(nextSteps(whoami.plan?.tier === "pro" ? [
1014
+ "npx wensity add gooey-navigation-menu",
1015
+ "npx wensity add monolith-hero --type block",
1016
+ "npx wensity whoami"
1017
+ ] : [
1018
+ "npx wensity add liquid-multimodal-input",
1019
+ "Upgrade to unlock Pro components and blocks: https://ui.wensity.com/pricing"
1020
+ ]));
901
1021
  }
902
1022
  async function logoutCommand(context) {
903
1023
  const removed = await deleteCredentials(context.env);
904
- print(removed ? "Logged out of Wensity." : "No Wensity credentials were stored.");
1024
+ printBrand("Session");
1025
+ print(removed ? success("Logged out of Wensity.") : action("No Wensity credentials were stored."));
905
1026
  }
906
1027
  async function whoamiCommand(context, parsed) {
907
1028
  const credentials = await requireCredentials(context.env);
@@ -912,10 +1033,8 @@ async function whoamiCommand(context, parsed) {
912
1033
  printJson({ ...whoami, apiBaseUrl: credentials.apiBaseUrl, credentialsPath: credentialsPath(context.env) });
913
1034
  return;
914
1035
  }
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
- }
1036
+ printBrand("Account");
1037
+ print(formatAccountPanel(whoami, credentialsPath(context.env)));
919
1038
  }
920
1039
  async function listCommand(context, parsed) {
921
1040
  const resourceType = getResourceType(parsed);
@@ -930,11 +1049,16 @@ async function listCommand(context, parsed) {
930
1049
  return;
931
1050
  }
932
1051
  if (response2.blocks.length === 0) {
933
- print("No Wensity blocks found.");
1052
+ printBrand("Blocks");
1053
+ print(action("No Wensity blocks found."));
934
1054
  return;
935
1055
  }
936
- print("Available Wensity blocks:");
1056
+ printBrand("Premium blocks");
937
1057
  print(formatBlockGroup(response2.blocks));
1058
+ print(nextSteps([
1059
+ "npx wensity login --token <pro-token>",
1060
+ `npx wensity add ${response2.blocks[0]?.slug ?? "<block-slug>"} --type block`
1061
+ ]));
938
1062
  return;
939
1063
  }
940
1064
  const response = await requestJson(registry);
@@ -943,32 +1067,39 @@ async function listCommand(context, parsed) {
943
1067
  return;
944
1068
  }
945
1069
  if (response.components.length === 0) {
946
- print("No registry components found.");
1070
+ printBrand("Components");
1071
+ print(action("No registry components found."));
947
1072
  return;
948
1073
  }
949
- print("Available Wensity components:");
1074
+ printBrand("Components");
950
1075
  print(formatComponentGroup("Free", response.components.filter((component) => component.access !== "pro")));
951
1076
  print(formatComponentGroup("Pro", response.components.filter((component) => component.access === "pro")));
1077
+ print(nextSteps([
1078
+ "npx wensity add liquid-multimodal-input",
1079
+ "npx wensity login --token <pro-token>",
1080
+ "npx wensity list --type block"
1081
+ ]));
952
1082
  }
953
1083
  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
- });
1084
+ const tone = label.toLowerCase() === "pro" ? "pro" : "free";
1085
+ const rows = components.map((component) => [
1086
+ component.slug,
1087
+ component.category ?? "uncategorized",
1088
+ badge(label, tone)
1089
+ ]);
958
1090
  return rows.length > 0 ? `
959
- ${label}
960
- ${rows.join("\n")}` : `
961
- ${label}
962
- No components`;
1091
+ ${badge(label, tone)}
1092
+ ${table(["Slug", "Category", "Access"], rows)}` : `
1093
+ ${badge(label, tone)}
1094
+ ${action("No components")}`;
963
1095
  }
964
1096
  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";
1097
+ const rows = blocks.map((block) => [
1098
+ block.slug,
1099
+ block.category ?? "uncategorized",
1100
+ badge("Pro", "pro")
1101
+ ]);
1102
+ return rows.length > 0 ? `${table(["Slug", "Category", "Access"], rows)}` : action("No blocks");
972
1103
  }
973
1104
  async function addCommand(context, parsed, positionals, updateMode) {
974
1105
  const resourceType = getResourceType(parsed);
@@ -994,54 +1125,93 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
994
1125
  const plannedFiles = summarizeFiles(loaded.projectRoot, prepared.files);
995
1126
  const missing = missingDependencies(loaded.projectRoot, prepared.dependencies);
996
1127
  if (dryRun) {
997
- print("Would write:");
1128
+ printBrand(updateMode ? "Update preview" : "Install preview");
1129
+ print(action("Would write:"));
998
1130
  print(formatList(plannedFiles));
999
- if (install && missing.length > 0) print(`Would install: ${missing.join(" ")}`);
1131
+ if (install && missing.length > 0) print(action(`Would install: ${missing.join(" ")}`));
1000
1132
  return;
1001
1133
  }
1002
1134
  await writePlannedFiles(prepared.files);
1003
1135
  if (install) {
1004
1136
  runInstall(loaded.projectRoot, missing);
1005
1137
  }
1006
- print(`${updateMode ? "Updated" : "Added"} ${payload.name}.`);
1007
- print("Files written:");
1008
- print(formatList(plannedFiles));
1138
+ printBrand(updateMode ? "Component updated" : "Component installed");
1139
+ print(panel(payload.name, [
1140
+ success(`${updateMode ? "Updated" : "Added"} ${payload.slug}`),
1141
+ ...plannedFiles.map((file) => success(file))
1142
+ ]));
1009
1143
  if (!install && missing.length > 0) {
1010
1144
  warn(`Dependencies were not installed: ${missing.join(" ")}`);
1145
+ print(nextSteps([`Install dependencies: ${missing.join(" ")}`]));
1146
+ return;
1011
1147
  }
1148
+ print(nextSteps([
1149
+ "Import the component from your Wensity components folder",
1150
+ resourceType === "block" ? "Customize the block copy, colors, and sections" : "Customize props and styling for your app"
1151
+ ]));
1012
1152
  }
1013
1153
  async function fetchComponentPayload(input) {
1014
- info(`Fetching ${input.slug}...`);
1015
- const metadata = await requestJson(registryMetadataUrl(input.registry, input.slug));
1154
+ const metadata = await withSpinner(
1155
+ `Finding ${input.slug}`,
1156
+ () => requestJson(registryMetadataUrl(input.registry, input.slug)),
1157
+ `Found ${input.slug}`
1158
+ );
1016
1159
  if (input.credentials) {
1160
+ const credentials = input.credentials;
1017
1161
  const cliEndpoint = metadata.endpoints?.cli ?? `/api/cli/${input.slug}`;
1018
- const endpoint2 = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(input.credentials.apiBaseUrl, cliEndpoint);
1162
+ const endpoint2 = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(credentials.apiBaseUrl, cliEndpoint);
1019
1163
  try {
1020
- return await requestJson(endpoint2, { token: input.credentials.token });
1164
+ return await withSpinner(
1165
+ `Downloading ${metadata.name}`,
1166
+ () => requestJson(endpoint2, { token: credentials.token }),
1167
+ `Downloaded ${metadata.name}`
1168
+ );
1021
1169
  } catch (error) {
1022
1170
  if (metadata.access === "pro" || !isAuthFailure(error)) throw error;
1023
1171
  warn("Saved Wensity credentials were rejected; falling back to the public free registry payload.");
1024
1172
  }
1025
1173
  }
1026
1174
  if (metadata.access === "pro") {
1027
- throw new CliError(`${metadata.name} is a premium component. Run wensity login, then try again.`);
1175
+ throw new CliError([
1176
+ `${metadata.name} is a Pro component.`,
1177
+ `Create an API token from the Wensity dashboard, then run:`,
1178
+ ` npx wensity login --token <token>`,
1179
+ `Upgrade: https://ui.wensity.com/pricing`
1180
+ ].join("\n"));
1028
1181
  }
1029
1182
  const publicSourceEndpoint = metadata.endpoints?.registry ?? registrySlugUrl(input.registry, input.slug);
1030
1183
  const endpoint = publicSourceEndpoint.startsWith("http") ? publicSourceEndpoint : joinUrl(registryOrigin(input.registry), publicSourceEndpoint);
1031
- return requestJson(endpoint);
1184
+ return withSpinner(
1185
+ `Downloading ${metadata.name}`,
1186
+ () => requestJson(endpoint),
1187
+ `Downloaded ${metadata.name}`
1188
+ );
1032
1189
  }
1033
1190
  function isAuthFailure(error) {
1034
1191
  return error instanceof CliError && /^(401|403)\b/.test(error.message);
1035
1192
  }
1036
1193
  async function fetchBlockPayload(input) {
1037
- info(`Fetching ${input.slug}...`);
1038
- const metadata = await requestJson(registryMetadataUrl(input.registry, input.slug));
1194
+ const metadata = await withSpinner(
1195
+ `Finding ${input.slug}`,
1196
+ () => requestJson(registryMetadataUrl(input.registry, input.slug)),
1197
+ `Found ${input.slug}`
1198
+ );
1039
1199
  if (!input.credentials) {
1040
- throw new CliError(`${metadata.name} is a premium block. Run wensity login, then try again.`);
1200
+ throw new CliError([
1201
+ `${metadata.name} is a Pro block.`,
1202
+ `Create an API token from the Wensity dashboard, then run:`,
1203
+ ` npx wensity login --token <token>`,
1204
+ `Upgrade: https://ui.wensity.com/pricing`
1205
+ ].join("\n"));
1041
1206
  }
1207
+ const credentials = input.credentials;
1042
1208
  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 });
1209
+ const endpoint = cliEndpoint.startsWith("http") ? cliEndpoint : joinUrl(credentials.apiBaseUrl, cliEndpoint);
1210
+ return withSpinner(
1211
+ `Downloading ${metadata.name}`,
1212
+ () => requestJson(endpoint, { token: credentials.token }),
1213
+ `Downloaded ${metadata.name}`
1214
+ );
1045
1215
  }
1046
1216
  function getResourceType(parsed) {
1047
1217
  const value = getStringOption(parsed, "type") ?? "component";
@@ -1072,6 +1242,32 @@ function registryOrigin(registry) {
1072
1242
  const url = new URL(registry);
1073
1243
  return `${url.protocol}//${url.host}`;
1074
1244
  }
1245
+ function formatHelp() {
1246
+ return [
1247
+ brandBanner("Own your component source."),
1248
+ "",
1249
+ HELP_TEXT.trimEnd(),
1250
+ "",
1251
+ nextSteps([
1252
+ "npx wensity init",
1253
+ "npx wensity list",
1254
+ "npx wensity add liquid-multimodal-input"
1255
+ ])
1256
+ ].join("\n");
1257
+ }
1258
+ function formatAccountPanel(whoami, savedPath) {
1259
+ const isPro = whoami.plan?.tier === "pro";
1260
+ const lines = [
1261
+ `${badge(isPro ? "Pro" : "Free", isPro ? "pro" : "free")} ${whoami.user.email}`,
1262
+ whoami.plan?.status ? `Status: ${whoami.plan.status}` : "Status: active",
1263
+ `Credentials: ${savedPath}`,
1264
+ "",
1265
+ isPro ? success("Pro components unlocked") : success("Free components unlocked"),
1266
+ isPro ? success("Pro blocks unlocked") : action("Pro blocks locked until upgrade"),
1267
+ isPro ? success("API token verified") : action("Upgrade for premium source access")
1268
+ ];
1269
+ return panel(isPro ? "Wensity Pro" : "Wensity Free", lines);
1270
+ }
1075
1271
  async function writeTextFile(path, content) {
1076
1272
  await mkdir4(dirname5(path), { recursive: true });
1077
1273
  await writeFile4(path, content, "utf8");
@@ -1084,7 +1280,7 @@ run({
1084
1280
  env: process.env
1085
1281
  }).catch((error) => {
1086
1282
  if (error instanceof CliError) {
1087
- process.stderr.write(`${error.message}
1283
+ process.stderr.write(`${formatCliError(error.message)}
1088
1284
  `);
1089
1285
  process.exitCode = error.exitCode;
1090
1286
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wensity",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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
  }