inai-react-components 1.6.1 → 2.0.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.
@@ -7,6 +7,7 @@ import { cloneRegistry, getCacheDir } from "../utils/registry-resolver.js";
7
7
  import { THEME_NAMES } from "../utils/themes.js";
8
8
  import { detectFramework, frameworkLabel, getCssEntryPoint, } from "../utils/framework-detect.js";
9
9
  import { detectProjectLayout, } from "../utils/tsconfig-detect.js";
10
+ import { stripRelativeJsExtensions } from "../utils/paths.js";
10
11
  const AVAILABLE_THEMES = THEME_NAMES;
11
12
  const FONT_PRESETS = [
12
13
  // Sans-serif — geometric & modern
@@ -89,6 +90,90 @@ function discoverThemes(registryDir) {
89
90
  }
90
91
  return [...AVAILABLE_THEMES];
91
92
  }
93
+ // User-supplied paths in `--config <init.json>` are written into the
94
+ // consumer project. Reject absolute paths and any `..` segment so a
95
+ // malicious config can't escape the project root.
96
+ function assertSafeRelativePath(value, field) {
97
+ const trimmed = value.trim();
98
+ if (!trimmed) {
99
+ throw new Error(`Invalid ${field}: must be a non-empty string`);
100
+ }
101
+ if (path.isAbsolute(trimmed)) {
102
+ throw new Error(`Invalid ${field}: must be a relative path, got "${trimmed}"`);
103
+ }
104
+ if (trimmed.split(/[\\/]/).includes("..")) {
105
+ throw new Error(`Invalid ${field}: must not contain ".." segments`);
106
+ }
107
+ return trimmed;
108
+ }
109
+ /**
110
+ * Load an InitConfig from a JSON file. Used by the `--config <path>` flag
111
+ * to skip interactive prompts in CI / automated bootstrap flows. Missing
112
+ * optional fields are filled with sensible defaults; invalid/missing
113
+ * required fields throw with a clear message.
114
+ *
115
+ * The accepted JSON schema lives at `packages/cli/src/schemas/init-config.schema.json`.
116
+ */
117
+ export function loadInitConfigFromFile(configPath, framework, layout) {
118
+ const absPath = path.isAbsolute(configPath)
119
+ ? configPath
120
+ : path.join(process.cwd(), configPath);
121
+ if (!fs.existsSync(absPath)) {
122
+ throw new Error(`Config file not found: ${absPath}`);
123
+ }
124
+ let raw;
125
+ try {
126
+ raw = JSON.parse(fs.readFileSync(absPath, "utf-8"));
127
+ }
128
+ catch (e) {
129
+ const msg = e instanceof Error ? e.message : String(e);
130
+ throw new Error(`Config file is not valid JSON: ${msg}`);
131
+ }
132
+ const sourceRoot = layout?.sourceRoot ?? "src";
133
+ const componentPath = typeof raw.componentPath === "string"
134
+ ? assertSafeRelativePath(raw.componentPath, "componentPath")
135
+ : `${sourceRoot}/components/ui`;
136
+ const blockPath = typeof raw.blockPath === "string"
137
+ ? assertSafeRelativePath(raw.blockPath, "blockPath")
138
+ : `${sourceRoot}/components/blocks`;
139
+ const theme = typeof raw.theme === "string" ? raw.theme : AVAILABLE_THEMES[0] ?? "inai";
140
+ if (!AVAILABLE_THEMES.includes(theme)) {
141
+ throw new Error(`Unknown theme "${theme}". Allowed: ${AVAILABLE_THEMES.join(", ")}`);
142
+ }
143
+ const fontNames = FONT_PRESETS.map((f) => f.name);
144
+ const fontBody = typeof raw.fontBody === "string" && fontNames.includes(raw.fontBody)
145
+ ? raw.fontBody
146
+ : "outfit";
147
+ const fontHeading = typeof raw.fontHeading === "string" && fontNames.includes(raw.fontHeading)
148
+ ? raw.fontHeading
149
+ : "outfit";
150
+ const radius = typeof raw.radius === "number" ? raw.radius : 0.5;
151
+ const tanstackRouter = raw.tanstackRouter === true;
152
+ const tanstackQuery = raw.tanstackQuery === true;
153
+ const tanstackForm = raw.tanstackForm === true;
154
+ const tanstackTable = raw.tanstackTable === true;
155
+ const resolvedFramework = raw.framework ?? framework;
156
+ const cssEntryPoint = typeof raw.cssEntryPoint === "string"
157
+ ? assertSafeRelativePath(raw.cssEntryPoint, "cssEntryPoint")
158
+ : resolvedFramework
159
+ ? getCssEntryPoint(resolvedFramework, sourceRoot)
160
+ : `${sourceRoot}/index.css`;
161
+ return {
162
+ componentPath,
163
+ blockPath,
164
+ theme,
165
+ fontBody,
166
+ fontHeading,
167
+ radius,
168
+ tanstackRouter,
169
+ tanstackQuery,
170
+ tanstackForm,
171
+ tanstackTable,
172
+ framework: resolvedFramework,
173
+ cssEntryPoint,
174
+ layout,
175
+ };
176
+ }
92
177
  export async function promptInitConfig(registryDir, framework, layout) {
93
178
  const themes = discoverThemes(registryDir);
94
179
  const sourceRoot = layout?.sourceRoot ?? "src";
@@ -351,14 +436,41 @@ function copyTokensToProject(registryDir, targetDir, theme, sourceRoot) {
351
436
  }
352
437
  return copied;
353
438
  }
439
+ /**
440
+ * Read a source file from the registry and write it to the user's lib
441
+ * directory with imports normalized — `.js` suffixes stripped so the
442
+ * installed code reads naturally under bundler-style resolution (Vite,
443
+ * Next.js, TanStack Start, …). Never overwrites a pre-existing file so
444
+ * user customizations stay intact across re-runs.
445
+ *
446
+ * Returns the relative destination path if written, or `null` if skipped
447
+ * (file missing in registry, or already present in the user project).
448
+ */
449
+ function copyLibFileNormalized(srcPath, destPath) {
450
+ if (!fs.existsSync(srcPath))
451
+ return false;
452
+ if (fs.existsSync(destPath))
453
+ return false; // don't clobber user files
454
+ const raw = fs.readFileSync(srcPath, "utf-8");
455
+ // Strip `.js` from relative sibling imports so `i18n-context.tsx`'s
456
+ // `./i18n-types.js` becomes `./i18n-types`. We intentionally DON'T
457
+ // apply `rewriteLibImports` here — these files live inside the lib
458
+ // directory already, so their sibling imports are correctly relative
459
+ // and shouldn't be rerouted through the `@/lib/*` alias.
460
+ const normalized = stripRelativeJsExtensions(raw);
461
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
462
+ fs.writeFileSync(destPath, normalized);
463
+ return true;
464
+ }
354
465
  /**
355
466
  * Copies the i18n base files (context, types, defaults) from the registry
356
- * to `<sourceRoot>/lib/`. ~75 components import `useUIMessages` from the
357
- * i18n context; the CLI rewrites those imports at install-time to the
358
- * unified `aliases.lib` directory, so the files must land there.
467
+ * to the unified lib directory. ~75 components import `useUIMessages`
468
+ * from the i18n context; the CLI rewrites those imports at install-time
469
+ * to the `aliases.lib` directory, so the files must land there.
359
470
  *
360
- * Never overwrites pre-existing files so a user's custom i18n provider
361
- * stays intact across re-runs.
471
+ * Normalizes `.js` import suffixes in the process (so the copied
472
+ * `i18n-context.tsx` reads without bundler-incompatible extensions) and
473
+ * never overwrites pre-existing files.
362
474
  */
363
475
  export function copyI18nFiles(registryDir, targetDir, libDirRelative) {
364
476
  if (!registryDir)
@@ -366,20 +478,31 @@ export function copyI18nFiles(registryDir, targetDir, libDirRelative) {
366
478
  const files = ["i18n-context.tsx", "i18n-types.ts", "i18n-defaults.ts"];
367
479
  const srcBase = path.join(registryDir, "packages", "ui", "src", "lib");
368
480
  const destBase = path.join(targetDir, libDirRelative);
369
- fs.mkdirSync(destBase, { recursive: true });
370
481
  const copied = [];
371
482
  for (const file of files) {
372
483
  const src = path.join(srcBase, file);
373
- if (!fs.existsSync(src))
374
- continue;
375
484
  const dest = path.join(destBase, file);
376
- if (fs.existsSync(dest))
377
- continue; // don't clobber user files
378
- fs.copyFileSync(src, dest);
379
- copied.push(`${libDirRelative}/${file}`);
485
+ if (copyLibFileNormalized(src, dest)) {
486
+ copied.push(`${libDirRelative}/${file}`);
487
+ }
380
488
  }
381
489
  return copied;
382
490
  }
491
+ /**
492
+ * Copies the canonical `cn.ts` from the cached registry to the user's
493
+ * lib directory. We copy the REAL source file (not a hardcoded template)
494
+ * because components import extra type exports like `ClassNames` that
495
+ * any minimal template would miss. Returns `true` if the file was
496
+ * written, `false` if the registry copy was missing or a user file was
497
+ * already present.
498
+ */
499
+ export function copyCnFile(registryDir, targetDir, libDirRelative) {
500
+ if (!registryDir)
501
+ return false;
502
+ const src = path.join(registryDir, "packages", "ui", "src", "lib", "cn.ts");
503
+ const dest = path.join(targetDir, libDirRelative, "cn.ts");
504
+ return copyLibFileNormalized(src, dest);
505
+ }
383
506
  export async function runInit(config, targetDir, repoUrl) {
384
507
  const filesCreated = [];
385
508
  const sourceRoot = config.layout?.sourceRoot ?? "src";
@@ -393,27 +516,30 @@ export async function runInit(config, targetDir, repoUrl) {
393
516
  fs.mkdirSync(componentDir, { recursive: true });
394
517
  const blockDir = path.join(targetDir, config.blockPath);
395
518
  fs.mkdirSync(blockDir, { recursive: true });
396
- // 3. Create the unified lib directory under `<sourceRoot>/lib/` and
397
- // seed it with `cn.ts`. Every component/block lib file ships here;
398
- // the CLI rewrites all imports at install-time to point at the
399
- // `aliases.lib` alias so there's a single source of truth.
400
- //
401
- // Never overwrite an existing `cn.ts` the user may have their own.
519
+ // 3. Create the unified lib directory under `<sourceRoot>/lib/`. Every
520
+ // component/block lib file ships here; the CLI rewrites all imports
521
+ // at install-time to point at the `aliases.lib` alias so there's a
522
+ // single source of truth. `cn.ts` is seeded here so `init` users
523
+ // can start writing app code immediately — the real file (not a
524
+ // hardcoded template) is copied from the registry so secondary
525
+ // exports like `ClassNames` stay available to components. Never
526
+ // overwrite existing user files.
402
527
  const libDirRelative = `${sourceRoot}/lib`;
403
528
  const libDir = path.join(targetDir, libDirRelative);
404
529
  fs.mkdirSync(libDir, { recursive: true });
405
- const cnPath = path.join(libDir, "cn.ts");
406
- if (!fs.existsSync(cnPath)) {
407
- fs.writeFileSync(cnPath, getCnTemplate());
408
- filesCreated.push(`${libDirRelative}/cn.ts`);
409
- }
410
530
  // 4. Copy tokens and create CSS
411
531
  const cssRelPath = config.cssEntryPoint ?? `${sourceRoot}/index.css`;
412
532
  const cssPath = path.join(targetDir, cssRelPath);
413
533
  fs.mkdirSync(path.dirname(cssPath), { recursive: true });
414
534
  if (repoUrl) {
415
- // Remote mode: copy tokens from cached registry
535
+ // Remote mode: copy tokens and canonical lib files from the cached
536
+ // registry so users get the real `cn.ts` (including type exports
537
+ // like `ClassNames` that downstream components import) rather than
538
+ // a minimal hardcoded template.
416
539
  const registryDir = getCacheDir();
540
+ if (copyCnFile(registryDir, targetDir, libDirRelative)) {
541
+ filesCreated.push(`${libDirRelative}/cn.ts`);
542
+ }
417
543
  const tokenFiles = copyTokensToProject(registryDir, targetDir, config.theme, sourceRoot);
418
544
  filesCreated.push(...tokenFiles);
419
545
  // Copy i18n base files to the unified lib directory so that components'
@@ -424,13 +550,21 @@ export async function runInit(config, targetDir, repoUrl) {
424
550
  fs.writeFileSync(cssPath, getLocalTailwindCssTemplate(config.theme, config.fontBody, config.fontHeading, config.radius));
425
551
  }
426
552
  else {
427
- // Local mode: reference @company/tokens package
553
+ // Local mode: reference @company/tokens package. Without a registry
554
+ // to read from, we seed `cn.ts` with a minimal template — users in
555
+ // this mode are almost always developing the monorepo itself, where
556
+ // the real `@company/ui` package is resolvable via workspace links.
557
+ const cnPath = path.join(libDir, "cn.ts");
558
+ if (!fs.existsSync(cnPath)) {
559
+ fs.writeFileSync(cnPath, getCnTemplate());
560
+ filesCreated.push(`${libDirRelative}/cn.ts`);
561
+ }
428
562
  fs.writeFileSync(cssPath, getLegacyTailwindCssTemplate(config.theme, config.fontBody, config.fontHeading, config.radius));
429
563
  }
430
564
  filesCreated.push(cssRelPath);
431
565
  return { success: true, filesCreated };
432
566
  }
433
- export async function initCommand(repoUrl) {
567
+ export async function initCommand(repoUrl, options = {}) {
434
568
  console.log(chalk.bold("\nInAI UI - Project Initialization\n"));
435
569
  const cwd = process.cwd();
436
570
  const detectedFramework = detectFramework(cwd);
@@ -452,7 +586,21 @@ export async function initCommand(repoUrl) {
452
586
  }
453
587
  }
454
588
  const registryDir = repoUrl ? getCacheDir() : undefined;
455
- const config = await promptInitConfig(registryDir, detectedFramework, detectedLayout);
589
+ let config;
590
+ if (options.config) {
591
+ try {
592
+ config = loadInitConfigFromFile(options.config, detectedFramework, detectedLayout);
593
+ console.log(chalk.dim(`Loaded config from ${options.config} (skipping prompts)`));
594
+ }
595
+ catch (err) {
596
+ const message = err instanceof Error ? err.message : String(err);
597
+ console.error(chalk.red(`\nFailed to load config: ${message}`));
598
+ process.exit(1);
599
+ }
600
+ }
601
+ else {
602
+ config = await promptInitConfig(registryDir, detectedFramework, detectedLayout);
603
+ }
456
604
  if (!config) {
457
605
  console.log(chalk.yellow("\nInitialization cancelled."));
458
606
  process.exit(0);
@@ -11,7 +11,7 @@ function padRight(str, len) {
11
11
  export function formatComponentTable(components, filterCategory) {
12
12
  let filtered = components;
13
13
  if (filterCategory) {
14
- filtered = components.filter((c) => c.type.toLowerCase() === filterCategory.toLowerCase());
14
+ filtered = components.filter((c) => (c.category ?? "").toLowerCase() === filterCategory.toLowerCase());
15
15
  }
16
16
  if (filtered.length === 0) {
17
17
  if (filterCategory) {
@@ -0,0 +1,9 @@
1
+ export interface ManifestOptions {
2
+ /** Output as JSON instead of a human-readable table. */
3
+ json?: boolean;
4
+ /** Only show entries where the installed version differs from the
5
+ * current registry version. */
6
+ outdated?: boolean;
7
+ }
8
+ export declare function manifestCommand(options?: ManifestOptions): Promise<void>;
9
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/commands/manifest.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;oCACgC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAuGD,wBAAsB,eAAe,CACnC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAiDf"}
@@ -0,0 +1,89 @@
1
+ import path from "node:path";
2
+ import chalk from "chalk";
3
+ import { readComponentsJson, readRegistryJson, } from "./status.js";
4
+ import { resolveRegistryDir } from "../utils/registry-resolver.js";
5
+ function buildEntries(installed, registryVersion) {
6
+ return installed
7
+ .map((c) => ({
8
+ name: c.name,
9
+ installedVersion: c.version,
10
+ installedAt: c.installedAt,
11
+ registryVersion,
12
+ outdated: registryVersion !== null && c.version !== registryVersion,
13
+ }))
14
+ .sort((a, b) => a.name.localeCompare(b.name));
15
+ }
16
+ function renderHumanReadable(componentsJson, entries, registryVersion) {
17
+ const lines = [];
18
+ lines.push(chalk.bold("\nInAI UI — Component Manifest\n"));
19
+ if (componentsJson.registrySource) {
20
+ lines.push(chalk.dim(`Registry: ${componentsJson.registrySource}`));
21
+ }
22
+ if (registryVersion) {
23
+ lines.push(chalk.dim(`Registry version: ${registryVersion}`));
24
+ }
25
+ else {
26
+ lines.push(chalk.dim("Registry version: unknown (no cached registry.json — run `inai-ui status` to refresh)"));
27
+ }
28
+ lines.push("");
29
+ if (entries.length === 0) {
30
+ lines.push(chalk.yellow("No components installed yet. Run `inai-ui add <name>` to install one."));
31
+ return lines.join("\n");
32
+ }
33
+ const nameWidth = Math.max(...entries.map((e) => e.name.length), 4) + 2;
34
+ const versionWidth = Math.max(...entries.map((e) => e.installedVersion.length), 7);
35
+ const dateWidth = 10;
36
+ lines.push(chalk.bold(`${"Name".padEnd(nameWidth)}${"Version".padEnd(versionWidth + 2)}${"Installed".padEnd(dateWidth + 2)}Status`));
37
+ lines.push(chalk.dim("─".repeat(nameWidth) +
38
+ "─".repeat(versionWidth + 2) +
39
+ "─".repeat(dateWidth + 2) +
40
+ "──────"));
41
+ for (const e of entries) {
42
+ const status = e.outdated
43
+ ? chalk.yellow(`outdated → ${e.registryVersion}`)
44
+ : chalk.green("up-to-date");
45
+ lines.push(`${e.name.padEnd(nameWidth)}${e.installedVersion.padEnd(versionWidth + 2)}${e.installedAt.padEnd(dateWidth + 2)}${status}`);
46
+ }
47
+ const outdatedCount = entries.filter((e) => e.outdated).length;
48
+ lines.push("");
49
+ if (outdatedCount > 0) {
50
+ lines.push(chalk.yellow(`${outdatedCount}/${entries.length} components are outdated. Run \`inai-ui update <name>\` or \`inai-ui update --all\` to upgrade.`));
51
+ }
52
+ else {
53
+ lines.push(chalk.green(`All ${entries.length} components are up to date.`));
54
+ }
55
+ return lines.join("\n");
56
+ }
57
+ export async function manifestCommand(options = {}) {
58
+ const rootDir = process.cwd();
59
+ const componentsJson = readComponentsJson(rootDir);
60
+ if (!componentsJson) {
61
+ console.error(chalk.red("\nNo components.json found. Run `inai-ui init` first to initialize the project."));
62
+ process.exit(1);
63
+ }
64
+ const installed = componentsJson.installedComponents ?? [];
65
+ // Resolve the registry to read its current version (best-effort; if it
66
+ // fails we still print installed versions, just without a comparison).
67
+ let registryVersion = null;
68
+ try {
69
+ const registryDir = resolveRegistryDir(rootDir);
70
+ const registryJson = readRegistryJson(path.join(registryDir, "registry.json"));
71
+ registryVersion = registryJson?.version ?? null;
72
+ }
73
+ catch {
74
+ // Offline / no registry cache yet — fine, just skip the comparison.
75
+ }
76
+ let entries = buildEntries(installed, registryVersion);
77
+ if (options.outdated) {
78
+ entries = entries.filter((e) => e.outdated);
79
+ }
80
+ if (options.json) {
81
+ console.log(JSON.stringify({
82
+ registrySource: componentsJson.registrySource ?? null,
83
+ registryVersion,
84
+ installed: entries,
85
+ }, null, 2));
86
+ return;
87
+ }
88
+ console.log(renderHumanReadable(componentsJson, entries, registryVersion));
89
+ }
@@ -16,6 +16,15 @@ export declare function handleListComponents(args: {
16
16
  export declare function handleViewComponent(args: {
17
17
  name?: string;
18
18
  } | undefined, cwd: string): Promise<ToolTextResult>;
19
+ /**
20
+ * Read the prop schema for a component from the registry's generated
21
+ * `props-data.json` (produced by `scripts/extract-props.ts`). Returns
22
+ * the schema as JSON text so AI assistants can validate props before
23
+ * generating JSX.
24
+ */
25
+ export declare function handleViewComponentSchema(args: {
26
+ name?: string;
27
+ } | undefined, cwd: string): Promise<ToolTextResult>;
19
28
  export declare function handleAddComponent(args: {
20
29
  name?: string;
21
30
  force?: boolean;
@@ -30,6 +39,92 @@ export declare function handleListThemes(): Promise<ToolTextResult>;
30
39
  export declare function handleStatus(args: {
31
40
  json?: boolean;
32
41
  } | undefined, cwd: string): Promise<ToolTextResult>;
42
+ /**
43
+ * Parse the moonshots registry source file as plain text. We intentionally
44
+ * do *not* `import` the TS file here — the CLI is compiled ahead of time
45
+ * and cannot transitively load UI sources. Regex is enough because the
46
+ * file has a stable `{ id: N, slug: "…", title: "…", description: "…",
47
+ * status: "…", category: "…" }` shape.
48
+ */
49
+ export interface MoonshotSummary {
50
+ id: number;
51
+ slug: string;
52
+ title: string;
53
+ description: string;
54
+ status: string;
55
+ category?: string;
56
+ components: string[];
57
+ demoPath?: string;
58
+ reducedMotionStrategy?: string;
59
+ }
60
+ export declare function parseMoonshotsFile(sourceText: string): MoonshotSummary[];
61
+ export declare function handleListMoonshots(args: {
62
+ status?: string;
63
+ category?: string;
64
+ } | undefined, cwd: string): Promise<ToolTextResult>;
65
+ export interface ScaffoldedPage {
66
+ targetPath: string;
67
+ source: string;
68
+ blocks: {
69
+ name: string;
70
+ slug: string;
71
+ score: number;
72
+ }[];
73
+ }
74
+ /**
75
+ * Given a brief and a target path, compose a React `.tsx` page that
76
+ * imports the top-matching blocks from the local registry and renders
77
+ * them in order. Used by the MCP `scaffold_page` tool so an agent can
78
+ * drop a proposal page into the current project during a live client
79
+ * demo without hand-typing import boilerplate.
80
+ *
81
+ * Exported for unit tests; the CLI command wraps it.
82
+ */
83
+ export declare function scaffoldPageSource(brief: string, blocks: Array<{
84
+ name: string;
85
+ type: string;
86
+ description: string;
87
+ }>): string;
88
+ /**
89
+ * Wrap the CLI's `runThemeFromHex` so an agent can generate a layered
90
+ * brand override from a single hex value during a live demo. Writes
91
+ * the CSS layer under `packages/tokens/src/themes/<name>.css` (when
92
+ * invoked from the monorepo root) or a project-local themes dir.
93
+ */
94
+ export declare function handleGenerateTheme(args: {
95
+ name?: string;
96
+ hex?: string;
97
+ } | undefined, cwd: string): Promise<ToolTextResult>;
98
+ /**
99
+ * Seed a state file that the docs app's `LiveThemeSwitcher` picks up on
100
+ * boot, so an agent can say "use the brand theme we just generated" and
101
+ * the next `pnpm --filter docs dev` run opens already themed.
102
+ *
103
+ * Implementation: writes `{ theme: "<slug>" }` JSON to `.inai/state.json`
104
+ * in the current project (matches the convention used by other CLI
105
+ * commands). Non-destructive: merges with existing state if present.
106
+ */
107
+ export declare function handleApplyTheme(args: {
108
+ name?: string;
109
+ } | undefined, cwd: string): Promise<ToolTextResult>;
110
+ /**
111
+ * Spawn `scripts/capture-moonshots.mjs` for the agent. Streams stdout
112
+ * + stderr back through the MCP result. Intended for the case where
113
+ * the team wants fresh video loops without dropping to the terminal.
114
+ */
115
+ export declare function handleCaptureMoonshots(args: {
116
+ slug?: string;
117
+ force?: boolean;
118
+ } | undefined, cwd: string): Promise<ToolTextResult>;
119
+ export declare function handleScaffoldPage(args: {
120
+ brief?: string;
121
+ target_path?: string;
122
+ limit?: number;
123
+ } | undefined, cwd: string): Promise<ToolTextResult>;
124
+ export declare function handleSuggestBlocks(args: {
125
+ brief?: string;
126
+ limit?: number;
127
+ } | undefined, cwd: string): Promise<ToolTextResult>;
33
128
  export declare const MCP_TOOLS: readonly [{
34
129
  readonly name: "list_components";
35
130
  readonly description: "List all available components, blocks and templates in the InAI UI registry.";
@@ -55,6 +150,19 @@ export declare const MCP_TOOLS: readonly [{
55
150
  };
56
151
  readonly required: readonly ["name"];
57
152
  };
153
+ }, {
154
+ readonly name: "view_component_schema";
155
+ readonly description: "Return the prop schema (name, type, required, description, default) for a component. Use before generating JSX so prop names, variants and types are correct without reading the source.";
156
+ readonly inputSchema: {
157
+ readonly type: "object";
158
+ readonly properties: {
159
+ readonly name: {
160
+ readonly type: "string";
161
+ readonly description: "Component name";
162
+ };
163
+ };
164
+ readonly required: readonly ["name"];
165
+ };
58
166
  }, {
59
167
  readonly name: "add_component";
60
168
  readonly description: "Install a component (and its transitive registry dependencies) into the current project.";
@@ -117,6 +225,106 @@ export declare const MCP_TOOLS: readonly [{
117
225
  };
118
226
  };
119
227
  };
228
+ }, {
229
+ readonly name: "list_moonshots";
230
+ readonly description: "List the creative signature moonshots registered in InAI UI (kinetic typography, gradient mesh, magnetic cursor, decrypt text, etc.). Filter by status ('stable' | 'experimental') and/or category ('interaction' | 'typography' | 'layout' | 'dataviz' | 'color' | 'ambient'). Returns JSON metadata including reduced-motion strategy so the agent can choose moonshots responsibly.";
231
+ readonly inputSchema: {
232
+ readonly type: "object";
233
+ readonly properties: {
234
+ readonly status: {
235
+ readonly type: "string";
236
+ readonly description: "Filter by maturity: 'stable' (default returns all).";
237
+ };
238
+ readonly category: {
239
+ readonly type: "string";
240
+ readonly description: "Filter by thematic category: interaction, typography, layout, dataviz, color, ambient.";
241
+ };
242
+ };
243
+ };
244
+ }, {
245
+ readonly name: "suggest_blocks";
246
+ readonly description: "Given a free-text brief (e.g. 'fintech dashboard with live KPIs and dark hero'), rank the blocks and templates in registry.json by keyword overlap and return the top matches. Use this to scaffold a client proposal page without reading the entire registry.";
247
+ readonly inputSchema: {
248
+ readonly type: "object";
249
+ readonly properties: {
250
+ readonly brief: {
251
+ readonly type: "string";
252
+ readonly description: "Short description of the desired page or section.";
253
+ };
254
+ readonly limit: {
255
+ readonly type: "number";
256
+ readonly description: "Max number of suggestions. Defaults to 6.";
257
+ };
258
+ };
259
+ readonly required: readonly ["brief"];
260
+ };
261
+ }, {
262
+ readonly name: "scaffold_page";
263
+ readonly description: "Compose a fresh React .tsx page that imports and renders the top blocks matching a brief. Writes the file to `target_path` (relative to cwd) and returns a Markdown summary. Pair with `suggest_blocks` if the caller wants to preview the picks before writing anything.";
264
+ readonly inputSchema: {
265
+ readonly type: "object";
266
+ readonly properties: {
267
+ readonly brief: {
268
+ readonly type: "string";
269
+ readonly description: "Short description of the desired page (e.g. 'fintech landing with pricing and stats').";
270
+ };
271
+ readonly target_path: {
272
+ readonly type: "string";
273
+ readonly description: "Relative path where the generated .tsx file will be written (e.g. 'src/pages/client-x-landing.tsx'). Must not exist yet.";
274
+ };
275
+ readonly limit: {
276
+ readonly type: "number";
277
+ readonly description: "Max number of blocks to compose. Defaults to 6, capped at 12.";
278
+ };
279
+ };
280
+ readonly required: readonly ["brief", "target_path"];
281
+ };
282
+ }, {
283
+ readonly name: "generate_theme";
284
+ readonly description: "Generate a brand override theme from a single hex color. Uses the same hex → OKLCH pipeline as ThemeStudio and the live theme switcher, and writes a layered `.css` file under `packages/tokens/src/themes/` that should be imported after a base theme.";
285
+ readonly inputSchema: {
286
+ readonly type: "object";
287
+ readonly properties: {
288
+ readonly name: {
289
+ readonly type: "string";
290
+ readonly description: "Theme slug, used as the CSS filename (e.g. 'client-orbis').";
291
+ };
292
+ readonly hex: {
293
+ readonly type: "string";
294
+ readonly description: "Brand hex color, e.g. '#6b46c1'.";
295
+ };
296
+ };
297
+ readonly required: readonly ["name", "hex"];
298
+ };
299
+ }, {
300
+ readonly name: "apply_theme";
301
+ readonly description: "Seed `.inai/state.json` with the active theme slug so the next docs app boot picks it up automatically. Non-destructive: merges with existing state. The live theme switcher also honours this state on mount.";
302
+ readonly inputSchema: {
303
+ readonly type: "object";
304
+ readonly properties: {
305
+ readonly name: {
306
+ readonly type: "string";
307
+ readonly description: "Theme slug to activate (must already exist).";
308
+ };
309
+ };
310
+ readonly required: readonly ["name"];
311
+ };
312
+ }, {
313
+ readonly name: "capture_moonshots";
314
+ readonly description: "Run `scripts/capture-moonshots.mjs` to regenerate the WebM loops consumed by the gallery. Accepts an optional `slug` filter and a `force` flag to re-record already-captured moonshots. Returns the script's stdout/stderr.";
315
+ readonly inputSchema: {
316
+ readonly type: "object";
317
+ readonly properties: {
318
+ readonly slug: {
319
+ readonly type: "string";
320
+ readonly description: "Optional moonshot slug to record in isolation (e.g. 'gradient-mesh-live').";
321
+ };
322
+ readonly force: {
323
+ readonly type: "boolean";
324
+ readonly description: "Re-capture slugs that already have a video. Defaults to false.";
325
+ };
326
+ };
327
+ };
120
328
  }];
121
329
  export declare function runMcp(cwd?: string): Promise<void>;
122
330
  export declare function mcpCommand(subcommand?: "init"): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAgCA;;;GAGG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAID,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AASD,wBAAsB,oBAAoB,CACxC,IAAI,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACvC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAKzB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACnC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CA8BzB;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EACpD,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAkBzB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACnC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAmCzB;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACnC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAgBzB;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,cAAc,CAAC,CAEhE;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EACpC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAGzB;AAID,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoFZ,CAAC;AAEX,wBAAsB,MAAM,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiEvE;AAED,wBAAsB,UAAU,CAC9B,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAMf"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAkCA;;;GAGG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAID,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AASD,wBAAsB,oBAAoB,CACxC,IAAI,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACvC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAKzB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACnC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CA8BzB;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACnC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAmDzB;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EACpD,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAkBzB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACnC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAmCzB;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACnC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAgBzB;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,cAAc,CAAC,CAEhE;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EACpC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAGzB;AAID;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAaD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,EAAE,CAqCxE;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACxD,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAqBzB;AAsCD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,GACjE,MAAM,CA2BR;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACjD,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CA2BzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACnC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAiCzB;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EACpD,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CA0CzB;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EAC1E,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAqFzB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACpD,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CA0CzB;AAID,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyNZ,CAAC;AAEX,wBAAsB,MAAM,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsGvE;AAED,wBAAsB,UAAU,CAC9B,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAMf"}