noslopui 0.1.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 +58 -18
- package/SKILL.md +104 -39
- package/dist/adapters/claude-code.js +21 -121
- package/dist/adapters/codex.js +196 -0
- package/dist/adapters/index.js +16 -5
- package/dist/adapters/json-adapter.js +135 -0
- package/dist/adapters/json-agents.js +88 -0
- package/dist/adapters/types.js +4 -4
- package/dist/commands/account.js +49 -0
- package/dist/commands/catalog.js +214 -0
- package/dist/commands/doctor.js +39 -28
- package/dist/commands/init.js +69 -35
- package/dist/commands/login.js +31 -21
- package/dist/commands/remove.js +40 -5
- package/dist/commands/skill.js +42 -0
- package/dist/commands/update.js +17 -11
- package/dist/index.js +109 -29
- package/dist/lib/api.js +92 -15
- package/dist/lib/json-file.js +7 -2
- package/dist/lib/paths.js +41 -0
- package/dist/lib/session.js +49 -0
- package/dist/lib/skill-file.js +26 -0
- package/dist/lib/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Connect your coding agent to [noslopUI](https://noslopui.com) — hand-crafted U
|
|
|
6
6
|
npx noslopui@latest init
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
That one command finds your
|
|
9
|
+
That one command finds the agents on your machine, signs you in once through the browser, adds the MCP server to each agent's **user-level** config, and installs the workflow skill. Restart your agents and ask one to build something.
|
|
10
10
|
|
|
11
11
|
## Why
|
|
12
12
|
|
|
@@ -14,46 +14,86 @@ Ask any agent for a landing page and you get the same page: gradient headline, p
|
|
|
14
14
|
|
|
15
15
|
The MCP server is what your agent calls. The skill is what makes it think to call it at all when you say "build me a website" — so `init` installs both, and you can skip the skill with `--no-skill`.
|
|
16
16
|
|
|
17
|
+
## Supported agents
|
|
18
|
+
|
|
19
|
+
| Agent | `--agent` | Config it writes | Skill it installs |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| Claude Code | `claude-code` | `~/.claude.json` | `~/.claude/skills/noslopui/` |
|
|
22
|
+
| Codex | `codex` | `~/.codex/config.toml` | `~/.agents/skills/noslopui/` |
|
|
23
|
+
| Cursor | `cursor` | `~/.cursor/mcp.json` | `~/.agents/skills/noslopui/` |
|
|
24
|
+
| VS Code (Copilot) | `vscode` | your user `mcp.json` ¹ | `~/.agents/skills/noslopui/` |
|
|
25
|
+
| Devin Desktop | `devin` | `~/.config/devin/mcp_config.json` ² | `~/.codeium/windsurf/skills/noslopui/` |
|
|
26
|
+
| Windsurf (Cascade) | `windsurf` | `~/.codeium/windsurf/mcp_config.json` | `~/.codeium/windsurf/skills/noslopui/` |
|
|
27
|
+
| OpenCode | `opencode` | `~/.config/opencode/opencode.json` | `~/.agents/skills/noslopui/` |
|
|
28
|
+
| Antigravity | `antigravity` | `~/.gemini/config/mcp_config.json` | `~/.gemini/config/skills/noslopui/` |
|
|
29
|
+
|
|
30
|
+
¹ `%APPDATA%\Code\User\mcp.json` on Windows, `~/Library/Application Support/Code/User/mcp.json` on macOS, `~/.config/Code/User/mcp.json` on Linux.
|
|
31
|
+
² `%APPDATA%\devin\mcp_config.json` on Windows. Windsurf became Devin Desktop in June 2026: its default agent (Devin Local) reads this file, the older Cascade agent reads the Windsurf one, so they're set up separately.
|
|
32
|
+
|
|
33
|
+
Every path and field comes from that agent's own documentation. Several agents read the same skills folder, so one copy there serves all of them.
|
|
34
|
+
|
|
35
|
+
Anything else that speaks MCP over HTTP can be connected by hand — add `https://noslopui.com/api/mcp` and sign in when it asks. Steps for each client: [noslopui.com/mcp](https://noslopui.com/mcp).
|
|
36
|
+
|
|
17
37
|
## Commands
|
|
18
38
|
|
|
39
|
+
**Set up**
|
|
40
|
+
|
|
19
41
|
| | |
|
|
20
42
|
|---|---|
|
|
21
|
-
| `npx noslopui init` | Set up
|
|
43
|
+
| `npx noslopui init` | Set up your agents: one sign-in, then the server and the skill for each |
|
|
22
44
|
| `npx noslopui doctor` | Check what's set up, and say what to do about anything that isn't |
|
|
23
|
-
| `npx noslopui login` | Get a fresh key for an agent that's already set up |
|
|
24
45
|
| `npx noslopui update` | Re-download the skill |
|
|
46
|
+
| `npx noslopui skill` | Install only the skill, for an agent you connected by hand (`--agent <id>`) |
|
|
25
47
|
| `npx noslopui remove` | Take the noslopUI server back out |
|
|
26
48
|
|
|
27
|
-
|
|
49
|
+
**Account**
|
|
28
50
|
|
|
29
|
-
|
|
51
|
+
| | |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `npx noslopui login` | Sign this CLI in, for the catalog commands below |
|
|
54
|
+
| `npx noslopui login --agent <id>` | A fresh key for an agent that's already set up |
|
|
55
|
+
| `npx noslopui whoami` | The account and plan this CLI is signed in to |
|
|
56
|
+
| `npx noslopui logout` | Forget this CLI's sign-in |
|
|
57
|
+
|
|
58
|
+
**Catalog**
|
|
59
|
+
|
|
60
|
+
| | |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `npx noslopui search <query>` | Search UI blocks and Motion Lab effects (`--section ui-blocks\|motion-lab`, `--tag style:editorial`, `--limit`) |
|
|
63
|
+
| `npx noslopui get <id>` | Print a component's code, or `--write src/Hero.tsx` (`--format tsx\|html\|vue`) |
|
|
64
|
+
| `npx noslopui prompt <id>` | Print a component's AI-prompt version |
|
|
65
|
+
| `npx noslopui design-systems [query]` | Search design systems (`--tag theme:dark`) |
|
|
66
|
+
| `npx noslopui design-system <id>` | Print a DESIGN.md, or `--write DESIGN.md` |
|
|
67
|
+
| `npx noslopui collections` | Your saved collections |
|
|
68
|
+
| `npx noslopui collection <name>` | What's in one |
|
|
30
69
|
|
|
31
|
-
|
|
70
|
+
The catalog commands call the same MCP tools your agent does, with the same access: search and collections are free on every account; code, prompts and DESIGN.md files need a paid plan or the account's free trial. Output is only the thing you asked for, so `get <id> > Hero.tsx` works; `--json` gives the raw result.
|
|
32
71
|
|
|
33
|
-
|
|
72
|
+
Options: `--agent <id>` (repeatable), `--no-skill`, `--no-browser`, `--key <key>`, `--json`, `--force`, `-y/--yes`.
|
|
34
73
|
|
|
35
74
|
## What it does to your machine
|
|
36
75
|
|
|
37
|
-
It edits your
|
|
76
|
+
It edits your agents' user-level config, and nothing else:
|
|
38
77
|
|
|
39
|
-
- Adds one entry, `noslopui`,
|
|
40
|
-
- **Copies the file first.** The backup
|
|
41
|
-
- **Never touches another server's entry**, or
|
|
78
|
+
- Adds one entry, `noslopui`, to each agent you choose, in the file listed above.
|
|
79
|
+
- **Copies the file first.** The backup sits next to the original as `<file>.noslopui-backup-<timestamp>`, and its path is printed.
|
|
80
|
+
- **Never touches another server's entry**, or anything else in the file. Codex's `config.toml` is edited table by table: every line outside `[mcp_servers.noslopui]` is left byte for byte as it was.
|
|
42
81
|
- **Never writes to a project config.** A key in a project file is a key that gets committed.
|
|
43
|
-
- **Stops if the config doesn't parse
|
|
44
|
-
- Writes the skill to
|
|
82
|
+
- **Stops if the config doesn't parse**, or if the `noslopui` entry is written in a form it doesn't recognise. It will not overwrite a file it couldn't read — that file is the only copy of your setup.
|
|
83
|
+
- Writes the skill to the folders listed above.
|
|
84
|
+
- Saves the CLI's own sign-in in `~/.config/noslopui/credentials.json` (`%APPDATA%\noslopui\` on Windows), readable only by you.
|
|
45
85
|
|
|
46
|
-
`remove` takes the entry back out and leaves everything else, including the skill unless you pass `--skill
|
|
86
|
+
`remove` takes the entry back out and leaves everything else, including the skill unless you pass `--skill` — and even then it keeps a skill folder another configured agent still reads. It does **not** revoke your key: the key belongs to your account, not to this machine. Revoke it at [noslopui.com/account](https://noslopui.com/account?tab=mcp).
|
|
47
87
|
|
|
48
88
|
### If Claude Code is running
|
|
49
89
|
|
|
50
90
|
`~/.claude.json` is also Claude Code's own state file, and a running Claude Code rewrites it from memory. Quit it before running `init`, or the entry can be overwritten seconds later. `doctor` re-reads the file rather than trusting the write, so it will tell you if that happened.
|
|
51
91
|
|
|
52
|
-
## Your
|
|
92
|
+
## Your keys
|
|
53
93
|
|
|
54
|
-
`init` issues one key per agent, named after the agent and this machine, so you can tell them apart and revoke one without breaking the others.
|
|
94
|
+
`init` issues one key per agent, named after the agent and this machine, so you can tell them apart on the account page and revoke one without breaking the others — plus one for the CLI itself if it isn't signed in yet. Keys are written to each agent's config and nowhere else; this tool never prints one in full, and never sends it anywhere except noslopui.com.
|
|
55
95
|
|
|
56
|
-
`--key <key>` skips the browser and uses a key you already made on the account page, for CI or a machine with no browser.
|
|
96
|
+
`--key <key>` skips the browser and uses a key you already made on the account page, for CI or a machine with no browser. `NOSLOPUI_API_KEY` does the same for the catalog commands.
|
|
57
97
|
|
|
58
98
|
## The skill
|
|
59
99
|
|
|
@@ -72,7 +112,7 @@ npm run typecheck
|
|
|
72
112
|
|
|
73
113
|
`NOSLOPUI_ORIGIN` points the CLI at a different server, which is how the flow is exercised against a local one.
|
|
74
114
|
|
|
75
|
-
Adding an agent is
|
|
115
|
+
Adding an agent is its facts in `src/adapters/json-agents.ts` (or its own module, if its config isn't JSON), one line in the registry, the same id in the server's `CLI_AGENTS`, and a row in `test/agents.test.ts`.
|
|
76
116
|
|
|
77
117
|
## Licence
|
|
78
118
|
|
package/SKILL.md
CHANGED
|
@@ -11,34 +11,39 @@ 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
|
|
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. **
|
|
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
|
-
Setup, if
|
|
21
|
-
`noslopui
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
starts.
|
|
21
|
+
Setup, if the noslopUI tools aren't available: the user runs
|
|
22
|
+
`npx noslopui@latest init`, or adds `https://noslopui.com/api/mcp` in their
|
|
23
|
+
agent and signs in when it asks (steps for each agent: `noslopui.com/mcp`).
|
|
24
|
+
Every call runs as the user's account. Search, metadata and collections are
|
|
25
|
+
free on every account. Code, prompts and DESIGN.md files need a paid plan or
|
|
26
|
+
the account's free 3-day trial, which the first such call starts.
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
29
30
|
## Step 0 — Which path?
|
|
30
31
|
|
|
31
|
-
**Call `list_collections` first.** It's free on every account.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
42
47
|
|
|
43
48
|
---
|
|
44
49
|
|
|
@@ -68,6 +73,17 @@ Do this on both paths, before choosing a system. Two groups of things.
|
|
|
68
73
|
No brand at all → say so plainly and use the design system's own palette. Don't
|
|
69
74
|
invent a logo.
|
|
70
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
|
+
|
|
71
87
|
If the user already stated all this, don't re-ask — restate it in one line and
|
|
72
88
|
move on.
|
|
73
89
|
|
|
@@ -76,8 +92,9 @@ move on.
|
|
|
76
92
|
## Path A — the user curated a collection
|
|
77
93
|
|
|
78
94
|
`get_collection({ listId })` (or `name`). Every item carries a `kind` —
|
|
79
|
-
`design_systems` or `
|
|
80
|
-
`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.
|
|
81
98
|
|
|
82
99
|
1. **The collection's design system wins.** One in it → that is the system, no
|
|
83
100
|
searching; `get_design_system_file` for its DESIGN.md. More than one → ask
|
|
@@ -87,8 +104,10 @@ move on.
|
|
|
87
104
|
something that covers a section, use it. Always. **Never substitute a
|
|
88
105
|
catalog component for one they explicitly saved**, even if you found
|
|
89
106
|
something you think is better — that was their call, not an oversight.
|
|
90
|
-
3. **Fill the gaps with `search_components
|
|
91
|
-
fit, and restyle what you find to
|
|
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.
|
|
92
111
|
4. **Report what you filled in.** "Hero, pricing and footer from your
|
|
93
112
|
collection; you had no testimonial section saved, so I pulled one from the
|
|
94
113
|
catalog and restyled it." They should never have to diff the result to find
|
|
@@ -111,12 +130,27 @@ Then filter with `tags` (ANDed) and/or a free-text `query` built from the tone
|
|
|
111
130
|
words plus purpose:
|
|
112
131
|
|
|
113
132
|
```
|
|
114
|
-
search_design_systems({ tags: ["
|
|
115
|
-
search_design_systems({
|
|
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"] })
|
|
116
136
|
```
|
|
117
137
|
|
|
118
|
-
|
|
119
|
-
|
|
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.
|
|
120
154
|
|
|
121
155
|
Every result comes back with its tags, its token summary (colours, fonts,
|
|
122
156
|
radius) and a `previewUrl`. **Present the 2-3 best matches to the user with
|
|
@@ -139,11 +173,32 @@ work.
|
|
|
139
173
|
|
|
140
174
|
### B2. Find components on fit
|
|
141
175
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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.
|
|
147
202
|
|
|
148
203
|
Then `get_component_code` for the ones you'll use.
|
|
149
204
|
|
|
@@ -168,6 +223,13 @@ result *theirs* rather than a nice demo:
|
|
|
168
223
|
Body text stays the system's text face unless they insist.
|
|
169
224
|
- Their photography replaces placeholder imagery at the same crops and ratios.
|
|
170
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
|
+
|
|
171
233
|
**Substitute values, keep the rules.** The system's spacing scale, radius
|
|
172
234
|
discipline, contrast requirements and "one accent moment per screen" all still
|
|
173
235
|
apply after the swap — that is the point of a system: it survives a brand
|
|
@@ -200,7 +262,9 @@ exists to avoid — catch them here rather than shipping them:
|
|
|
200
262
|
- **Spacing** — compose from the DESIGN.md's spacing scale; don't invent
|
|
201
263
|
in-between values. Prefer more whitespace over a denser grid.
|
|
202
264
|
- **Motion** — only where it communicates a real state change; the DESIGN.md's
|
|
203
|
-
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.
|
|
204
268
|
- **Named slop-tells to actively reject**: gradient text, purple-on-purple
|
|
205
269
|
palettes, bounce/spring easing on everything, every layout centered with no
|
|
206
270
|
asymmetry, decorative-only animation.
|
|
@@ -208,8 +272,8 @@ exists to avoid — catch them here rather than shipping them:
|
|
|
208
272
|
## Step 5 — Point back to the catalog for variations
|
|
209
273
|
|
|
210
274
|
If the result doesn't land, don't re-prompt the same component blind. Point the
|
|
211
|
-
user at `noslopui.com/explore` (
|
|
212
|
-
|
|
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
|
|
213
277
|
Collections tab on `noslopui.com/account` to save the ones they like — which
|
|
214
278
|
turns the next build into Path A. Then repeat from Step 3 with whatever they
|
|
215
279
|
pick.
|
|
@@ -222,18 +286,19 @@ pick.
|
|
|
222
286
|
list_collections({}) -> { collections: [{ id, name, itemCount }] }
|
|
223
287
|
get_collection({ listId?, name? }) -> { collection, designSystems: [{id,name}], items: [{ id, name, kind, ... }], guidance }
|
|
224
288
|
|
|
225
|
-
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 }
|
|
226
290
|
get_design_system({ id }) -> { id, name, tokens, previewUrl, detailUrl }
|
|
227
291
|
get_design_system_file({ id }) -> { id, name, designMd } [gated: paid or trial]
|
|
228
292
|
|
|
229
|
-
search_components({ query?, section?,
|
|
293
|
+
search_components({ query?, section?, tags?, tag?, limit? }) -> { results: [{ id, name, kind, tags, url }] }
|
|
230
294
|
get_component({ id }) -> { id, name, dependencies, builtWithDesignSystem, previewUrl, detailUrl }
|
|
295
|
+
motion_lab items add { usage, props, colorVars, libraries }
|
|
231
296
|
get_component_code({ id }) -> { id, name, snippets: [{ language, code }] } [gated: paid or trial]
|
|
232
297
|
get_component_prompt({ id }) -> { id, name, promptText } [gated: paid or trial]
|
|
233
298
|
```
|
|
234
299
|
|
|
235
|
-
The
|
|
236
|
-
|
|
300
|
+
The account rides on the connection (a key or a sign-in, sent as the
|
|
301
|
+
`Authorization` header), so no tool takes a credential argument. Every search, metadata and collection tool is free on
|
|
237
302
|
every account; `get_design_system_file`, `get_component_code` and
|
|
238
303
|
`get_component_prompt` need the account on a paid plan or inside its free
|
|
239
304
|
3-day trial.
|
|
@@ -17,130 +17,30 @@
|
|
|
17
17
|
// is open while this runs, it can overwrite our entry when it next saves.
|
|
18
18
|
// That's why install tells the user to quit Claude Code first, and why
|
|
19
19
|
// `doctor` re-checks rather than trusting that the write stuck.
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
export class ClaudeCodeAdapter {
|
|
44
|
-
id = 'claude-code';
|
|
45
|
-
label = 'Claude Code';
|
|
46
|
-
configPath() {
|
|
47
|
-
return join(homedir(), '.claude.json');
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { JsonConfigAdapter, SERVER_NAME } from './json-adapter.js';
|
|
22
|
+
import { home } from '../lib/paths.js';
|
|
23
|
+
import { installSkillAt } from '../lib/skill-file.js';
|
|
24
|
+
export { SERVER_NAME };
|
|
25
|
+
export class ClaudeCodeAdapter extends JsonConfigAdapter {
|
|
26
|
+
constructor() {
|
|
27
|
+
super({
|
|
28
|
+
id: 'claude-code',
|
|
29
|
+
label: 'Claude Code',
|
|
30
|
+
configPath: () => join(home(), '.claude.json'),
|
|
31
|
+
container: 'mcpServers',
|
|
32
|
+
urlField: 'url',
|
|
33
|
+
fixed: { type: 'http' },
|
|
34
|
+
detectPaths: () => [join(home(), '.claude.json'), join(home(), '.claude')],
|
|
35
|
+
detectBinaries: ['claude'],
|
|
36
|
+
skillPath: () => join(home(), '.claude', 'skills', 'noslopui', 'SKILL.md'),
|
|
37
|
+
readBackHint: 'If Claude Code is running, quit it and run this again.',
|
|
38
|
+
});
|
|
48
39
|
}
|
|
49
40
|
skillPath() {
|
|
50
|
-
return join(
|
|
51
|
-
}
|
|
52
|
-
async detect() {
|
|
53
|
-
try {
|
|
54
|
-
return onPath('claude') || existsSync(this.configPath()) || existsSync(join(homedir(), '.claude'));
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
async install(entry) {
|
|
61
|
-
const path = this.configPath();
|
|
62
|
-
const config = readJsonFile(path) ?? {};
|
|
63
|
-
const backupPath = backupFile(path);
|
|
64
|
-
const servers = (typeof config.mcpServers === 'object' && config.mcpServers !== null && !Array.isArray(config.mcpServers)
|
|
65
|
-
? config.mcpServers
|
|
66
|
-
: {});
|
|
67
|
-
const replacedExisting = SERVER_NAME in servers;
|
|
68
|
-
// Spread the existing entry so anything Claude Code (or the user) added to
|
|
69
|
-
// it that we don't know about survives the update.
|
|
70
|
-
const existing = (typeof servers[SERVER_NAME] === 'object' && servers[SERVER_NAME] !== null ? servers[SERVER_NAME] : {});
|
|
71
|
-
const existingHeaders = (typeof existing.headers === 'object' && existing.headers !== null ? existing.headers : {});
|
|
72
|
-
servers[SERVER_NAME] = {
|
|
73
|
-
...existing,
|
|
74
|
-
type: 'http',
|
|
75
|
-
url: entry.url,
|
|
76
|
-
headers: { ...existingHeaders, Authorization: `Bearer ${entry.key}` },
|
|
77
|
-
};
|
|
78
|
-
config.mcpServers = servers;
|
|
79
|
-
writeJsonFile(path, config, { indent: existsSync(path) ? detectIndent(path) : 2 });
|
|
80
|
-
// Read back: this file has another writer, so a successful write is not
|
|
81
|
-
// the same as a config that says what we think it says.
|
|
82
|
-
const after = await this.status();
|
|
83
|
-
if (!after.installed || after.url !== entry.url || !after.hasKey) {
|
|
84
|
-
throw new Error(`The entry was written to ${path} but reading it back didn't show it. If Claude Code is running, quit it and run this again.`);
|
|
85
|
-
}
|
|
86
|
-
return { configPath: path, backupPath, method: 'config-file', replacedExisting };
|
|
87
|
-
}
|
|
88
|
-
async status() {
|
|
89
|
-
const path = this.configPath();
|
|
90
|
-
const config = readJsonFile(path);
|
|
91
|
-
if (!config)
|
|
92
|
-
return { installed: false, configPath: path, url: null, hasKey: false };
|
|
93
|
-
const servers = config.mcpServers;
|
|
94
|
-
const entry = servers?.[SERVER_NAME];
|
|
95
|
-
if (!entry)
|
|
96
|
-
return { installed: false, configPath: path, url: null, hasKey: false };
|
|
97
|
-
const url = typeof entry.url === 'string' ? entry.url : null;
|
|
98
|
-
const headers = (entry.headers ?? {});
|
|
99
|
-
const auth = typeof headers.Authorization === 'string' ? headers.Authorization : '';
|
|
100
|
-
const hasKey = /^Bearer\s+\S+/i.test(auth);
|
|
101
|
-
let problem;
|
|
102
|
-
if (!url)
|
|
103
|
-
problem = 'The entry has no url.';
|
|
104
|
-
else if (!hasKey)
|
|
105
|
-
problem = 'The entry has no Authorization header, so gated tools will be refused.';
|
|
106
|
-
else if (entry.type !== 'http')
|
|
107
|
-
problem = `The entry's type is "${String(entry.type)}", expected "http".`;
|
|
108
|
-
return { installed: true, configPath: path, url, hasKey, ...(problem ? { problem } : {}) };
|
|
109
|
-
}
|
|
110
|
-
async uninstall() {
|
|
111
|
-
const path = this.configPath();
|
|
112
|
-
const config = readJsonFile(path);
|
|
113
|
-
if (!config)
|
|
114
|
-
return false;
|
|
115
|
-
const servers = config.mcpServers;
|
|
116
|
-
if (!servers || !(SERVER_NAME in servers))
|
|
117
|
-
return false;
|
|
118
|
-
backupFile(path);
|
|
119
|
-
delete servers[SERVER_NAME];
|
|
120
|
-
writeJsonFile(path, config, { indent: detectIndent(path) });
|
|
121
|
-
return true;
|
|
41
|
+
return join(home(), '.claude', 'skills', 'noslopui', 'SKILL.md');
|
|
122
42
|
}
|
|
123
43
|
async installSkill(markdown) {
|
|
124
|
-
|
|
125
|
-
if (existsSync(path)) {
|
|
126
|
-
try {
|
|
127
|
-
if (readFileSync(path, 'utf8') === markdown)
|
|
128
|
-
return { path, changed: false };
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
// Unreadable but present: fall through and replace it.
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
135
|
-
writeFileSync(path, markdown, 'utf8');
|
|
136
|
-
return { path, changed: true };
|
|
137
|
-
}
|
|
138
|
-
async removeSkill() {
|
|
139
|
-
const path = this.skillPath();
|
|
140
|
-
if (!existsSync(path))
|
|
141
|
-
return false;
|
|
142
|
-
// Only our own folder, and only because we created it.
|
|
143
|
-
rmSync(dirname(path), { recursive: true, force: true });
|
|
144
|
-
return true;
|
|
44
|
+
return installSkillAt(this.skillPath(), markdown);
|
|
145
45
|
}
|
|
146
46
|
}
|