From 70200ed41aa8561d66423666f579e57f1720b783 Mon Sep 17 00:00:00 2001 From: Viet Tran Date: Tue, 2 Dec 2025 18:55:29 +0700 Subject: [PATCH] feat: add uxpro-cli for easy skill installation (#4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add uxpro-cli for easy skill installation - Add CLI tool (uxpro-cli) with commands: init, versions, update - Support multiple AI assistants: claude, cursor, windsurf, antigravity, all - Update README with CLI installation guide and usage examples - Add CC BY-NC 4.0 license - Update feature counts to accurate numbers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * refactor: rename CLI from uxpro to uipro - Package: uxpro-cli -> uipro-cli - Command: uxpro -> uipro 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- LICENSE | 17 +++ README.md | 212 +++++++++++++++++++---------------- cli/.gitignore | 4 + cli/README.md | 45 ++++++++ cli/bun.lock | 77 +++++++++++++ cli/package.json | 38 +++++++ cli/src/commands/init.ts | 125 +++++++++++++++++++++ cli/src/commands/update.ts | 36 ++++++ cli/src/commands/versions.ts | 42 +++++++ cli/src/index.ts | 56 +++++++++ cli/src/types/index.ts | 30 +++++ cli/src/utils/detect.ts | 50 +++++++++ cli/src/utils/extract.ts | 72 ++++++++++++ cli/src/utils/github.ts | 59 ++++++++++ cli/src/utils/logger.ts | 11 ++ cli/tsconfig.json | 17 +++ 16 files changed, 796 insertions(+), 95 deletions(-) create mode 100644 LICENSE create mode 100644 cli/.gitignore create mode 100644 cli/README.md create mode 100644 cli/bun.lock create mode 100644 cli/package.json create mode 100644 cli/src/commands/init.ts create mode 100644 cli/src/commands/update.ts create mode 100644 cli/src/commands/versions.ts create mode 100644 cli/src/index.ts create mode 100644 cli/src/types/index.ts create mode 100644 cli/src/utils/detect.ts create mode 100644 cli/src/utils/extract.ts create mode 100644 cli/src/utils/github.ts create mode 100644 cli/src/utils/logger.ts create mode 100644 cli/tsconfig.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8e9b41a --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0) + +Copyright (c) 2024 Next Level Builder + +You are free to: +- Share — copy and redistribute the material in any medium or format +- Adapt — remix, transform, and build upon the material + +Under the following terms: +- Attribution — You must give appropriate credit, provide a link to the license, + and indicate if changes were made. +- NonCommercial — You may not use the material for commercial purposes. + +No additional restrictions — You may not apply legal terms or technological +measures that legally restrict others from doing anything the license permits. + +Full license text: https://creativecommons.org/licenses/by-nc/4.0/legalcode diff --git a/README.md b/README.md index 6f98868..6762204 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,147 @@ -# UI UX Pro Max UI SKILL +# UI UX Pro Max An AI skill that provides design intelligence for building professional UI/UX across multiple platforms and frameworks. ## Overview -UI UX Pro Max is a searchable database of UI styles, color palettes, font pairings, chart types, product recommendations, UX guidelines, and stack-specific best practices. It's designed to work as a skill/workflow for AI coding assistants (Claude, Windsurf, Cursor, etc.). +UI UX Pro Max is a searchable database of UI styles, color palettes, font pairings, chart types, product recommendations, UX guidelines, and stack-specific best practices. It works as a skill/workflow for AI coding assistants (Claude Code, Cursor, Windsurf, etc.). ## Features -- **50+ UI Styles** - Glassmorphism, Claymorphism, Minimalism, Brutalism, Neumorphism, Bento Grid, Dark Mode, and more -- **21 Color Palettes** - Industry-specific palettes for SaaS, E-commerce, Healthcare, Fintech, Beauty, etc. -- **50 Font Pairings** - Curated typography combinations with Google Fonts imports -- **20+ Chart Types** - Recommendations for dashboards and analytics +- **57 UI Styles** - Glassmorphism, Claymorphism, Minimalism, Brutalism, Neumorphism, Bento Grid, Dark Mode, and more +- **95 Color Palettes** - Industry-specific palettes for SaaS, E-commerce, Healthcare, Fintech, Beauty, etc. +- **56 Font Pairings** - Curated typography combinations with Google Fonts imports +- **24 Chart Types** - Recommendations for dashboards and analytics - **8 Tech Stacks** - React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, HTML+Tailwind -- **UX Guidelines** - Best practices, anti-patterns, and accessibility rules +- **98 UX Guidelines** - Best practices, anti-patterns, and accessibility rules + +## Installation + +### Using CLI (Recommended) + +```bash +# Install CLI globally +npm install -g uipro-cli + +# Go to your project +cd /path/to/your/project + +# Install for your AI assistant +uipro init --ai claude # Claude Code +uipro init --ai cursor # Cursor +uipro init --ai windsurf # Windsurf +uipro init --ai antigravity # Antigravity (.agent + .shared) +uipro init --ai all # All assistants +``` + +### Other CLI Commands + +```bash +uipro versions # List available versions +uipro update # Update to latest version +uipro init --version v1.0.0 # Install specific version +``` + +### Manual Installation + +Copy the appropriate folders to your project: + +| AI Assistant | Folders to Copy | +|--------------|-----------------| +| Claude Code | `.claude/skills/ui-ux-pro-max/` | +| Cursor | `.cursor/commands/ui-ux-pro-max.md` + `.shared/ui-ux-pro-max/` | +| Windsurf | `.windsurf/workflows/ui-ux-pro-max.md` + `.shared/ui-ux-pro-max/` | +| Antigravity | `.agent/workflows/ui-ux-pro-max.md` + `.shared/ui-ux-pro-max/` | + +## Prerequisites + +Python 3.x is required for the search script. + +```bash +# Check if Python is installed +python3 --version + +# macOS +brew install python3 + +# Ubuntu/Debian +sudo apt update && sudo apt install python3 + +# Windows +winget install Python.Python.3.12 +``` ## Project Structure ``` -ui-ux-pro-max/ -├── .claude/skills/ui-ux-pro-max/ # Claude Code skill -│ ├── SKILL.md # Skill definition -│ ├── scripts/ # Search tools -│ │ ├── search.py # Main search script -│ │ └── core.py # Core search logic -│ └── data/ # Design database -│ ├── styles.csv # UI styles -│ ├── colors.csv # Color palettes -│ ├── typography.csv # Font pairings -│ ├── charts.csv # Chart types -│ ├── products.csv # Product recommendations -│ ├── landing.csv # Landing page structures -│ ├── ux-guidelines.csv # UX best practices -│ ├── prompts.csv # AI prompts -│ └── stacks/ # Stack-specific guidelines -│ ├── html-tailwind.csv -│ ├── react.csv -│ ├── nextjs.csv -│ ├── vue.csv -│ ├── svelte.csv -│ ├── swiftui.csv -│ ├── react-native.csv -│ └── flutter.csv +ui-ux-pro-max-skill/ +├── cli/ # CLI installer (uipro-cli) +│ ├── package.json +│ └── src/ +├── .claude/skills/ui-ux-pro-max/ # Claude Code skill +│ ├── SKILL.md +│ ├── scripts/ +│ │ ├── search.py +│ │ └── core.py +│ └── data/ +│ ├── styles.csv +│ ├── colors.csv +│ ├── typography.csv +│ ├── charts.csv +│ ├── products.csv +│ ├── landing.csv +│ ├── ux-guidelines.csv +│ ├── prompts.csv +│ └── stacks/ +├── .cursor/commands/ # Cursor command +│ └── ui-ux-pro-max.md ├── .windsurf/workflows/ # Windsurf workflow │ └── ui-ux-pro-max.md -├── .agent/workflows/ # Generic agent workflow -│ └── ui-ux-pro-max/ -└── .shared/ui-ux-pro-max/ # Shared data +├── .agent/workflows/ # Antigravity workflow +│ └── ui-ux-pro-max.md +└── .shared/ui-ux-pro-max/ # Shared scripts & data + ├── scripts/ + └── data/ ``` -## Prerequisites - -- Python 3.x - -## Installation - -### For Claude Code - -The skill is automatically available in `.claude/skills/ui-ux-pro-max/`. - -### For Windsurf - -Use the workflow via `/ui-ux-pro-max` slash command. - -### For Other Agents - -Copy the workflow from `.agent/workflows/ui-ux-pro-max/`. - ## Usage -### Search Command +After installation, just chat with your AI assistant naturally. The skill will automatically activate when you request UI/UX work. -```bash -python3 .claude/skills/ui-ux-pro-max/scripts/search.py "" --domain [-n ] +### Example Prompts + +``` +Build a landing page for my SaaS product + +Create a dashboard for healthcare analytics + +Design a portfolio website with dark mode + +Make a mobile app UI for e-commerce ``` -### Available Domains +### How It Works -| Domain | Use For | Example Keywords | -| ------------ | ---------------------------- | ------------------------------ | -| `product` | Product type recommendations | SaaS, e-commerce, portfolio | -| `style` | UI styles, colors, effects | glassmorphism, minimalism | -| `typography` | Font pairings | elegant, playful, professional | -| `color` | Color palettes | saas, healthcare, beauty | -| `landing` | Page structure | hero, testimonial, pricing | -| `chart` | Chart types | trend, comparison, funnel | -| `ux` | Best practices | animation, accessibility | -| `prompt` | AI prompts | (style name) | +1. **You ask** - Request any UI/UX task (build, design, create, implement, review, fix, improve) +2. **Skill activates** - The AI automatically searches the design database for relevant styles, colors, typography, and guidelines +3. **Smart recommendations** - Based on your product type and requirements, it finds the best matching design system +4. **Code generation** - Implements the UI with proper colors, fonts, spacing, and best practices -### Available Stacks +### Supported Stacks -| Stack | Focus | -| --------------- | ---------------------------------------- | -| `html-tailwind` | Tailwind utilities, responsive (DEFAULT) | -| `react` | State, hooks, performance | -| `nextjs` | SSR, routing, images | -| `vue` | Composition API, Pinia | -| `svelte` | Runes, stores, SvelteKit | -| `swiftui` | Views, State, Navigation | -| `react-native` | Components, Navigation | -| `flutter` | Widgets, State, Layout | +The skill provides stack-specific guidelines for: -### Example Workflow +- **HTML + Tailwind** (default) +- **React** / **Next.js** +- **Vue** / **Svelte** +- **SwiftUI** / **React Native** / **Flutter** -```bash -# 1. Search product type -python3 .claude/skills/ui-ux-pro-max/scripts/search.py "SaaS dashboard" --domain product - -# 2. Search style -python3 .claude/skills/ui-ux-pro-max/scripts/search.py "glassmorphism dark" --domain style - -# 3. Search typography -python3 .claude/skills/ui-ux-pro-max/scripts/search.py "modern professional" --domain typography - -# 4. Search color palette -python3 .claude/skills/ui-ux-pro-max/scripts/search.py "saas" --domain color - -# 5. Search stack guidelines -python3 .claude/skills/ui-ux-pro-max/scripts/search.py "layout responsive" --stack html-tailwind -``` +Just mention your preferred stack in the prompt, or let it default to HTML + Tailwind. ## License -MIT +This work is licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). + +- **Free for personal use** - Use, modify, and share for non-commercial purposes +- **Attribution required** - Credit "UI UX Pro Max" when sharing +- **No commercial use** - Cannot be used for commercial purposes without permission diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 0000000..dd6e803 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.log +.DS_Store diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000..eb1b904 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,45 @@ +# uipro-cli + +CLI to install UI/UX Pro Max skill for AI coding assistants. + +## Installation + +```bash +npm install -g uipro-cli +``` + +## Usage + +```bash +# Install for specific AI assistant +uipro init --ai claude # Claude Code +uipro init --ai cursor # Cursor +uipro init --ai windsurf # Windsurf +uipro init --ai antigravity # Antigravity +uipro init --ai all # All assistants + +# Other commands +uipro versions # List available versions +uipro update # Update to latest version +uipro init --version v1.0.0 # Install specific version +``` + +## Development + +```bash +# Install dependencies +bun install + +# Run locally +bun run src/index.ts --help + +# Build +bun run build + +# Link for local testing +bun link +``` + +## License + +CC-BY-NC-4.0 diff --git a/cli/bun.lock b/cli/bun.lock new file mode 100644 index 0000000..17135fb --- /dev/null +++ b/cli/bun.lock @@ -0,0 +1,77 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "uxpro-cli", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^12.1.0", + "ora": "^8.1.1", + "prompts": "^2.4.2", + }, + "devDependencies": { + "@types/bun": "^1.1.14", + "@types/node": "^22.10.1", + "@types/prompts": "^2.4.9", + "typescript": "^5.7.2", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], + + "@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="], + + "@types/prompts": ["@types/prompts@2.4.9", "", { "dependencies": { "@types/node": "*", "kleur": "^3.0.3" } }, "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + + "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], + + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + } +} diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 0000000..05e5a0f --- /dev/null +++ b/cli/package.json @@ -0,0 +1,38 @@ +{ + "name": "uipro-cli", + "version": "1.0.0", + "description": "CLI to install UI/UX Pro Max skill for AI coding assistants", + "type": "module", + "bin": { + "uipro": "./dist/index.js" + }, + "scripts": { + "build": "bun build src/index.ts --outdir dist --target node", + "dev": "bun run src/index.ts", + "prepublishOnly": "bun run build" + }, + "keywords": [ + "ui", + "ux", + "design", + "claude", + "cursor", + "windsurf", + "ai", + "skill" + ], + "author": "", + "license": "CC-BY-NC-4.0", + "dependencies": { + "commander": "^12.1.0", + "chalk": "^5.3.0", + "ora": "^8.1.1", + "prompts": "^2.4.2" + }, + "devDependencies": { + "@types/bun": "^1.1.14", + "@types/node": "^22.10.1", + "@types/prompts": "^2.4.9", + "typescript": "^5.7.2" + } +} diff --git a/cli/src/commands/init.ts b/cli/src/commands/init.ts new file mode 100644 index 0000000..36782d5 --- /dev/null +++ b/cli/src/commands/init.ts @@ -0,0 +1,125 @@ +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { mkdir } from 'node:fs/promises'; +import chalk from 'chalk'; +import ora from 'ora'; +import prompts from 'prompts'; +import type { AIType } from '../types/index.js'; +import { AI_TYPES } from '../types/index.js'; +import { fetchReleases, getLatestRelease, downloadRelease, getAssetUrl } from '../utils/github.js'; +import { extractZip, copyFolders, cleanup } from '../utils/extract.js'; +import { detectAIType, getAITypeDescription } from '../utils/detect.js'; +import { logger } from '../utils/logger.js'; + +interface InitOptions { + ai?: AIType; + version?: string; + force?: boolean; +} + +export async function initCommand(options: InitOptions): Promise { + logger.title('UI/UX Pro Max Installer'); + + let aiType = options.ai; + + // Auto-detect or prompt for AI type + if (!aiType) { + const { detected, suggested } = detectAIType(); + + if (detected.length > 0) { + logger.info(`Detected: ${detected.map(t => chalk.cyan(t)).join(', ')}`); + } + + const response = await prompts({ + type: 'select', + name: 'aiType', + message: 'Select AI assistant to install for:', + choices: AI_TYPES.map(type => ({ + title: getAITypeDescription(type), + value: type, + })), + initial: suggested ? AI_TYPES.indexOf(suggested) : 0, + }); + + if (!response.aiType) { + logger.warn('Installation cancelled'); + return; + } + + aiType = response.aiType as AIType; + } + + logger.info(`Installing for: ${chalk.cyan(getAITypeDescription(aiType))}`); + + // Fetch release + const spinner = ora('Fetching release info...').start(); + + try { + let release; + if (options.version) { + const releases = await fetchReleases(); + release = releases.find(r => r.tag_name === options.version); + if (!release) { + spinner.fail(`Version ${options.version} not found`); + process.exit(1); + } + } else { + release = await getLatestRelease(); + } + + spinner.text = `Found version: ${release.tag_name}`; + + const assetUrl = getAssetUrl(release); + if (!assetUrl) { + spinner.fail('No downloadable asset found in release'); + process.exit(1); + } + + // Download + spinner.text = 'Downloading...'; + const tempDir = join(tmpdir(), `uipro-${Date.now()}`); + await mkdir(tempDir, { recursive: true }); + + const zipPath = join(tempDir, 'release.zip'); + await downloadRelease(assetUrl, zipPath); + + // Extract + spinner.text = 'Extracting...'; + const extractDir = join(tempDir, 'extracted'); + await mkdir(extractDir, { recursive: true }); + await extractZip(zipPath, extractDir); + + // Copy folders + spinner.text = 'Installing files...'; + const cwd = process.cwd(); + const copiedFolders = await copyFolders(extractDir, cwd, aiType); + + // Cleanup + await cleanup(tempDir); + + spinner.succeed('Installation complete!'); + + // Summary + console.log(); + logger.info('Installed folders:'); + copiedFolders.forEach(folder => { + console.log(` ${chalk.green('+')} ${folder}`); + }); + + console.log(); + logger.success(`UI/UX Pro Max ${release.tag_name} installed successfully!`); + + // Next steps + console.log(); + console.log(chalk.bold('Next steps:')); + console.log(chalk.dim(' 1. Restart your AI coding assistant')); + console.log(chalk.dim(' 2. Try: "Build a landing page for a SaaS product"')); + console.log(); + } catch (error) { + spinner.fail('Installation failed'); + if (error instanceof Error) { + logger.error(error.message); + } + process.exit(1); + } +} diff --git a/cli/src/commands/update.ts b/cli/src/commands/update.ts new file mode 100644 index 0000000..39ea09b --- /dev/null +++ b/cli/src/commands/update.ts @@ -0,0 +1,36 @@ +import chalk from 'chalk'; +import ora from 'ora'; +import { getLatestRelease } from '../utils/github.js'; +import { logger } from '../utils/logger.js'; +import { initCommand } from './init.js'; +import type { AIType } from '../types/index.js'; + +interface UpdateOptions { + ai?: AIType; +} + +export async function updateCommand(options: UpdateOptions): Promise { + logger.title('UI/UX Pro Max Updater'); + + const spinner = ora('Checking for updates...').start(); + + try { + const release = await getLatestRelease(); + spinner.succeed(`Latest version: ${chalk.cyan(release.tag_name)}`); + + console.log(); + logger.info('Running update (same as init with latest version)...'); + console.log(); + + await initCommand({ + ai: options.ai, + force: true, + }); + } catch (error) { + spinner.fail('Update check failed'); + if (error instanceof Error) { + logger.error(error.message); + } + process.exit(1); + } +} diff --git a/cli/src/commands/versions.ts b/cli/src/commands/versions.ts new file mode 100644 index 0000000..dca88e6 --- /dev/null +++ b/cli/src/commands/versions.ts @@ -0,0 +1,42 @@ +import chalk from 'chalk'; +import ora from 'ora'; +import { fetchReleases } from '../utils/github.js'; +import { logger } from '../utils/logger.js'; + +export async function versionsCommand(): Promise { + const spinner = ora('Fetching available versions...').start(); + + try { + const releases = await fetchReleases(); + + if (releases.length === 0) { + spinner.warn('No releases found'); + return; + } + + spinner.succeed(`Found ${releases.length} version(s)\n`); + + console.log(chalk.bold('Available versions:\n')); + + releases.forEach((release, index) => { + const isLatest = index === 0; + const tag = release.tag_name; + const date = new Date(release.published_at).toLocaleDateString(); + + if (isLatest) { + console.log(` ${chalk.green('*')} ${chalk.bold(tag)} ${chalk.dim(`(${date})`)} ${chalk.green('[latest]')}`); + } else { + console.log(` ${tag} ${chalk.dim(`(${date})`)}`); + } + }); + + console.log(); + logger.dim('Use: uipro init --version to install a specific version'); + } catch (error) { + spinner.fail('Failed to fetch versions'); + if (error instanceof Error) { + logger.error(error.message); + } + process.exit(1); + } +} diff --git a/cli/src/index.ts b/cli/src/index.ts new file mode 100644 index 0000000..67b797f --- /dev/null +++ b/cli/src/index.ts @@ -0,0 +1,56 @@ +#!/usr/bin/env node + +import { Command } from 'commander'; +import { initCommand } from './commands/init.js'; +import { versionsCommand } from './commands/versions.js'; +import { updateCommand } from './commands/update.js'; +import type { AIType } from './types/index.js'; +import { AI_TYPES } from './types/index.js'; + +const program = new Command(); + +program + .name('uipro') + .description('CLI to install UI/UX Pro Max skill for AI coding assistants') + .version('1.0.0'); + +program + .command('init') + .description('Install UI/UX Pro Max skill to current project') + .option('-a, --ai ', `AI assistant type (${AI_TYPES.join(', ')})`) + .option('-v, --version ', 'Specific version to install') + .option('-f, --force', 'Overwrite existing files') + .action(async (options) => { + if (options.ai && !AI_TYPES.includes(options.ai)) { + console.error(`Invalid AI type: ${options.ai}`); + console.error(`Valid types: ${AI_TYPES.join(', ')}`); + process.exit(1); + } + await initCommand({ + ai: options.ai as AIType | undefined, + version: options.version, + force: options.force, + }); + }); + +program + .command('versions') + .description('List available versions') + .action(versionsCommand); + +program + .command('update') + .description('Update UI/UX Pro Max to latest version') + .option('-a, --ai ', `AI assistant type (${AI_TYPES.join(', ')})`) + .action(async (options) => { + if (options.ai && !AI_TYPES.includes(options.ai)) { + console.error(`Invalid AI type: ${options.ai}`); + console.error(`Valid types: ${AI_TYPES.join(', ')}`); + process.exit(1); + } + await updateCommand({ + ai: options.ai as AIType | undefined, + }); + }); + +program.parse(); diff --git a/cli/src/types/index.ts b/cli/src/types/index.ts new file mode 100644 index 0000000..5ee720f --- /dev/null +++ b/cli/src/types/index.ts @@ -0,0 +1,30 @@ +export type AIType = 'claude' | 'cursor' | 'windsurf' | 'antigravity' | 'all'; + +export interface Release { + tag_name: string; + name: string; + published_at: string; + html_url: string; + assets: Asset[]; +} + +export interface Asset { + name: string; + browser_download_url: string; + size: number; +} + +export interface InstallConfig { + aiType: AIType; + version?: string; + force?: boolean; +} + +export const AI_TYPES: AIType[] = ['claude', 'cursor', 'windsurf', 'antigravity', 'all']; + +export const AI_FOLDERS: Record, string[]> = { + claude: ['.claude'], + cursor: ['.cursor', '.shared'], + windsurf: ['.windsurf', '.shared'], + antigravity: ['.agent', '.shared'], +}; diff --git a/cli/src/utils/detect.ts b/cli/src/utils/detect.ts new file mode 100644 index 0000000..d01d935 --- /dev/null +++ b/cli/src/utils/detect.ts @@ -0,0 +1,50 @@ +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import type { AIType } from '../types/index.js'; + +interface DetectionResult { + detected: AIType[]; + suggested: AIType | null; +} + +export function detectAIType(cwd: string = process.cwd()): DetectionResult { + const detected: AIType[] = []; + + if (existsSync(join(cwd, '.claude'))) { + detected.push('claude'); + } + if (existsSync(join(cwd, '.cursor'))) { + detected.push('cursor'); + } + if (existsSync(join(cwd, '.windsurf'))) { + detected.push('windsurf'); + } + if (existsSync(join(cwd, '.agent'))) { + detected.push('antigravity'); + } + + // Suggest based on what's detected + let suggested: AIType | null = null; + if (detected.length === 1) { + suggested = detected[0]; + } else if (detected.length > 1) { + suggested = 'all'; + } + + return { detected, suggested }; +} + +export function getAITypeDescription(aiType: AIType): string { + switch (aiType) { + case 'claude': + return 'Claude Code (.claude/skills/)'; + case 'cursor': + return 'Cursor (.cursor/commands/ + .shared/)'; + case 'windsurf': + return 'Windsurf (.windsurf/workflows/ + .shared/)'; + case 'antigravity': + return 'Antigravity (.agent/workflows/ + .shared/)'; + case 'all': + return 'All AI assistants'; + } +} diff --git a/cli/src/utils/extract.ts b/cli/src/utils/extract.ts new file mode 100644 index 0000000..886f23f --- /dev/null +++ b/cli/src/utils/extract.ts @@ -0,0 +1,72 @@ +import { mkdir } from 'node:fs/promises'; +import { join } from 'node:path'; +import type { AIType } from '../types/index.js'; +import { AI_FOLDERS } from '../types/index.js'; + +export async function extractZip(zipPath: string, destDir: string): Promise { + const proc = Bun.spawn(['unzip', '-o', zipPath, '-d', destDir], { + stdout: 'pipe', + stderr: 'pipe', + }); + + const exitCode = await proc.exited; + if (exitCode !== 0) { + throw new Error(`Failed to extract zip: exit code ${exitCode}`); + } +} + +export async function copyFolders( + sourceDir: string, + targetDir: string, + aiType: AIType +): Promise { + const copiedFolders: string[] = []; + + const foldersToCopy = aiType === 'all' + ? ['.claude', '.cursor', '.windsurf', '.agent', '.shared'] + : AI_FOLDERS[aiType]; + + // Deduplicate folders (e.g., .shared might be listed multiple times) + const uniqueFolders = [...new Set(foldersToCopy)]; + + for (const folder of uniqueFolders) { + const sourcePath = join(sourceDir, folder); + const targetPath = join(targetDir, folder); + + // Check if source folder exists + const sourceExists = await Bun.file(sourcePath).exists().catch(() => false); + if (!sourceExists) { + // Try checking if it's a directory + try { + const proc = Bun.spawn(['test', '-d', sourcePath]); + await proc.exited; + } catch { + continue; + } + } + + // Create target directory if needed + await mkdir(targetPath, { recursive: true }); + + // Copy using cp -r + const proc = Bun.spawn(['cp', '-r', `${sourcePath}/.`, targetPath], { + stdout: 'pipe', + stderr: 'pipe', + }); + + const exitCode = await proc.exited; + if (exitCode === 0) { + copiedFolders.push(folder); + } + } + + return copiedFolders; +} + +export async function cleanup(tempDir: string): Promise { + const proc = Bun.spawn(['rm', '-rf', tempDir], { + stdout: 'pipe', + stderr: 'pipe', + }); + await proc.exited; +} diff --git a/cli/src/utils/github.ts b/cli/src/utils/github.ts new file mode 100644 index 0000000..60c71d2 --- /dev/null +++ b/cli/src/utils/github.ts @@ -0,0 +1,59 @@ +import type { Release } from '../types/index.js'; + +const REPO_OWNER = 'nextlevelbuilder'; +const REPO_NAME = 'ui-ux-pro-max-skill'; +const API_BASE = 'https://api.github.com'; + +export async function fetchReleases(): Promise { + const url = `${API_BASE}/repos/${REPO_OWNER}/${REPO_NAME}/releases`; + + const response = await fetch(url, { + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'uipro-cli', + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch releases: ${response.statusText}`); + } + + return response.json(); +} + +export async function getLatestRelease(): Promise { + const url = `${API_BASE}/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`; + + const response = await fetch(url, { + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'uipro-cli', + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch latest release: ${response.statusText}`); + } + + return response.json(); +} + +export async function downloadRelease(url: string, dest: string): Promise { + const response = await fetch(url, { + headers: { + 'User-Agent': 'uipro-cli', + }, + }); + + if (!response.ok) { + throw new Error(`Failed to download: ${response.statusText}`); + } + + const buffer = await response.arrayBuffer(); + await Bun.write(dest, buffer); +} + +export function getAssetUrl(release: Release): string | null { + const asset = release.assets.find(a => a.name.endsWith('.zip')); + return asset?.browser_download_url ?? null; +} diff --git a/cli/src/utils/logger.ts b/cli/src/utils/logger.ts new file mode 100644 index 0000000..7816022 --- /dev/null +++ b/cli/src/utils/logger.ts @@ -0,0 +1,11 @@ +import chalk from 'chalk'; + +export const logger = { + info: (msg: string) => console.log(chalk.blue('info'), msg), + success: (msg: string) => console.log(chalk.green('success'), msg), + warn: (msg: string) => console.log(chalk.yellow('warn'), msg), + error: (msg: string) => console.log(chalk.red('error'), msg), + + title: (msg: string) => console.log(chalk.bold.cyan(`\n${msg}\n`)), + dim: (msg: string) => console.log(chalk.dim(msg)), +}; diff --git a/cli/tsconfig.json b/cli/tsconfig.json new file mode 100644 index 0000000..7fea45f --- /dev/null +++ b/cli/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}