noslopui 0.2.0 → 0.3.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/README.md CHANGED
@@ -59,7 +59,7 @@ Anything else that speaks MCP over HTTP can be connected by hand — add `https:
59
59
 
60
60
  | | |
61
61
  |---|---|
62
- | `npx noslopui search <query>` | Search components and blocks (`--tag style:editorial`, `--framework`, `--limit`) |
62
+ | `npx noslopui search <query>` | Search UI blocks and Motion Lab effects (`--section ui-blocks\|motion-lab`, `--tag style:editorial`, `--limit`) |
63
63
  | `npx noslopui get <id>` | Print a component's code, or `--write src/Hero.tsx` (`--format tsx\|html\|vue`) |
64
64
  | `npx noslopui prompt <id>` | Print a component's AI-prompt version |
65
65
  | `npx noslopui design-systems [query]` | Search design systems (`--tag theme:dark`) |
package/SKILL.md CHANGED
@@ -11,11 +11,12 @@ AI-generated) UI components, full page examples, and portable Design Systems.
11
11
  ## The rules
12
12
 
13
13
  1. **Choose one design system before fetching any component, then restyle every component to it** — colours, type scale, spacing, radius, motion. One system applied everywhere is what stops a multi-page build reading as a collage.
14
- 2. **Search the whole catalog on fit.** A design system is a set of rules, not an inventory of parts: any component can be built in any system, so nothing is off-limits once one is chosen.
14
+ 2. **Search the catalog before hand-building a section, not instead of hand-building it.** Check `search_components` for every required section first — having the design system's tokens and prose isn't a reason to skip that, and any component can be built in any system, so nothing is off-limits once one is chosen. Only build a section yourself if nothing in the catalog fits it, and tell the user that's what happened.
15
15
  3. **Never substitute a catalog component for one the user explicitly saved** in a collection. Their pick was a decision, not an oversight.
16
16
  4. **Ask about their brand before building** — logo, brand colours, typeface, real photography. Put their values into the system's tokens (their colour replaces `accent`) and keep the system's rules.
17
- 5. **Check for a collection first:** call `list_collections`. If one fits the request, build from it. If none fits, don't guess a collection — shortlist two or three design systems with `search_design_systems` and let the user pick.
17
+ 5. **A collection existing is not the same as the user asking for it.** `list_collections` is free to call, but only build from what it returns when the user asked for their saved items by name or in substance, or confirmed it when you asked. The default "Favorites" list is just where the save button drops a single item with zero setup — a lone design system sitting in it is not a decision to build the whole thing around. One collection with content and no explicit ask → surface it as a question, don't assume either answer. Nothing confirmed → shortlist two or three design systems with `search_design_systems` and let the user pick.
18
18
  6. **If a gated tool says the trial has ended or the fair-use limit is reached, tell the user and stop** — don't retry, and don't swap in something generated from scratch.
19
+ 7. **A Motion Lab effect is a signature moment, not a section.** Reach for one (`search_components` with `section: "motion_lab"`) when the user asks for an effect, motion, 3D or a showpiece, or the brief clearly calls for one — then use one per page unless they ask for more. Recolour it through its `colorVars` to the design system's tokens, follow its `usage`, and keep its reduced-motion fallback.
19
20
 
20
21
  Setup, if the noslopUI tools aren't available: the user runs
21
22
  `npx noslopui@latest init`, or adds `https://noslopui.com/api/mcp` in their
@@ -28,13 +29,21 @@ the account's free 3-day trial, which the first such call starts.
28
29
 
29
30
  ## Step 0 — Which path?
30
31
 
31
- **Call `list_collections` first.** It's free on every account.
32
-
33
- - A relevant collection exists → **Path A**. The user already did the choosing.
34
- - No collection, or nothing relevant → **Path B**.
35
-
36
- Several collections and no obvious match → ask. Don't guess between two curated
37
- sets.
32
+ **Call `list_collections` first.** It's free on every account. But a
33
+ collection existing is not the same as the user asking you to build from it —
34
+ the default "Favorites" list is just where the save button puts a single item
35
+ with zero setup, so a design system sitting there alone is very often a saved
36
+ reference, not a decision. Don't let its mere presence override what the user
37
+ actually asked for.
38
+
39
+ - The user already said to use their saved items ("build from my collection",
40
+ "use what I've saved", names a list by name) → **Path A**, no need to ask.
41
+ - Nothing was said either way, but exactly one collection has content that
42
+ looks relevant → **ask**: name what's in it and let them choose Path A or
43
+ Path B. Don't assume silently in either direction.
44
+ - No collection, or nothing in any of them fits → **Path B**.
45
+ - Several collections and no obvious match → ask which one. Don't guess
46
+ between two curated sets.
38
47
 
39
48
  ---
40
49
 
@@ -64,6 +73,17 @@ Do this on both paths, before choosing a system. Two groups of things.
64
73
  No brand at all → say so plainly and use the design system's own palette. Don't
65
74
  invent a logo.
66
75
 
76
+ No real photography → don't ship a grey box. Use a seeded placeholder from
77
+ [Picsum](https://picsum.photos) — `https://picsum.photos/seed/<brand-slug>-<slot-name>/<w>/<h>`
78
+ — so the same build gets the same photo on every regenerate instead of a new
79
+ random one. Mark every one with `<!-- TODO: Replace with real photo, target
80
+ size <w>x<h> -->` immediately above it, and write alt text for the *intended*
81
+ subject ("Hand-poured ceramic mug, studio lighting"), not the placeholder
82
+ ("Picsum image"), so it's already correct once they swap it in. Skip
83
+ photography altogether where the chosen design system's own register is
84
+ photo-free (type-led, editorial, most SaaS) rather than forcing a stock photo
85
+ in where the system doesn't call for one.
86
+
67
87
  If the user already stated all this, don't re-ask — restate it in one line and
68
88
  move on.
69
89
 
@@ -72,8 +92,9 @@ move on.
72
92
  ## Path A — the user curated a collection
73
93
 
74
94
  `get_collection({ listId })` (or `name`). Every item carries a `kind` —
75
- `design_systems` or `ui_blocks` — plus a top-level `designSystems` array and a
76
- `guidance` line.
95
+ `design_systems`, `ui_blocks` or `motion_lab` (an effect) — plus a top-level
96
+ `designSystems` array and a `guidance` line. A saved effect is a pick like any
97
+ other: use it where it fits, restyled as in Step 3.
77
98
 
78
99
  1. **The collection's design system wins.** One in it → that is the system, no
79
100
  searching; `get_design_system_file` for its DESIGN.md. More than one → ask
@@ -83,8 +104,10 @@ move on.
83
104
  something that covers a section, use it. Always. **Never substitute a
84
105
  catalog component for one they explicitly saved**, even if you found
85
106
  something you think is better — that was their call, not an oversight.
86
- 3. **Fill the gaps with `search_components`**, searching the whole catalog on
87
- fit, and restyle what you find to the collection's design system.
107
+ 3. **Fill the gaps with `search_components`, checked before you hand-build
108
+ anything.** Search the whole catalog on fit, and restyle what you find to
109
+ the collection's design system. Only build a gap section yourself if
110
+ nothing in the catalog fits it, and say so in step 4's report.
88
111
  4. **Report what you filled in.** "Hero, pricing and footer from your
89
112
  collection; you had no testimonial section saved, so I pulled one from the
90
113
  catalog and restyled it." They should never have to diff the result to find
@@ -107,12 +130,27 @@ Then filter with `tags` (ANDed) and/or a free-text `query` built from the tone
107
130
  words plus purpose:
108
131
 
109
132
  ```
110
- search_design_systems({ tags: ["theme:light", "industry:local-business"] })
111
- search_design_systems({ query: "calm minimal saas", tags: ["theme:light"] })
133
+ search_design_systems({ tags: ["industry:hair-beauty"] })
134
+ search_design_systems({ tags: ["industry:dentist", "theme:light"] })
135
+ search_design_systems({ query: "calm minimal", tags: ["industry:saas"] })
112
136
  ```
113
137
 
114
- A system can carry several `industry:` tags — a developer-tools system is
115
- often a SaaS system too — so don't treat one tag as excluding the others.
138
+ `industry:` is a two-level tree, shaped like a template marketplace's: a
139
+ **field** (`food-drink`, `medical`, `home-services`, `technology`…) and, under
140
+ it, **niches** (`restaurant`, `dentist`, `roofing`, `saas`…). Every system
141
+ tagged with a niche is also tagged with its field, so start with the niche if
142
+ the brief names one and widen to the field when that returns fewer than three.
143
+
144
+ **"Local business" is not an industry — name the trade.** A plumber is
145
+ `home-services`, a barber `hair-beauty`, a café `food-drink`, a physio
146
+ `medical`, a yoga studio `wellness`, an accountant `professional-services`.
147
+ Each of those looks and reads differently, and filtering on the specific field
148
+ is what keeps two local businesses from getting the same site.
149
+
150
+ A system can carry several fields — a warm serif system serves a restaurant, a
151
+ wedding planner and a spa alike — so don't treat one tag as excluding the
152
+ others, and don't pick the same system for every brief in a field: read the
153
+ descriptions and match the tone the user described.
116
154
 
117
155
  Every result comes back with its tags, its token summary (colours, fonts,
118
156
  radius) and a `previewUrl`. **Present the 2-3 best matches to the user with
@@ -135,11 +173,32 @@ work.
135
173
 
136
174
  ### B2. Find components on fit
137
175
 
138
- For each required section, call `search_components` — `section`, `framework`
139
- and `tags` filters as needed, with `tags` ANDed the same way
140
- (`["style:editorial"]`, `["animation:carousel"]`). Pick the best
141
- **structural** match: the thing that actually does what the section needs.
142
- Largely ignore how it currently looks; you are restyling it regardless.
176
+ **Never write a section's markup from scratch before checking the catalog.**
177
+ Having the DESIGN.md in hand is not the same as being done searching — for
178
+ *every* required section, call `search_components` first — `section` and
179
+ `tags` filters as needed, with `tags` ANDed the same way (`["style:editorial"]`,
180
+ `["animation:carousel"]`). Pick the best **structural** match: the thing that
181
+ actually does what the section needs. Largely ignore how it currently looks;
182
+ you are restyling it regardless. Only once nothing in the catalog fits a
183
+ section is hand-building it acceptable — and say so, don't pass it off as a
184
+ catalog pick.
185
+
186
+ Every result is published as React/TSX with Tailwind classes, whatever stack
187
+ the build target is. That's not a limitation — the markup and styling aren't
188
+ React-specific, so converting a result to plain HTML, Vue, or anything else is
189
+ a mechanical step (`className` to `class`, drop event-handler props, etc.),
190
+ not a rebuild. Never skip a structurally-right component, or conclude the
191
+ catalog has nothing for a non-React project, over its source format.
192
+
193
+ **Effects are a separate kind.** `section: "motion_lab"` searches Motion Lab:
194
+ shader backgrounds, 3D, liquid glass, text, cursor and scroll effects. An
195
+ effect isn't a section of the page — it's the one moment people remember the
196
+ page for. Use one where the brief asks for it (an effect, motion, 3D, "make it
197
+ pop") or clearly calls for a showpiece, and one per page unless the user asks
198
+ for more. `get_component` tells you how: `usage` says where it earns its
199
+ place, `props` are its knobs, `colorVars` are the CSS custom properties its
200
+ colours come from, and `libraries` are the exact npm packages and versions to
201
+ install.
143
202
 
144
203
  Then `get_component_code` for the ones you'll use.
145
204
 
@@ -164,6 +223,13 @@ result *theirs* rather than a nice demo:
164
223
  Body text stays the system's text face unless they insist.
165
224
  - Their photography replaces placeholder imagery at the same crops and ratios.
166
225
 
226
+ **C. Effects take the system's colours through their variables.** A Motion
227
+ Lab effect exposes each colour as a CSS custom property (`colorVars`, e.g.
228
+ `--ml-accent`) and as a prop. Set those from the system's tokens — the brand
229
+ accent, the darkest surface — and tune its `props` toward the system's motion
230
+ rules (slower if the system is calm). Never recolour inside a shader or
231
+ script, and never remove its reduced-motion fallback.
232
+
167
233
  **Substitute values, keep the rules.** The system's spacing scale, radius
168
234
  discipline, contrast requirements and "one accent moment per screen" all still
169
235
  apply after the swap — that is the point of a system: it survives a brand
@@ -196,7 +262,9 @@ exists to avoid — catch them here rather than shipping them:
196
262
  - **Spacing** — compose from the DESIGN.md's spacing scale; don't invent
197
263
  in-between values. Prefer more whitespace over a denser grid.
198
264
  - **Motion** — only where it communicates a real state change; the DESIGN.md's
199
- durations and easing, never a default bounce or spring.
265
+ durations and easing, never a default bounce or spring. The one exception is
266
+ a Motion Lab effect placed where its `usage` says it earns its place — one
267
+ per page, recoloured to the system.
200
268
  - **Named slop-tells to actively reject**: gradient text, purple-on-purple
201
269
  palettes, bounce/spring easing on everything, every layout centered with no
202
270
  asymmetry, decorative-only animation.
@@ -204,8 +272,8 @@ exists to avoid — catch them here rather than shipping them:
204
272
  ## Step 5 — Point back to the catalog for variations
205
273
 
206
274
  If the result doesn't land, don't re-prompt the same component blind. Point the
207
- user at `noslopui.com/explore` (cross-catalog search across UI Blocks and
208
- Design Systems in one grid) to browse real alternatives, and at the
275
+ user at `noslopui.com/explore` (every UI block in one grid, with Motion Lab and
276
+ design-system shelves) to browse real alternatives, and at the
209
277
  Collections tab on `noslopui.com/account` to save the ones they like — which
210
278
  turns the next build into Path A. Then repeat from Step 3 with whatever they
211
279
  pick.
@@ -218,12 +286,13 @@ pick.
218
286
  list_collections({}) -> { collections: [{ id, name, itemCount }] }
219
287
  get_collection({ listId?, name? }) -> { collection, designSystems: [{id,name}], items: [{ id, name, kind, ... }], guidance }
220
288
 
221
- search_design_systems({ query?, tags?, tag?, limit? }) -> { results: [{ id, name, description, tags, tokens, previewUrl, url }], availableTags }
289
+ search_design_systems({ query?, tags?, tag?, limit? }) -> { results: [{ id, name, description, tags, tokens, previewUrl, url }], availableTags, industryTree }
222
290
  get_design_system({ id }) -> { id, name, tokens, previewUrl, detailUrl }
223
291
  get_design_system_file({ id }) -> { id, name, designMd } [gated: paid or trial]
224
292
 
225
- search_components({ query?, section?, framework?, tags?, tag?, limit? }) -> { results: [{ id, name, kind, tags, url }] }
293
+ search_components({ query?, section?, tags?, tag?, limit? }) -> { results: [{ id, name, kind, tags, url }] }
226
294
  get_component({ id }) -> { id, name, dependencies, builtWithDesignSystem, previewUrl, detailUrl }
295
+ motion_lab items add { usage, props, colorVars, libraries }
227
296
  get_component_code({ id }) -> { id, name, snippets: [{ language, code }] } [gated: paid or trial]
228
297
  get_component_prompt({ id }) -> { id, name, promptText } [gated: paid or trial]
229
298
  ```
@@ -13,6 +13,32 @@ import { dirname, resolve } from 'node:path';
13
13
  import { callTool } from '../lib/api.js';
14
14
  import { readSession } from '../lib/session.js';
15
15
  import { color } from '../lib/ui.js';
16
+ // The catalog's two component sections, as search_components names them.
17
+ // Hyphens are accepted too, since that's how they read in URLs.
18
+ const SECTIONS = {
19
+ ui_blocks: 'ui_blocks',
20
+ 'ui-blocks': 'ui_blocks',
21
+ blocks: 'ui_blocks',
22
+ motion_lab: 'motion_lab',
23
+ 'motion-lab': 'motion_lab',
24
+ effects: 'motion_lab',
25
+ };
26
+ /** search_components arguments for these options; throws on a section it doesn't know. */
27
+ export function searchArguments(options) {
28
+ let section;
29
+ if (options.section !== undefined) {
30
+ section = SECTIONS[options.section.toLowerCase()];
31
+ if (!section)
32
+ throw new Error(`--section takes ui-blocks or motion-lab, not "${options.section}".`);
33
+ }
34
+ return {
35
+ ...(options.args.length ? { query: options.args.join(' ') } : {}),
36
+ ...(section ? { section } : {}),
37
+ ...(options.tags.length ? { tags: options.tags } : {}),
38
+ ...(options.framework ? { framework: options.framework } : {}),
39
+ ...(options.limit ? { limit: options.limit } : {}),
40
+ };
41
+ }
16
42
  const out = (s = '') => process.stdout.write(`${s}\n`);
17
43
  const note = (s = '') => process.stderr.write(`${s}\n`);
18
44
  function requireKey() {
@@ -48,13 +74,9 @@ function writeOut(path, text, force) {
48
74
  }
49
75
  const truncate = (s, n) => (s && s.length > n ? `${s.slice(0, n - 1)}…` : s ?? '');
50
76
  export async function search(options) {
77
+ const args = searchArguments(options);
51
78
  const key = requireKey();
52
- const r = check(await callTool(key, 'search_components', {
53
- ...(options.args.length ? { query: options.args.join(' ') } : {}),
54
- ...(options.tags.length ? { tags: options.tags } : {}),
55
- ...(options.framework ? { framework: options.framework } : {}),
56
- ...(options.limit ? { limit: options.limit } : {}),
57
- }));
79
+ const r = check(await callTool(key, 'search_components', args));
58
80
  if (options.json)
59
81
  return out(JSON.stringify(r.data, null, 2)), 0;
60
82
  const results = r.data?.results ?? [];
@@ -63,7 +85,8 @@ export async function search(options) {
63
85
  return 1;
64
86
  }
65
87
  for (const item of results) {
66
- out(`${color.bold(item.name)} ${color.dim(item.id)}`);
88
+ const kind = item.kind === 'motion_lab' ? ` ${color.cyan('Motion Lab')}` : '';
89
+ out(`${color.bold(item.name)} ${color.dim(item.id)}${kind}`);
67
90
  if (item.description)
68
91
  out(` ${truncate(item.description, 110)}`);
69
92
  out(` ${color.cyan(item.url)}`);
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ ${color.bold('Account')}
37
37
  logout Forget this CLI's sign-in
38
38
 
39
39
  ${color.bold('Catalog')}
40
- search <query> Search components and blocks --tag <facet:value>, --framework, --limit
40
+ search <query> Search blocks and effects --section ui-blocks|motion-lab, --tag <facet:value>, --limit
41
41
  get <id> Print a component's code --format tsx|html|vue, --write <file>
42
42
  prompt <id> Print a component's AI-prompt version
43
43
  design-systems [query] Search design systems --tag theme:dark …
@@ -98,6 +98,8 @@ function parse(argv) {
98
98
  parsed.format = take();
99
99
  else if (flag === '--framework')
100
100
  parsed.framework = take();
101
+ else if (flag === '--section')
102
+ parsed.section = take();
101
103
  else if (flag === '--write' || flag === '-o')
102
104
  parsed.write = take();
103
105
  else if (flag === '--limit') {
@@ -136,6 +138,7 @@ async function main() {
136
138
  ...(args.limit ? { limit: args.limit } : {}),
137
139
  ...(args.format ? { format: args.format } : {}),
138
140
  ...(args.framework ? { framework: args.framework } : {}),
141
+ ...(args.section ? { section: args.section } : {}),
139
142
  ...(args.write ? { write: args.write } : {}),
140
143
  };
141
144
  switch (args.command) {
@@ -1,3 +1,3 @@
1
1
  // One place for the version, so package.json, --version and the User-Agent
2
2
  // can't disagree. Checked against package.json by a test.
3
- export const CLI_VERSION = '0.2.0';
3
+ export const CLI_VERSION = '0.3.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noslopui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Connect your coding agent to noslopUI \u2014 hand-crafted UI components and portable design systems, over MCP.",
5
5
  "keywords": [
6
6
  "mcp",