vectorvesper 0.1.1 → 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 +665 -257
  2. package/package.json +8 -5
package/dist/index.js CHANGED
@@ -144,7 +144,17 @@ var RegistryComponentSchema = z.object({
144
144
  supportsReducedMotion: z.boolean(),
145
145
  usesWebGL: z.boolean(),
146
146
  usesPointer: z.boolean(),
147
- usesScroll: z.boolean()
147
+ usesScroll: z.boolean(),
148
+ // Optional metadata — kept in sync with the registry builder schema.
149
+ // Declared here (rather than relying on .passthrough()) so the CLI can
150
+ // read them with full type-safety for post-install guidance.
151
+ entry: z.string().optional(),
152
+ exportName: z.string().optional(),
153
+ usesTailwind: z.boolean().optional(),
154
+ fallbacks: z.array(z.string()).optional(),
155
+ recipes: z.array(z.string()).optional(),
156
+ docsUrl: z.string().optional(),
157
+ previewUrl: z.string().optional()
148
158
  }).passthrough();
149
159
  var IndexComponentSchema = z.object({
150
160
  slug: z.string(),
@@ -168,7 +178,10 @@ var IndexComponentSchema = z.object({
168
178
  supportsReducedMotion: z.boolean(),
169
179
  usesWebGL: z.boolean(),
170
180
  usesPointer: z.boolean(),
171
- usesScroll: z.boolean()
181
+ usesScroll: z.boolean(),
182
+ entry: z.string().optional(),
183
+ exportName: z.string().optional(),
184
+ usesTailwind: z.boolean().optional()
172
185
  }).passthrough();
173
186
  var RegistryIndexSchema = z.object({
174
187
  version: z.string(),
@@ -218,7 +231,7 @@ function loadConfig(cwd = process.cwd()) {
218
231
  raw = JSON.parse(content);
219
232
  } catch {
220
233
  throw new Error(
221
- `Failed to parse vv.config.json \u2014 the file contains invalid JSON. Delete it and run "vv init" to regenerate.`
234
+ `Failed to parse vv.config.json \u2014 the file contains invalid JSON. Delete it and run "npx vectorvesper init" to regenerate.`
222
235
  );
223
236
  }
224
237
  const result = VVConfigSchema.safeParse(raw);
@@ -227,7 +240,7 @@ function loadConfig(cwd = process.cwd()) {
227
240
  throw new Error(
228
241
  `Invalid vv.config.json \u2014 schema validation failed:
229
242
  ${issues}
230
- Delete the file and run "vv init" to regenerate.`
243
+ Delete the file and run "npx vectorvesper init" to regenerate.`
231
244
  );
232
245
  }
233
246
  return result.data;
@@ -385,7 +398,7 @@ async function initCommand(options = {}) {
385
398
  \u2022 Target directory created at ${pc2.cyan(targetLocalDir)}
386
399
  \u2022 Manifest created at ${pc2.cyan("vv-manifest.json")}
387
400
 
388
- \u{1F449} Next, run: ${pc2.bold(pc2.cyan("vv add <slug>"))} to add a component.`
401
+ \u{1F449} Next, run: ${pc2.bold(pc2.cyan("npx vectorvesper add <slug>"))} to add a component.`
389
402
  );
390
403
  } catch (error) {
391
404
  const message = error instanceof Error ? error.message : String(error);
@@ -414,25 +427,25 @@ function handleHttpError(status, url) {
414
427
  case 401:
415
428
  throw new Error(
416
429
  `Authentication required. This component requires a valid license key.
417
- \u{1F449} Run "vv login <your-license-key>" to authenticate.
430
+ \u{1F449} Run "npx vectorvesper login <your-license-key>" to authenticate.
418
431
  \u{1F511} Get a license at https://vectorvesper.dev/pricing`
419
432
  );
420
433
  case 402:
421
434
  throw new Error(
422
- `This is a Pro component and requires an active subscription.
423
- \u{1F449} Upgrade at https://vectorvesper.dev/pricing
424
- \u{1F4A1} Run "vv list" to see all free components.`
435
+ `This is a Pro component and requires a license.
436
+ \u{1F449} Get lifetime access at https://vectorvesper.dev/pricing
437
+ \u{1F4A1} Run "npx vectorvesper list" to see all free components.`
425
438
  );
426
439
  case 403:
427
440
  throw new Error(
428
- `Access denied. Your license may have expired or doesn't include this component.
441
+ `Access denied. Your license doesn't include this component.
429
442
  \u{1F449} Check your account at https://vectorvesper.dev/account
430
- \u{1F4A1} Run "vv whoami" to see your current plan.`
443
+ \u{1F4A1} Run "npx vectorvesper whoami" to see your current access.`
431
444
  );
432
445
  case 404:
433
446
  throw new Error(
434
447
  `Component not found in the registry.
435
- \u{1F449} Run "vv list" to see all available components.`
448
+ \u{1F449} Run "npx vectorvesper list" to see all available components.`
436
449
  );
437
450
  case 429:
438
451
  throw new Error(
@@ -586,10 +599,10 @@ async function listCommand() {
586
599
  console.log(`
587
600
  ${pc3.green(String(freeCount))} free components${proCount > 0 ? ` \xB7 ${pc3.magenta(String(proCount))} pro components` : ""}`);
588
601
  if (proCount > 0 && !hasAuth) {
589
- console.log(` \u{1F511} ${pc3.dim("Unlock pro components:")} ${pc3.cyan("vv login <key>")} ${pc3.dim("\xB7")} ${pc3.cyan("https://vectorvesper.dev/pricing")}`);
602
+ console.log(` \u{1F511} ${pc3.dim("Unlock pro components:")} ${pc3.cyan("npx vectorvesper login <key>")} ${pc3.dim("\xB7")} ${pc3.cyan("https://vectorvesper.dev/pricing")}`);
590
603
  }
591
604
  console.log(`
592
- \u{1F449} Run ${pc3.bold(pc3.cyan("vv add <slug>"))} to add a component to your project.
605
+ \u{1F449} Run ${pc3.bold(pc3.cyan("npx vectorvesper add <slug>"))} to add a component to your project.
593
606
  `);
594
607
  } catch (error) {
595
608
  const message = error instanceof Error ? error.message : String(error);
@@ -599,43 +612,281 @@ async function listCommand() {
599
612
  }
600
613
 
601
614
  // src/commands/add.ts
615
+ import fs7 from "fs";
616
+ import path7 from "path";
617
+ import ora2 from "ora";
618
+ import pc6 from "picocolors";
619
+ import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
620
+
621
+ // src/utils/installer.ts
602
622
  import fs5 from "fs";
603
623
  import path5 from "path";
604
- import ora2 from "ora";
605
624
  import pc4 from "picocolors";
606
- import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
625
+ var MAX_DEPTH = 20;
607
626
  function toForwardSlash(p) {
608
627
  return p.replace(/\\/g, "/");
609
628
  }
610
- function reportMissingDependencies(resolvedComponents, projectRoot, projectInfo) {
611
- const pkgPath = path5.join(projectRoot, "package.json");
612
- if (!fs5.existsSync(pkgPath)) return;
629
+ async function resolveComponentTree(slug) {
630
+ const resolved = [];
631
+ const visited = /* @__PURE__ */ new Set();
632
+ async function walk(componentSlug, depth) {
633
+ if (visited.has(componentSlug)) return;
634
+ if (depth > MAX_DEPTH) {
635
+ throw new Error(
636
+ `Dependency resolution exceeded maximum depth (${MAX_DEPTH}). Possible circular dependency involving "${componentSlug}".`
637
+ );
638
+ }
639
+ visited.add(componentSlug);
640
+ const component = await fetchComponent(componentSlug);
641
+ if (component.tier === "pro" && !getAuthToken()) {
642
+ throw new Error(
643
+ `"${componentSlug}" is a Pro component and requires a license.
644
+ \u{1F449} Run ${pc4.cyan("npx vectorvesper login <your-license-key>")} to unlock pro components.
645
+ \u{1F511} Get a license at ${pc4.cyan("https://vectorvesper.dev/pricing")}`
646
+ );
647
+ }
648
+ for (const depSlug of component.registryDependencies ?? []) {
649
+ await walk(depSlug, depth + 1);
650
+ }
651
+ resolved.push(component);
652
+ }
653
+ await walk(slug, 0);
654
+ return resolved;
655
+ }
656
+ function planFiles(components, installDir, projectRoot) {
657
+ const planned = [];
658
+ const compDir = path5.join(projectRoot, installDir);
659
+ for (const component of components) {
660
+ for (const file of component.files) {
661
+ const absolutePath = path5.resolve(compDir, file.target);
662
+ const relativeFromRoot = path5.relative(projectRoot, absolutePath);
663
+ if (relativeFromRoot.startsWith("..") || path5.isAbsolute(relativeFromRoot)) {
664
+ throw new Error(
665
+ `Security Error: component target tries to escape project root: ${file.target}`
666
+ );
667
+ }
668
+ planned.push({
669
+ absolutePath,
670
+ relativePath: relativeFromRoot,
671
+ target: file.target,
672
+ content: file.content,
673
+ exists: fs5.existsSync(absolutePath)
674
+ });
675
+ }
676
+ }
677
+ return planned;
678
+ }
679
+ function writeFiles(files) {
680
+ for (const file of files) {
681
+ const folder = path5.dirname(file.absolutePath);
682
+ if (!fs5.existsSync(folder)) {
683
+ fs5.mkdirSync(folder, { recursive: true });
684
+ }
685
+ fs5.writeFileSync(file.absolutePath, file.content, "utf-8");
686
+ }
687
+ }
688
+ var MANIFEST_NAME = "vv-manifest.json";
689
+ function getManifestPath(projectRoot) {
690
+ return path5.join(projectRoot, MANIFEST_NAME);
691
+ }
692
+ function readManifest(projectRoot) {
693
+ const manifestPath = getManifestPath(projectRoot);
694
+ if (!fs5.existsSync(manifestPath)) {
695
+ return { components: {} };
696
+ }
697
+ const parsed = JSON.parse(fs5.readFileSync(manifestPath, "utf-8"));
698
+ if (!parsed.components) parsed.components = {};
699
+ return parsed;
700
+ }
701
+ function writeManifest(projectRoot, manifest) {
702
+ fs5.writeFileSync(getManifestPath(projectRoot), JSON.stringify(manifest, null, 2), "utf-8");
703
+ }
704
+ function recordInstall(components, installDir, projectRoot) {
705
+ const manifest = readManifest(projectRoot);
706
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
707
+ for (const component of components) {
708
+ manifest.components[component.slug] = {
709
+ version: component.version,
710
+ installedAt: toForwardSlash(path5.join(installDir, component.slug)),
711
+ installedOn: today,
712
+ files: component.files.map((f) => f.target)
713
+ };
714
+ }
715
+ writeManifest(projectRoot, manifest);
716
+ }
717
+
718
+ // src/utils/install.ts
719
+ import fs6 from "fs";
720
+ import path6 from "path";
721
+ import { spawnSync } from "child_process";
722
+ var NPM_NAME_REGEX = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
723
+ function isValidPackageName(name) {
724
+ return NPM_NAME_REGEX.test(name);
725
+ }
726
+ function getMissingDependencies(components, projectRoot) {
727
+ const pkgPath = path6.join(projectRoot, "package.json");
728
+ if (!fs6.existsSync(pkgPath)) return [];
729
+ let installed = {};
613
730
  try {
614
- const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
615
- const installedDeps = {
731
+ const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
732
+ installed = {
616
733
  ...pkg.dependencies,
617
734
  ...pkg.devDependencies,
618
735
  ...pkg.peerDependencies
619
736
  };
620
- const missingDeps = /* @__PURE__ */ new Set();
621
- for (const component of resolvedComponents) {
622
- if (component.dependencies) {
623
- for (const dep of component.dependencies) {
624
- if (!installedDeps[dep]) {
625
- missingDeps.add(dep);
626
- }
627
- }
628
- }
737
+ } catch {
738
+ return [];
739
+ }
740
+ const missing = /* @__PURE__ */ new Set();
741
+ for (const component of components) {
742
+ for (const dep of component.dependencies ?? []) {
743
+ if (!installed[dep]) missing.add(dep);
629
744
  }
630
- if (missingDeps.size > 0) {
631
- console.log(pc4.yellow(`
632
- \u26A0\uFE0F Missing peer dependencies in your project:`));
633
- const pm = projectInfo.packageManager;
634
- const depsStr = Array.from(missingDeps).join(" ");
635
- const installCmd = pm === "pnpm" ? `pnpm add ${depsStr}` : pm === "yarn" ? `yarn add ${depsStr}` : pm === "bun" ? `bun add ${depsStr}` : `npm install ${depsStr}`;
636
- console.log(` Run: ${pc4.bold(pc4.cyan(installCmd))}`);
745
+ }
746
+ return Array.from(missing);
747
+ }
748
+ function getInstallCommand(pm, deps) {
749
+ const depsStr = deps.join(" ");
750
+ switch (pm) {
751
+ case "pnpm":
752
+ return `pnpm add ${depsStr}`;
753
+ case "yarn":
754
+ return `yarn add ${depsStr}`;
755
+ case "bun":
756
+ return `bun add ${depsStr}`;
757
+ default:
758
+ return `npm install ${depsStr}`;
759
+ }
760
+ }
761
+ function installDependencies(deps, pm, projectRoot) {
762
+ const invalid = deps.filter((d) => !isValidPackageName(d));
763
+ if (invalid.length > 0) {
764
+ throw new Error(`Refusing to install \u2014 invalid package name(s): ${invalid.join(", ")}`);
765
+ }
766
+ const command = getInstallCommand(pm, deps);
767
+ const result = spawnSync(command, {
768
+ cwd: projectRoot,
769
+ stdio: "inherit",
770
+ shell: true
771
+ });
772
+ return result.status === 0;
773
+ }
774
+
775
+ // src/utils/guidance.ts
776
+ import pc5 from "picocolors";
777
+ function toPascalCase(input) {
778
+ return input.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
779
+ }
780
+ function toImportPath(alias, entryTarget) {
781
+ let p = entryTarget.replace(/\.(tsx|ts|jsx|js)$/i, "");
782
+ p = p.replace(/\/index$/i, "");
783
+ const base = alias.replace(/\/+$/, "");
784
+ return `${base}/${p}`;
785
+ }
786
+ function buildGuidance(component, ctx) {
787
+ const entryTarget = component.entry ?? component.files[0]?.target ?? `${component.slug}/index.ts`;
788
+ const importPath = toImportPath(ctx.alias, entryTarget);
789
+ const exportName = component.exportName ?? "default";
790
+ const isDefault = exportName === "default";
791
+ const varName = isDefault ? toPascalCase(component.name || component.slug) : exportName;
792
+ const importSnippet = isDefault ? `import ${varName} from "${importPath}";` : `import { ${exportName} } from "${importPath}";`;
793
+ const guidance = {
794
+ importSnippet,
795
+ notes: [],
796
+ warnings: [],
797
+ docsUrl: component.docsUrl
798
+ };
799
+ if (component.usesWebGL) {
800
+ const resolver = isDefault ? `() => import("${importPath}")` : `() => import("${importPath}").then((m) => m.${exportName})`;
801
+ if (ctx.framework === "next") {
802
+ guidance.ssrSnippet = `"use client";
803
+ import dynamic from "next/dynamic";
804
+
805
+ const ${varName} = dynamic(
806
+ ${resolver},
807
+ { ssr: false }
808
+ );`;
809
+ } else if (ctx.framework === "unknown") {
810
+ guidance.notes.push(
811
+ "This is a WebGL component. If you server-render (e.g. Next.js), load it with `next/dynamic` and `{ ssr: false }` to avoid hydration errors."
812
+ );
637
813
  }
638
- } catch {
814
+ }
815
+ if (component.supportsReducedMotion) {
816
+ guidance.notes.push("Honors `prefers-reduced-motion` automatically \u2014 no extra setup needed.");
817
+ }
818
+ if (component.usesTailwind && !ctx.hasTailwind) {
819
+ guidance.warnings.push(
820
+ "Styled with Tailwind CSS, which wasn't detected in this project. Install Tailwind or swap the classNames for your own styles."
821
+ );
822
+ }
823
+ return guidance;
824
+ }
825
+ function printGuidance(component, ctx) {
826
+ const g = buildGuidance(component, ctx);
827
+ console.log(pc5.bold(pc5.white(`
828
+ Using ${component.title || component.name}:`)));
829
+ if (g.ssrSnippet) {
830
+ console.log(pc5.dim(" // SSR-safe (Next.js) \u2014 disables server rendering for WebGL:"));
831
+ for (const line of g.ssrSnippet.split("\n")) {
832
+ console.log(line ? ` ${pc5.cyan(line)}` : "");
833
+ }
834
+ } else {
835
+ console.log(` ${pc5.cyan(g.importSnippet)}`);
836
+ }
837
+ for (const note of g.notes) {
838
+ console.log(` ${pc5.green("\u2022")} ${pc5.dim(note)}`);
839
+ }
840
+ for (const warning of g.warnings) {
841
+ console.log(` ${pc5.yellow("\u26A0")} ${pc5.yellow(warning)}`);
842
+ }
843
+ if (g.docsUrl) {
844
+ console.log(` ${pc5.dim("Docs:")} ${pc5.cyan(g.docsUrl)}`);
845
+ }
846
+ }
847
+
848
+ // src/commands/add.ts
849
+ async function handleDependencies(components, projectRoot, projectInfo, options) {
850
+ const missing = getMissingDependencies(components, projectRoot);
851
+ if (missing.length === 0) return;
852
+ const installCmd = getInstallCommand(projectInfo.packageManager, missing);
853
+ const depsLabel = missing.map((d) => pc6.cyan(d)).join(", ");
854
+ if (options.install === false) {
855
+ console.log(pc6.yellow(`
856
+ \u26A0\uFE0F Missing dependencies: ${depsLabel}`));
857
+ console.log(` Run: ${pc6.bold(pc6.cyan(installCmd))}`);
858
+ return;
859
+ }
860
+ let shouldInstall = options.yes === true;
861
+ if (!shouldInstall) {
862
+ console.log("");
863
+ const answer = await confirm2({
864
+ message: `Install ${missing.length} missing ${missing.length === 1 ? "dependency" : "dependencies"} (${missing.join(", ")}) with ${projectInfo.packageManager}?`,
865
+ initialValue: true
866
+ });
867
+ if (isCancel2(answer)) {
868
+ console.log(pc6.dim(` Skipped. Run: ${pc6.cyan(installCmd)}`));
869
+ return;
870
+ }
871
+ shouldInstall = answer;
872
+ }
873
+ if (!shouldInstall) {
874
+ console.log(pc6.dim(` Skipped. Run: ${pc6.cyan(installCmd)} when ready.`));
875
+ return;
876
+ }
877
+ console.log(pc6.dim(`
878
+ Installing dependencies with ${projectInfo.packageManager}...`));
879
+ try {
880
+ const ok = installDependencies(missing, projectInfo.packageManager, projectRoot);
881
+ if (ok) {
882
+ console.log(pc6.green(`\u2714 Dependencies installed.`));
883
+ } else {
884
+ console.log(pc6.yellow(`\u26A0\uFE0F Dependency install did not complete. Run manually: ${pc6.cyan(installCmd)}`));
885
+ }
886
+ } catch (error) {
887
+ const message = error instanceof Error ? error.message : String(error);
888
+ console.log(pc6.yellow(`\u26A0\uFE0F ${message}`));
889
+ console.log(` Run manually: ${pc6.bold(pc6.cyan(installCmd))}`);
639
890
  }
640
891
  }
641
892
  async function addCommand(slug, options = {}) {
@@ -643,7 +894,7 @@ async function addCommand(slug, options = {}) {
643
894
  validateSlug(slug);
644
895
  } catch (error) {
645
896
  const message = error instanceof Error ? error.message : String(error);
646
- console.error(`${pc4.red(pc4.bold("Error:"))} ${message}`);
897
+ console.error(`${pc6.red(pc6.bold("Error:"))} ${message}`);
647
898
  process.exit(1);
648
899
  }
649
900
  let config;
@@ -651,173 +902,324 @@ async function addCommand(slug, options = {}) {
651
902
  config = loadConfig();
652
903
  } catch (error) {
653
904
  const message = error instanceof Error ? error.message : String(error);
654
- console.error(`${pc4.red(pc4.bold("Error:"))} ${message}`);
905
+ console.error(`${pc6.red(pc6.bold("Error:"))} ${message}`);
655
906
  process.exit(1);
656
907
  }
657
908
  if (!config) {
658
909
  console.error(
659
- `${pc4.red(pc4.bold("Error:"))} No ${pc4.cyan("vv.config.json")} found in this directory.
660
- \u{1F449} Run ${pc4.bold(pc4.cyan("vv init"))} first to set up your project.`
910
+ `${pc6.red(pc6.bold("Error:"))} No ${pc6.cyan("vv.config.json")} found in this directory.
911
+ \u{1F449} Run ${pc6.bold(pc6.cyan("npx vectorvesper init"))} first to set up your project.`
661
912
  );
662
913
  process.exit(1);
663
914
  return;
664
915
  }
665
916
  const projectInfo = detectProject();
666
917
  const componentInstallDir = resolveAlias(config.aliases.vv, projectInfo.hasSrcDir);
667
- const spinner = ora2({
668
- text: `Resolving component ${pc4.cyan(slug)}...`,
669
- color: "cyan"
670
- }).start();
671
- const resolvedComponents = [];
672
- const visited = /* @__PURE__ */ new Set();
673
- async function resolveComponentTree(componentSlug, depth = 0) {
674
- if (visited.has(componentSlug)) return;
675
- if (depth > 20) {
676
- throw new Error(`Dependency resolution exceeded maximum depth (20). Possible circular dependency involving "${componentSlug}".`);
677
- }
678
- visited.add(componentSlug);
679
- const component = await fetchComponent(componentSlug);
680
- if (component.tier === "pro" && !getAuthToken()) {
681
- throw new Error(
682
- `"${componentSlug}" is a Pro component and requires authentication.
683
- \u{1F449} Run ${pc4.cyan("vv login <your-license-key>")} to unlock pro components.
684
- \u{1F511} Get a key at ${pc4.cyan("https://vectorvesper.dev/pricing")}`
685
- );
686
- }
687
- if (component.registryDependencies && component.registryDependencies.length > 0) {
688
- for (const depSlug of component.registryDependencies) {
689
- await resolveComponentTree(depSlug, depth + 1);
690
- }
691
- }
692
- resolvedComponents.push(component);
693
- }
918
+ const projectRoot = process.cwd();
919
+ const spinner = ora2({ text: `Resolving component ${pc6.cyan(slug)}...`, color: "cyan" }).start();
920
+ let resolvedComponents;
694
921
  try {
695
- await resolveComponentTree(slug);
922
+ resolvedComponents = await resolveComponentTree(slug);
696
923
  } catch (error) {
697
924
  const message = error instanceof Error ? error.message : String(error);
698
- spinner.fail(pc4.red(`Failed to resolve component "${slug}": ${message}`));
925
+ spinner.fail(pc6.red(`Failed to resolve component "${slug}": ${message}`));
699
926
  process.exit(1);
927
+ return;
700
928
  }
701
929
  spinner.text = "Checking file conflicts...";
930
+ let planned;
931
+ try {
932
+ planned = planFiles(resolvedComponents, componentInstallDir, projectRoot);
933
+ } catch (error) {
934
+ const message = error instanceof Error ? error.message : String(error);
935
+ spinner.fail(pc6.red(message));
936
+ process.exit(1);
937
+ return;
938
+ }
702
939
  const filesToWrite = [];
703
- const projectRoot = process.cwd();
704
- for (const component of resolvedComponents) {
705
- const compDir = path5.join(projectRoot, componentInstallDir);
706
- for (const file of component.files) {
707
- const fileWritePath = path5.resolve(compDir, file.target);
708
- const relativeFromRoot = path5.relative(projectRoot, fileWritePath);
709
- if (relativeFromRoot.startsWith("..") || path5.isAbsolute(relativeFromRoot)) {
710
- spinner.fail(
711
- `${pc4.red(pc4.bold("Security Error:"))} Component target file path tries to escape project root: ${file.target}`
712
- );
713
- process.exit(1);
714
- }
715
- if (fs5.existsSync(fileWritePath)) {
716
- if (options.dryRun) {
717
- } else if (!options.overwrite && !options.yes) {
718
- spinner.stop();
719
- const relativeDisplayPath = path5.relative(projectRoot, fileWritePath);
720
- const shouldOverwrite = await confirm2({
721
- message: pc4.yellow(`File already exists: "${relativeDisplayPath}". Overwrite?`),
722
- initialValue: false
723
- });
724
- if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
725
- console.log(pc4.dim(` Skipping existing file: ${relativeDisplayPath}`));
726
- spinner.start("Continuing installation...");
727
- continue;
728
- }
729
- spinner.start("Continuing installation...");
730
- }
731
- }
732
- filesToWrite.push({
733
- absolutePath: fileWritePath,
734
- relativePath: relativeFromRoot,
735
- content: file.content
940
+ for (const file of planned) {
941
+ if (file.exists && !options.dryRun && !options.overwrite && !options.yes) {
942
+ spinner.stop();
943
+ const answer = await confirm2({
944
+ message: pc6.yellow(`File already exists: "${file.relativePath}". Overwrite?`),
945
+ initialValue: false
736
946
  });
947
+ if (isCancel2(answer) || !answer) {
948
+ console.log(pc6.dim(` Skipping existing file: ${file.relativePath}`));
949
+ spinner.start("Continuing installation...");
950
+ continue;
951
+ }
952
+ spinner.start("Continuing installation...");
737
953
  }
954
+ filesToWrite.push({
955
+ absolutePath: file.absolutePath,
956
+ relativePath: file.relativePath,
957
+ content: file.content
958
+ });
738
959
  }
960
+ const targetComponent = resolvedComponents[resolvedComponents.length - 1];
961
+ const installPath = toForwardSlash(path7.join(componentInstallDir, targetComponent.slug));
739
962
  if (options.dryRun) {
740
- spinner.succeed(pc4.yellow("Dry-run: Simulation completed. No files were written."));
741
- console.log(pc4.bold(pc4.yellow("\n[Dry Run] Files that would be created/modified:")));
963
+ spinner.succeed(pc6.yellow("Dry-run: simulation completed. No files were written."));
964
+ console.log(pc6.bold(pc6.yellow("\n[Dry Run] Files that would be created/modified:")));
742
965
  for (const file of filesToWrite) {
743
- const status = fs5.existsSync(file.absolutePath) ? pc4.yellow("(exists, would overwrite)") : pc4.green("(new)");
744
- console.log(` ${pc4.cyan(file.relativePath)} ${status}`);
966
+ const status = fs7.existsSync(file.absolutePath) ? pc6.yellow("(exists, would overwrite)") : pc6.green("(new)");
967
+ console.log(` ${pc6.cyan(file.relativePath)} ${status}`);
968
+ }
969
+ const missing = getMissingDependencies(resolvedComponents, projectRoot);
970
+ if (missing.length > 0) {
971
+ console.log(pc6.yellow(`
972
+ \u26A0\uFE0F Would need dependencies: ${missing.map((d) => pc6.cyan(d)).join(", ")}`));
973
+ console.log(` ${pc6.dim(getInstallCommand(projectInfo.packageManager, missing))}`);
745
974
  }
746
- reportMissingDependencies(resolvedComponents, projectRoot, projectInfo);
747
- const targetComponent2 = resolvedComponents[resolvedComponents.length - 1];
748
- console.log("");
749
- console.log(
750
- pc4.bold(pc4.yellow("\u2714 [Dry Run] Would add ")) + pc4.bold(pc4.cyan(targetComponent2.slug)) + pc4.bold(pc4.yellow(` to ${toForwardSlash(path5.join(componentInstallDir, targetComponent2.slug))}`))
751
- );
752
975
  console.log("");
753
- console.log(pc4.gray("\u{1F449} Loving Vector Vesper? Star the repo: https://github.com/vectorvesper/vv-components"));
754
- console.log(pc4.gray("\u{1F680} Need premium WebGL effects? Get the Pixel Library: https://vectorvesper.dev/library"));
976
+ console.log(pc6.bold(pc6.yellow(`\u2714 [Dry Run] Would add ${pc6.cyan(targetComponent.slug)} to ${installPath}`)));
755
977
  console.log("");
756
978
  return;
757
979
  }
758
980
  if (filesToWrite.length === 0) {
759
- spinner.succeed(pc4.green("All files skipped (already up to date)."));
981
+ spinner.succeed(pc6.green("All files skipped (already up to date)."));
760
982
  return;
761
983
  }
762
984
  spinner.text = "Writing component files...";
763
985
  try {
764
- for (const file of filesToWrite) {
765
- const folder = path5.dirname(file.absolutePath);
766
- if (!fs5.existsSync(folder)) {
767
- fs5.mkdirSync(folder, { recursive: true });
768
- }
769
- fs5.writeFileSync(file.absolutePath, file.content, "utf-8");
770
- }
986
+ writeFiles(filesToWrite);
771
987
  } catch (error) {
772
988
  const message = error instanceof Error ? error.message : String(error);
773
- spinner.fail(pc4.red(`Failed to write files: ${message}`));
989
+ spinner.fail(pc6.red(`Failed to write files: ${message}`));
774
990
  process.exit(1);
991
+ return;
775
992
  }
776
993
  try {
777
- const manifestPath = path5.join(projectRoot, "vv-manifest.json");
778
- let manifest = { components: {} };
779
- if (fs5.existsSync(manifestPath)) {
780
- manifest = JSON.parse(fs5.readFileSync(manifestPath, "utf-8"));
781
- }
782
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
783
- for (const component of resolvedComponents) {
784
- manifest.components[component.slug] = {
785
- version: component.version,
786
- installedAt: toForwardSlash(path5.join(componentInstallDir, component.slug)),
787
- installedOn: today,
788
- files: component.files.map((f) => f.target)
789
- };
790
- }
791
- fs5.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
994
+ recordInstall(resolvedComponents, componentInstallDir, projectRoot);
792
995
  } catch (error) {
793
996
  const message = error instanceof Error ? error.message : String(error);
794
997
  logger.warn(`Failed to update vv-manifest.json: ${message}`);
795
- logger.warn("Component was installed but may not appear in 'vv info' or 'vv diff'.");
998
+ logger.warn("Component was installed but may not appear in 'npx vectorvesper info' or 'npx vectorvesper diff'.");
796
999
  }
797
- spinner.succeed(pc4.green("Files written successfully!"));
798
- console.log(pc4.dim("\nCreated files:"));
1000
+ spinner.succeed(pc6.green("Files written successfully!"));
1001
+ console.log(pc6.dim("\nCreated files:"));
799
1002
  for (const file of filesToWrite) {
800
- console.log(` ${pc4.green("\u2714")} ${pc4.cyan(file.relativePath)}`);
1003
+ console.log(` ${pc6.green("\u2714")} ${pc6.cyan(file.relativePath)}`);
801
1004
  }
802
- reportMissingDependencies(resolvedComponents, projectRoot, projectInfo);
803
- const targetComponent = resolvedComponents[resolvedComponents.length - 1];
1005
+ await handleDependencies(resolvedComponents, projectRoot, projectInfo, options);
1006
+ printGuidance(targetComponent, {
1007
+ alias: config.aliases.vv,
1008
+ framework: projectInfo.framework,
1009
+ hasTailwind: projectInfo.hasTailwind
1010
+ });
804
1011
  console.log("");
805
- console.log(
806
- pc4.bold(pc4.green("\u2714 Added ")) + pc4.bold(pc4.cyan(targetComponent.slug)) + pc4.bold(pc4.green(` to ${toForwardSlash(path5.join(componentInstallDir, targetComponent.slug))}`))
807
- );
1012
+ console.log(pc6.bold(pc6.green(`\u2714 Added ${pc6.cyan(targetComponent.slug)} to ${installPath}`)));
1013
+ console.log("");
1014
+ console.log(pc6.gray("\u{1F449} Loving Vector Vesper? Star the repo: https://github.com/vectorvesper/vv-components"));
1015
+ console.log(pc6.gray("\u{1F680} Premium shader & pixel systems: https://vectorvesper.dev/pricing"));
1016
+ console.log("");
1017
+ }
1018
+
1019
+ // src/commands/update.ts
1020
+ import pc7 from "picocolors";
1021
+ import ora3 from "ora";
1022
+ async function updateCommand(slug, options = {}) {
1023
+ const projectRoot = process.cwd();
1024
+ let config;
1025
+ try {
1026
+ config = loadConfig();
1027
+ } catch (error) {
1028
+ const message = error instanceof Error ? error.message : String(error);
1029
+ console.error(`${pc7.red(pc7.bold("Error:"))} ${message}`);
1030
+ process.exit(1);
1031
+ return;
1032
+ }
1033
+ if (!config) {
1034
+ console.error(
1035
+ `${pc7.red(pc7.bold("Error:"))} No ${pc7.cyan("vv.config.json")} found.
1036
+ \u{1F449} Run ${pc7.bold(pc7.cyan("npx vectorvesper init"))} first.`
1037
+ );
1038
+ process.exit(1);
1039
+ return;
1040
+ }
1041
+ const projectInfo = detectProject();
1042
+ const installDir = resolveAlias(config.aliases.vv, projectInfo.hasSrcDir);
1043
+ const manifest = readManifest(projectRoot);
1044
+ const installedSlugs = Object.keys(manifest.components);
1045
+ if (installedSlugs.length === 0) {
1046
+ console.log(pc7.yellow(`
1047
+ \u26A0\uFE0F No components are installed in this project.`));
1048
+ console.log(`\u{1F449} Add one with ${pc7.bold(pc7.cyan("npx vectorvesper add <slug>"))}
1049
+ `);
1050
+ return;
1051
+ }
1052
+ let targets;
1053
+ if (slug) {
1054
+ try {
1055
+ validateSlug(slug);
1056
+ } catch (error) {
1057
+ const message = error instanceof Error ? error.message : String(error);
1058
+ console.error(pc7.red(`
1059
+ \u274C ${message}
1060
+ `));
1061
+ process.exit(1);
1062
+ return;
1063
+ }
1064
+ if (!manifest.components[slug]) {
1065
+ console.log(pc7.red(`
1066
+ \u274C Component "${slug}" is not installed in this project.`));
1067
+ console.log(`\u{1F449} Run ${pc7.cyan(`npx vectorvesper add ${slug}`)} to install it.
1068
+ `);
1069
+ return;
1070
+ }
1071
+ targets = [slug];
1072
+ } else {
1073
+ targets = installedSlugs;
1074
+ }
1075
+ const spinner = ora3({ text: "Checking for updates...", color: "cyan" }).start();
1076
+ const updated = [];
1077
+ const upToDate = [];
1078
+ const failed = [];
1079
+ for (const target of targets) {
1080
+ spinner.text = `Updating ${pc7.cyan(target)}...`;
1081
+ try {
1082
+ const components = await resolveComponentTree(target);
1083
+ const remote = components[components.length - 1];
1084
+ const localVersion = manifest.components[target]?.version;
1085
+ if (localVersion === remote.version && !options.force) {
1086
+ upToDate.push(target);
1087
+ continue;
1088
+ }
1089
+ const planned = planFiles(components, installDir, projectRoot);
1090
+ writeFiles(planned.map((f) => ({ absolutePath: f.absolutePath, content: f.content })));
1091
+ recordInstall(components, installDir, projectRoot);
1092
+ updated.push(`${target} \u2192 ${remote.version}`);
1093
+ } catch (error) {
1094
+ const message = error instanceof Error ? error.message : String(error);
1095
+ failed.push({ slug: target, reason: message });
1096
+ }
1097
+ }
1098
+ spinner.stop();
1099
+ console.log(pc7.bold(pc7.cyan("\nVector Vesper Update\n")));
1100
+ if (updated.length > 0) {
1101
+ console.log(pc7.bold(pc7.green(` Updated (${updated.length}):`)));
1102
+ for (const u of updated) console.log(` ${pc7.green("\u2714")} ${u}`);
1103
+ console.log(pc7.dim(" Note: updating overwrites local edits to these files."));
1104
+ }
1105
+ if (upToDate.length > 0) {
1106
+ console.log(pc7.bold(pc7.gray(`
1107
+ Already up to date (${upToDate.length}):`)));
1108
+ console.log(` ${pc7.dim(upToDate.join(", "))}`);
1109
+ }
1110
+ if (failed.length > 0) {
1111
+ console.log(pc7.bold(pc7.red(`
1112
+ Failed (${failed.length}):`)));
1113
+ for (const f of failed) console.log(` ${pc7.red("\u2716")} ${f.slug}: ${pc7.dim(f.reason)}`);
1114
+ }
808
1115
  console.log("");
809
- console.log(pc4.gray("\u{1F449} Loving Vector Vesper? Star the repo: https://github.com/vectorvesper/vv-components"));
810
- console.log(pc4.gray("\u{1F680} Need premium WebGL effects? Get the Pixel Library: https://vectorvesper.dev/library"));
1116
+ if (failed.length > 0) process.exit(1);
1117
+ }
1118
+
1119
+ // src/commands/remove.ts
1120
+ import fs8 from "fs";
1121
+ import path8 from "path";
1122
+ import pc8 from "picocolors";
1123
+ import { confirm as confirm3, isCancel as isCancel3 } from "@clack/prompts";
1124
+ async function removeCommand(slug, options = {}) {
1125
+ try {
1126
+ validateSlug(slug);
1127
+ } catch (error) {
1128
+ const message = error instanceof Error ? error.message : String(error);
1129
+ console.error(`${pc8.red(pc8.bold("Error:"))} ${message}`);
1130
+ process.exit(1);
1131
+ return;
1132
+ }
1133
+ const projectRoot = process.cwd();
1134
+ let config;
1135
+ try {
1136
+ config = loadConfig();
1137
+ } catch (error) {
1138
+ const message = error instanceof Error ? error.message : String(error);
1139
+ console.error(`${pc8.red(pc8.bold("Error:"))} ${message}`);
1140
+ process.exit(1);
1141
+ return;
1142
+ }
1143
+ if (!config) {
1144
+ console.error(
1145
+ `${pc8.red(pc8.bold("Error:"))} No ${pc8.cyan("vv.config.json")} found.
1146
+ \u{1F449} Run ${pc8.bold(pc8.cyan("npx vectorvesper init"))} first.`
1147
+ );
1148
+ process.exit(1);
1149
+ return;
1150
+ }
1151
+ const manifest = readManifest(projectRoot);
1152
+ const entry = manifest.components[slug];
1153
+ if (!entry) {
1154
+ console.log(pc8.yellow(`
1155
+ \u26A0\uFE0F Component "${slug}" is not installed (not in vv-manifest.json).`));
1156
+ console.log(`\u{1F449} See installed components with ${pc8.bold(pc8.cyan("npx vectorvesper info"))}
1157
+ `);
1158
+ return;
1159
+ }
1160
+ const projectInfo = detectProject();
1161
+ const installDir = resolveAlias(config.aliases.vv, projectInfo.hasSrcDir);
1162
+ const baseDir = path8.join(projectRoot, installDir);
1163
+ const targets = entry.files ?? [];
1164
+ const absoluteFiles = targets.map((t) => path8.resolve(baseDir, t));
1165
+ console.log(pc8.bold(pc8.cyan(`
1166
+ Remove ${slug}
1167
+ `)));
1168
+ console.log(pc8.dim(" The following files will be deleted:"));
1169
+ for (const f of absoluteFiles) {
1170
+ console.log(` ${fs8.existsSync(f) ? pc8.red("\u2212") : pc8.dim("\xB7")} ${pc8.cyan(path8.relative(projectRoot, f))}`);
1171
+ }
1172
+ if (!options.yes) {
1173
+ const answer = await confirm3({
1174
+ message: pc8.yellow(`Delete ${absoluteFiles.length} file(s) for "${slug}"?`),
1175
+ initialValue: false
1176
+ });
1177
+ if (isCancel3(answer) || !answer) {
1178
+ console.log(pc8.dim("\n Cancelled. Nothing was removed.\n"));
1179
+ return;
1180
+ }
1181
+ }
1182
+ let deleted = 0;
1183
+ for (const f of absoluteFiles) {
1184
+ try {
1185
+ if (fs8.existsSync(f)) {
1186
+ fs8.rmSync(f);
1187
+ deleted++;
1188
+ }
1189
+ } catch (error) {
1190
+ const message = error instanceof Error ? error.message : String(error);
1191
+ console.log(pc8.yellow(` \u26A0\uFE0F Could not delete ${path8.relative(projectRoot, f)}: ${message}`));
1192
+ }
1193
+ }
1194
+ const componentDir = path8.resolve(baseDir, slug);
1195
+ try {
1196
+ if (fs8.existsSync(componentDir) && fs8.readdirSync(componentDir).length === 0) {
1197
+ fs8.rmdirSync(componentDir);
1198
+ }
1199
+ } catch {
1200
+ }
1201
+ delete manifest.components[slug];
1202
+ try {
1203
+ writeManifest(projectRoot, manifest);
1204
+ } catch (error) {
1205
+ const message = error instanceof Error ? error.message : String(error);
1206
+ console.log(pc8.yellow(` \u26A0\uFE0F Removed files but failed to update vv-manifest.json: ${message}`));
1207
+ }
1208
+ console.log("");
1209
+ console.log(pc8.bold(pc8.green(`\u2714 Removed ${pc8.cyan(slug)} (${deleted} file(s) deleted).`)));
1210
+ if ((entry.files ?? []).length === 0) {
1211
+ console.log(pc8.dim(" No files were tracked for this component; manifest entry cleared."));
1212
+ }
811
1213
  console.log("");
812
1214
  }
813
1215
 
814
1216
  // src/commands/info.ts
815
- import fs6 from "fs";
816
- import path6 from "path";
817
- import pc5 from "picocolors";
818
- var CLI_VERSION = true ? "0.1.1" : "0.0.0-dev";
1217
+ import fs9 from "fs";
1218
+ import path9 from "path";
1219
+ import pc9 from "picocolors";
1220
+ var CLI_VERSION = true ? "0.2.1" : "0.0.0-dev";
819
1221
  async function infoCommand() {
820
- console.log(pc5.bold(pc5.cyan("\nVector Vesper Diagnostics\n")));
1222
+ console.log(pc9.bold(pc9.cyan("\nVector Vesper Diagnostics\n")));
821
1223
  const projectInfo = detectProject();
822
1224
  let config;
823
1225
  try {
@@ -825,95 +1227,95 @@ async function infoCommand() {
825
1227
  } catch {
826
1228
  config = null;
827
1229
  }
828
- console.log(pc5.bold(pc5.white("Environment:")));
829
- console.log(` \u2022 CLI Version: ${pc5.cyan(CLI_VERSION)}`);
830
- console.log(` \u2022 Node Version: ${pc5.cyan(process.version)}`);
831
- console.log(` \u2022 OS: ${pc5.cyan(`${process.platform} ${process.arch}`)}`);
832
- console.log(` \u2022 Package Manager: ${pc5.cyan(projectInfo.packageManager)}`);
833
- console.log(` \u2022 Framework: ${pc5.cyan(projectInfo.framework)}`);
834
- console.log(` \u2022 TypeScript: ${pc5.cyan(projectInfo.isTypeScript ? "Yes" : "No")}`);
835
- console.log(` \u2022 Has src/ folder: ${pc5.cyan(projectInfo.hasSrcDir ? "Yes" : "No")}`);
836
- console.log(` \u2022 Tailwind CSS: ${pc5.cyan(projectInfo.hasTailwind ? "Yes" : "No")}`);
1230
+ console.log(pc9.bold(pc9.white("Environment:")));
1231
+ console.log(` \u2022 CLI Version: ${pc9.cyan(CLI_VERSION)}`);
1232
+ console.log(` \u2022 Node Version: ${pc9.cyan(process.version)}`);
1233
+ console.log(` \u2022 OS: ${pc9.cyan(`${process.platform} ${process.arch}`)}`);
1234
+ console.log(` \u2022 Package Manager: ${pc9.cyan(projectInfo.packageManager)}`);
1235
+ console.log(` \u2022 Framework: ${pc9.cyan(projectInfo.framework)}`);
1236
+ console.log(` \u2022 TypeScript: ${pc9.cyan(projectInfo.isTypeScript ? "Yes" : "No")}`);
1237
+ console.log(` \u2022 Has src/ folder: ${pc9.cyan(projectInfo.hasSrcDir ? "Yes" : "No")}`);
1238
+ console.log(` \u2022 Tailwind CSS: ${pc9.cyan(projectInfo.hasTailwind ? "Yes" : "No")}`);
837
1239
  console.log("");
838
- console.log(pc5.bold(pc5.white("Authentication:")));
1240
+ console.log(pc9.bold(pc9.white("Authentication:")));
839
1241
  const token = getAuthToken();
840
1242
  if (token) {
841
1243
  const maskedKey = token.slice(0, 6) + "\u2026" + token.slice(-4);
842
- console.log(` \u2022 Status: ${pc5.green("Authenticated")}`);
843
- console.log(` \u2022 Key: ${pc5.cyan(maskedKey)}`);
1244
+ console.log(` \u2022 Status: ${pc9.green("Authenticated")}`);
1245
+ console.log(` \u2022 Key: ${pc9.cyan(maskedKey)}`);
844
1246
  } else {
845
- console.log(` \u2022 Status: ${pc5.yellow("Anonymous (free tier)")}`);
846
- console.log(` ${pc5.dim(" Run")} ${pc5.cyan("vv login <key>")} ${pc5.dim("to unlock pro components")}`);
1247
+ console.log(` \u2022 Status: ${pc9.yellow("Anonymous (free tier)")}`);
1248
+ console.log(` ${pc9.dim(" Run")} ${pc9.cyan("npx vectorvesper login <key>")} ${pc9.dim("to unlock pro components")}`);
847
1249
  }
848
1250
  console.log("");
849
- console.log(pc5.bold(pc5.white("Configuration (vv.config.json):")));
1251
+ console.log(pc9.bold(pc9.white("Configuration (vv.config.json):")));
850
1252
  if (config) {
851
- console.log(` \u2022 Framework Set: ${pc5.cyan(config.framework)}`);
852
- console.log(` \u2022 TypeScript Set: ${pc5.cyan(config.tsx ? "Yes" : "No")}`);
853
- console.log(` \u2022 Component Alias: ${pc5.cyan(config.aliases.vv)}`);
1253
+ console.log(` \u2022 Framework Set: ${pc9.cyan(config.framework)}`);
1254
+ console.log(` \u2022 TypeScript Set: ${pc9.cyan(config.tsx ? "Yes" : "No")}`);
1255
+ console.log(` \u2022 Component Alias: ${pc9.cyan(config.aliases.vv)}`);
854
1256
  } else {
855
- console.log(` ${pc5.yellow("\u26A0\uFE0F No vv.config.json found in this directory.")}`);
1257
+ console.log(` ${pc9.yellow("\u26A0\uFE0F No vv.config.json found in this directory.")}`);
856
1258
  }
857
1259
  console.log("");
858
- console.log(pc5.bold(pc5.white("Installed Components (vv-manifest.json):")));
859
- const manifestPath = path6.join(process.cwd(), "vv-manifest.json");
860
- if (fs6.existsSync(manifestPath)) {
1260
+ console.log(pc9.bold(pc9.white("Installed Components (vv-manifest.json):")));
1261
+ const manifestPath = path9.join(process.cwd(), "vv-manifest.json");
1262
+ if (fs9.existsSync(manifestPath)) {
861
1263
  try {
862
- const manifest = JSON.parse(fs6.readFileSync(manifestPath, "utf-8"));
1264
+ const manifest = JSON.parse(fs9.readFileSync(manifestPath, "utf-8"));
863
1265
  const components = manifest.components || {};
864
1266
  const slugs = Object.keys(components);
865
1267
  if (slugs.length === 0) {
866
- console.log(` ${pc5.dim("No components installed yet.")}`);
1268
+ console.log(` ${pc9.dim("No components installed yet.")}`);
867
1269
  } else {
868
- console.log(pc5.dim(" ------------------------------------------------------------"));
869
- console.log(` ${pc5.bold(pc5.white("Component".padEnd(25)))} ${pc5.bold(pc5.white("Version".padEnd(10)))} ${pc5.bold(pc5.white("Installed At"))}`);
870
- console.log(pc5.dim(" ------------------------------------------------------------"));
1270
+ console.log(pc9.dim(" ------------------------------------------------------------"));
1271
+ console.log(` ${pc9.bold(pc9.white("Component".padEnd(25)))} ${pc9.bold(pc9.white("Version".padEnd(10)))} ${pc9.bold(pc9.white("Installed At"))}`);
1272
+ console.log(pc9.dim(" ------------------------------------------------------------"));
871
1273
  for (const slug of slugs) {
872
1274
  const info = components[slug];
873
1275
  const version2 = info?.version ?? "unknown";
874
1276
  const installedAt = info?.installedAt ?? "unknown";
875
- console.log(` ${pc5.cyan(slug.padEnd(25))} ${pc5.green(version2.padEnd(10))} ${pc5.gray(installedAt)}`);
1277
+ console.log(` ${pc9.cyan(slug.padEnd(25))} ${pc9.green(version2.padEnd(10))} ${pc9.gray(installedAt)}`);
876
1278
  }
877
- console.log(pc5.dim(" ------------------------------------------------------------"));
1279
+ console.log(pc9.dim(" ------------------------------------------------------------"));
878
1280
  }
879
1281
  } catch {
880
- console.log(` ${pc5.red("\u274C Error reading vv-manifest.json: Invalid JSON format.")}`);
1282
+ console.log(` ${pc9.red("\u274C Error reading vv-manifest.json: Invalid JSON format.")}`);
881
1283
  }
882
1284
  } else {
883
- console.log(` ${pc5.dim("No components installed yet (vv-manifest.json not found).")}`);
1285
+ console.log(` ${pc9.dim("No components installed yet (vv-manifest.json not found).")}`);
884
1286
  }
885
1287
  console.log("");
886
1288
  }
887
1289
 
888
1290
  // src/commands/diff.ts
889
- import fs7 from "fs";
890
- import path7 from "path";
891
- import pc6 from "picocolors";
892
- import ora3 from "ora";
1291
+ import fs10 from "fs";
1292
+ import path10 from "path";
1293
+ import pc10 from "picocolors";
1294
+ import ora4 from "ora";
893
1295
  async function diffCommand(slug) {
894
1296
  const projectRoot = process.cwd();
895
- const manifestPath = path7.join(projectRoot, "vv-manifest.json");
896
- if (!fs7.existsSync(manifestPath)) {
897
- console.log(pc6.yellow(`
1297
+ const manifestPath = path10.join(projectRoot, "vv-manifest.json");
1298
+ if (!fs10.existsSync(manifestPath)) {
1299
+ console.log(pc10.yellow(`
898
1300
  \u26A0\uFE0F No vv-manifest.json found in this directory.`));
899
- console.log(`\u{1F449} Add components first using ${pc6.bold(pc6.cyan("vv add <slug>"))}
1301
+ console.log(`\u{1F449} Add components first using ${pc10.bold(pc10.cyan("npx vectorvesper add <slug>"))}
900
1302
  `);
901
1303
  return;
902
1304
  }
903
1305
  let manifest;
904
1306
  try {
905
- manifest = JSON.parse(fs7.readFileSync(manifestPath, "utf-8"));
1307
+ manifest = JSON.parse(fs10.readFileSync(manifestPath, "utf-8"));
906
1308
  } catch {
907
- console.error(pc6.red(`\u274C Error reading vv-manifest.json: Invalid JSON format.`));
1309
+ console.error(pc10.red(`\u274C Error reading vv-manifest.json: Invalid JSON format.`));
908
1310
  process.exit(1);
909
1311
  return;
910
1312
  }
911
1313
  const installedComponents = manifest.components || {};
912
1314
  const installedSlugs = Object.keys(installedComponents);
913
1315
  if (installedSlugs.length === 0) {
914
- console.log(pc6.yellow(`
1316
+ console.log(pc10.yellow(`
915
1317
  \u26A0\uFE0F No components are registered as installed in vv-manifest.json.`));
916
- console.log(`\u{1F449} Add components using ${pc6.bold(pc6.cyan("vv add <slug>"))}
1318
+ console.log(`\u{1F449} Add components using ${pc10.bold(pc10.cyan("npx vectorvesper add <slug>"))}
917
1319
  `);
918
1320
  return;
919
1321
  }
@@ -922,21 +1324,21 @@ async function diffCommand(slug) {
922
1324
  validateSlug(slug);
923
1325
  } catch (error) {
924
1326
  const message = error instanceof Error ? error.message : String(error);
925
- console.error(pc6.red(`
1327
+ console.error(pc10.red(`
926
1328
  \u274C ${message}
927
1329
  `));
928
1330
  process.exit(1);
929
1331
  }
930
1332
  if (!installedComponents[slug]) {
931
- console.log(pc6.red(`
1333
+ console.log(pc10.red(`
932
1334
  \u274C Component "${slug}" is not installed in this project.`));
933
- console.log(`\u{1F449} Run ${pc6.cyan(`vv add ${slug}`)} to install it.
1335
+ console.log(`\u{1F449} Run ${pc10.cyan(`npx vectorvesper add ${slug}`)} to install it.
934
1336
  `);
935
1337
  return;
936
1338
  }
937
1339
  const localInfo = installedComponents[slug];
938
- const spinner = ora3({
939
- text: `Checking updates for ${pc6.cyan(slug)}...`,
1340
+ const spinner = ora4({
1341
+ text: `Checking updates for ${pc10.cyan(slug)}...`,
940
1342
  color: "cyan"
941
1343
  }).start();
942
1344
  try {
@@ -944,39 +1346,39 @@ async function diffCommand(slug) {
944
1346
  spinner.stop();
945
1347
  const localVersion = localInfo.version ?? "unknown";
946
1348
  const remoteVersion = remoteComponent.version;
947
- console.log(pc6.bold(pc6.cyan(`
1349
+ console.log(pc10.bold(pc10.cyan(`
948
1350
  Component Diff: ${slug}`)));
949
- console.log(` \u2022 Local version: ${pc6.yellow(localVersion)}`);
950
- console.log(` \u2022 Latest version: ${pc6.green(remoteVersion)}`);
1351
+ console.log(` \u2022 Local version: ${pc10.yellow(localVersion)}`);
1352
+ console.log(` \u2022 Latest version: ${pc10.green(remoteVersion)}`);
951
1353
  if (localVersion === remoteVersion) {
952
- console.log(` \u2022 Status: ${pc6.green("\u2714 Up to date")}
1354
+ console.log(` \u2022 Status: ${pc10.green("\u2714 Up to date")}
953
1355
  `);
954
1356
  } else {
955
- console.log(` \u2022 Status: ${pc6.bold(pc6.yellow("\u26A0\uFE0F Update available!"))}`);
956
- console.log(` \u2022 Run ${pc6.bold(pc6.cyan(`vv add ${slug} --overwrite`))} to update.
1357
+ console.log(` \u2022 Status: ${pc10.bold(pc10.yellow("\u26A0\uFE0F Update available!"))}`);
1358
+ console.log(` \u2022 Run ${pc10.bold(pc10.cyan(`npx vectorvesper add ${slug} --overwrite`))} to update.
957
1359
  `);
958
1360
  }
959
1361
  } catch (error) {
960
1362
  const message = error instanceof Error ? error.message : String(error);
961
- spinner.fail(pc6.red(`Failed to fetch remote component details: ${message}`));
1363
+ spinner.fail(pc10.red(`Failed to fetch remote component details: ${message}`));
962
1364
  process.exit(1);
963
1365
  }
964
1366
  } else {
965
- const spinner = ora3({
1367
+ const spinner = ora4({
966
1368
  text: "Comparing local components with registry index...",
967
1369
  color: "cyan"
968
1370
  }).start();
969
1371
  try {
970
1372
  const registryIndex = await fetchRegistryIndex();
971
1373
  spinner.stop();
972
- console.log(pc6.bold(pc6.cyan("\nVector Vesper Component Version Comparison\n")));
973
- console.log(pc6.dim(" ----------------------------------------------------------------------"));
1374
+ console.log(pc10.bold(pc10.cyan("\nVector Vesper Component Version Comparison\n")));
1375
+ console.log(pc10.dim(" ----------------------------------------------------------------------"));
974
1376
  console.log(
975
- ` ${pc6.bold(pc6.white("Component".padEnd(25)))} ${pc6.bold(
976
- pc6.white("Local".padEnd(10))
977
- )} ${pc6.bold(pc6.white("Latest".padEnd(10)))} ${pc6.bold(pc6.white("Status"))}`
1377
+ ` ${pc10.bold(pc10.white("Component".padEnd(25)))} ${pc10.bold(
1378
+ pc10.white("Local".padEnd(10))
1379
+ )} ${pc10.bold(pc10.white("Latest".padEnd(10)))} ${pc10.bold(pc10.white("Status"))}`
978
1380
  );
979
- console.log(pc6.dim(" ----------------------------------------------------------------------"));
1381
+ console.log(pc10.dim(" ----------------------------------------------------------------------"));
980
1382
  let updatesAvailableCount = 0;
981
1383
  for (const instSlug of installedSlugs) {
982
1384
  const localInfo = installedComponents[instSlug];
@@ -984,59 +1386,59 @@ Component Diff: ${slug}`)));
984
1386
  const remoteInfo = registryIndex.components.find((c) => c.slug === instSlug);
985
1387
  if (!remoteInfo) {
986
1388
  console.log(
987
- ` ${pc6.cyan(instSlug.padEnd(25))} ${pc6.yellow(
1389
+ ` ${pc10.cyan(instSlug.padEnd(25))} ${pc10.yellow(
988
1390
  localVersion.padEnd(10)
989
- )} ${pc6.red("unknown".padEnd(10))} ${pc6.red("Not found in registry")}`
1391
+ )} ${pc10.red("unknown".padEnd(10))} ${pc10.red("Not found in registry")}`
990
1392
  );
991
1393
  } else {
992
1394
  const remoteVersion = remoteInfo.version;
993
1395
  if (localVersion === remoteVersion) {
994
1396
  console.log(
995
- ` ${pc6.cyan(instSlug.padEnd(25))} ${pc6.gray(
1397
+ ` ${pc10.cyan(instSlug.padEnd(25))} ${pc10.gray(
996
1398
  localVersion.padEnd(10)
997
- )} ${pc6.gray(remoteVersion.padEnd(10))} ${pc6.green("\u2714 Up to date")}`
1399
+ )} ${pc10.gray(remoteVersion.padEnd(10))} ${pc10.green("\u2714 Up to date")}`
998
1400
  );
999
1401
  } else {
1000
1402
  updatesAvailableCount++;
1001
1403
  console.log(
1002
- ` ${pc6.cyan(instSlug.padEnd(25))} ${pc6.yellow(
1404
+ ` ${pc10.cyan(instSlug.padEnd(25))} ${pc10.yellow(
1003
1405
  localVersion.padEnd(10)
1004
- )} ${pc6.green(remoteVersion.padEnd(10))} ${pc6.bold(
1005
- pc6.yellow("\u26A0\uFE0F Update available")
1406
+ )} ${pc10.green(remoteVersion.padEnd(10))} ${pc10.bold(
1407
+ pc10.yellow("\u26A0\uFE0F Update available")
1006
1408
  )}`
1007
1409
  );
1008
1410
  }
1009
1411
  }
1010
1412
  }
1011
- console.log(pc6.dim(" ----------------------------------------------------------------------"));
1413
+ console.log(pc10.dim(" ----------------------------------------------------------------------"));
1012
1414
  if (updatesAvailableCount > 0) {
1013
1415
  console.log(
1014
1416
  `
1015
- \u{1F4E2} ${pc6.bold(pc6.yellow(String(updatesAvailableCount)))} component(s) have updates available.`
1417
+ \u{1F4E2} ${pc10.bold(pc10.yellow(String(updatesAvailableCount)))} component(s) have updates available.`
1016
1418
  );
1017
- console.log(`\u{1F449} Run ${pc6.bold(pc6.cyan("vv add <slug> --overwrite"))} to update a component.
1419
+ console.log(`\u{1F449} Run ${pc10.bold(pc10.cyan("npx vectorvesper add <slug> --overwrite"))} to update a component.
1018
1420
  `);
1019
1421
  } else {
1020
1422
  console.log(`
1021
- ${pc6.green("\u2714")} All components are up to date!
1423
+ ${pc10.green("\u2714")} All components are up to date!
1022
1424
  `);
1023
1425
  }
1024
1426
  } catch (error) {
1025
1427
  const message = error instanceof Error ? error.message : String(error);
1026
- spinner.fail(pc6.red(`Failed to check updates: ${message}`));
1428
+ spinner.fail(pc10.red(`Failed to check updates: ${message}`));
1027
1429
  process.exit(1);
1028
1430
  }
1029
1431
  }
1030
1432
  }
1031
1433
 
1032
1434
  // src/commands/auth.ts
1033
- import pc7 from "picocolors";
1435
+ import pc11 from "picocolors";
1034
1436
  async function loginCommand(key) {
1035
1437
  if (!key || key.trim().length === 0) {
1036
1438
  logger.error("Please provide a license key.");
1037
1439
  console.log(`
1038
- Usage: ${pc7.cyan("vv login <your-license-key>")}`);
1039
- console.log(` \u{1F511} Get a key at ${pc7.cyan("https://vectorvesper.dev/pricing")}
1440
+ Usage: ${pc11.cyan("npx vectorvesper login <your-license-key>")}`);
1441
+ console.log(` \u{1F511} Get a key at ${pc11.cyan("https://vectorvesper.dev/pricing")}
1040
1442
  `);
1041
1443
  process.exit(1);
1042
1444
  }
@@ -1044,7 +1446,7 @@ async function loginCommand(key) {
1044
1446
  if (trimmedKey.length < 8) {
1045
1447
  logger.error("That doesn't look like a valid license key.");
1046
1448
  console.log(`
1047
- \u{1F511} License keys are available at ${pc7.cyan("https://vectorvesper.dev/pricing")}
1449
+ \u{1F511} License keys are available at ${pc11.cyan("https://vectorvesper.dev/pricing")}
1048
1450
  `);
1049
1451
  process.exit(1);
1050
1452
  }
@@ -1061,18 +1463,18 @@ async function loginCommand(key) {
1061
1463
  process.exit(1);
1062
1464
  }
1063
1465
  console.log("");
1064
- console.log(` ${pc7.green(pc7.bold("\u2714"))} License key saved successfully!`);
1065
- console.log(` ${pc7.dim("Stored at:")} ${pc7.cyan(getGlobalConfigPath())}`);
1466
+ console.log(` ${pc11.green(pc11.bold("\u2714"))} License key saved successfully!`);
1467
+ console.log(` ${pc11.dim("Stored at:")} ${pc11.cyan(getGlobalConfigPath())}`);
1066
1468
  console.log("");
1067
- console.log(` ${pc7.dim("Pro components are now available when you run")} ${pc7.cyan("vv add <slug>")}`);
1068
- console.log(` ${pc7.dim("To verify your account:")} ${pc7.cyan("vv whoami")}`);
1469
+ console.log(` ${pc11.dim("Pro components are now available when you run")} ${pc11.cyan("npx vectorvesper add <slug>")}`);
1470
+ console.log(` ${pc11.dim("To verify your account:")} ${pc11.cyan("npx vectorvesper whoami")}`);
1069
1471
  console.log("");
1070
1472
  }
1071
1473
  async function logoutCommand() {
1072
1474
  const config = loadGlobalConfig();
1073
1475
  if (!config.auth?.token) {
1074
1476
  console.log(`
1075
- ${pc7.dim("You're not logged in. Nothing to do.")}
1477
+ ${pc11.dim("You're not logged in. Nothing to do.")}
1076
1478
  `);
1077
1479
  return;
1078
1480
  }
@@ -1085,46 +1487,46 @@ async function logoutCommand() {
1085
1487
  process.exit(1);
1086
1488
  }
1087
1489
  console.log("");
1088
- console.log(` ${pc7.green(pc7.bold("\u2714"))} Logged out successfully.`);
1089
- console.log(` ${pc7.dim("Your license key has been removed from")} ${pc7.cyan(getGlobalConfigPath())}`);
1090
- console.log(` ${pc7.dim("Free components remain available.")}`);
1490
+ console.log(` ${pc11.green(pc11.bold("\u2714"))} Logged out successfully.`);
1491
+ console.log(` ${pc11.dim("Your license key has been removed from")} ${pc11.cyan(getGlobalConfigPath())}`);
1492
+ console.log(` ${pc11.dim("Free components remain available.")}`);
1091
1493
  console.log("");
1092
1494
  }
1093
1495
  async function whoamiCommand() {
1094
1496
  const config = loadGlobalConfig();
1095
1497
  console.log("");
1096
- console.log(pc7.bold(pc7.cyan(" Vector Vesper Account Status\n")));
1498
+ console.log(pc11.bold(pc11.cyan(" Vector Vesper Account Status\n")));
1097
1499
  if (config.auth?.token) {
1098
1500
  const maskedKey = config.auth.token.slice(0, 6) + "\u2026" + config.auth.token.slice(-4);
1099
1501
  const tier = config.auth.tier || "pro";
1100
- console.log(` ${pc7.bold("Status:")} ${pc7.green("Authenticated")}`);
1101
- console.log(` ${pc7.bold("Key:")} ${pc7.cyan(maskedKey)}`);
1102
- console.log(` ${pc7.bold("Tier:")} ${pc7.magenta(tier)}`);
1502
+ console.log(` ${pc11.bold("Status:")} ${pc11.green("Authenticated")}`);
1503
+ console.log(` ${pc11.bold("Key:")} ${pc11.cyan(maskedKey)}`);
1504
+ console.log(` ${pc11.bold("Tier:")} ${pc11.magenta(tier)}`);
1103
1505
  if (config.auth.email) {
1104
- console.log(` ${pc7.bold("Email:")} ${pc7.cyan(config.auth.email)}`);
1506
+ console.log(` ${pc11.bold("Email:")} ${pc11.cyan(config.auth.email)}`);
1105
1507
  }
1106
1508
  if (config.auth.expiresAt) {
1107
1509
  const expiry = new Date(config.auth.expiresAt);
1108
1510
  const now = /* @__PURE__ */ new Date();
1109
1511
  const isExpired = expiry < now;
1110
- console.log(` ${pc7.bold("Expires:")} ${isExpired ? pc7.red("EXPIRED") : pc7.green(expiry.toLocaleDateString())}`);
1512
+ console.log(` ${pc11.bold("Expires:")} ${isExpired ? pc11.red("EXPIRED") : pc11.green(expiry.toLocaleDateString())}`);
1111
1513
  }
1112
1514
  console.log("");
1113
- console.log(` ${pc7.dim("Config:")} ${pc7.cyan(getGlobalConfigPath())}`);
1114
- console.log(` ${pc7.dim("Manage your account at")} ${pc7.cyan("https://vectorvesper.dev/account")}`);
1515
+ console.log(` ${pc11.dim("Config:")} ${pc11.cyan(getGlobalConfigPath())}`);
1516
+ console.log(` ${pc11.dim("Manage your account at")} ${pc11.cyan("https://vectorvesper.dev/account")}`);
1115
1517
  } else {
1116
- console.log(` ${pc7.bold("Status:")} ${pc7.yellow("Anonymous (free tier)")}`);
1117
- console.log(` ${pc7.bold("Access:")} ${pc7.dim("Free components only")}`);
1518
+ console.log(` ${pc11.bold("Status:")} ${pc11.yellow("Anonymous (free tier)")}`);
1519
+ console.log(` ${pc11.bold("Access:")} ${pc11.dim("Free components only")}`);
1118
1520
  console.log("");
1119
- console.log(` ${pc7.dim("To unlock pro components:")}`);
1120
- console.log(` ${pc7.cyan("vv login <your-license-key>")}`);
1121
- console.log(` \u{1F511} ${pc7.dim("Get a key at")} ${pc7.cyan("https://vectorvesper.dev/pricing")}`);
1521
+ console.log(` ${pc11.dim("To unlock pro components:")}`);
1522
+ console.log(` ${pc11.cyan("npx vectorvesper login <your-license-key>")}`);
1523
+ console.log(` \u{1F511} ${pc11.dim("Get a key at")} ${pc11.cyan("https://vectorvesper.dev/pricing")}`);
1122
1524
  }
1123
1525
  console.log("");
1124
1526
  }
1125
1527
 
1126
1528
  // src/index.ts
1127
- var version = true ? "0.1.1" : "0.0.0-dev";
1529
+ var version = true ? "0.2.1" : "0.0.0-dev";
1128
1530
  var program = new Command();
1129
1531
  program.name("vv").description("Vector Vesper CLI \u2014 add visual components to your React project").version(version).option("--verbose", "Show detailed debug output").hook("preAction", (thisCommand) => {
1130
1532
  const opts = thisCommand.opts();
@@ -1138,9 +1540,15 @@ program.command("init").description("Initialize Vector Vesper configuration in y
1138
1540
  program.command("list").description("List all available components in the registry").action(async () => {
1139
1541
  await listCommand();
1140
1542
  });
1141
- program.command("add <slug>").description("Add a component to your project").option("-o, --overwrite", "Overwrite existing files without prompting").option("-y, --yes", "Accept all default prompts").option("-d, --dry-run", "Simulate the installation without writing files").action(async (slug, options) => {
1543
+ program.command("add <slug>").description("Add a component to your project").option("-o, --overwrite", "Overwrite existing files without prompting").option("-y, --yes", "Accept all default prompts").option("-d, --dry-run", "Simulate the installation without writing files").option("--no-install", "Skip auto-installing missing dependencies").action(async (slug, options) => {
1142
1544
  await addCommand(slug, options);
1143
1545
  });
1546
+ program.command("update [slug]").description("Update installed components to the latest registry version").option("-f, --force", "Re-write files even if the version already matches").action(async (slug, options) => {
1547
+ await updateCommand(slug, options);
1548
+ });
1549
+ program.command("remove <slug>").alias("rm").description("Remove an installed component and its files").option("-y, --yes", "Skip the confirmation prompt").action(async (slug, options) => {
1550
+ await removeCommand(slug, options);
1551
+ });
1144
1552
  program.command("info").description("Show project diagnostics and list installed components").action(async () => {
1145
1553
  await infoCommand();
1146
1554
  });
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "vectorvesper",
3
- "version": "0.1.1",
4
- "description": "Add premium WebGL and React visual components to your project via CLI",
3
+ "version": "0.2.1",
4
+ "description": "Add WebGL, React Three Fiber & advanced motion components to your project via CLI — motion, engineered.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "./dist/index.js",
8
8
  "bin": {
9
- "vv": "./dist/index.js"
9
+ "vv": "dist/index.js"
10
10
  },
11
11
  "files": [
12
12
  "dist",
@@ -41,7 +41,9 @@
41
41
  "scripts": {
42
42
  "build": "tsup",
43
43
  "dev": "tsup --watch",
44
- "typecheck": "tsc --noEmit"
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest"
45
47
  },
46
48
  "dependencies": {
47
49
  "@clack/prompts": "^0.7.0",
@@ -53,6 +55,7 @@
53
55
  "devDependencies": {
54
56
  "@types/node": "^25.9.2",
55
57
  "tsup": "^8.0.2",
56
- "typescript": "^6.0.3"
58
+ "typescript": "^6.0.3",
59
+ "vitest": "^2.1.8"
57
60
  }
58
61
  }