get-ui-cli 0.0.0-stage → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yash Jaiswal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,63 @@
1
- # Temporary Holding Version
1
+ # get-ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Every UI component. One command.**
4
+
5
+ Search, preview and install copy-paste React + Tailwind components from nine open-source libraries, with everything each one depends on.
6
+
7
+ ```bash
8
+ npx get-ui-cli add magicui/shimmer-button
9
+ ```
10
+
11
+ Or install it once and use the short command:
12
+
13
+ ```bash
14
+ npm i -g get-ui-cli
15
+ get-ui add magicui/shimmer-button
16
+ ```
17
+
18
+ Browse and live-preview everything at **[get-ui.dev](https://get-ui.dev)**.
19
+
20
+ ## What it does
21
+
22
+ `get-ui add` reads your project (path aliases, Tailwind version, global CSS file, package manager) and then:
23
+
24
+ - **Resolves the whole tree.** Sub-components from any library come along, e.g. a Kokonut UI component that needs shadcn/ui's Button, Textarea and Dropdown Menu.
25
+ - **Writes the files** with imports rewritten to your aliases.
26
+ - **Merges CSS** (theme variables, keyframes) into your global stylesheet without overwriting anything you already have. In a project that never ran `shadcn init`, it adds shadcn's base theme the first time a component needs it.
27
+ - **Installs only the missing npm packages**, with your package manager. If you already use `motion`, `framer-motion` imports are rewritten instead of installing both.
28
+
29
+ ## Libraries
30
+
31
+ | Library | Free components |
32
+ | --- | ---: |
33
+ | [shadcn/ui](https://ui.shadcn.com) | 61 |
34
+ | [Magic UI](https://magicui.design) | 78 |
35
+ | [Aceternity UI](https://ui.aceternity.com) | 119 |
36
+ | [ReUI](https://reui.io) | 1,254 |
37
+ | [Kokonut UI](https://kokonutui.com) | 46 |
38
+ | [Cult UI](https://www.cult-ui.com) | 154 |
39
+ | [Motion Primitives](https://motion-primitives.com) | 33 |
40
+ | [Unlumen UI](https://ui.unlumen.com) | 109 |
41
+ | [Amicro](https://amicro.vercel.app) | 169 |
42
+
43
+ Paid ("Pro") items are listed but can't be installed without the library's own licence.
44
+
45
+ ## Commands
46
+
47
+ ```bash
48
+ npx get-ui-cli add <id|name|search text...> # install one or more components
49
+ npx get-ui-cli search <query> # search the index
50
+ npx get-ui-cli list [-l <library>] # counts per library / category
51
+ npx get-ui-cli view <id> # files and dependencies, no writes
52
+ npx get-ui-cli init # add lib/utils.ts (cn helper)
53
+ ```
54
+
55
+ Useful `add` flags: `--dry-run` (show the plan only), `-y` (no prompts), `-o` (overwrite), `-l <library>` (restrict matches), `--no-install` (print the install command instead of running it), `--cwd <dir>`.
56
+
57
+ ## Credit
58
+
59
+ get-ui is an index, not a host. Component code is fetched from each library at install time and stays under its author's licence. Credit the creators you ship.
60
+
61
+ ## License
62
+
63
+ MIT
@@ -0,0 +1,25 @@
1
+ import type { CssTree, CssVars } from './resolve.js';
2
+ interface Block {
3
+ prelude: string;
4
+ /** index right after `{` */
5
+ bodyStart: number;
6
+ /** index of the closing `}` */
7
+ bodyEnd: number;
8
+ depth: number;
9
+ }
10
+ /** Minimal CSS block scanner: records every `{...}` with its prelude. */
11
+ export declare function scanBlocks(css: string): Block[];
12
+ export interface MergeCssInput {
13
+ cssVars?: CssVars;
14
+ css?: CssTree;
15
+ tailwindVersion?: 3 | 4 | null;
16
+ }
17
+ export interface MergeCssResult {
18
+ css: string;
19
+ changed: boolean;
20
+ /** Human-readable list of what was added. */
21
+ added: string[];
22
+ warnings: string[];
23
+ }
24
+ export declare function mergeCss(source: string, input: MergeCssInput): MergeCssResult;
25
+ export {};
@@ -0,0 +1,129 @@
1
+ // Merge registry `cssVars` / `css` into a global stylesheet without clobbering
2
+ // anything that already exists. Pure string manipulation.
3
+ function normPrelude(s) {
4
+ return s.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\s+/g, ' ').trim();
5
+ }
6
+ /** Minimal CSS block scanner: records every `{...}` with its prelude. */
7
+ export function scanBlocks(css) {
8
+ const blocks = [];
9
+ const stack = [];
10
+ let preludeStart = 0;
11
+ for (let i = 0; i < css.length; i++) {
12
+ const ch = css[i];
13
+ if (ch === '/' && css[i + 1] === '*') {
14
+ const end = css.indexOf('*/', i + 2);
15
+ i = end === -1 ? css.length : end + 1;
16
+ continue;
17
+ }
18
+ if (ch === '"' || ch === "'") {
19
+ const end = css.indexOf(ch, i + 1);
20
+ i = end === -1 ? css.length : end;
21
+ continue;
22
+ }
23
+ if (ch === '{') {
24
+ stack.push({ prelude: normPrelude(css.slice(preludeStart, i)), bodyStart: i + 1 });
25
+ preludeStart = i + 1;
26
+ }
27
+ else if (ch === '}') {
28
+ const open = stack.pop();
29
+ if (open)
30
+ blocks.push({ prelude: open.prelude, bodyStart: open.bodyStart, bodyEnd: i, depth: stack.length });
31
+ preludeStart = i + 1;
32
+ }
33
+ else if (ch === ';') {
34
+ preludeStart = i + 1;
35
+ }
36
+ }
37
+ return blocks;
38
+ }
39
+ function findBlock(css, prelude) {
40
+ const want = normPrelude(prelude);
41
+ return scanBlocks(css).find((b) => b.prelude === want);
42
+ }
43
+ function existingVars(body) {
44
+ return new Set([...body.matchAll(/(--[\w-]+)\s*:/g)].map((m) => m[1]));
45
+ }
46
+ function serialize(key, value, indent = '') {
47
+ if (typeof value === 'string')
48
+ return `${indent}${key}: ${value};`;
49
+ const entries = Object.entries(value);
50
+ if (!entries.length && /^@(plugin|import|custom-variant|source)\b/.test(key))
51
+ return `${indent}${key};`;
52
+ const inner = entries.map(([k, v]) => serialize(k, v, indent + ' ')).join('\n');
53
+ return `${indent}${key} {\n${inner}\n${indent}}`;
54
+ }
55
+ function addVars(css, prelude, vars, added) {
56
+ const entries = Object.entries(vars);
57
+ if (!entries.length)
58
+ return css;
59
+ const block = findBlock(css, prelude);
60
+ const have = block ? existingVars(css.slice(block.bodyStart, block.bodyEnd)) : new Set();
61
+ const missing = entries.filter(([k]) => !have.has(`--${k.replace(/^--/, '')}`));
62
+ if (!missing.length)
63
+ return css;
64
+ const lines = missing.map(([k, v]) => ` --${k.replace(/^--/, '')}: ${v};`);
65
+ added.push(`${prelude}: ${missing.map(([k]) => `--${k.replace(/^--/, '')}`).join(', ')}`);
66
+ if (block) {
67
+ const before = css.slice(0, block.bodyEnd).replace(/\s*$/, '');
68
+ return `${before}\n${lines.join('\n')}\n${css.slice(block.bodyEnd)}`;
69
+ }
70
+ return `${css.replace(/\s*$/, '')}\n\n${prelude} {\n${lines.join('\n')}\n}\n`;
71
+ }
72
+ function hasRule(css, key) {
73
+ const want = normPrelude(key);
74
+ if (want.startsWith('@keyframes ')) {
75
+ const name = want.slice('@keyframes '.length).trim();
76
+ return new RegExp(`@(-webkit-)?keyframes\\s+${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\{`).test(css);
77
+ }
78
+ return scanBlocks(css).some((b) => b.prelude === want);
79
+ }
80
+ export function mergeCss(source, input) {
81
+ let css = source;
82
+ const added = [];
83
+ const warnings = [];
84
+ const v4 = input.tailwindVersion !== 3;
85
+ const { theme = {}, light = {}, dark = {} } = input.cssVars ?? {};
86
+ if (Object.keys(theme).length) {
87
+ if (v4) {
88
+ const prelude = findBlock(css, '@theme inline') || !findBlock(css, '@theme') ? '@theme inline' : '@theme';
89
+ css = addVars(css, prelude, theme, added);
90
+ }
91
+ else {
92
+ warnings.push(`Tailwind v3: add these theme values to tailwind.config manually: ${Object.entries(theme)
93
+ .map(([k, v]) => `${k}=${v}`)
94
+ .join(', ')}`);
95
+ }
96
+ }
97
+ css = addVars(css, ':root', light, added);
98
+ css = addVars(css, '.dark', dark, added);
99
+ for (const [key, value] of Object.entries(input.css ?? {})) {
100
+ const k = normPrelude(key);
101
+ if (/^@(plugin|import|custom-variant|source)\b/.test(k) && (typeof value === 'string' || !Object.keys(value).length)) {
102
+ const stmt = typeof value === 'string' && value ? `${k} ${value};` : `${k};`;
103
+ if (css.includes(k))
104
+ continue;
105
+ const lastImport = [...css.matchAll(/^@(import|plugin)[^\n]*\n/gm)].pop();
106
+ if (lastImport) {
107
+ const at = lastImport.index + lastImport[0].length;
108
+ css = `${css.slice(0, at)}${stmt}\n${css.slice(at)}`;
109
+ }
110
+ else
111
+ css = `${stmt}\n${css}`;
112
+ added.push(k);
113
+ continue;
114
+ }
115
+ if (k.startsWith('@layer') && typeof value === 'object') {
116
+ const missing = Object.entries(value).filter(([sel]) => !hasRule(css, sel));
117
+ if (!missing.length)
118
+ continue;
119
+ css = `${css.replace(/\s*$/, '')}\n\n${serialize(k, Object.fromEntries(missing))}\n`;
120
+ added.push(`${k} { ${missing.map(([s]) => s).join(', ')} }`);
121
+ continue;
122
+ }
123
+ if (hasRule(css, k))
124
+ continue;
125
+ css = `${css.replace(/\s*$/, '')}\n\n${serialize(k, value)}\n`;
126
+ added.push(k);
127
+ }
128
+ return { css, changed: css !== source, added, warnings };
129
+ }
@@ -0,0 +1,7 @@
1
+ export * from './search.js';
2
+ export * from './resolve.js';
3
+ export * from './transform.js';
4
+ export * from './css.js';
5
+ export type * from '../registry-types.js';
6
+ export * from './shadcn-theme.js';
7
+ export * from './versions.js';
@@ -0,0 +1,6 @@
1
+ export * from './search.js';
2
+ export * from './resolve.js';
3
+ export * from './transform.js';
4
+ export * from './css.js';
5
+ export * from './shadcn-theme.js';
6
+ export * from './versions.js';
@@ -0,0 +1,58 @@
1
+ import { type LibraryId, type Registry, type RegistryItem } from '../registry-types.js';
2
+ export declare const SHADCN_BASE = "https://ui.shadcn.com/r/styles/new-york-v4/";
3
+ export declare const SHADCN_REGISTRIES_URL = "https://ui.shadcn.com/r/registries.json";
4
+ export interface CssVars {
5
+ theme?: Record<string, string>;
6
+ light?: Record<string, string>;
7
+ dark?: Record<string, string>;
8
+ }
9
+ /** A nested CSS object as used by the shadcn registry `css` field. */
10
+ export type CssTree = {
11
+ [selector: string]: string | CssTree;
12
+ };
13
+ export interface ResolvedFile {
14
+ path: string;
15
+ content: string;
16
+ type: string;
17
+ target?: string;
18
+ /** Registry item this file came from (e.g. `shadcn/button`). */
19
+ item: string;
20
+ /** Library context of the item that shipped this file. */
21
+ library: LibraryId | 'external';
22
+ }
23
+ export interface ResolveResult {
24
+ files: ResolvedFile[];
25
+ dependencies: string[];
26
+ devDependencies: string[];
27
+ cssVars: CssVars;
28
+ css: CssTree;
29
+ warnings: string[];
30
+ /** Items that were pulled in, root first (e.g. `kokonutui/ai-prompt`, `shadcn/button`). */
31
+ tree: string[];
32
+ }
33
+ export interface ResolveOptions {
34
+ fetch?: typeof fetch;
35
+ /** Extra headers per request (e.g. a licence key for paid registries). */
36
+ headers?: (url: string) => Record<string, string> | undefined;
37
+ /** Shared cache across calls (url -> parsed JSON / text). */
38
+ cache?: Map<string, Promise<unknown>>;
39
+ concurrency?: number;
40
+ /** Request timeout per fetch (ms). */
41
+ timeoutMs?: number;
42
+ }
43
+ export declare class FetchError extends Error {
44
+ status: number;
45
+ url: string;
46
+ constructor(message: string, status: number, url: string);
47
+ }
48
+ /** `motion@^11` -> `motion`, `@scope/pkg@1` -> `@scope/pkg` */
49
+ export declare function packageName(dep: string): string;
50
+ export declare function createFetcher(opts?: ResolveOptions): {
51
+ json(url: string): Promise<unknown>;
52
+ text(url: string): Promise<string>;
53
+ };
54
+ /**
55
+ * Fetch an item and all of its registryDependencies (recursively, deduped,
56
+ * cycle-safe, concurrently).
57
+ */
58
+ export declare function resolveItem(registry: Registry, item: RegistryItem, opts?: ResolveOptions): Promise<ResolveResult>;