inai-react-components 1.6.2 → 2.0.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.
@@ -1,4 +1,4 @@
1
- import { type RegistryComponent, type ComponentsJsonFile } from "./status.js";
1
+ import { type RegistryComponent, type RegistryJson, type RegistryTheme, type ComponentsJsonFile } from "./status.js";
2
2
  export interface AddOptions {
3
3
  /** Overwrite existing files without prompting. */
4
4
  force?: boolean;
@@ -53,6 +53,12 @@ export declare function findInRegistry(registry: {
53
53
  blocks: RegistryComponent[];
54
54
  templates: RegistryComponent[];
55
55
  }, name: string, prefix: string | null): RegistryComponent | null;
56
+ /**
57
+ * Find a theme in the registry by name. Themes live in a separate list
58
+ * from components because they are plain CSS files — no hook deps, no
59
+ * snapshot pipeline, no import rewriting.
60
+ */
61
+ export declare function findThemeInRegistry(registry: RegistryJson, name: string): RegistryTheme | null;
56
62
  /**
57
63
  * Determine the target directory for a component based on its type and the
58
64
  * project's aliases + layout from `components.json`.
@@ -1 +1 @@
1
- {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,iBAAiB,EAEtB,KAAK,kBAAkB,EAExB,MAAM,aAAa,CAAC;AA0BrB,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kEAAkE;IAClE,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAChD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,CAsCA;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE;IAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAAC,SAAS,EAAE,iBAAiB,EAAE,CAAA;CAAE,EAC1G,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,IAAI,GACpB,iBAAiB,GAAG,IAAI,CAc1B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,iBAAiB,EAC5B,cAAc,EAAE,kBAAkB,GACjC,MAAM,CAiBR;AAgRD,wBAAsB,MAAM,CAC1B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CAmMpB;AAqCD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,UAAU,CAC9B,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAqIf"}
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAExB,MAAM,aAAa,CAAC;AA0BrB,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kEAAkE;IAClE,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAChD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,CAyCA;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE;IAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAAC,SAAS,EAAE,iBAAiB,EAAE,CAAA;CAAE,EAC1G,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,IAAI,GACpB,iBAAiB,GAAG,IAAI,CAc1B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,MAAM,GACX,aAAa,GAAG,IAAI,CAGtB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,iBAAiB,EAC5B,cAAc,EAAE,kBAAkB,GACjC,MAAM,CAiBR;AAsYD,wBAAsB,MAAM,CAC1B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CA0NpB;AAqCD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,UAAU,CAC9B,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAqIf"}
@@ -46,6 +46,10 @@ export function parseComponentSpec(spec) {
46
46
  prefix = "template";
47
47
  name = rest.slice(9).trim();
48
48
  }
49
+ else if (rest.startsWith("theme/")) {
50
+ prefix = "theme";
51
+ name = rest.slice(6).trim();
52
+ }
49
53
  else {
50
54
  name = rest;
51
55
  }
@@ -75,6 +79,16 @@ export function findInRegistry(registry, name, prefix) {
75
79
  ];
76
80
  return allItems.find((c) => c.name === name) ?? null;
77
81
  }
82
+ /**
83
+ * Find a theme in the registry by name. Themes live in a separate list
84
+ * from components because they are plain CSS files — no hook deps, no
85
+ * snapshot pipeline, no import rewriting.
86
+ */
87
+ export function findThemeInRegistry(registry, name) {
88
+ if (!registry.themes)
89
+ return null;
90
+ return registry.themes.find((t) => t.name === name) ?? null;
91
+ }
78
92
  /**
79
93
  * Determine the target directory for a component based on its type and the
80
94
  * project's aliases + layout from `components.json`.
@@ -243,6 +257,10 @@ async function installOne(component, registry, componentsJson, registryDir, root
243
257
  depFileName = `${depBase}.tsx`;
244
258
  }
245
259
  else {
260
+ // A registry-declared internalDep is missing on disk. Skipping it
261
+ // silently produces a runtime ReferenceError in the consumer's app
262
+ // (the copied component still imports the dep). Surface it.
263
+ console.warn(chalk.yellow(` ⚠ internalDep "${dep}" declared by "${component.name}" was not found in registry source (expected .ts or .tsx at packages/ui/src/${dep}). The copied component may have a broken import.`));
246
264
  continue;
247
265
  }
248
266
  // e.g. libDir = "src/lib" → target: "src/lib/i18n-context.tsx"
@@ -339,6 +357,96 @@ async function installOne(component, registry, componentsJson, registryDir, root
339
357
  skipped: skippedFiles,
340
358
  };
341
359
  }
360
+ /**
361
+ * Install a single theme preset (plain CSS file). Themes bypass the
362
+ * component pipeline entirely — no hook deps, no snapshot, no import
363
+ * rewriting. The destination directory is derived from the user's
364
+ * components.json aliases, defaulting to `<sourceRoot>/styles/themes/`.
365
+ */
366
+ async function installTheme(theme, componentsJson, registryDir, rootDir, options) {
367
+ const name = theme.name;
368
+ // Destination: `<sourceRoot>/styles/themes/` by convention. The v2.0
369
+ // `components.json` schema doesn't yet expose `aliases.themes` — when
370
+ // it eventually does, read it here. For now use the source root.
371
+ const sourceRoot = getSourceRoot(componentsJson);
372
+ const themesDir = `${sourceRoot}/styles/themes`;
373
+ const copiedFiles = [];
374
+ const skippedFiles = [];
375
+ for (const filePath of theme.files) {
376
+ const registryFilePath = path.join(registryDir, filePath);
377
+ if (!fs.existsSync(registryFilePath)) {
378
+ continue;
379
+ }
380
+ const destRel = path.basename(filePath); // e.g. "ultimate.css"
381
+ const localFilePath = path.join(rootDir, themesDir, destRel);
382
+ const displayName = `${name}/${destRel}`;
383
+ const exists = fs.existsSync(localFilePath);
384
+ if (exists && !options.force) {
385
+ if (options.skipExisting) {
386
+ skippedFiles.push(displayName);
387
+ continue;
388
+ }
389
+ if (options.dryRun) {
390
+ // would-overwrite
391
+ }
392
+ else if (process.stdin.isTTY) {
393
+ const res = await prompts({
394
+ type: "confirm",
395
+ name: "overwrite",
396
+ message: `Theme file "${path.relative(rootDir, localFilePath)}" already exists. Overwrite?`,
397
+ initial: false,
398
+ });
399
+ if (!res.overwrite) {
400
+ skippedFiles.push(displayName);
401
+ continue;
402
+ }
403
+ }
404
+ else {
405
+ throw new Error(`Theme file "${path.relative(rootDir, localFilePath)}" already exists. Use --force or --skip-existing.`);
406
+ }
407
+ }
408
+ const content = fs.readFileSync(registryFilePath, "utf-8");
409
+ if (options.dryRun) {
410
+ console.log(chalk.dim(` [dry-run] would write ${path.relative(rootDir, localFilePath)}`));
411
+ }
412
+ else {
413
+ fs.mkdirSync(path.dirname(localFilePath), { recursive: true });
414
+ fs.writeFileSync(localFilePath, content);
415
+ }
416
+ copiedFiles.push(displayName);
417
+ }
418
+ // Track as "installedComponents" so `status` / `list` / `update` can
419
+ // still find it — the shape is compatible and avoids a parallel store.
420
+ if (!options.dryRun) {
421
+ const installed = componentsJson.installedComponents ?? [];
422
+ const existingIdx = installed.findIndex((c) => c.name === `theme/${name}`);
423
+ const entry = {
424
+ name: `theme/${name}`,
425
+ version: "2.0.0",
426
+ installedAt: new Date().toISOString().split("T")[0],
427
+ };
428
+ if (existingIdx >= 0) {
429
+ installed[existingIdx] = entry;
430
+ }
431
+ else {
432
+ installed.push(entry);
433
+ }
434
+ componentsJson.installedComponents = installed;
435
+ const componentsJsonPath = path.join(rootDir, "components.json");
436
+ fs.writeFileSync(componentsJsonPath, JSON.stringify(componentsJson, null, 2) + "\n");
437
+ }
438
+ const relThemesDir = themesDir;
439
+ const importHint = `@import "./${relThemesDir}/${name}.css";`;
440
+ return {
441
+ added: true,
442
+ message: `Theme "${name}" installed to ${relThemesDir}/. ` +
443
+ `Add to your main stylesheet: ${importHint}`,
444
+ files: copiedFiles,
445
+ npmDeps: [],
446
+ internalDeps: [],
447
+ skipped: skippedFiles,
448
+ };
449
+ }
342
450
  export async function runAdd(componentSpec, rootDir, options = {}) {
343
451
  const componentsJson = readComponentsJson(rootDir);
344
452
  if (!componentsJson) {
@@ -424,6 +532,27 @@ export async function runAdd(componentSpec, rootDir, options = {}) {
424
532
  internalDeps: [],
425
533
  };
426
534
  }
535
+ // ─── Theme install fast path ─────────────────────────────────────
536
+ // Themes bypass the component dependency graph entirely — they are
537
+ // plain CSS files with no hooks, no registryDependencies and no
538
+ // transformation pipeline. Handle them first when the spec uses the
539
+ // `theme/` prefix or when a theme with this name exists in the registry.
540
+ if (prefix === "theme") {
541
+ const theme = findThemeInRegistry(registry, name);
542
+ if (!theme) {
543
+ const themeNames = registry.themes?.map((t) => t.name) ?? [];
544
+ const suggestions = fuzzySearchNames(name, themeNames, 0.4).slice(0, 5);
545
+ return {
546
+ added: false,
547
+ message: `Theme "${name}" not found in registry.`,
548
+ files: [],
549
+ npmDeps: [],
550
+ internalDeps: [],
551
+ suggestions,
552
+ };
553
+ }
554
+ return installTheme(theme, componentsJson, registryDir, rootDir, options);
555
+ }
427
556
  const component = findInRegistry(registry, name, prefix);
428
557
  if (!component) {
429
558
  const label = prefix ? `${prefix}/${name}` : name;
@@ -432,6 +561,7 @@ export async function runAdd(componentSpec, rootDir, options = {}) {
432
561
  ...registry.components.map((c) => c.name),
433
562
  ...registry.blocks.map((c) => c.name),
434
563
  ...registry.templates.map((c) => c.name),
564
+ ...(registry.themes?.map((t) => `theme/${t.name}`) ?? []),
435
565
  ];
436
566
  const suggestions = fuzzySearchNames(name, allNames, 0.4).slice(0, 5);
437
567
  return {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `inai-ui demo start` — launches the docs dev server and prints the
3
+ * keyboard shortcut that flips it into Pitch mode.
4
+ *
5
+ * Thin wrapper around `pnpm --filter docs dev`: the heavy lifting lives
6
+ * in the docs app's `PitchModeProvider` + `LiveThemeSwitcher`, so this
7
+ * command is really a convenience so the team can run a single command
8
+ * before a client call.
9
+ *
10
+ * Flags:
11
+ * --theme <name> preselect a theme by writing `localStorage` seed
12
+ * (delegated — currently prints the instructions).
13
+ * --port <number> vite port (defaults to 3000).
14
+ */
15
+ export interface DemoStartOptions {
16
+ theme?: string;
17
+ port?: number;
18
+ }
19
+ export declare function demoStartCommand(options?: DemoStartOptions): Promise<void>;
20
+ //# sourceMappingURL=demo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../src/commands/demo.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -0,0 +1,15 @@
1
+ import { spawn } from "node:child_process";
2
+ import chalk from "chalk";
3
+ export async function demoStartCommand(options = {}) {
4
+ const port = options.port ?? 3000;
5
+ console.log(chalk.bold.cyan("▶ Starting InAI UI demo shell"), chalk.dim(`(port ${port})`));
6
+ console.log(chalk.dim(" Press ⌘⇧P (macOS) or Ctrl+Shift+P once the app is up to enter pitch mode."));
7
+ console.log(chalk.dim(" Use ← / → or PageUp / PageDown to walk the demo stops, Esc to exit."));
8
+ if (options.theme) {
9
+ console.log(chalk.yellow(` Theme preselect requested: "${options.theme}" — once the dock opens, the preset will be available in the 'Presets' tab.`));
10
+ }
11
+ const proc = spawn("pnpm", ["--filter", "docs", "dev", "--port", String(port)], { stdio: "inherit", env: { ...process.env } });
12
+ proc.on("exit", (code) => {
13
+ process.exit(code ?? 0);
14
+ });
15
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `inai-ui generate block <description>` — scaffold a brand-new block
3
+ * from a natural-language description using the Anthropic API.
4
+ *
5
+ * Design goals:
6
+ * - **Zero new dependencies**: we talk to the Anthropic REST API via
7
+ * `fetch` directly. No `@anthropic-ai/sdk`.
8
+ * - **Safe by default**: we never overwrite existing files without an
9
+ * explicit `--force` flag and dry-run is always available.
10
+ * - **Grounded output**: the system prompt carries the canonical InAI UI
11
+ * conventions + a short catalog of available building blocks so the
12
+ * model reuses primitives instead of hallucinating new ones.
13
+ * - **Extractable**: responses are expected to carry a fenced ```tsx
14
+ * block followed by an optional ```ts data block. A thin parser pulls
15
+ * both sections out of the response.
16
+ */
17
+ export interface GenerateOptions {
18
+ description?: string;
19
+ slug?: string;
20
+ output?: string;
21
+ model?: string;
22
+ dryRun?: boolean;
23
+ force?: boolean;
24
+ apiKey?: string;
25
+ maxTokens?: number;
26
+ }
27
+ export declare function toPascalCase(slug: string): string;
28
+ export declare function sanitizeSlug(raw: string): string;
29
+ export interface ParsedResponse {
30
+ pageTsx: string;
31
+ dataTs: string | null;
32
+ raw: string;
33
+ }
34
+ /**
35
+ * Pulls the first ```tsx / ```ts / ```typescript fenced block out of the
36
+ * assistant's response. We expect exactly two at most — one for the page
37
+ * and one for the optional data.ts.
38
+ */
39
+ export declare function extractCodeBlocks(text: string): ParsedResponse;
40
+ export declare function callClaude(args: {
41
+ apiKey: string;
42
+ model: string;
43
+ description: string;
44
+ maxTokens: number;
45
+ }): Promise<string>;
46
+ export declare function generateBlockCommand(description: string | undefined, options: GenerateOptions): Promise<void>;
47
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+CD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOhD;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAmB9D;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClB;AAED,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC,CA2Jf"}
@@ -0,0 +1,204 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import chalk from "chalk";
4
+ import ora from "ora";
5
+ import prompts from "prompts";
6
+ const DEFAULT_MODEL = "claude-sonnet-4-6";
7
+ const DEFAULT_OUTPUT = "apps/docs/src/registry/blocks";
8
+ const SYSTEM_PROMPT = `You are a senior React engineer generating a production-ready "block" for the InAI UI component library. A block is a full-bleed section component that ships under \`apps/docs/src/registry/blocks/<slug>/page.tsx\` and, when helpful, a sibling \`data.ts\` for mock content.
9
+
10
+ Hard requirements, in order of importance:
11
+
12
+ 1. Stack: React 19, TypeScript strict, Tailwind CSS v4 with \`@theme\`.
13
+ 2. Always import UI primitives from \`@company/ui/<name>\` (e.g. \`import { Button } from "@company/ui/button"\`). Never barrel-import. Never hardcode colours — always use CSS custom properties from the active theme:
14
+ - \`--color-primary\`, \`--color-accent\`, \`--color-info\`, \`--color-success\`, \`--color-warning\`, \`--color-destructive\`
15
+ - Backgrounds: \`--color-background\`, \`--color-surface\`, \`--color-surface-muted\`
16
+ - Text: \`--color-text\`, \`--color-text-secondary\`, \`--color-text-tertiary\`
17
+ - Borders: \`--color-border\`
18
+ - Radii: \`--radius-sm\`, \`--radius-md\`, \`--radius-lg\`
19
+ - Shadows: \`--shadow-sm\`, \`--shadow-md\`, \`--shadow-lg\`
20
+ - Fonts: \`--font-display\`, \`--font-body\`, \`--font-mono\`
21
+ 3. Use Motion (imported from \`motion/react\`) for animations. Spring physics, not duration-based easing. Always respect \`useReducedMotion\`.
22
+ 4. Prefer existing primitives from the library when possible. Catalogue (non-exhaustive, most useful for blocks): Button, Card, Badge, Input, Textarea, Select, Checkbox, Switch, Tabs, Dialog, Sheet, Tooltip, Avatar, Progress, Separator, MarqueeInfinite, GradientMesh, TextMask, KineticHeading, DecryptText, ParticleText, MagneticCursor, NoiseTexture, HexbinHeatmap, SparklineLive, RadialProgress, FunnelAnimated, RadarMotion, HeatmapCalendar, CardStackDeal, MorphSVG, ForceGraphMini, WaveformCanvas, ChordDiagram, SceneTransitions, PremiumLineChart, PremiumBarChart, PremiumAreaChart, PremiumPieChart.
23
+ 5. The block MUST export a default React component named from the slug in PascalCase + "Page" (e.g. slug \`hero-magnetic\` → \`HeroMagneticPage\`).
24
+ 6. The block MUST be accessible: semantic HTML, aria-labels where appropriate, focus-visible states, and respect \`prefers-reduced-motion\`.
25
+ 7. If the block needs content data (stats, testimonials, products, nav items, etc.), emit a sibling \`data.ts\` with a typed export and import it from the page.
26
+ 8. No "lorem ipsum" — use believable, brand-neutral copy.
27
+ 9. No external image URLs. For visuals use CSS gradients, SVG, \`GradientMesh\`, \`TextMask\`, or noise textures.
28
+ 10. Keep the block self-contained. Do not modify \`block-registry.tsx\`, \`block-categories.ts\`, or any other file — the CLI will handle registration.
29
+
30
+ Response format: respond ONLY with one or two fenced code blocks, nothing else.
31
+
32
+ - First block, required: \`\`\`tsx ...full page.tsx content... \`\`\`
33
+ - Second block, optional: \`\`\`ts ...full data.ts content... \`\`\`
34
+
35
+ No prose between, before, or after the code fences.`;
36
+ export function toPascalCase(slug) {
37
+ return slug
38
+ .split(/[-_\s]+/)
39
+ .filter(Boolean)
40
+ .map((w) => w[0].toUpperCase() + w.slice(1).toLowerCase())
41
+ .join("");
42
+ }
43
+ export function sanitizeSlug(raw) {
44
+ return raw
45
+ .toLowerCase()
46
+ .trim()
47
+ .replace(/[^a-z0-9]+/g, "-")
48
+ .replace(/^-+|-+$/g, "")
49
+ .slice(0, 64);
50
+ }
51
+ /**
52
+ * Pulls the first ```tsx / ```ts / ```typescript fenced block out of the
53
+ * assistant's response. We expect exactly two at most — one for the page
54
+ * and one for the optional data.ts.
55
+ */
56
+ export function extractCodeBlocks(text) {
57
+ const pageMatch = text.match(/```(?:tsx|jsx|typescript|ts)\s*\n([\s\S]*?)\n```/);
58
+ if (!pageMatch) {
59
+ throw new Error("Could not find a fenced tsx code block in the model response. Re-run with a more specific description.");
60
+ }
61
+ const pageTsx = pageMatch[1].trim();
62
+ // Strip everything through the first block and look for a second.
63
+ const afterFirst = text.slice((pageMatch.index ?? 0) + pageMatch[0].length);
64
+ const dataMatch = afterFirst.match(/```(?:ts|typescript)\s*\n([\s\S]*?)\n```/);
65
+ const dataTs = dataMatch ? dataMatch[1].trim() : null;
66
+ return { pageTsx, dataTs, raw: text };
67
+ }
68
+ export async function callClaude(args) {
69
+ const { apiKey, model, description, maxTokens } = args;
70
+ const userPrompt = `Generate a block for this description:\n\n${description.trim()}\n\nRemember: respond only with the fenced code blocks — no prose.`;
71
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
72
+ method: "POST",
73
+ headers: {
74
+ "content-type": "application/json",
75
+ "x-api-key": apiKey,
76
+ "anthropic-version": "2023-06-01",
77
+ },
78
+ body: JSON.stringify({
79
+ model,
80
+ max_tokens: maxTokens,
81
+ system: SYSTEM_PROMPT,
82
+ messages: [{ role: "user", content: userPrompt }],
83
+ }),
84
+ });
85
+ if (!response.ok) {
86
+ const body = await response.text();
87
+ throw new Error(`Anthropic API error ${response.status}: ${body.slice(0, 400)}`);
88
+ }
89
+ const payload = (await response.json());
90
+ const textParts = payload.content
91
+ .filter((c) => c.type === "text")
92
+ .map((c) => c.text);
93
+ if (textParts.length === 0) {
94
+ throw new Error("Model returned no text content. Try again.");
95
+ }
96
+ return textParts.join("\n");
97
+ }
98
+ export async function generateBlockCommand(description, options) {
99
+ let desc = description ?? options.description;
100
+ if (!desc) {
101
+ const res = await prompts({
102
+ type: "text",
103
+ name: "description",
104
+ message: "Describe the block you want to generate (e.g. 'hero with gradient mesh and kinetic headline')",
105
+ validate: (v) => (typeof v === "string" && v.trim().length > 10) ||
106
+ "Please provide more detail",
107
+ });
108
+ if (!res.description) {
109
+ console.log(chalk.yellow("✖ Aborted."));
110
+ return;
111
+ }
112
+ desc = res.description;
113
+ }
114
+ const apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY;
115
+ if (!apiKey) {
116
+ console.error(chalk.red("✖ Missing ANTHROPIC_API_KEY. Set it in your environment or pass --api-key."));
117
+ console.error(chalk.dim(" Get a key at https://console.anthropic.com/ — free tier includes generous credits."));
118
+ process.exitCode = 1;
119
+ return;
120
+ }
121
+ // Pick a slug.
122
+ let slug = options.slug ? sanitizeSlug(options.slug) : "";
123
+ if (!slug) {
124
+ const shortWords = desc
125
+ .toLowerCase()
126
+ .split(/\s+/)
127
+ .filter((w) => w.length > 2)
128
+ .slice(0, 4)
129
+ .join("-");
130
+ const suggestion = sanitizeSlug(shortWords || "generated-block");
131
+ const res = await prompts({
132
+ type: "text",
133
+ name: "slug",
134
+ message: "Slug for the new block (kebab-case):",
135
+ initial: suggestion,
136
+ validate: (v) => (typeof v === "string" && /^[a-z0-9-]+$/.test(v)) ||
137
+ "Use lowercase letters, numbers, and hyphens only",
138
+ });
139
+ if (!res.slug) {
140
+ console.log(chalk.yellow("✖ Aborted."));
141
+ return;
142
+ }
143
+ slug = sanitizeSlug(res.slug);
144
+ }
145
+ const outputRoot = options.output ?? DEFAULT_OUTPUT;
146
+ const blockDir = path.resolve(process.cwd(), outputRoot, slug);
147
+ const pagePath = path.join(blockDir, "page.tsx");
148
+ const dataPath = path.join(blockDir, "data.ts");
149
+ if (fs.existsSync(blockDir) && !options.force && !options.dryRun) {
150
+ console.error(chalk.red(`✖ ${path.relative(process.cwd(), blockDir)} already exists. Use --force to overwrite.`));
151
+ process.exitCode = 1;
152
+ return;
153
+ }
154
+ const model = options.model ?? DEFAULT_MODEL;
155
+ const maxTokens = options.maxTokens ?? 4096;
156
+ const spinner = ora(`Calling ${chalk.cyan(model)} to generate ${chalk.bold(slug)}…`).start();
157
+ let parsed;
158
+ try {
159
+ const text = await callClaude({ apiKey, model, description: desc, maxTokens });
160
+ parsed = extractCodeBlocks(text);
161
+ spinner.succeed(`Model returned ${text.length} chars.`);
162
+ }
163
+ catch (err) {
164
+ spinner.fail(err.message);
165
+ process.exitCode = 1;
166
+ return;
167
+ }
168
+ // Light post-processing: ensure the exported component name ends with "Page".
169
+ const expectedName = `${toPascalCase(slug)}Page`;
170
+ if (!parsed.pageTsx.includes(expectedName)) {
171
+ console.warn(chalk.yellow(`! Model did not export \`${expectedName}\`. You may need to rename the default export.`));
172
+ }
173
+ console.log();
174
+ console.log(chalk.bold("Generated files:"));
175
+ console.log(` ${chalk.cyan(path.relative(process.cwd(), pagePath))} ${chalk.dim(`(${parsed.pageTsx.split("\n").length} lines)`)}`);
176
+ if (parsed.dataTs) {
177
+ console.log(` ${chalk.cyan(path.relative(process.cwd(), dataPath))} ${chalk.dim(`(${parsed.dataTs.split("\n").length} lines)`)}`);
178
+ }
179
+ if (options.dryRun) {
180
+ console.log();
181
+ console.log(chalk.bold("── page.tsx ──"));
182
+ console.log(parsed.pageTsx);
183
+ if (parsed.dataTs) {
184
+ console.log();
185
+ console.log(chalk.bold("── data.ts ──"));
186
+ console.log(parsed.dataTs);
187
+ }
188
+ console.log();
189
+ console.log(chalk.dim("Dry run — no files written."));
190
+ return;
191
+ }
192
+ fs.mkdirSync(blockDir, { recursive: true });
193
+ fs.writeFileSync(pagePath, `${parsed.pageTsx}\n`, "utf8");
194
+ if (parsed.dataTs) {
195
+ fs.writeFileSync(dataPath, `${parsed.dataTs}\n`, "utf8");
196
+ }
197
+ console.log();
198
+ console.log(chalk.green(`✔ Block scaffolded at ${path.relative(process.cwd(), blockDir)}`));
199
+ console.log();
200
+ console.log(chalk.bold("Next steps:"));
201
+ console.log(` 1. Register in ${chalk.cyan("apps/docs/src/lib/block-registry.tsx")} so the route is discoverable.`);
202
+ console.log(` 2. Add ${chalk.bold(slug)} to a category in ${chalk.cyan("apps/docs/src/lib/block-categories.ts")} so it appears on ${chalk.cyan("/blocks")}.`);
203
+ console.log(` 3. Run ${chalk.cyan("pnpm --filter docs check-types")} and review the generated code before shipping.`);
204
+ }
@@ -260,6 +260,15 @@ export interface ComponentsJson {
260
260
  table: boolean;
261
261
  };
262
262
  }
263
+ /**
264
+ * Load an InitConfig from a JSON file. Used by the `--config <path>` flag
265
+ * to skip interactive prompts in CI / automated bootstrap flows. Missing
266
+ * optional fields are filled with sensible defaults; invalid/missing
267
+ * required fields throw with a clear message.
268
+ *
269
+ * The accepted JSON schema lives at `packages/cli/src/schemas/init-config.schema.json`.
270
+ */
271
+ export declare function loadInitConfigFromFile(configPath: string, framework?: Framework, layout?: ProjectLayout): InitConfig;
263
272
  export declare function promptInitConfig(registryDir?: string, framework?: Framework, layout?: ProjectLayout): Promise<InitConfig | null>;
264
273
  export declare function buildComponentsJson(config: InitConfig, repoUrl?: string): ComponentsJson;
265
274
  export declare function getCnTemplate(): string;
@@ -296,6 +305,13 @@ export declare function runInit(config: InitConfig, targetDir: string, repoUrl?:
296
305
  success: boolean;
297
306
  filesCreated: string[];
298
307
  }>;
299
- export declare function initCommand(repoUrl?: string): Promise<void>;
308
+ export interface InitCommandOptions {
309
+ /** Path to a JSON config file. When provided, interactive prompts are
310
+ * skipped and the config is loaded from disk. Enables CI/automated
311
+ * bootstrap. See `packages/cli/src/schemas/init-config.schema.json`
312
+ * for the accepted shape. */
313
+ config?: string;
314
+ }
315
+ export declare function initCommand(repoUrl?: string, options?: InitCommandOptions): Promise<void>;
300
316
  export {};
301
317
  //# sourceMappingURL=init.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}