inai-react-components 0.1.7 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +80 -0
  2. package/dist/commands/add.d.ts +38 -5
  3. package/dist/commands/add.d.ts.map +1 -1
  4. package/dist/commands/add.js +391 -80
  5. package/dist/commands/diff.d.ts +6 -0
  6. package/dist/commands/diff.d.ts.map +1 -1
  7. package/dist/commands/diff.js +92 -20
  8. package/dist/commands/init.d.ts +6 -3
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +33 -36
  11. package/dist/commands/mcp.d.ts +123 -0
  12. package/dist/commands/mcp.d.ts.map +1 -0
  13. package/dist/commands/mcp.js +289 -0
  14. package/dist/commands/migrate.d.ts +41 -0
  15. package/dist/commands/migrate.d.ts.map +1 -0
  16. package/dist/commands/migrate.js +139 -0
  17. package/dist/commands/registries.d.ts +46 -0
  18. package/dist/commands/registries.d.ts.map +1 -0
  19. package/dist/commands/registries.js +152 -0
  20. package/dist/commands/remove.d.ts +11 -0
  21. package/dist/commands/remove.d.ts.map +1 -0
  22. package/dist/commands/remove.js +170 -0
  23. package/dist/commands/schema.d.ts +16 -0
  24. package/dist/commands/schema.d.ts.map +1 -0
  25. package/dist/commands/schema.js +32 -0
  26. package/dist/commands/status.d.ts +32 -3
  27. package/dist/commands/status.d.ts.map +1 -1
  28. package/dist/commands/status.js +148 -25
  29. package/dist/commands/theme.d.ts.map +1 -1
  30. package/dist/commands/theme.js +3 -37
  31. package/dist/commands/update.d.ts +18 -1
  32. package/dist/commands/update.d.ts.map +1 -1
  33. package/dist/commands/update.js +195 -5
  34. package/dist/index.js +76 -5
  35. package/dist/schemas/registry-config.schema.json +62 -0
  36. package/dist/schemas/registry-item.schema.json +63 -0
  37. package/dist/schemas/registry.schema.json +15 -0
  38. package/dist/types/registry.d.ts +50 -0
  39. package/dist/types/registry.d.ts.map +1 -0
  40. package/dist/types/registry.js +10 -0
  41. package/dist/utils/auto-install.d.ts +11 -0
  42. package/dist/utils/auto-install.d.ts.map +1 -0
  43. package/dist/utils/auto-install.js +29 -0
  44. package/dist/utils/framework-detect.d.ts +15 -0
  45. package/dist/utils/framework-detect.d.ts.map +1 -0
  46. package/dist/utils/framework-detect.js +90 -0
  47. package/dist/utils/fuzzy-search.d.ts +16 -0
  48. package/dist/utils/fuzzy-search.d.ts.map +1 -0
  49. package/dist/utils/fuzzy-search.js +67 -0
  50. package/dist/utils/registry-config.d.ts +27 -0
  51. package/dist/utils/registry-config.d.ts.map +1 -0
  52. package/dist/utils/registry-config.js +94 -0
  53. package/dist/utils/registry-resolver.d.ts +26 -0
  54. package/dist/utils/registry-resolver.d.ts.map +1 -1
  55. package/dist/utils/registry-resolver.js +134 -10
  56. package/dist/utils/snapshot.d.ts +20 -0
  57. package/dist/utils/snapshot.d.ts.map +1 -0
  58. package/dist/utils/snapshot.js +32 -0
  59. package/dist/utils/themes.d.ts +17 -0
  60. package/dist/utils/themes.d.ts.map +1 -0
  61. package/dist/utils/themes.js +49 -0
  62. package/package.json +9 -3
@@ -3,9 +3,55 @@ import path from "node:path";
3
3
  import chalk from "chalk";
4
4
  import ora from "ora";
5
5
  import prompts from "prompts";
6
+ import diff3Merge from "diff3";
6
7
  import { readComponentsJson, readRegistryJson, } from "./status.js";
7
8
  import { findComponentInRegistry, computeDiff } from "./diff.js";
8
9
  import { resolveRegistryDir } from "../utils/registry-resolver.js";
10
+ import { loadSnapshot, saveSnapshot, sha256, } from "../utils/snapshot.js";
11
+ /**
12
+ * Run a 3-way line merge between base (snapshot), mine (local) and theirs
13
+ * (registry). When a conflict is encountered we emit the standard git-style
14
+ * markers so the user can resolve them in their editor.
15
+ */
16
+ export function threeWayMerge(mine, base, theirs) {
17
+ // Fast path — nothing to do.
18
+ if (mine === theirs) {
19
+ return { merged: mine, hasConflicts: false, conflictCount: 0 };
20
+ }
21
+ // If mine === base the user never touched the file — take theirs wholesale.
22
+ if (mine === base) {
23
+ return { merged: theirs, hasConflicts: false, conflictCount: 0 };
24
+ }
25
+ // If theirs === base nothing changed upstream — keep mine.
26
+ if (theirs === base) {
27
+ return { merged: mine, hasConflicts: false, conflictCount: 0 };
28
+ }
29
+ const mineLines = mine.split("\n");
30
+ const baseLines = base.split("\n");
31
+ const theirsLines = theirs.split("\n");
32
+ const regions = diff3Merge(mineLines, baseLines, theirsLines);
33
+ const output = [];
34
+ let conflictCount = 0;
35
+ for (const region of regions) {
36
+ if ("ok" in region) {
37
+ output.push(...region.ok);
38
+ }
39
+ else {
40
+ conflictCount++;
41
+ const { a, b } = region.conflict;
42
+ output.push("<<<<<<< local");
43
+ output.push(...a);
44
+ output.push("=======");
45
+ output.push(...b);
46
+ output.push(">>>>>>> registry");
47
+ }
48
+ }
49
+ return {
50
+ merged: output.join("\n"),
51
+ hasConflicts: conflictCount > 0,
52
+ conflictCount,
53
+ };
54
+ }
9
55
  export async function runUpdate(componentName, rootDir, skipPrompts = false) {
10
56
  const componentsJson = readComponentsJson(rootDir);
11
57
  if (!componentsJson) {
@@ -29,7 +75,11 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
29
75
  const registryVersion = registry?.version ?? "unknown";
30
76
  const localComponentDir = componentsJson.aliases.components.replace(/^@\//, "src/");
31
77
  const updatedFiles = [];
78
+ const cleanMerges = [];
79
+ const conflictedFiles = [];
32
80
  let needsUpdate = false;
81
+ const snapshot = loadSnapshot(rootDir, componentName);
82
+ const newSnapshotFiles = {};
33
83
  for (const filePath of registryComponent.files) {
34
84
  const registryFilePath = path.join(registryDir, filePath);
35
85
  const fileName = path.basename(filePath);
@@ -44,15 +94,64 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
44
94
  fs.mkdirSync(dir, { recursive: true });
45
95
  fs.writeFileSync(localFilePath, registryContent);
46
96
  updatedFiles.push(fileName);
97
+ cleanMerges.push(fileName);
47
98
  needsUpdate = true;
99
+ const rel = path.relative(rootDir, localFilePath);
100
+ newSnapshotFiles[rel] = {
101
+ localPath: rel,
102
+ hash: sha256(registryContent),
103
+ content: registryContent,
104
+ };
48
105
  continue;
49
106
  }
50
107
  const localContent = fs.readFileSync(localFilePath, "utf-8");
51
108
  if (localContent === registryContent) {
109
+ // No-op: retain the previous snapshot entry if one exists.
110
+ const rel = path.relative(rootDir, localFilePath);
111
+ const prev = snapshot?.files[rel];
112
+ if (prev) {
113
+ newSnapshotFiles[rel] = {
114
+ ...prev,
115
+ hash: sha256(registryContent),
116
+ content: registryContent,
117
+ };
118
+ }
119
+ else {
120
+ newSnapshotFiles[rel] = {
121
+ localPath: rel,
122
+ hash: sha256(registryContent),
123
+ content: registryContent,
124
+ };
125
+ }
52
126
  continue;
53
127
  }
54
128
  // There is a difference
55
129
  needsUpdate = true;
130
+ const rel = path.relative(rootDir, localFilePath);
131
+ const baseContent = snapshot?.files[rel]?.content;
132
+ if (baseContent !== undefined) {
133
+ // 3-way merge path — we know what the user started from, so we can
134
+ // reconcile their local edits with the new registry version.
135
+ const outcome = threeWayMerge(localContent, baseContent, registryContent);
136
+ fs.writeFileSync(localFilePath, outcome.merged);
137
+ updatedFiles.push(fileName);
138
+ if (outcome.hasConflicts) {
139
+ conflictedFiles.push(fileName);
140
+ }
141
+ else {
142
+ cleanMerges.push(fileName);
143
+ }
144
+ // Snapshot now records the new registry content as the base for
145
+ // future updates (regardless of conflict state — the user will
146
+ // resolve markers on disk).
147
+ newSnapshotFiles[rel] = {
148
+ localPath: rel,
149
+ hash: sha256(registryContent),
150
+ content: registryContent,
151
+ };
152
+ continue;
153
+ }
154
+ // Legacy path — no snapshot, fall back to the pre-S2.5 overwrite prompt.
56
155
  if (!skipPrompts) {
57
156
  const diff = computeDiff(localContent, registryContent);
58
157
  console.log(chalk.bold(`\nChanges for ${fileName}:`));
@@ -72,6 +171,12 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
72
171
  if (response.action === "overwrite") {
73
172
  fs.writeFileSync(localFilePath, registryContent);
74
173
  updatedFiles.push(fileName);
174
+ cleanMerges.push(fileName);
175
+ newSnapshotFiles[rel] = {
176
+ localPath: rel,
177
+ hash: sha256(registryContent),
178
+ content: registryContent,
179
+ };
75
180
  }
76
181
  // "keep" and "skip" both leave the file as-is
77
182
  }
@@ -79,6 +184,12 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
79
184
  // Non-interactive mode: overwrite
80
185
  fs.writeFileSync(localFilePath, registryContent);
81
186
  updatedFiles.push(fileName);
187
+ cleanMerges.push(fileName);
188
+ newSnapshotFiles[rel] = {
189
+ localPath: rel,
190
+ hash: sha256(registryContent),
191
+ content: registryContent,
192
+ };
82
193
  }
83
194
  }
84
195
  if (!needsUpdate) {
@@ -105,13 +216,33 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
105
216
  componentsJson.installedComponents = installed;
106
217
  const componentsJsonPath = path.join(rootDir, "components.json");
107
218
  fs.writeFileSync(componentsJsonPath, JSON.stringify(componentsJson, null, 2) + "\n");
219
+ // Refresh the snapshot so future updates merge from the new baseline.
220
+ if (Object.keys(newSnapshotFiles).length > 0) {
221
+ const refreshed = {
222
+ name: componentName,
223
+ version: registryVersion,
224
+ files: newSnapshotFiles,
225
+ capturedAt: new Date().toISOString(),
226
+ };
227
+ saveSnapshot(rootDir, refreshed);
228
+ }
108
229
  return {
109
230
  updated: true,
110
231
  message: `Component "${componentName}" updated to registry v${registryVersion}.`,
111
232
  files: updatedFiles,
233
+ cleanMerges,
234
+ conflictedFiles,
112
235
  };
113
236
  }
114
- export async function updateCommand(componentName) {
237
+ export async function updateCommand(componentName, options) {
238
+ if (options?.all) {
239
+ await updateAll();
240
+ return;
241
+ }
242
+ if (!componentName) {
243
+ console.error(chalk.red("Please specify a component name or use --all to update all installed components."));
244
+ process.exit(1);
245
+ }
115
246
  const spinner = ora(`Checking "${componentName}" for updates...`).start();
116
247
  try {
117
248
  const rootDir = process.cwd();
@@ -119,10 +250,16 @@ export async function updateCommand(componentName) {
119
250
  const result = await runUpdate(componentName, rootDir);
120
251
  if (result.updated) {
121
252
  console.log(chalk.green(`\n${result.message}`));
122
- if (result.files.length > 0) {
123
- console.log(chalk.dim("\nUpdated files:"));
124
- for (const file of result.files) {
125
- console.log(chalk.dim(` - ${file}`));
253
+ if (result.cleanMerges && result.cleanMerges.length > 0) {
254
+ console.log(chalk.green("\nMerged cleanly:"));
255
+ for (const file of result.cleanMerges) {
256
+ console.log(chalk.dim(` ${file}`));
257
+ }
258
+ }
259
+ if (result.conflictedFiles && result.conflictedFiles.length > 0) {
260
+ console.log(chalk.yellow(`\nMerged with conflicts — please resolve the <<<<<<< markers manually:`));
261
+ for (const file of result.conflictedFiles) {
262
+ console.log(chalk.yellow(` ! ${file}`));
126
263
  }
127
264
  }
128
265
  }
@@ -137,3 +274,56 @@ export async function updateCommand(componentName) {
137
274
  process.exit(1);
138
275
  }
139
276
  }
277
+ async function updateAll() {
278
+ const rootDir = process.cwd();
279
+ const componentsJson = readComponentsJson(rootDir);
280
+ if (!componentsJson) {
281
+ console.error(chalk.red("No components.json found. Run `inai-ui init` first to initialize your project."));
282
+ process.exit(1);
283
+ }
284
+ const installed = componentsJson.installedComponents ?? [];
285
+ if (installed.length === 0) {
286
+ console.log(chalk.yellow("No installed components found."));
287
+ return;
288
+ }
289
+ console.log(chalk.bold(`\nChecking ${installed.length} installed component${installed.length > 1 ? "s" : ""} for updates...\n`));
290
+ let updatedCount = 0;
291
+ let skippedCount = 0;
292
+ let conflictCount = 0;
293
+ for (const entry of installed) {
294
+ const spinner = ora(`Checking "${entry.name}"...`).start();
295
+ try {
296
+ const result = await runUpdate(entry.name, rootDir, true);
297
+ if (result.updated) {
298
+ const hasConflicts = result.conflictedFiles && result.conflictedFiles.length > 0;
299
+ if (hasConflicts) {
300
+ spinner.warn(`Updated "${entry.name}" with ${result.conflictedFiles.length} conflict(s)`);
301
+ conflictCount++;
302
+ }
303
+ else {
304
+ spinner.succeed(`Updated "${entry.name}"`);
305
+ }
306
+ updatedCount++;
307
+ }
308
+ else {
309
+ spinner.info(chalk.dim(`"${entry.name}" is up to date`));
310
+ skippedCount++;
311
+ }
312
+ }
313
+ catch (error) {
314
+ spinner.fail(`Failed to update "${entry.name}"`);
315
+ const message = error instanceof Error ? error.message : String(error);
316
+ console.error(chalk.red(` Error: ${message}`));
317
+ }
318
+ }
319
+ console.log(chalk.bold("\nSummary:"));
320
+ if (updatedCount > 0) {
321
+ console.log(chalk.green(` ${updatedCount} component${updatedCount > 1 ? "s" : ""} updated`));
322
+ }
323
+ if (skippedCount > 0) {
324
+ console.log(chalk.dim(` ${skippedCount} component${skippedCount > 1 ? "s" : ""} already up to date`));
325
+ }
326
+ if (conflictCount > 0) {
327
+ console.log(chalk.yellow(` ${conflictCount} component${conflictCount > 1 ? "s" : ""} with merge conflicts — resolve <<<<<<< markers manually`));
328
+ }
329
+ }
package/dist/index.js CHANGED
@@ -8,11 +8,16 @@ import { listCommand } from "./commands/list.js";
8
8
  import { updateCommand } from "./commands/update.js";
9
9
  import { syncCommand } from "./commands/sync.js";
10
10
  import { themeCreateCommand } from "./commands/theme.js";
11
+ import { mcpCommand } from "./commands/mcp.js";
12
+ import { migrateCommand } from "./commands/migrate.js";
13
+ import { runSchema } from "./commands/schema.js";
14
+ import { registriesListCommand, registriesAddCommand, registriesRemoveCommand, } from "./commands/registries.js";
15
+ import { removeCommand } from "./commands/remove.js";
11
16
  const program = new Command();
12
17
  program
13
18
  .name("inai-ui")
14
19
  .description("CLI for InAI UI component library")
15
- .version("0.1.7");
20
+ .version("1.2.1");
16
21
  program
17
22
  .command("init [repo-url]")
18
23
  .description("Initialize InAI UI in your project. Optionally pass a Git repo URL to fetch components remotely.")
@@ -21,6 +26,11 @@ program
21
26
  .command("add [component]")
22
27
  .description("Add a component to your project. Without arguments, opens an interactive picker to select multiple components.")
23
28
  .option("-a, --all", "Install all available components, blocks, and templates")
29
+ .option("-f, --force", "Overwrite existing files without prompting")
30
+ .option("--dry-run", "Print what would be written without touching the filesystem")
31
+ .option("--skip-existing", "Skip files that already exist instead of prompting")
32
+ .option("--offline", "Use cached registries without network access")
33
+ .option("--skip-install", "Skip automatic npm dependency installation")
24
34
  .action((component, options) => addCommand(component, options));
25
35
  program
26
36
  .command("sync")
@@ -28,8 +38,17 @@ program
28
38
  .action(syncCommand);
29
39
  program
30
40
  .command("status")
31
- .description("Show installed components and their versions")
32
- .action(statusCommand);
41
+ .description("Show installed components with drift / outdated / missing state")
42
+ .option("--json", "Emit the status report as JSON (machine-readable)")
43
+ .action((options) => statusCommand(options));
44
+ const mcpCmd = program
45
+ .command("mcp")
46
+ .description("Start the MCP (Model Context Protocol) server over stdio so AI assistants can drive the CLI.")
47
+ .action(() => mcpCommand());
48
+ mcpCmd
49
+ .command("init")
50
+ .description("Print the JSON config fragment for Claude Desktop / Cursor mcpServers.")
51
+ .action(() => mcpCommand("init"));
33
52
  program
34
53
  .command("diff <component>")
35
54
  .description("Show differences between local component and registry version")
@@ -40,9 +59,15 @@ program
40
59
  .option("-c, --category <category>", "Filter by component category")
41
60
  .action((options) => listCommand(options));
42
61
  program
43
- .command("update <component>")
62
+ .command("update [component]")
44
63
  .description("Update a component to the latest registry version")
45
- .action((component) => updateCommand(component));
64
+ .option("-a, --all", "Update all installed components that are outdated")
65
+ .action((component, options) => updateCommand(component, options));
66
+ program
67
+ .command("remove <component>")
68
+ .description("Remove an installed component from your project")
69
+ .option("-f, --force", "Skip dependency warning and remove anyway")
70
+ .action((component, options) => removeCommand(component, options));
46
71
  const themeCmd = program
47
72
  .command("theme")
48
73
  .description("Theme management commands");
@@ -52,4 +77,50 @@ themeCmd
52
77
  .requiredOption("-n, --name <name>", "Name for the new theme")
53
78
  .requiredOption("-b, --base <base>", "Base theme to extend (monday, linear, notion, vercel)")
54
79
  .action((options) => themeCreateCommand(options));
80
+ program
81
+ .command("migrate")
82
+ .description("Apply deprecated-prop rename codemods to your project (inputSize→size, selectSize→size, comboBoxSize→size, checkboxState→state).")
83
+ .option("--dry-run", "Show what would change without writing files")
84
+ .action((options) => migrateCommand({ dryRun: !!options.dryRun }));
85
+ program
86
+ .command("schema")
87
+ .description("Print or write the InAI registry JSON Schema")
88
+ .option("--kind <kind>", "Schema kind: 'item', 'registry', or 'registry-config'", "item")
89
+ .option("--output <path>", "Write to file instead of stdout", "stdout")
90
+ .action(async (options) => {
91
+ await runSchema({
92
+ kind: options.kind,
93
+ output: options.output,
94
+ });
95
+ });
96
+ const registriesCmd = program
97
+ .command("registries")
98
+ .description("Manage federated component registries (M3)");
99
+ registriesCmd
100
+ .command("list")
101
+ .alias("ls")
102
+ .description("List configured registries")
103
+ .action(async () => {
104
+ await registriesListCommand();
105
+ });
106
+ registriesCmd
107
+ .command("add <name>")
108
+ .description("Add a new registry to components.json")
109
+ .option("--source <source>", "Source type: local | git | https", "git")
110
+ .option("--url <url>", "Registry URL (for source=git or source=https)")
111
+ .option("--path <path>", "Local path (for source=local)")
112
+ .option("--branch <branch>", "Git branch", "main")
113
+ .option("--token-env <env>", "Environment variable holding the bearer token")
114
+ .option("--token-file <file>", "Path to a file containing the bearer token")
115
+ .option("--default", "Mark this registry as the default (clears any prior default)")
116
+ .action(async (name, opts) => {
117
+ await registriesAddCommand(name, opts);
118
+ });
119
+ registriesCmd
120
+ .command("remove <name>")
121
+ .alias("rm")
122
+ .description("Remove a registry from components.json")
123
+ .action(async (name) => {
124
+ await registriesRemoveCommand(name);
125
+ });
55
126
  program.parse();
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://inai.dev/schemas/registry-config.json",
4
+ "title": "InAI components.json — federated registry configuration",
5
+ "description": "Shape of the `registries` array in components.json for multi-source component federation (M3). Consumer projects may configure multiple registries simultaneously with bearer-token auth and offline mirroring.",
6
+ "type": "object",
7
+ "properties": {
8
+ "registries": {
9
+ "type": "array",
10
+ "description": "Ordered list of registries. The first with `default: true` (or the first entry) is used when a component spec has no `@namespace/` prefix.",
11
+ "items": {
12
+ "type": "object",
13
+ "required": ["name", "source"],
14
+ "additionalProperties": false,
15
+ "properties": {
16
+ "name": {
17
+ "type": "string",
18
+ "pattern": "^[a-z0-9][a-z0-9-]*$",
19
+ "description": "Stable alias used in `@namespace/name` syntax."
20
+ },
21
+ "source": {
22
+ "enum": ["local", "git", "https"],
23
+ "description": "How the registry is fetched."
24
+ },
25
+ "path": {
26
+ "type": "string",
27
+ "description": "Filesystem path relative to the project root (for source=local)."
28
+ },
29
+ "url": {
30
+ "type": "string",
31
+ "format": "uri",
32
+ "description": "Git clone URL (source=git) or registry.json URL (source=https)."
33
+ },
34
+ "branch": {
35
+ "type": "string",
36
+ "description": "Git branch. Defaults to 'main'."
37
+ },
38
+ "default": {
39
+ "type": "boolean",
40
+ "description": "Use this registry when a component has no @namespace/."
41
+ },
42
+ "auth": {
43
+ "type": "object",
44
+ "required": ["type"],
45
+ "additionalProperties": false,
46
+ "properties": {
47
+ "type": { "const": "bearer" },
48
+ "tokenEnv": {
49
+ "type": "string",
50
+ "description": "Environment variable holding the bearer token."
51
+ },
52
+ "tokenFile": {
53
+ "type": "string",
54
+ "description": "Absolute path to a file containing the token."
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://inai.dev/schemas/registry-item.json",
4
+ "title": "InAI Registry Item",
5
+ "description": "Shape of a component, block, or template entry in the InAI UI registry.",
6
+ "type": "object",
7
+ "required": ["name", "type", "files"],
8
+ "properties": {
9
+ "name": {
10
+ "type": "string",
11
+ "description": "Unique name used by `inai-ui add <name>`"
12
+ },
13
+ "type": {
14
+ "type": "string",
15
+ "enum": ["component", "form", "block", "template"],
16
+ "description": "Registry classification"
17
+ },
18
+ "description": {
19
+ "type": "string"
20
+ },
21
+ "files": {
22
+ "type": "array",
23
+ "items": { "type": "string" },
24
+ "description": "Relative paths from the registry root to the files copied when this item is installed"
25
+ },
26
+ "npmDeps": {
27
+ "type": "array",
28
+ "items": { "type": "string" },
29
+ "description": "NPM packages required by this item (installed automatically by the CLI)"
30
+ },
31
+ "internalDeps": {
32
+ "type": "array",
33
+ "items": { "type": "string" },
34
+ "description": "Internal library helpers (`lib/cn`, `lib/animations`) this item imports"
35
+ },
36
+ "registryDependencies": {
37
+ "type": "array",
38
+ "items": { "type": "string" },
39
+ "description": "Other registry items this one depends on. Resolved transitively by `inai-ui add`."
40
+ },
41
+ "tokenUsage": {
42
+ "type": "array",
43
+ "items": { "type": "string" },
44
+ "description": "CSS custom properties this item consumes"
45
+ },
46
+ "tanstackCompatibility": {
47
+ "type": "object",
48
+ "description": "Which TanStack adapters this item ships with",
49
+ "additionalProperties": { "type": "boolean" }
50
+ },
51
+ "classNames": {
52
+ "type": "array",
53
+ "items": { "type": "string" },
54
+ "description": "Slot keys exposed by the item's `classNames` prop"
55
+ },
56
+ "fieldWrappers": {
57
+ "type": "array",
58
+ "items": { "type": "string" },
59
+ "description": "TanStack Form field wrapper components this item exports"
60
+ }
61
+ },
62
+ "additionalProperties": false
63
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://inai.dev/schemas/registry.json",
4
+ "title": "InAI Registry",
5
+ "description": "Top-level registry document listing components, blocks, and templates.",
6
+ "type": "object",
7
+ "required": ["version", "components", "blocks", "templates"],
8
+ "properties": {
9
+ "version": { "type": "string" },
10
+ "components": { "type": "array", "items": { "$ref": "./registry-item.schema.json" } },
11
+ "blocks": { "type": "array", "items": { "$ref": "./registry-item.schema.json" } },
12
+ "templates": { "type": "array", "items": { "$ref": "./registry-item.schema.json" } }
13
+ },
14
+ "additionalProperties": false
15
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Multi-registry federation types for InAI UI CLI (M3).
3
+ *
4
+ * A consumer project's `components.json` may declare multiple component
5
+ * sources ("registries") — the default `inai` one plus any number of
6
+ * private, git-hosted, or HTTPS registries. Components are resolved per
7
+ * registry, with optional `@namespace/` syntax in the `add` command to
8
+ * disambiguate between registries that expose the same component name.
9
+ */
10
+ /** How a registry is physically sourced. */
11
+ export type RegistrySource = "local" | "git" | "https";
12
+ /** Bearer-token authentication for a private registry. */
13
+ export interface RegistryAuth {
14
+ type: "bearer";
15
+ /** Environment variable holding the token (preferred). */
16
+ tokenEnv?: string;
17
+ /** Absolute path to a file containing the token (fallback). */
18
+ tokenFile?: string;
19
+ }
20
+ /** A single registry entry in the federated `components.json`. */
21
+ export interface Registry {
22
+ /** Stable alias used in `@namespace/name` syntax (lowercase kebab-case). */
23
+ name: string;
24
+ source: RegistrySource;
25
+ /** Local filesystem path (for `source: "local"`). */
26
+ path?: string;
27
+ /** Git clone URL or HTTPS registry.json URL. */
28
+ url?: string;
29
+ /** Git branch (defaults to "main"). */
30
+ branch?: string;
31
+ /** Bearer-token auth config for private registries. */
32
+ auth?: RegistryAuth;
33
+ /** If true, this registry is used when a component name has no namespace. */
34
+ default?: boolean;
35
+ }
36
+ /** Parsed result of `parseComponentSpec` — namespace + type + name. */
37
+ export interface ResolvedComponentSpec {
38
+ /** Registry name from `@namespace/name` syntax, or null for the default. */
39
+ registry: string | null;
40
+ /** Component name (no prefixes). */
41
+ name: string;
42
+ /** Type derived from `block/` or `template/` prefix. */
43
+ type: "component" | "block" | "template";
44
+ }
45
+ /** Options passed to registry resolution. */
46
+ export interface ResolveRegistryOptions {
47
+ /** Use cached registry without network access. */
48
+ offline?: boolean;
49
+ }
50
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/types/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,4CAA4C;AAC5C,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;AAEvD,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,IAAI,EAAE,WAAW,GAAG,OAAO,GAAG,UAAU,CAAC;CAC1C;AAED,6CAA6C;AAC7C,MAAM,WAAW,sBAAsB;IACrC,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Multi-registry federation types for InAI UI CLI (M3).
3
+ *
4
+ * A consumer project's `components.json` may declare multiple component
5
+ * sources ("registries") — the default `inai` one plus any number of
6
+ * private, git-hosted, or HTTPS registries. Components are resolved per
7
+ * registry, with optional `@namespace/` syntax in the `add` command to
8
+ * disambiguate between registries that expose the same component name.
9
+ */
10
+ export {};
@@ -0,0 +1,11 @@
1
+ export type PackageManager = "pnpm" | "npm" | "yarn" | "bun";
2
+ /**
3
+ * Detect which package manager a project uses by checking for lockfiles.
4
+ */
5
+ export declare function detectPackageManager(projectDir: string): PackageManager;
6
+ /**
7
+ * Run the package manager's install command for the given dependencies.
8
+ * Returns the command that was executed for display purposes.
9
+ */
10
+ export declare function autoInstallDeps(projectDir: string, deps: string[]): string | null;
11
+ //# sourceMappingURL=auto-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-install.d.ts","sourceRoot":"","sources":["../../src/utils/auto-install.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7D;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAKvE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,GACb,MAAM,GAAG,IAAI,CAWf"}
@@ -0,0 +1,29 @@
1
+ import { execSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ /**
5
+ * Detect which package manager a project uses by checking for lockfiles.
6
+ */
7
+ export function detectPackageManager(projectDir) {
8
+ if (fs.existsSync(path.join(projectDir, "pnpm-lock.yaml")))
9
+ return "pnpm";
10
+ if (fs.existsSync(path.join(projectDir, "bun.lockb")))
11
+ return "bun";
12
+ if (fs.existsSync(path.join(projectDir, "yarn.lock")))
13
+ return "yarn";
14
+ return "npm";
15
+ }
16
+ /**
17
+ * Run the package manager's install command for the given dependencies.
18
+ * Returns the command that was executed for display purposes.
19
+ */
20
+ export function autoInstallDeps(projectDir, deps) {
21
+ if (deps.length === 0)
22
+ return null;
23
+ const pm = detectPackageManager(projectDir);
24
+ const cmd = pm === "npm"
25
+ ? `npm install ${deps.join(" ")}`
26
+ : `${pm} add ${deps.join(" ")}`;
27
+ execSync(cmd, { cwd: projectDir, stdio: "inherit" });
28
+ return cmd;
29
+ }
@@ -0,0 +1,15 @@
1
+ export type Framework = "next-app" | "next-pages" | "vite" | "remix" | "astro" | "tanstack-start" | "unknown";
2
+ /**
3
+ * Detect the React meta-framework in use for a given project directory.
4
+ * Order matters: Next / Remix / Astro win over a bare vite.config because
5
+ * those frameworks often *also* ship a vite config under the hood.
6
+ */
7
+ export declare function detectFramework(cwd: string): Framework;
8
+ /**
9
+ * Default CSS entry point for a given framework. Used by `inai-ui init`
10
+ * to pre-populate the prompt that asks where tailwind/tokens should be
11
+ * imported.
12
+ */
13
+ export declare function getCssEntryPoint(framework: Framework): string;
14
+ export declare function frameworkLabel(framework: Framework): string;
15
+ //# sourceMappingURL=framework-detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework-detect.d.ts","sourceRoot":"","sources":["../../src/utils/framework-detect.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GACjB,UAAU,GACV,YAAY,GACZ,MAAM,GACN,OAAO,GACP,OAAO,GACP,gBAAgB,GAChB,SAAS,CAAC;AAEd;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAkDtD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAiB7D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAiB3D"}