vectorvesper 0.1.0

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 +1162 -0
  2. package/package.json +58 -0
package/dist/index.js ADDED
@@ -0,0 +1,1162 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/utils/logger.ts
7
+ import pc from "picocolors";
8
+ var verboseMode = false;
9
+ function setVerbose(enabled) {
10
+ verboseMode = enabled;
11
+ }
12
+ var logger = {
13
+ /** Standard info message */
14
+ info(message) {
15
+ console.log(message);
16
+ },
17
+ /** Success message with green checkmark */
18
+ success(message) {
19
+ console.log(`${pc.green("\u2714")} ${message}`);
20
+ },
21
+ /** Warning message */
22
+ warn(message) {
23
+ console.log(pc.yellow(`\u26A0\uFE0F ${message}`));
24
+ },
25
+ /** Error message */
26
+ error(message) {
27
+ console.error(pc.red(`\u274C ${message}`));
28
+ },
29
+ /** Debug message — only shown when --verbose is active */
30
+ debug(message) {
31
+ if (verboseMode) {
32
+ console.log(pc.dim(` [debug] ${message}`));
33
+ }
34
+ },
35
+ /** Blank line */
36
+ break() {
37
+ console.log("");
38
+ }
39
+ };
40
+
41
+ // src/commands/init.ts
42
+ import { intro, outro, select, text, confirm, isCancel } from "@clack/prompts";
43
+ import pc2 from "picocolors";
44
+ import fs3 from "fs";
45
+ import path3 from "path";
46
+
47
+ // src/utils/project.ts
48
+ import fs from "fs";
49
+ import path from "path";
50
+ function detectProject(cwd = process.cwd()) {
51
+ const rootPath = cwd;
52
+ let packageManager = "npm";
53
+ if (fs.existsSync(path.join(rootPath, "pnpm-lock.yaml"))) {
54
+ packageManager = "pnpm";
55
+ } else if (fs.existsSync(path.join(rootPath, "yarn.lock"))) {
56
+ packageManager = "yarn";
57
+ } else if (fs.existsSync(path.join(rootPath, "bun.lockb")) || fs.existsSync(path.join(rootPath, "bun.lock"))) {
58
+ packageManager = "bun";
59
+ }
60
+ const isTypeScript = fs.existsSync(path.join(rootPath, "tsconfig.json"));
61
+ const hasSrcDir = fs.existsSync(path.join(rootPath, "src"));
62
+ const tailwindConfigs = [
63
+ "tailwind.config.js",
64
+ "tailwind.config.ts",
65
+ "tailwind.config.cjs",
66
+ "tailwind.config.mjs"
67
+ ];
68
+ const hasTailwind = tailwindConfigs.some(
69
+ (config) => fs.existsSync(path.join(rootPath, config))
70
+ );
71
+ let framework = "unknown";
72
+ let nextRouter;
73
+ const pkgPath = path.join(rootPath, "package.json");
74
+ let dependencies = {};
75
+ let devDependencies = {};
76
+ if (fs.existsSync(pkgPath)) {
77
+ try {
78
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
79
+ dependencies = pkg.dependencies || {};
80
+ devDependencies = pkg.devDependencies || {};
81
+ } catch (e) {
82
+ }
83
+ }
84
+ if (dependencies["next"] || devDependencies["next"]) {
85
+ framework = "next";
86
+ const searchPath = hasSrcDir ? path.join(rootPath, "src") : rootPath;
87
+ if (fs.existsSync(path.join(searchPath, "app"))) {
88
+ nextRouter = "app";
89
+ } else if (fs.existsSync(path.join(searchPath, "pages"))) {
90
+ nextRouter = "pages";
91
+ } else {
92
+ nextRouter = "app";
93
+ }
94
+ } else if (dependencies["vite"] || devDependencies["vite"] || fs.existsSync(path.join(rootPath, "vite.config.ts")) || fs.existsSync(path.join(rootPath, "vite.config.js"))) {
95
+ framework = "vite";
96
+ }
97
+ return {
98
+ packageManager,
99
+ isTypeScript,
100
+ framework,
101
+ nextRouter,
102
+ hasSrcDir,
103
+ hasTailwind,
104
+ rootPath
105
+ };
106
+ }
107
+
108
+ // src/utils/config.ts
109
+ import fs2 from "fs";
110
+ import path2 from "path";
111
+ import os from "os";
112
+
113
+ // src/utils/types.ts
114
+ import { z } from "zod";
115
+ var SLUG_REGEX = /^[a-z0-9][a-z0-9-]*$/;
116
+ function validateSlug(slug) {
117
+ if (!SLUG_REGEX.test(slug)) {
118
+ throw new Error(
119
+ `Invalid component slug "${slug}". Slugs must contain only lowercase letters, numbers, and hyphens, and cannot start with a hyphen.`
120
+ );
121
+ }
122
+ }
123
+ var ComponentFileSchema = z.object({
124
+ path: z.string(),
125
+ target: z.string(),
126
+ type: z.enum(["component", "types", "shader", "lib", "docs"]),
127
+ content: z.string()
128
+ });
129
+ var RegistryComponentSchema = z.object({
130
+ slug: z.string(),
131
+ name: z.string(),
132
+ title: z.string(),
133
+ description: z.string(),
134
+ version: z.string(),
135
+ tier: z.enum(["free", "pro"]),
136
+ category: z.string(),
137
+ type: z.string(),
138
+ frameworks: z.array(z.string()),
139
+ files: z.array(ComponentFileSchema),
140
+ dependencies: z.array(z.string()),
141
+ devDependencies: z.array(z.string()),
142
+ registryDependencies: z.array(z.string()),
143
+ requiresClient: z.boolean(),
144
+ supportsReducedMotion: z.boolean(),
145
+ usesWebGL: z.boolean(),
146
+ usesPointer: z.boolean(),
147
+ usesScroll: z.boolean()
148
+ }).passthrough();
149
+ var IndexComponentSchema = z.object({
150
+ slug: z.string(),
151
+ name: z.string(),
152
+ title: z.string(),
153
+ description: z.string(),
154
+ version: z.string(),
155
+ tier: z.enum(["free", "pro"]),
156
+ category: z.string(),
157
+ type: z.string(),
158
+ frameworks: z.array(z.string()),
159
+ files: z.array(z.object({
160
+ path: z.string(),
161
+ target: z.string(),
162
+ type: z.enum(["component", "types", "shader", "lib", "docs"])
163
+ })),
164
+ dependencies: z.array(z.string()),
165
+ devDependencies: z.array(z.string()),
166
+ registryDependencies: z.array(z.string()),
167
+ requiresClient: z.boolean(),
168
+ supportsReducedMotion: z.boolean(),
169
+ usesWebGL: z.boolean(),
170
+ usesPointer: z.boolean(),
171
+ usesScroll: z.boolean()
172
+ }).passthrough();
173
+ var RegistryIndexSchema = z.object({
174
+ version: z.string(),
175
+ components: z.array(IndexComponentSchema)
176
+ });
177
+ var VVConfigSchema = z.object({
178
+ tsx: z.boolean(),
179
+ framework: z.enum(["next", "vite", "unknown"]),
180
+ aliases: z.object({
181
+ vv: z.string()
182
+ })
183
+ });
184
+ var VVGlobalConfigSchema = z.object({
185
+ // Auth — license key for paid components
186
+ auth: z.object({
187
+ token: z.string().optional(),
188
+ email: z.string().optional(),
189
+ tier: z.enum(["free", "pro", "team"]).optional(),
190
+ expiresAt: z.string().optional()
191
+ }).optional(),
192
+ // Preferences
193
+ telemetry: z.boolean().optional()
194
+ }).passthrough();
195
+ var ManifestEntrySchema = z.object({
196
+ version: z.string(),
197
+ installedAt: z.string(),
198
+ installedOn: z.string(),
199
+ files: z.array(z.string())
200
+ });
201
+ var ManifestSchema = z.object({
202
+ components: z.record(z.string(), ManifestEntrySchema)
203
+ });
204
+
205
+ // src/utils/config.ts
206
+ var CONFIG_FILE_NAME = "vv.config.json";
207
+ function getConfigPath(cwd = process.cwd()) {
208
+ return path2.join(cwd, CONFIG_FILE_NAME);
209
+ }
210
+ function loadConfig(cwd = process.cwd()) {
211
+ const configPath = getConfigPath(cwd);
212
+ if (!fs2.existsSync(configPath)) {
213
+ return null;
214
+ }
215
+ let raw;
216
+ try {
217
+ const content = fs2.readFileSync(configPath, "utf-8");
218
+ raw = JSON.parse(content);
219
+ } catch {
220
+ throw new Error(
221
+ `Failed to parse vv.config.json \u2014 the file contains invalid JSON. Delete it and run "vv init" to regenerate.`
222
+ );
223
+ }
224
+ const result = VVConfigSchema.safeParse(raw);
225
+ if (!result.success) {
226
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
227
+ throw new Error(
228
+ `Invalid vv.config.json \u2014 schema validation failed:
229
+ ${issues}
230
+ Delete the file and run "vv init" to regenerate.`
231
+ );
232
+ }
233
+ return result.data;
234
+ }
235
+ function writeConfig(config, cwd = process.cwd()) {
236
+ const configPath = getConfigPath(cwd);
237
+ fs2.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
238
+ }
239
+ var GLOBAL_CONFIG_DIR = path2.join(os.homedir(), ".vv");
240
+ var GLOBAL_CONFIG_PATH = path2.join(GLOBAL_CONFIG_DIR, "config.json");
241
+ function getGlobalConfigPath() {
242
+ return GLOBAL_CONFIG_PATH;
243
+ }
244
+ function loadGlobalConfig() {
245
+ if (!fs2.existsSync(GLOBAL_CONFIG_PATH)) {
246
+ return {};
247
+ }
248
+ try {
249
+ const content = fs2.readFileSync(GLOBAL_CONFIG_PATH, "utf-8");
250
+ const raw = JSON.parse(content);
251
+ const result = VVGlobalConfigSchema.safeParse(raw);
252
+ if (!result.success) {
253
+ logger.debug(`Global config validation failed: ${result.error.message}`);
254
+ return {};
255
+ }
256
+ return result.data;
257
+ } catch {
258
+ logger.debug("Failed to parse global config, using defaults.");
259
+ return {};
260
+ }
261
+ }
262
+ function saveGlobalConfig(config) {
263
+ if (!fs2.existsSync(GLOBAL_CONFIG_DIR)) {
264
+ fs2.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
265
+ }
266
+ fs2.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
267
+ }
268
+ function getAuthToken() {
269
+ const config = loadGlobalConfig();
270
+ return config.auth?.token;
271
+ }
272
+ function resolveAlias(alias, hasSrcDir) {
273
+ if (alias.startsWith("@/")) {
274
+ return alias.replace("@/", hasSrcDir ? "src/" : "");
275
+ }
276
+ if (alias.startsWith("~/")) {
277
+ return alias.replace("~/", hasSrcDir ? "src/" : "");
278
+ }
279
+ return alias;
280
+ }
281
+
282
+ // src/commands/init.ts
283
+ async function initCommand(options = {}) {
284
+ console.log("");
285
+ intro(pc2.cyan(pc2.bold(" Vector Vesper Setup ")));
286
+ const projectInfo = detectProject();
287
+ let tsx = projectInfo.isTypeScript;
288
+ let framework = projectInfo.framework;
289
+ let aliasInput = "@/components/vv";
290
+ if (!options.yes) {
291
+ try {
292
+ const existingConfig = loadConfig();
293
+ if (existingConfig) {
294
+ const shouldOverwrite = await confirm({
295
+ message: pc2.yellow("vv.config.json already exists. Overwrite it?"),
296
+ initialValue: false
297
+ });
298
+ if (isCancel(shouldOverwrite) || !shouldOverwrite) {
299
+ outro(pc2.red("Setup cancelled."));
300
+ return;
301
+ }
302
+ }
303
+ } catch (error) {
304
+ const message = error instanceof Error ? error.message : String(error);
305
+ console.log(pc2.yellow(`
306
+ \u26A0\uFE0F ${message}
307
+ `));
308
+ const shouldReplace = await confirm({
309
+ message: pc2.yellow("Replace the corrupted config?"),
310
+ initialValue: true
311
+ });
312
+ if (isCancel(shouldReplace) || !shouldReplace) {
313
+ outro(pc2.red("Setup cancelled."));
314
+ return;
315
+ }
316
+ }
317
+ const tsxResponse = await confirm({
318
+ message: `Use TypeScript? ${pc2.dim(`(Detected: ${projectInfo.isTypeScript ? "Yes" : "No"})`)}`,
319
+ initialValue: projectInfo.isTypeScript
320
+ });
321
+ if (isCancel(tsxResponse)) {
322
+ outro(pc2.red("Setup cancelled."));
323
+ return;
324
+ }
325
+ tsx = tsxResponse;
326
+ const frameworkResponse = await select({
327
+ message: `Configure your framework: ${pc2.dim(`(Detected: ${projectInfo.framework})`)}`,
328
+ options: [
329
+ { value: "next", label: "Next.js" },
330
+ { value: "vite", label: "Vite" },
331
+ { value: "unknown", label: "Other / Manual configuration" }
332
+ ],
333
+ initialValue: projectInfo.framework
334
+ });
335
+ if (isCancel(frameworkResponse)) {
336
+ outro(pc2.red("Setup cancelled."));
337
+ return;
338
+ }
339
+ framework = frameworkResponse;
340
+ const defaultAlias = "@/components/vv";
341
+ const aliasResponse = await text({
342
+ message: "Configure components import alias:",
343
+ placeholder: defaultAlias,
344
+ defaultValue: defaultAlias,
345
+ validate(value) {
346
+ if (!value.startsWith("@/") && !value.startsWith("~/") && !value.startsWith("src/")) {
347
+ return "Alias must start with '@/', '~/', or 'src/'";
348
+ }
349
+ }
350
+ });
351
+ if (isCancel(aliasResponse)) {
352
+ outro(pc2.red("Setup cancelled."));
353
+ return;
354
+ }
355
+ aliasInput = aliasResponse;
356
+ } else {
357
+ console.log(pc2.dim(` \u26A1 Running in non-interactive mode (-y)`));
358
+ console.log(pc2.dim(` \u2022 TypeScript: ${tsx ? "Yes" : "No"}`));
359
+ console.log(pc2.dim(` \u2022 Framework: ${framework}`));
360
+ console.log(pc2.dim(` \u2022 Components Alias: ${aliasInput}`));
361
+ }
362
+ const config = {
363
+ tsx,
364
+ framework,
365
+ aliases: {
366
+ vv: aliasInput
367
+ }
368
+ };
369
+ try {
370
+ writeConfig(config);
371
+ const targetLocalDir = resolveAlias(aliasInput, projectInfo.hasSrcDir);
372
+ const absoluteTargetDir = path3.join(process.cwd(), targetLocalDir);
373
+ if (!fs3.existsSync(absoluteTargetDir)) {
374
+ fs3.mkdirSync(absoluteTargetDir, { recursive: true });
375
+ }
376
+ const manifestPath = path3.join(process.cwd(), "vv-manifest.json");
377
+ if (!fs3.existsSync(manifestPath)) {
378
+ fs3.writeFileSync(manifestPath, JSON.stringify({ components: {} }, null, 2), "utf-8");
379
+ }
380
+ console.log("");
381
+ outro(
382
+ `${pc2.green(pc2.bold("\u2714 Success!"))} Vector Vesper initialized.
383
+
384
+ \u2022 Configuration written to ${pc2.cyan("vv.config.json")}
385
+ \u2022 Target directory created at ${pc2.cyan(targetLocalDir)}
386
+ \u2022 Manifest created at ${pc2.cyan("vv-manifest.json")}
387
+
388
+ \u{1F449} Next, run: ${pc2.bold(pc2.cyan("vv add <slug>"))} to add a component.`
389
+ );
390
+ } catch (error) {
391
+ const message = error instanceof Error ? error.message : String(error);
392
+ outro(pc2.red(`Failed to initialize: ${message}`));
393
+ process.exit(1);
394
+ }
395
+ }
396
+
397
+ // src/commands/list.ts
398
+ import pc3 from "picocolors";
399
+ import ora from "ora";
400
+
401
+ // src/utils/registry.ts
402
+ import fs4 from "fs";
403
+ import path4 from "path";
404
+ import { fileURLToPath } from "url";
405
+ var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/vectorvesper/vv-components/main/r";
406
+ var FETCH_TIMEOUT_MS = 15e3;
407
+ var MAX_RETRIES = 1;
408
+ var RETRY_DELAY_MS = 1e3;
409
+ function getRegistryBaseUrl() {
410
+ return process.env.VV_REGISTRY_URL || DEFAULT_REGISTRY_URL;
411
+ }
412
+ function handleHttpError(status, url) {
413
+ switch (status) {
414
+ case 401:
415
+ throw new Error(
416
+ `Authentication required. This component requires a valid license key.
417
+ \u{1F449} Run "vv login <your-license-key>" to authenticate.
418
+ \u{1F511} Get a license at https://vectorvesper.dev/pricing`
419
+ );
420
+ case 402:
421
+ 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.`
425
+ );
426
+ case 403:
427
+ throw new Error(
428
+ `Access denied. Your license may have expired or doesn't include this component.
429
+ \u{1F449} Check your account at https://vectorvesper.dev/account
430
+ \u{1F4A1} Run "vv whoami" to see your current plan.`
431
+ );
432
+ case 404:
433
+ throw new Error(
434
+ `Component not found in the registry.
435
+ \u{1F449} Run "vv list" to see all available components.`
436
+ );
437
+ case 429:
438
+ throw new Error(
439
+ `Rate limited by the registry. Please wait a moment and try again.`
440
+ );
441
+ default:
442
+ throw new Error(
443
+ `Registry request failed: HTTP ${status}
444
+ URL: ${url}`
445
+ );
446
+ }
447
+ }
448
+ async function fetchWithRetry(url) {
449
+ const token = getAuthToken();
450
+ const headers = {
451
+ "User-Agent": "vv-cli",
452
+ "Accept": "application/json"
453
+ };
454
+ if (token) {
455
+ headers["Authorization"] = `Bearer ${token}`;
456
+ logger.debug(`Using auth token for request`);
457
+ }
458
+ let lastError = null;
459
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
460
+ try {
461
+ if (attempt > 0) {
462
+ logger.debug(`Retry attempt ${attempt} for ${url}`);
463
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt));
464
+ }
465
+ logger.debug(`Fetching: ${url}`);
466
+ const response = await fetch(url, {
467
+ headers,
468
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
469
+ });
470
+ if (!response.ok) {
471
+ if (response.status >= 400 && response.status < 500) {
472
+ handleHttpError(response.status, url);
473
+ }
474
+ throw new Error(`HTTP ${response.status}`);
475
+ }
476
+ return await response.text();
477
+ } catch (error) {
478
+ if (error instanceof Error) {
479
+ if (error.message.includes("Authentication required") || error.message.includes("Pro component") || error.message.includes("Access denied") || error.message.includes("not found in the registry")) {
480
+ throw error;
481
+ }
482
+ if (error.name === "TimeoutError" || error.name === "AbortError") {
483
+ lastError = new Error(
484
+ `Request timed out after ${FETCH_TIMEOUT_MS / 1e3}s. Check your internet connection or try again.`
485
+ );
486
+ continue;
487
+ }
488
+ lastError = error;
489
+ } else {
490
+ lastError = new Error(String(error));
491
+ }
492
+ }
493
+ }
494
+ throw lastError || new Error(`Failed to fetch: ${url}`);
495
+ }
496
+ async function fetchRegistryData(urlOrPath) {
497
+ if (urlOrPath.startsWith("http://") || urlOrPath.startsWith("https://")) {
498
+ if (urlOrPath.startsWith("http://") && !urlOrPath.includes("localhost") && !urlOrPath.includes("127.0.0.1")) {
499
+ logger.warn("Using insecure HTTP for registry. Consider switching to HTTPS.");
500
+ }
501
+ return fetchWithRetry(urlOrPath);
502
+ }
503
+ let cleanPath = urlOrPath;
504
+ if (urlOrPath.startsWith("file://")) {
505
+ cleanPath = fileURLToPath(urlOrPath);
506
+ }
507
+ cleanPath = path4.resolve(cleanPath);
508
+ if (!fs4.existsSync(cleanPath)) {
509
+ throw new Error(`Registry path not found: ${cleanPath}`);
510
+ }
511
+ logger.debug(`Reading local registry: ${cleanPath}`);
512
+ return fs4.readFileSync(cleanPath, "utf-8");
513
+ }
514
+ async function fetchRegistryIndex() {
515
+ const baseUrl = getRegistryBaseUrl();
516
+ const url = baseUrl.endsWith("/") ? `${baseUrl}registry.json` : `${baseUrl}/registry.json`;
517
+ const content = await fetchRegistryData(url);
518
+ let raw;
519
+ try {
520
+ raw = JSON.parse(content);
521
+ } catch {
522
+ throw new Error("Failed to parse registry index \u2014 received invalid JSON.");
523
+ }
524
+ const result = RegistryIndexSchema.safeParse(raw);
525
+ if (!result.success) {
526
+ logger.debug(`Registry index validation errors: ${result.error.message}`);
527
+ throw new Error("Registry index failed validation. The registry may be corrupted or incompatible with this CLI version.");
528
+ }
529
+ return result.data;
530
+ }
531
+ async function fetchComponent(slug) {
532
+ validateSlug(slug);
533
+ const baseUrl = getRegistryBaseUrl();
534
+ const url = baseUrl.endsWith("/") ? `${baseUrl}${slug}.json` : `${baseUrl}/${slug}.json`;
535
+ const content = await fetchRegistryData(url);
536
+ let raw;
537
+ try {
538
+ raw = JSON.parse(content);
539
+ } catch {
540
+ throw new Error(`Failed to parse component "${slug}" \u2014 received invalid JSON.`);
541
+ }
542
+ const result = RegistryComponentSchema.safeParse(raw);
543
+ if (!result.success) {
544
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
545
+ logger.debug(`Component "${slug}" validation errors:
546
+ ${issues}`);
547
+ throw new Error(
548
+ `Component "${slug}" failed validation. This may indicate an incompatible registry version. Try updating the CLI.`
549
+ );
550
+ }
551
+ return result.data;
552
+ }
553
+
554
+ // src/commands/list.ts
555
+ async function listCommand() {
556
+ const spinner = ora({
557
+ text: "Fetching components list...",
558
+ color: "cyan"
559
+ }).start();
560
+ try {
561
+ const registry = await fetchRegistryIndex();
562
+ spinner.succeed(pc3.green("Fetched components list successfully!\n"));
563
+ if (registry.components.length === 0) {
564
+ console.log(pc3.dim(" No components available in the registry yet."));
565
+ console.log(pc3.dim(" Check back soon or visit https://vectorvesper.dev\n"));
566
+ return;
567
+ }
568
+ const hasAuth = !!getAuthToken();
569
+ console.log(pc3.bold(pc3.white("Vector Vesper Available Components:")));
570
+ console.log(pc3.dim("--------------------------------------------------------------------------------"));
571
+ for (const c of registry.components) {
572
+ const slugCol = pc3.cyan(c.slug.padEnd(25));
573
+ const categoryCol = pc3.yellow(c.category.padEnd(15));
574
+ let tierBadge;
575
+ if (c.tier === "pro") {
576
+ tierBadge = hasAuth ? pc3.magenta("pro ") : pc3.dim("\u{1F512} ");
577
+ } else {
578
+ tierBadge = pc3.green("free");
579
+ }
580
+ console.log(`${slugCol} [${tierBadge}] ${categoryCol} ${pc3.gray(c.description)}`);
581
+ }
582
+ console.log(pc3.dim("--------------------------------------------------------------------------------"));
583
+ const freeCount = registry.components.filter((c) => c.tier === "free").length;
584
+ const proCount = registry.components.filter((c) => c.tier === "pro").length;
585
+ console.log(`
586
+ ${pc3.green(String(freeCount))} free components${proCount > 0 ? ` \xB7 ${pc3.magenta(String(proCount))} pro components` : ""}`);
587
+ if (proCount > 0 && !hasAuth) {
588
+ console.log(` \u{1F511} ${pc3.dim("Unlock pro components:")} ${pc3.cyan("vv login <key>")} ${pc3.dim("\xB7")} ${pc3.cyan("https://vectorvesper.dev/pricing")}`);
589
+ }
590
+ console.log(`
591
+ \u{1F449} Run ${pc3.bold(pc3.cyan("vv add <slug>"))} to add a component to your project.
592
+ `);
593
+ } catch (error) {
594
+ const message = error instanceof Error ? error.message : String(error);
595
+ spinner.fail(pc3.red(`Failed to fetch components list: ${message}`));
596
+ process.exit(1);
597
+ }
598
+ }
599
+
600
+ // src/commands/add.ts
601
+ import fs5 from "fs";
602
+ import path5 from "path";
603
+ import ora2 from "ora";
604
+ import pc4 from "picocolors";
605
+ import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
606
+ function toForwardSlash(p) {
607
+ return p.replace(/\\/g, "/");
608
+ }
609
+ function reportMissingDependencies(resolvedComponents, projectRoot, projectInfo) {
610
+ const pkgPath = path5.join(projectRoot, "package.json");
611
+ if (!fs5.existsSync(pkgPath)) return;
612
+ try {
613
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
614
+ const installedDeps = {
615
+ ...pkg.dependencies,
616
+ ...pkg.devDependencies,
617
+ ...pkg.peerDependencies
618
+ };
619
+ const missingDeps = /* @__PURE__ */ new Set();
620
+ for (const component of resolvedComponents) {
621
+ if (component.dependencies) {
622
+ for (const dep of component.dependencies) {
623
+ if (!installedDeps[dep]) {
624
+ missingDeps.add(dep);
625
+ }
626
+ }
627
+ }
628
+ }
629
+ if (missingDeps.size > 0) {
630
+ console.log(pc4.yellow(`
631
+ \u26A0\uFE0F Missing peer dependencies in your project:`));
632
+ const pm = projectInfo.packageManager;
633
+ const depsStr = Array.from(missingDeps).join(" ");
634
+ const installCmd = pm === "pnpm" ? `pnpm add ${depsStr}` : pm === "yarn" ? `yarn add ${depsStr}` : pm === "bun" ? `bun add ${depsStr}` : `npm install ${depsStr}`;
635
+ console.log(` Run: ${pc4.bold(pc4.cyan(installCmd))}`);
636
+ }
637
+ } catch {
638
+ }
639
+ }
640
+ async function addCommand(slug, options = {}) {
641
+ try {
642
+ validateSlug(slug);
643
+ } catch (error) {
644
+ const message = error instanceof Error ? error.message : String(error);
645
+ console.error(`${pc4.red(pc4.bold("Error:"))} ${message}`);
646
+ process.exit(1);
647
+ }
648
+ let config;
649
+ try {
650
+ config = loadConfig();
651
+ } catch (error) {
652
+ const message = error instanceof Error ? error.message : String(error);
653
+ console.error(`${pc4.red(pc4.bold("Error:"))} ${message}`);
654
+ process.exit(1);
655
+ }
656
+ if (!config) {
657
+ console.error(
658
+ `${pc4.red(pc4.bold("Error:"))} No ${pc4.cyan("vv.config.json")} found in this directory.
659
+ \u{1F449} Run ${pc4.bold(pc4.cyan("vv init"))} first to set up your project.`
660
+ );
661
+ process.exit(1);
662
+ return;
663
+ }
664
+ const projectInfo = detectProject();
665
+ const componentInstallDir = resolveAlias(config.aliases.vv, projectInfo.hasSrcDir);
666
+ const spinner = ora2({
667
+ text: `Resolving component ${pc4.cyan(slug)}...`,
668
+ color: "cyan"
669
+ }).start();
670
+ const resolvedComponents = [];
671
+ const visited = /* @__PURE__ */ new Set();
672
+ async function resolveComponentTree(componentSlug, depth = 0) {
673
+ if (visited.has(componentSlug)) return;
674
+ if (depth > 20) {
675
+ throw new Error(`Dependency resolution exceeded maximum depth (20). Possible circular dependency involving "${componentSlug}".`);
676
+ }
677
+ visited.add(componentSlug);
678
+ const component = await fetchComponent(componentSlug);
679
+ if (component.tier === "pro" && !getAuthToken()) {
680
+ throw new Error(
681
+ `"${componentSlug}" is a Pro component and requires authentication.
682
+ \u{1F449} Run ${pc4.cyan("vv login <your-license-key>")} to unlock pro components.
683
+ \u{1F511} Get a key at ${pc4.cyan("https://vectorvesper.dev/pricing")}`
684
+ );
685
+ }
686
+ if (component.registryDependencies && component.registryDependencies.length > 0) {
687
+ for (const depSlug of component.registryDependencies) {
688
+ await resolveComponentTree(depSlug, depth + 1);
689
+ }
690
+ }
691
+ resolvedComponents.push(component);
692
+ }
693
+ try {
694
+ await resolveComponentTree(slug);
695
+ } catch (error) {
696
+ const message = error instanceof Error ? error.message : String(error);
697
+ spinner.fail(pc4.red(`Failed to resolve component "${slug}": ${message}`));
698
+ process.exit(1);
699
+ }
700
+ spinner.text = "Checking file conflicts...";
701
+ const filesToWrite = [];
702
+ const projectRoot = process.cwd();
703
+ for (const component of resolvedComponents) {
704
+ const compDir = path5.join(projectRoot, componentInstallDir);
705
+ for (const file of component.files) {
706
+ const fileWritePath = path5.resolve(compDir, file.target);
707
+ const relativeFromRoot = path5.relative(projectRoot, fileWritePath);
708
+ if (relativeFromRoot.startsWith("..") || path5.isAbsolute(relativeFromRoot)) {
709
+ spinner.fail(
710
+ `${pc4.red(pc4.bold("Security Error:"))} Component target file path tries to escape project root: ${file.target}`
711
+ );
712
+ process.exit(1);
713
+ }
714
+ if (fs5.existsSync(fileWritePath)) {
715
+ if (options.dryRun) {
716
+ } else if (!options.overwrite && !options.yes) {
717
+ spinner.stop();
718
+ const relativeDisplayPath = path5.relative(projectRoot, fileWritePath);
719
+ const shouldOverwrite = await confirm2({
720
+ message: pc4.yellow(`File already exists: "${relativeDisplayPath}". Overwrite?`),
721
+ initialValue: false
722
+ });
723
+ if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
724
+ console.log(pc4.dim(` Skipping existing file: ${relativeDisplayPath}`));
725
+ spinner.start("Continuing installation...");
726
+ continue;
727
+ }
728
+ spinner.start("Continuing installation...");
729
+ }
730
+ }
731
+ filesToWrite.push({
732
+ absolutePath: fileWritePath,
733
+ relativePath: relativeFromRoot,
734
+ content: file.content
735
+ });
736
+ }
737
+ }
738
+ if (options.dryRun) {
739
+ spinner.succeed(pc4.yellow("Dry-run: Simulation completed. No files were written."));
740
+ console.log(pc4.bold(pc4.yellow("\n[Dry Run] Files that would be created/modified:")));
741
+ for (const file of filesToWrite) {
742
+ const status = fs5.existsSync(file.absolutePath) ? pc4.yellow("(exists, would overwrite)") : pc4.green("(new)");
743
+ console.log(` ${pc4.cyan(file.relativePath)} ${status}`);
744
+ }
745
+ reportMissingDependencies(resolvedComponents, projectRoot, projectInfo);
746
+ const targetComponent2 = resolvedComponents[resolvedComponents.length - 1];
747
+ console.log("");
748
+ console.log(
749
+ 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))}`))
750
+ );
751
+ console.log("");
752
+ console.log(pc4.gray("\u{1F449} Loving Vector Vesper? Star the repo: https://github.com/vectorvesper/vv-components"));
753
+ console.log(pc4.gray("\u{1F680} Need premium WebGL effects? Get the Pixel Library: https://vectorvesper.dev/library"));
754
+ console.log("");
755
+ return;
756
+ }
757
+ if (filesToWrite.length === 0) {
758
+ spinner.succeed(pc4.green("All files skipped (already up to date)."));
759
+ return;
760
+ }
761
+ spinner.text = "Writing component files...";
762
+ try {
763
+ for (const file of filesToWrite) {
764
+ const folder = path5.dirname(file.absolutePath);
765
+ if (!fs5.existsSync(folder)) {
766
+ fs5.mkdirSync(folder, { recursive: true });
767
+ }
768
+ fs5.writeFileSync(file.absolutePath, file.content, "utf-8");
769
+ }
770
+ } catch (error) {
771
+ const message = error instanceof Error ? error.message : String(error);
772
+ spinner.fail(pc4.red(`Failed to write files: ${message}`));
773
+ process.exit(1);
774
+ }
775
+ try {
776
+ const manifestPath = path5.join(projectRoot, "vv-manifest.json");
777
+ let manifest = { components: {} };
778
+ if (fs5.existsSync(manifestPath)) {
779
+ manifest = JSON.parse(fs5.readFileSync(manifestPath, "utf-8"));
780
+ }
781
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
782
+ for (const component of resolvedComponents) {
783
+ manifest.components[component.slug] = {
784
+ version: component.version,
785
+ installedAt: toForwardSlash(path5.join(componentInstallDir, component.slug)),
786
+ installedOn: today,
787
+ files: component.files.map((f) => f.target)
788
+ };
789
+ }
790
+ fs5.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
791
+ } catch (error) {
792
+ const message = error instanceof Error ? error.message : String(error);
793
+ logger.warn(`Failed to update vv-manifest.json: ${message}`);
794
+ logger.warn("Component was installed but may not appear in 'vv info' or 'vv diff'.");
795
+ }
796
+ spinner.succeed(pc4.green("Files written successfully!"));
797
+ console.log(pc4.dim("\nCreated files:"));
798
+ for (const file of filesToWrite) {
799
+ console.log(` ${pc4.green("\u2714")} ${pc4.cyan(file.relativePath)}`);
800
+ }
801
+ reportMissingDependencies(resolvedComponents, projectRoot, projectInfo);
802
+ const targetComponent = resolvedComponents[resolvedComponents.length - 1];
803
+ console.log("");
804
+ console.log(
805
+ pc4.bold(pc4.green("\u2714 Added ")) + pc4.bold(pc4.cyan(targetComponent.slug)) + pc4.bold(pc4.green(` to ${toForwardSlash(path5.join(componentInstallDir, targetComponent.slug))}`))
806
+ );
807
+ console.log("");
808
+ console.log(pc4.gray("\u{1F449} Loving Vector Vesper? Star the repo: https://github.com/vectorvesper/vv-components"));
809
+ console.log(pc4.gray("\u{1F680} Need premium WebGL effects? Get the Pixel Library: https://vectorvesper.dev/library"));
810
+ console.log("");
811
+ }
812
+
813
+ // src/commands/info.ts
814
+ import fs6 from "fs";
815
+ import path6 from "path";
816
+ import pc5 from "picocolors";
817
+ var CLI_VERSION = true ? "0.1.0" : "0.0.0-dev";
818
+ async function infoCommand() {
819
+ console.log(pc5.bold(pc5.cyan("\nVector Vesper Diagnostics\n")));
820
+ const projectInfo = detectProject();
821
+ let config;
822
+ try {
823
+ config = loadConfig();
824
+ } catch {
825
+ config = null;
826
+ }
827
+ console.log(pc5.bold(pc5.white("Environment:")));
828
+ console.log(` \u2022 CLI Version: ${pc5.cyan(CLI_VERSION)}`);
829
+ console.log(` \u2022 Node Version: ${pc5.cyan(process.version)}`);
830
+ console.log(` \u2022 OS: ${pc5.cyan(`${process.platform} ${process.arch}`)}`);
831
+ console.log(` \u2022 Package Manager: ${pc5.cyan(projectInfo.packageManager)}`);
832
+ console.log(` \u2022 Framework: ${pc5.cyan(projectInfo.framework)}`);
833
+ console.log(` \u2022 TypeScript: ${pc5.cyan(projectInfo.isTypeScript ? "Yes" : "No")}`);
834
+ console.log(` \u2022 Has src/ folder: ${pc5.cyan(projectInfo.hasSrcDir ? "Yes" : "No")}`);
835
+ console.log(` \u2022 Tailwind CSS: ${pc5.cyan(projectInfo.hasTailwind ? "Yes" : "No")}`);
836
+ console.log("");
837
+ console.log(pc5.bold(pc5.white("Authentication:")));
838
+ const token = getAuthToken();
839
+ if (token) {
840
+ const maskedKey = token.slice(0, 6) + "\u2026" + token.slice(-4);
841
+ console.log(` \u2022 Status: ${pc5.green("Authenticated")}`);
842
+ console.log(` \u2022 Key: ${pc5.cyan(maskedKey)}`);
843
+ } else {
844
+ console.log(` \u2022 Status: ${pc5.yellow("Anonymous (free tier)")}`);
845
+ console.log(` ${pc5.dim(" Run")} ${pc5.cyan("vv login <key>")} ${pc5.dim("to unlock pro components")}`);
846
+ }
847
+ console.log("");
848
+ console.log(pc5.bold(pc5.white("Configuration (vv.config.json):")));
849
+ if (config) {
850
+ console.log(` \u2022 Framework Set: ${pc5.cyan(config.framework)}`);
851
+ console.log(` \u2022 TypeScript Set: ${pc5.cyan(config.tsx ? "Yes" : "No")}`);
852
+ console.log(` \u2022 Component Alias: ${pc5.cyan(config.aliases.vv)}`);
853
+ } else {
854
+ console.log(` ${pc5.yellow("\u26A0\uFE0F No vv.config.json found in this directory.")}`);
855
+ }
856
+ console.log("");
857
+ console.log(pc5.bold(pc5.white("Installed Components (vv-manifest.json):")));
858
+ const manifestPath = path6.join(process.cwd(), "vv-manifest.json");
859
+ if (fs6.existsSync(manifestPath)) {
860
+ try {
861
+ const manifest = JSON.parse(fs6.readFileSync(manifestPath, "utf-8"));
862
+ const components = manifest.components || {};
863
+ const slugs = Object.keys(components);
864
+ if (slugs.length === 0) {
865
+ console.log(` ${pc5.dim("No components installed yet.")}`);
866
+ } else {
867
+ console.log(pc5.dim(" ------------------------------------------------------------"));
868
+ console.log(` ${pc5.bold(pc5.white("Component".padEnd(25)))} ${pc5.bold(pc5.white("Version".padEnd(10)))} ${pc5.bold(pc5.white("Installed At"))}`);
869
+ console.log(pc5.dim(" ------------------------------------------------------------"));
870
+ for (const slug of slugs) {
871
+ const info = components[slug];
872
+ const version2 = info?.version ?? "unknown";
873
+ const installedAt = info?.installedAt ?? "unknown";
874
+ console.log(` ${pc5.cyan(slug.padEnd(25))} ${pc5.green(version2.padEnd(10))} ${pc5.gray(installedAt)}`);
875
+ }
876
+ console.log(pc5.dim(" ------------------------------------------------------------"));
877
+ }
878
+ } catch {
879
+ console.log(` ${pc5.red("\u274C Error reading vv-manifest.json: Invalid JSON format.")}`);
880
+ }
881
+ } else {
882
+ console.log(` ${pc5.dim("No components installed yet (vv-manifest.json not found).")}`);
883
+ }
884
+ console.log("");
885
+ }
886
+
887
+ // src/commands/diff.ts
888
+ import fs7 from "fs";
889
+ import path7 from "path";
890
+ import pc6 from "picocolors";
891
+ import ora3 from "ora";
892
+ async function diffCommand(slug) {
893
+ const projectRoot = process.cwd();
894
+ const manifestPath = path7.join(projectRoot, "vv-manifest.json");
895
+ if (!fs7.existsSync(manifestPath)) {
896
+ console.log(pc6.yellow(`
897
+ \u26A0\uFE0F No vv-manifest.json found in this directory.`));
898
+ console.log(`\u{1F449} Add components first using ${pc6.bold(pc6.cyan("vv add <slug>"))}
899
+ `);
900
+ return;
901
+ }
902
+ let manifest;
903
+ try {
904
+ manifest = JSON.parse(fs7.readFileSync(manifestPath, "utf-8"));
905
+ } catch {
906
+ console.error(pc6.red(`\u274C Error reading vv-manifest.json: Invalid JSON format.`));
907
+ process.exit(1);
908
+ return;
909
+ }
910
+ const installedComponents = manifest.components || {};
911
+ const installedSlugs = Object.keys(installedComponents);
912
+ if (installedSlugs.length === 0) {
913
+ console.log(pc6.yellow(`
914
+ \u26A0\uFE0F No components are registered as installed in vv-manifest.json.`));
915
+ console.log(`\u{1F449} Add components using ${pc6.bold(pc6.cyan("vv add <slug>"))}
916
+ `);
917
+ return;
918
+ }
919
+ if (slug) {
920
+ try {
921
+ validateSlug(slug);
922
+ } catch (error) {
923
+ const message = error instanceof Error ? error.message : String(error);
924
+ console.error(pc6.red(`
925
+ \u274C ${message}
926
+ `));
927
+ process.exit(1);
928
+ }
929
+ if (!installedComponents[slug]) {
930
+ console.log(pc6.red(`
931
+ \u274C Component "${slug}" is not installed in this project.`));
932
+ console.log(`\u{1F449} Run ${pc6.cyan(`vv add ${slug}`)} to install it.
933
+ `);
934
+ return;
935
+ }
936
+ const localInfo = installedComponents[slug];
937
+ const spinner = ora3({
938
+ text: `Checking updates for ${pc6.cyan(slug)}...`,
939
+ color: "cyan"
940
+ }).start();
941
+ try {
942
+ const remoteComponent = await fetchComponent(slug);
943
+ spinner.stop();
944
+ const localVersion = localInfo.version ?? "unknown";
945
+ const remoteVersion = remoteComponent.version;
946
+ console.log(pc6.bold(pc6.cyan(`
947
+ Component Diff: ${slug}`)));
948
+ console.log(` \u2022 Local version: ${pc6.yellow(localVersion)}`);
949
+ console.log(` \u2022 Latest version: ${pc6.green(remoteVersion)}`);
950
+ if (localVersion === remoteVersion) {
951
+ console.log(` \u2022 Status: ${pc6.green("\u2714 Up to date")}
952
+ `);
953
+ } else {
954
+ console.log(` \u2022 Status: ${pc6.bold(pc6.yellow("\u26A0\uFE0F Update available!"))}`);
955
+ console.log(` \u2022 Run ${pc6.bold(pc6.cyan(`vv add ${slug} --overwrite`))} to update.
956
+ `);
957
+ }
958
+ } catch (error) {
959
+ const message = error instanceof Error ? error.message : String(error);
960
+ spinner.fail(pc6.red(`Failed to fetch remote component details: ${message}`));
961
+ process.exit(1);
962
+ }
963
+ } else {
964
+ const spinner = ora3({
965
+ text: "Comparing local components with registry index...",
966
+ color: "cyan"
967
+ }).start();
968
+ try {
969
+ const registryIndex = await fetchRegistryIndex();
970
+ spinner.stop();
971
+ console.log(pc6.bold(pc6.cyan("\nVector Vesper Component Version Comparison\n")));
972
+ console.log(pc6.dim(" ----------------------------------------------------------------------"));
973
+ console.log(
974
+ ` ${pc6.bold(pc6.white("Component".padEnd(25)))} ${pc6.bold(
975
+ pc6.white("Local".padEnd(10))
976
+ )} ${pc6.bold(pc6.white("Latest".padEnd(10)))} ${pc6.bold(pc6.white("Status"))}`
977
+ );
978
+ console.log(pc6.dim(" ----------------------------------------------------------------------"));
979
+ let updatesAvailableCount = 0;
980
+ for (const instSlug of installedSlugs) {
981
+ const localInfo = installedComponents[instSlug];
982
+ const localVersion = localInfo?.version ?? "unknown";
983
+ const remoteInfo = registryIndex.components.find((c) => c.slug === instSlug);
984
+ if (!remoteInfo) {
985
+ console.log(
986
+ ` ${pc6.cyan(instSlug.padEnd(25))} ${pc6.yellow(
987
+ localVersion.padEnd(10)
988
+ )} ${pc6.red("unknown".padEnd(10))} ${pc6.red("Not found in registry")}`
989
+ );
990
+ } else {
991
+ const remoteVersion = remoteInfo.version;
992
+ if (localVersion === remoteVersion) {
993
+ console.log(
994
+ ` ${pc6.cyan(instSlug.padEnd(25))} ${pc6.gray(
995
+ localVersion.padEnd(10)
996
+ )} ${pc6.gray(remoteVersion.padEnd(10))} ${pc6.green("\u2714 Up to date")}`
997
+ );
998
+ } else {
999
+ updatesAvailableCount++;
1000
+ console.log(
1001
+ ` ${pc6.cyan(instSlug.padEnd(25))} ${pc6.yellow(
1002
+ localVersion.padEnd(10)
1003
+ )} ${pc6.green(remoteVersion.padEnd(10))} ${pc6.bold(
1004
+ pc6.yellow("\u26A0\uFE0F Update available")
1005
+ )}`
1006
+ );
1007
+ }
1008
+ }
1009
+ }
1010
+ console.log(pc6.dim(" ----------------------------------------------------------------------"));
1011
+ if (updatesAvailableCount > 0) {
1012
+ console.log(
1013
+ `
1014
+ \u{1F4E2} ${pc6.bold(pc6.yellow(String(updatesAvailableCount)))} component(s) have updates available.`
1015
+ );
1016
+ console.log(`\u{1F449} Run ${pc6.bold(pc6.cyan("vv add <slug> --overwrite"))} to update a component.
1017
+ `);
1018
+ } else {
1019
+ console.log(`
1020
+ ${pc6.green("\u2714")} All components are up to date!
1021
+ `);
1022
+ }
1023
+ } catch (error) {
1024
+ const message = error instanceof Error ? error.message : String(error);
1025
+ spinner.fail(pc6.red(`Failed to check updates: ${message}`));
1026
+ process.exit(1);
1027
+ }
1028
+ }
1029
+ }
1030
+
1031
+ // src/commands/auth.ts
1032
+ import pc7 from "picocolors";
1033
+ async function loginCommand(key) {
1034
+ if (!key || key.trim().length === 0) {
1035
+ logger.error("Please provide a license key.");
1036
+ console.log(`
1037
+ Usage: ${pc7.cyan("vv login <your-license-key>")}`);
1038
+ console.log(` \u{1F511} Get a key at ${pc7.cyan("https://vectorvesper.dev/pricing")}
1039
+ `);
1040
+ process.exit(1);
1041
+ }
1042
+ const trimmedKey = key.trim();
1043
+ if (trimmedKey.length < 8) {
1044
+ logger.error("That doesn't look like a valid license key.");
1045
+ console.log(`
1046
+ \u{1F511} License keys are available at ${pc7.cyan("https://vectorvesper.dev/pricing")}
1047
+ `);
1048
+ process.exit(1);
1049
+ }
1050
+ const config = loadGlobalConfig();
1051
+ config.auth = {
1052
+ ...config.auth,
1053
+ token: trimmedKey
1054
+ };
1055
+ try {
1056
+ saveGlobalConfig(config);
1057
+ } catch (error) {
1058
+ const message = error instanceof Error ? error.message : String(error);
1059
+ logger.error(`Failed to save credentials: ${message}`);
1060
+ process.exit(1);
1061
+ }
1062
+ console.log("");
1063
+ console.log(` ${pc7.green(pc7.bold("\u2714"))} License key saved successfully!`);
1064
+ console.log(` ${pc7.dim("Stored at:")} ${pc7.cyan(getGlobalConfigPath())}`);
1065
+ console.log("");
1066
+ console.log(` ${pc7.dim("Pro components are now available when you run")} ${pc7.cyan("vv add <slug>")}`);
1067
+ console.log(` ${pc7.dim("To verify your account:")} ${pc7.cyan("vv whoami")}`);
1068
+ console.log("");
1069
+ }
1070
+ async function logoutCommand() {
1071
+ const config = loadGlobalConfig();
1072
+ if (!config.auth?.token) {
1073
+ console.log(`
1074
+ ${pc7.dim("You're not logged in. Nothing to do.")}
1075
+ `);
1076
+ return;
1077
+ }
1078
+ delete config.auth;
1079
+ try {
1080
+ saveGlobalConfig(config);
1081
+ } catch (error) {
1082
+ const message = error instanceof Error ? error.message : String(error);
1083
+ logger.error(`Failed to clear credentials: ${message}`);
1084
+ process.exit(1);
1085
+ }
1086
+ console.log("");
1087
+ console.log(` ${pc7.green(pc7.bold("\u2714"))} Logged out successfully.`);
1088
+ console.log(` ${pc7.dim("Your license key has been removed from")} ${pc7.cyan(getGlobalConfigPath())}`);
1089
+ console.log(` ${pc7.dim("Free components remain available.")}`);
1090
+ console.log("");
1091
+ }
1092
+ async function whoamiCommand() {
1093
+ const config = loadGlobalConfig();
1094
+ console.log("");
1095
+ console.log(pc7.bold(pc7.cyan(" Vector Vesper Account Status\n")));
1096
+ if (config.auth?.token) {
1097
+ const maskedKey = config.auth.token.slice(0, 6) + "\u2026" + config.auth.token.slice(-4);
1098
+ const tier = config.auth.tier || "pro";
1099
+ console.log(` ${pc7.bold("Status:")} ${pc7.green("Authenticated")}`);
1100
+ console.log(` ${pc7.bold("Key:")} ${pc7.cyan(maskedKey)}`);
1101
+ console.log(` ${pc7.bold("Tier:")} ${pc7.magenta(tier)}`);
1102
+ if (config.auth.email) {
1103
+ console.log(` ${pc7.bold("Email:")} ${pc7.cyan(config.auth.email)}`);
1104
+ }
1105
+ if (config.auth.expiresAt) {
1106
+ const expiry = new Date(config.auth.expiresAt);
1107
+ const now = /* @__PURE__ */ new Date();
1108
+ const isExpired = expiry < now;
1109
+ console.log(` ${pc7.bold("Expires:")} ${isExpired ? pc7.red("EXPIRED") : pc7.green(expiry.toLocaleDateString())}`);
1110
+ }
1111
+ console.log("");
1112
+ console.log(` ${pc7.dim("Config:")} ${pc7.cyan(getGlobalConfigPath())}`);
1113
+ console.log(` ${pc7.dim("Manage your account at")} ${pc7.cyan("https://vectorvesper.dev/account")}`);
1114
+ } else {
1115
+ console.log(` ${pc7.bold("Status:")} ${pc7.yellow("Anonymous (free tier)")}`);
1116
+ console.log(` ${pc7.bold("Access:")} ${pc7.dim("Free components only")}`);
1117
+ console.log("");
1118
+ console.log(` ${pc7.dim("To unlock pro components:")}`);
1119
+ console.log(` ${pc7.cyan("vv login <your-license-key>")}`);
1120
+ console.log(` \u{1F511} ${pc7.dim("Get a key at")} ${pc7.cyan("https://vectorvesper.dev/pricing")}`);
1121
+ }
1122
+ console.log("");
1123
+ }
1124
+
1125
+ // src/index.ts
1126
+ var version = true ? "0.1.0" : "0.0.0-dev";
1127
+ var program = new Command();
1128
+ 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) => {
1129
+ const opts = thisCommand.opts();
1130
+ if (opts.verbose) {
1131
+ setVerbose(true);
1132
+ }
1133
+ });
1134
+ program.command("init").description("Initialize Vector Vesper configuration in your project").option("-y, --yes", "Accept all default prompts based on project detection").action(async (options) => {
1135
+ await initCommand(options);
1136
+ });
1137
+ program.command("list").description("List all available components in the registry").action(async () => {
1138
+ await listCommand();
1139
+ });
1140
+ 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) => {
1141
+ await addCommand(slug, options);
1142
+ });
1143
+ program.command("info").description("Show project diagnostics and list installed components").action(async () => {
1144
+ await infoCommand();
1145
+ });
1146
+ program.command("diff [slug]").description("Check for updates against the component registry").action(async (slug) => {
1147
+ await diffCommand(slug);
1148
+ });
1149
+ program.command("login <key>").description("Authenticate with a license key for pro components").action(async (key) => {
1150
+ await loginCommand(key);
1151
+ });
1152
+ program.command("logout").description("Clear stored license credentials").action(async () => {
1153
+ await logoutCommand();
1154
+ });
1155
+ program.command("whoami").description("Show current authentication status").action(async () => {
1156
+ await whoamiCommand();
1157
+ });
1158
+ process.on("unhandledRejection", (error) => {
1159
+ console.error("An unexpected error occurred:", error instanceof Error ? error.message : error);
1160
+ process.exit(1);
1161
+ });
1162
+ await program.parseAsync(process.argv);
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "vectorvesper",
3
+ "version": "0.1.0",
4
+ "description": "Add premium WebGL and React visual components to your project via CLI",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "bin": {
9
+ "vv": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "package.json"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/vectorvesper/vector-vesper.git",
21
+ "directory": "packages/cli"
22
+ },
23
+ "homepage": "https://vectorvesper.dev",
24
+ "bugs": {
25
+ "url": "https://github.com/vectorvesper/vector-vesper/issues"
26
+ },
27
+ "keywords": [
28
+ "react",
29
+ "webgl",
30
+ "three",
31
+ "threejs",
32
+ "components",
33
+ "cli",
34
+ "ui",
35
+ "visual",
36
+ "animation",
37
+ "gsap",
38
+ "shader",
39
+ "interactive"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "dev": "tsup --watch",
44
+ "typecheck": "tsc --noEmit"
45
+ },
46
+ "dependencies": {
47
+ "@clack/prompts": "^0.7.0",
48
+ "commander": "^12.0.0",
49
+ "ora": "^8.0.1",
50
+ "picocolors": "^1.0.0",
51
+ "zod": "^3.22.4"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^25.9.2",
55
+ "tsup": "^8.0.2",
56
+ "typescript": "^6.0.3"
57
+ }
58
+ }