* Fix invalid Tailwind config output (missing comma before plugins)
`tailwind_config_gen.py` serialized the config by json.dumps()-ing
everything except `plugins`, stripping the outer braces, then appending
`plugins: [...]` in the template. The JSON block's last property (`theme`)
has no trailing comma, so the emitted config was:
"theme": { ... }
plugins: [],
which is a syntax error (`Unexpected identifier 'plugins'`) — every
generated tailwind.config.ts / tailwind.config.js failed to parse.
Add the missing comma after the serialized block in both
`_generate_typescript` and `_generate_javascript`.
The existing 54 tests passed because they only assert on the data
structure, never parsing the emitted string as JS. Add a
`TestGeneratedConfigIsValidJs` regression class that (1) asserts the
property before `plugins` is comma-terminated (pure Python) and (2)
parses the generated config with `node --check` when node is available.
Both new cases fail on the pre-fix code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix validate-tokens.cjs missing violations on lines that use a token
The scanner skipped any line containing `var(--` outright, so a hardcoded
value sharing a line with a token reference was never reported:
.btn { background: #FF6B6B; color: var(--color-primary); } // missed
This is a broad false-negative: real CSS routinely puts several properties
on one line, and minified CSS is a single line — one `var()` anywhere
suppressed every violation in the file. (The Python slide validator already
handles this correctly, so the two validators disagreed.)
Remove the line-level skip. Token-definition files are already excluded by
`skipPatterns` at the file level, and none of the detection regexes can
match inside a `var(--...)` reference, so this adds no false positives.
Add a pytest regression test (drives the CLI via node) asserting a hex
sharing a line with a token is flagged, and that a token-only line stays
clean. The first case fails on the pre-fix code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix sync-brand-to-tokens.cjs crashing on the bundled starter template
The color parser required a parenthesized name in the Quick Reference row
(`#2563EB (name)`) and a bolded label in the color tables
(`**Primary Blue** | #hex`). The bundled `brand-guidelines-starter.md` uses
neither — its rows are `| Primary Color | #2563EB |` and
`| Primary Blue | #2563EB |`. So no base hex was extracted, and
`generateColorScale(undefined)` → `adjustBrightness(undefined)` threw:
TypeError: Cannot read properties of undefined (reading 'replace')
i.e. the documented "edit guidelines → sync" flow crashed on the skill's
own starter template.
- Rewrite the parser: Quick Reference matches hex without a parenthesized
name; color-table rows match with optional bold; an accent swatch living
in another table (the starter's "Accent Green" under Secondary Colors) is
picked up by a fallback scan.
- Skip any role with no base hex (with a warning) instead of crashing, guard
adjustBrightness against non-string input, and initialize tokens.primitive
so a tokens file without it doesn't throw.
Add a pytest regression test (drives the CLI via node) that syncs the bundled
starter template and asserts the expected base colors are written. It fails
on the pre-fix code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Vendor ui-ux-pro-max scripts/data so a standalone skill install works
`.claude/skills/ui-ux-pro-max/scripts` and `.../data` were symlinks into the
repo's top-level `src/ui-ux-pro-max/`:
scripts -> ../../../src/ui-ux-pro-max/scripts
data -> ../../../src/ui-ux-pro-max/data
That resolves in a full checkout, but installing a single skill by copying
`.claude/skills/<name>/` into `~/.claude/skills/` leaves the links pointing at
`~/src/ui-ux-pro-max/`, which does not exist. The links dangle and `search.py`
— the entire searchable engine behind --design-system / --domain / --persist —
fails with `No such file or directory`. (The embedded rule tables in SKILL.md
still work, masking the breakage.)
Replace the two symlinks with the real `scripts/` and `data/` files so the
skill is self-contained, matching how the sibling skills (design,
design-system, ui-styling, brand) already ship real files under their own
`scripts/`. Maintainers who prefer to keep the `src/` + symlink architecture
may instead document that this skill requires a full-repo checkout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: abukardini7 <abukardini7@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The skill body claimed "React Native (this project's only tech stack)"
and listed only `react-native` under Available Stacks. That contradicts:
- the SKILL.md frontmatter description ("web and mobile", multi-stack)
- plugin.json, which advertises 15 stacks
- the shipped data/stacks/ engine data (16 stack CSVs)
So `--stack nextjs`/`react`/`shadcn`/`vue`/etc. have always worked; the
docs just never said so, steering every project toward React Native.
Make the guidance framework-agnostic:
- Step 1 "Stack" bullet: match the project's framework, not RN
- Step 4 heading/command: generic `--stack <your-stack>`
- Available Stacks table: list all shipped stacks
- Example workflow (a homepage) uses a web stack
- Tips line generalized
Adds WPF, WinUI 3, UWP, Avalonia, and Uno Platform stacks (17 -> 22 total),
each with its own guidelines CSV, registered in core.py and synced to cli/assets.
- New stack CSVs in src/ and cli/assets/data/stacks/
- search.py / core.py registry updated
- smoke-stacks.sh: EXPECTED_STACK_COUNT 21 -> 22 with a smoke-test workflow
- platform template descriptions and README bumped to 22 technology stacks
The bin mapping was "./dist/index.js". On publish, npm normalizes bin
values and the leading ./ triggers a warning -- on the npm version in the
release runner it reports `bin[uipro] script name dist/index.js was
invalid and removed`, i.e. the published package ships with no `uipro`
command, so `npx uipro-cli init` would not work even once publishing
succeeds (see #353).
Change the value to the canonical `dist/index.js` (exactly what
`npm pkg fix` produces). src/index.ts already has the
`#!/usr/bin/env node` shebang, which bun build preserves, so the bin is
executable.
Verified: `npm publish --dry-run` no longer emits the bin warning, and
dist/index.js is included in the tarball; typecheck passes.
Addresses the package-metadata half of #353. Actually landing on npm
still requires a release run with a token that has publish rights for
uipro-cli.
design_system.py prints box-drawing characters and swatches (--, OK marks,
block swatches), but unlike search.py it never reconfigured stdout. On a
non-UTF-8 Windows console (cp1252/gbk) running the CLI crashes with
UnicodeEncodeError, the same failure reported in #112 for search.py.
Mirror the wrapper search.py already uses: re-wrap stdout/stderr in a
UTF-8 TextIOWrapper when the console encoding is not UTF-8. Applied to
the source of truth and the synced cli/assets copy.
Verified: the CLI runs clean under PYTHONIOENCODING=cp1252 (exit 0, no
UnicodeEncodeError) while still emitting the box/swatch output;
check:assets passes.
Related to #112 (search.py is already fixed on main; this covers the
remaining design_system.py instance).
The marketplace-facing manifests understated the catalog. Actual data:
84 styles (styles.csv), 73 font pairings (typography.csv), and 17 stacks
(data/stacks/*.csv), but plugin.json, marketplace.json, and skill.json
all claimed 67 styles / 57 font pairings / 15 stacks.
Update the three manifests to 84 / 73 / 17, and extend the stack
enumeration to the full 17 (adding Angular, Laravel, JavaFX, Three.js,
and Nuxt UI). Palettes (161) and charts (25) were already correct.
Resolves the count-drift finding in #289 (supersedes the stuck #282 and
#329, which only partially corrected the numbers).
* fix(cli): install all 7 skills via uipro init, not just the orchestrator
`uipro init` rendered only the orchestrator (ui-ux-pro-max) and never
delivered the 6 sibling skills (banner-design, brand, design,
design-system, slides, ui-styling), so users got 1 of 7 skills (#362).
- sync-assets.mjs: bundle the 6 sub-skills into cli/assets/skills/ as
static copies (source of truth: .claude/skills/), with sync + check
coverage. Excludes ui-styling/canvas-fonts (~5.8MB of TTF) and
__pycache__/.pyc cruft — a skill registers from its SKILL.md, not its
fonts — so the bundle adds ~0.9MB, not ~6.6MB.
- template.ts: after rendering the orchestrator, install each bundled
sub-skill as a sibling. The skills parent is derived from the
platform's skillPath (skills/ for most, prompts/ for copilot,
steering/ for kiro) rather than hardcoded.
- uninstall.ts: remove the sub-skills too.
Verified: check:assets in sync, tsc passes, and a per-platform install
harness delivers all 7 skills to the correct parent dir with no fonts.
Closes#362
* fix(cli): filter excluded files from target side of check:assets
check:assets filtered sourceFiles with isExcludedAssetFile but not
targetFiles, so a stray cli/assets/scripts/__pycache__/*.pyc (generated
by a local Python run) was reported as an "extra asset file" and failed
the gate. Apply the same predicate to targetFiles in both the
dirsToSync and sub-skill loops.
Verified: check:assets now passes with __pycache__/*.pyc present in the
target tree; typecheck passes.
* fix(cli): uninstall from each platform's real skills dir, not hardcoded skills/
removeSkillDir() hardcoded <folder>/skills/<name>, but the installer
places skills under each platform config's skillPath parent — copilot in
.github/prompts/, kiro in .kiro/steering/. So uninstall left those
platforms' skills (orchestrator + sub-skills) behind.
Derive the install parent from loadPlatformConfig(aiType).folderStructure
(same source the installer uses), and keep the legacy <folder>/skills/
cleanup (incl. .shared/) for older installs. Deduped via a Set.
Verified: typecheck passes; an install+uninstall harness removes all 7
skills with zero leftovers for claude (.claude/skills), copilot
(.github/prompts) and kiro (.kiro/steering).
* fix(cli): re-sync bundled sub-skills after #385 stripped ckm- names
#385 merged to main and removed the ckm- prefix from the six
.claude/skills/*/SKILL.md name fields. This branch's bundled copies
under cli/assets/skills/ still carried the old ckm- names, so after the
PR merges with main the source no longer matched the bundle and the
check-asset-sync CI gate failed (stale asset file: skills/*/SKILL.md).
Merge main and regenerate the bundle so cli/assets/skills matches the
current .claude/skills source of truth. check:assets and typecheck pass.
Two issues caused Claude Code plugin users to see only 1 (or 0) of the
7 bundled skills:
- .claude-plugin/plugin.json declared `"skills": ["./.claude/skills/ui-ux-pro-max"]`
— an array pointing at a single subfolder. With a marketplace-root
source this replaces the default scan, so the other 6 skills never
load. Point it at the parent directory ("./.claude/skills/") so the
loader scans all 7 <name>/SKILL.md subfolders. (#360)
- The 6 sub-skill SKILL.md `name:` fields kept a `ckm-` prefix
(e.g. `ckm-brand` in dir `brand`) that no longer matched their
directory names, tripping loaders that require name == directory.
Strip the prefix so each name equals its folder. (#266)
Closes#360Closes#266
persist_design_system crashed with AttributeError: 'NoneType' object has
no attribute 'lower' when the design_system dict carries an explicit
project_name of None (the default of generate(query, project_name=None)).
dict.get("project_name", "default") only substitutes the default for a
MISSING key, not a present-but-None value, so project_name.lower() blew
up.
Coalesce falsy values (None/""/missing) to "default" before slugifying.
Applied to both the source of truth and the bundled cli/assets copy.
The "search blindness" half of #159 (core.py tokenize length filter) is
already resolved on main (`len(w) >= 2`), so this targets the remaining
crash only.
Closes#159
Co-authored-by: YangKuoshih <155388493+YangKuoshih@users.noreply.github.com>
On Windows, an older install (or a git checkout that materialized the
repo's symlinked data/scripts as plain "pointer" files) leaves a regular
file at <skill>/data or <skill>/scripts. copyDataAndScripts then calls
mkdir on that path, which throws EEXIST; under `uipro init --ai all` the
per-platform error is swallowed, leaving e.g. codex with the stale
pointer files and no real directories.
Add ensureCleanDir(): before mkdir, lstat the target and remove it if it
is not already a directory. Existing real directories are preserved
(re-install is unaffected). Verified with a repro harness.
Closes#237
* fix CLI asset sync
* fix(cli): normalize line endings in asset sync/check
check:assets hashed raw bytes, so identical CSV/JSON/py content with
CRLF vs LF (git autocrlf on checkout) was reported as stale drift,
blocking the release guard on Windows/mixed checkouts.
- fileHash now normalizes CRLF->LF before hashing, so check:assets
compares content, not line endings.
- sync:assets writes LF-normalized copies instead of a raw byte copy,
so re-syncing is deterministic across platforms.
All synced assets are text (csv/json/md/py); no binaries affected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: enforce CLI asset sync on PRs
Adds a Check asset sync workflow that runs `npm run check:assets` on any
PR touching src/ui-ux-pro-max/** or cli/assets/**, so the bundled CLI
assets can't silently drift from the source of truth.
The check uses only node builtins (no install step) and normalizes
line endings before hashing, so it hard-fails on real content drift
without the CRLF/LF soft-fail hack other approaches needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: V-001 security vulnerability
Automated security fix generated by Orbis Security AI
* feat(cli): add optional GitHub token support with proper UX and docs
- Rename env var from GITHUB_TOKEN to UI_PRO_MAX_GITHUB_TOKEN (primary),
with GITHUB_TOKEN as fallback — avoids silently attaching CI workflow
credentials that GitHub Actions injects automatically
- Add whitespace trim guard to prevent malformed Authorization headers
- Add getGitHubTokenGuidance() helper exported from github.ts so rate-limit
errors and spinner warnings tell users exactly how to fix the problem
- Thread optional token param through fetchReleases, getLatestRelease,
downloadRelease signatures
- Add --token flag to init, versions, update commands (Commander.js wiring)
- Update rate-limit catch in tryGitHubInstall to show token guidance
- Document token options (flag, env var, fallback) in cli/README.md with
CI warning about GITHUB_TOKEN scope
Supersedes the narrower env-only approach in PR #294 and incorporates the
safeguards requested during review of closed PR #186.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The Agent Skills spec requires names to match [a-z0-9-] only.
The colon in 'ckm:' prefix causes daily validation errors:
name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)
This affects all six ckm-prefixed skills. Replace ':' with '-'.
Co-authored-by: Thomas Schneider <tho.schneider@sap.com>
* fix: bump skill.json version to 2.6.2
* fix: bump marketplace.json and plugin.json version to 2.6.2
---------
Co-authored-by: shipengqi <pooky.shi@gamil.com>
Add html.escape() to all 46 user-controlled data.get() calls across
all 7 slide generator functions (title, problem, solution, metrics,
chart, testimonial, cta) and the deck title.
Add URL scheme validation for cta_url href to block javascript: URI
injection.
Closes#247
The _format_plugins() method interpolated plugin names directly into
require() statements without sanitization. A plugin name containing
a single quote could break out of require() and inject arbitrary
JavaScript that executes when Node.js loads the generated config.
Add a strict regex allowlist matching valid npm package name patterns
(optional @scope, alphanumeric/hyphen/underscore, optional subpath).
Reject any plugin name that doesn't match before generating output.
Closes#246
Breaking changes fixed:
- Row 8/9: icon format corrected from 'lucide:home' (v3) to 'i-lucide-home' (v4 Iconify i-prefix)
- Row 18: useOverlay API updated from .open(Component, {props}) to .create(Component).open({props})
New v4 guidelines added (rows 51-70):
- Auto-registered modules: do not re-add @nuxt/icon @nuxt/fonts @nuxtjs/color-mode
- Official nuxi init templates for project bootstrapping
- Local Iconify JSON install for SSR reliability
- Global icon overrides via appConfig.ui.icons
- New form components: UFileUpload, UInputDate, UInputTags, UColorPicker
- New data components: UTree, UMarquee
- UContextMenu for right-click menus
- Awaiting overlay result Promise for confirmation dialogs
- UCommandPalette grouped items and extractShortcuts
- UHeader/UFooter/UPageAside layout components
- ClientOnly pattern for color mode hydration safety
- Generated theme file (.nuxt/ui/) for slot discovery
- defineShortcuts whenever condition guard
- UApp locale prop for i18n (50+ built-in locales)
Sync cli/assets to match src.
execSync with a backtick template string creates a shell-expansion
surface. Although all three variables (generateScript, DESIGN_TOKENS_JSON,
DESIGN_TOKENS_CSS) are currently hardcoded constants, the pattern is
fragile — any future substitution of user-controlled data would create
shell injection.
Replace with execFileSync('node', [...args]) to eliminate the shell
entirely and make the boundary explicit.
Co-authored-by: claude[bot] <claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
All three are in the canonical src/ tree, reproduced on Python 3.14.5:
1. data/typography.csv — all 73 'Google Fonts URL' values were unusable
fonts.google.com/share *UI* links, not stylesheet hrefs. Replaced with the
proper fonts.googleapis.com/css2 URLs (extracted verbatim from each row's
correct 'CSS Import' column), so the URL column is actually loadable.
2. scripts/core.py — BM25 tokenizer kept only words >2 chars, so common design
terms 'ux', 'ui', 'ai', '3d' tokenized to nothing and returned 0 results
(e.g. `--domain style "3d"`). Changed >2 to >=2; '3d'/'ai' now match.
3. scripts/search.py — `--persist` without `-p` printed
'design-system/default/' but actually wrote to the query slug
(e.g. design-system/fintech-crypto/). Use the same default the writer uses
so the printed path matches reality.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add cross-platform Python command note to skills and docs:
- CLAUDE.md: Add Windows note in Prerequisites
- ui-ux-pro-max/SKILL.md: Add Windows note in Prerequisites
- design/SKILL.md: Add Prerequisites section with Windows note
- banner-design/SKILL.md: Add Prerequisites section with Windows note
- templates/base/skill-content.md: Add Windows note (source template)
Windows uses 'python' instead of 'python3' to run scripts.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Line 14 contained a raw `<args>$ARGUMENTS</args>` tag that was never
replaced during skill generation. This literal XML-like string renders
as part of the skill body and can confuse LLM consumers.
Co-authored-by: claude[bot] <claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
OpenClaw (https://openclaw.ai) is an open-source agent gateway that
runs Claude as its primary backend. This skill works out-of-the-box
inside OpenClaw agents — same SKILL.md + python3 search.py invocation
pattern as the other supported platforms. Adding it to the list so
the install flow can target it.
Co-authored-by: yinhaoti <yinhaoti@macmini.home>
The --force option was accepted by Commander but ignored: writeFile()
always overwrote existing files silently. Users expecting protection
against accidental overwrites had no way to preserve existing configs.
- generatePlatformFiles: check file existence before writing; skip with
a clear message when file exists and force=false
- generateAllPlatformFiles / templateInstall: propagate force parameter
- initCommand: pass options.force through to templateInstall
Behavior change:
uipro init --ai claude → skips if SKILL.md already exists
uipro init --ai claude --force → overwrites regardless
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Both files were stuck at v2.2.1 while plugin.json, skill.json and
cli/package.json had moved to v2.5.0. Claude marketplace reads
marketplace.json for version and palette count — the stale entry
showed outdated data (96 palettes vs actual 161, 13 stacks vs 15).
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Merged STACK_CONFIG: kept main's full stack list + added angular & laravel from PR
- Updated platform templates: 161 palettes, 16 stacks across all descriptions
- Listed all 16 stacks in claude.json frontmatter description
- Warn when --global and --legacy are used together (init.ts)
- Update metadata across all platform templates (161 palettes, 15 stacks)
- Bump skill.json version from 2.0.0 to 2.5.0
- Re-throw non-ENOENT errors in uninstall.ts removeSkillDir
- Hoist detectAIType call to avoid double invocation in uninstall
- Add clarifying comment for .shared in AI_FOLDERS mapping