synthesisui 0.16.59 → 0.16.60
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/dist/commands/connect.js +25 -1
- package/dist/skill-import.js +220 -0
- package/package.json +1 -1
package/dist/commands/connect.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
1
3
|
import { wireAgent } from "../agent-wiring.js";
|
|
2
4
|
import { syncClaudeMd } from "../claude-md.js";
|
|
3
5
|
import { body, section, snippet } from "../output.js";
|
|
6
|
+
import { IMPORT_SKILL, IMPORT_SKILL_PATH } from "../skill-import.js";
|
|
4
7
|
/**
|
|
5
8
|
* `synthesisui connect` - put the three layers where they actually run.
|
|
6
9
|
*
|
|
@@ -39,10 +42,31 @@ export async function connect(opts) {
|
|
|
39
42
|
: "· .mcp.json already had it"));
|
|
40
43
|
}
|
|
41
44
|
console.log(body("✓ CLAUDE.md rewritten for what is installed"));
|
|
45
|
+
/**
|
|
46
|
+
* The fourth layer, and the one that had no installer at all: the import
|
|
47
|
+
* skill existed only in our own repo, so the only way to get it was to clone
|
|
48
|
+
* the monorepo and copy a folder (dono, 31/07). A skill nobody can install is
|
|
49
|
+
* a skill nobody has.
|
|
50
|
+
*
|
|
51
|
+
* It belongs here because `connect` is already "wire your agent", it is
|
|
52
|
+
* already idempotent, and rerunning it is already the answer to "did
|
|
53
|
+
* something change" - so nobody has to remember a second command.
|
|
54
|
+
*/
|
|
55
|
+
const skillPath = join(root, IMPORT_SKILL_PATH);
|
|
56
|
+
const before = await readFile(skillPath, "utf8").catch(() => null);
|
|
57
|
+
if (before !== IMPORT_SKILL) {
|
|
58
|
+
await mkdir(dirname(skillPath), { recursive: true });
|
|
59
|
+
await writeFile(skillPath, IMPORT_SKILL, "utf8");
|
|
60
|
+
}
|
|
61
|
+
console.log(body(before === IMPORT_SKILL
|
|
62
|
+
? "· /import-design-system already current"
|
|
63
|
+
: before == null
|
|
64
|
+
? "✓ /import-design-system the import, orchestrated"
|
|
65
|
+
: "✓ /import-design-system updated to this CLI's pipeline"));
|
|
42
66
|
// The step that cost a round trip the first time this was tried by hand,
|
|
43
67
|
// and would cost every single person one.
|
|
44
68
|
console.log("");
|
|
45
|
-
console.log(body("Reopen your editor session -
|
|
69
|
+
console.log(body("Reopen your editor session - all of them are read at startup."));
|
|
46
70
|
if (want.mcp) {
|
|
47
71
|
console.log(body('A project MCP server needs approving once; say yes when it asks. Then "/mcp" lists synthesisui.'));
|
|
48
72
|
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE IMPORT SKILL, as the CLI ships it.
|
|
3
|
+
*
|
|
4
|
+
* It lives here rather than as a file copied at build time for one reason: the
|
|
5
|
+
* build is `tsc` and nothing else, so a `.md` would need a copy step that can
|
|
6
|
+
* silently not run. A TypeScript module cannot fail to be packaged.
|
|
7
|
+
*
|
|
8
|
+
* THIS IS THE SOURCE. The copy in our own `.claude/skills/` is what our editor
|
|
9
|
+
* reads, and `skill-import.spec.ts` asserts the two are identical - so drift
|
|
10
|
+
* turns a test red instead of shipping a skill that describes a pipeline the
|
|
11
|
+
* CLI no longer has.
|
|
12
|
+
*/
|
|
13
|
+
export const IMPORT_SKILL = `---
|
|
14
|
+
name: import-design-system
|
|
15
|
+
description: Turn a codebase the user ALREADY has into a SynthesisUI design system. Use when someone points at an existing repo, app or component library and asks to import it, adopt it, bring it in, or "make a design system from this" (e.g. "/import-design-system", "importa o meu packages/ui", "turn this app into a design system"). Drives the full pipeline - census → your reading → import → v2 proposal - and answers the questions arithmetic cannot.
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Import Design System
|
|
19
|
+
|
|
20
|
+
Read a project the user already built and give it back to them as a system that governs it.
|
|
21
|
+
|
|
22
|
+
The census is **arithmetic** and it is not yours to redo. Every colour ranked, every ΔE
|
|
23
|
+
measured, every component crosswalked - the CLI does all of it, deterministically, for free,
|
|
24
|
+
and it will be right. Your job is the part with no closed form: **understanding how this
|
|
25
|
+
project is structured.**
|
|
26
|
+
|
|
27
|
+
## The golden rule
|
|
28
|
+
|
|
29
|
+
**You may judge. You may never invent.**
|
|
30
|
+
|
|
31
|
+
Every hex you name has to be one the census already observed - the endpoint drops the rest
|
|
32
|
+
silently, and a "faithful" import carrying a colour the author never wrote is the one failure
|
|
33
|
+
this whole pipeline exists to prevent. The same discipline applies to everything else you
|
|
34
|
+
report: if you did not read it in their files, do not claim it.
|
|
35
|
+
|
|
36
|
+
When you are unsure, say so in \`concept\` and leave the field out. An absent answer falls back
|
|
37
|
+
to arithmetic, which is honest. A confident wrong answer inverts someone's product.
|
|
38
|
+
|
|
39
|
+
## The pipeline
|
|
40
|
+
|
|
41
|
+
### 0. Check the login FIRST, and stop if there is none
|
|
42
|
+
|
|
43
|
+
\`\`\`
|
|
44
|
+
cat ~/.synthesisui/credentials.json 2>/dev/null || echo "NO TOKEN"
|
|
45
|
+
\`\`\`
|
|
46
|
+
|
|
47
|
+
No token, or a run that later comes back **HTTP 401**, means there is nothing to send to. **Stop
|
|
48
|
+
there.** Do not measure, do not read the project, do not write a reading - say this and wait:
|
|
49
|
+
|
|
50
|
+
\`\`\`
|
|
51
|
+
You need to authenticate first:
|
|
52
|
+
|
|
53
|
+
npx synthesisui login
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
It is a device flow: it opens a browser and needs a person. You cannot complete it, and there
|
|
57
|
+
is no point doing twenty minutes of reading against a session that will refuse the payload at
|
|
58
|
+
the end (dono, 31/07 - a full read ended on a 401 that a first command would have caught).
|
|
59
|
+
|
|
60
|
+
**Read the \`registry\` field in that file, and pass it to EVERY later command.** A token belongs
|
|
61
|
+
to the host that issued it. One minted by \`login --registry http://localhost:3000\` and then
|
|
62
|
+
sent to the default (production) earns a 401 that reads as an expired session - and a session
|
|
63
|
+
cannot expire on a host that never issued it. That is how a full read ended in a dead end
|
|
64
|
+
twice (dono, 31/07).
|
|
65
|
+
|
|
66
|
+
So if the file says \`"registry": "http://localhost:3000"\`, every command in this skill carries
|
|
67
|
+
\`--registry http://localhost:3000\`. If it says production, pass nothing. The CLI now refuses a
|
|
68
|
+
mismatch instead of letting the server reject it, but the point is not to reach that.
|
|
69
|
+
|
|
70
|
+
### 1. Measure
|
|
71
|
+
|
|
72
|
+
\`\`\`
|
|
73
|
+
npx synthesisui import --dry --dir <the folder that holds the design decisions>
|
|
74
|
+
\`\`\`
|
|
75
|
+
|
|
76
|
+
In a monorepo that folder is almost never the repo root. Look for the package every app
|
|
77
|
+
imports from - \`packages/ui\`, \`packages/design-system\`, \`libs/shared-ui\`. The CLI names the
|
|
78
|
+
candidates and ranks them by how many tokens they declare; if it printed a workspace summary,
|
|
79
|
+
read it before choosing. Scoping to the wrong folder measures the wrong thing, and the score
|
|
80
|
+
it reports will be meaninglessly low.
|
|
81
|
+
|
|
82
|
+
This writes \`_synthesisui/census.json\` and **sends nothing**. Read that file.
|
|
83
|
+
|
|
84
|
+
### 2. Read what arithmetic cannot
|
|
85
|
+
|
|
86
|
+
Open the project and answer the questions below. Then add a \`reading\` object to
|
|
87
|
+
\`_synthesisui/census.json\`:
|
|
88
|
+
|
|
89
|
+
\`\`\`json
|
|
90
|
+
"reading": {
|
|
91
|
+
"themes": { "default": "dark", "has": ["dark"] },
|
|
92
|
+
"roles": { "canvas": "#050505", "foreground": "#f9fafb", "primary": "#4A90E2" },
|
|
93
|
+
"fonts": { "display": "Inter", "body": "Inter" },
|
|
94
|
+
"concept": "one paragraph on what this product is",
|
|
95
|
+
"by": "claude"
|
|
96
|
+
}
|
|
97
|
+
\`\`\`
|
|
98
|
+
|
|
99
|
+
Every field is optional. Leave out what you did not establish.
|
|
100
|
+
|
|
101
|
+
**\`themes\` - which themes the product actually ships, and which one it opens in.**
|
|
102
|
+
|
|
103
|
+
This is the field that matters most and the one only you can fill. The ladder can say which
|
|
104
|
+
*values* exist; it cannot say which themes the product *has*, because that answer lives in a
|
|
105
|
+
\`dark:\` class name, a \`ThemeProvider\`, a toggle component, a \`prefers-color-scheme\` query -
|
|
106
|
+
places no token reader sees. A real library declared nine near-blacks and five light greys in
|
|
107
|
+
one \`:root\` block, and the import handed its near-black dashboard a white page.
|
|
108
|
+
|
|
109
|
+
Go and look:
|
|
110
|
+
|
|
111
|
+
- \`grep -rn "dark:" --include=*.tsx\` - Tailwind's dark variant, invisible to a token reader
|
|
112
|
+
- a theme provider, \`useTheme\`, \`next-themes\`, a \`data-theme\` attribute, a toggle in the shell
|
|
113
|
+
- \`prefers-color-scheme\` in the CSS
|
|
114
|
+
- the root layout: what class or attribute does \`<html>\` or \`<body>\` carry by default
|
|
115
|
+
- failing all of that, open the app's main page component and see what it paints
|
|
116
|
+
|
|
117
|
+
\`has: ["dark"]\` on a dark-only product is the right answer. It is better than filling a light
|
|
118
|
+
slot on their behalf, and it makes adding light a deliberate act they take later.
|
|
119
|
+
|
|
120
|
+
**\`roles\` - which of their values carries which meaning**, when their token names do not say.
|
|
121
|
+
A project naming its neutrals by temperature (\`--color-darkgray-900\`) has told you nothing
|
|
122
|
+
about which one is the page. Read a screen and see.
|
|
123
|
+
|
|
124
|
+
**\`fonts\` and \`concept\`** - the voice, and one paragraph on what this product is. The concept
|
|
125
|
+
feeds every recommendation downstream, so a real one beats a generic one by a wide margin.
|
|
126
|
+
|
|
127
|
+
### 3. Send it
|
|
128
|
+
|
|
129
|
+
\`\`\`
|
|
130
|
+
npx synthesisui import --census _synthesisui/census.json --name "<their name for it>" [--registry <the one from step 0>]
|
|
131
|
+
\`\`\`
|
|
132
|
+
|
|
133
|
+
**One real write, and it creates a system on their account.** Say what you are about to send
|
|
134
|
+
and what it will be called before you run it.
|
|
135
|
+
|
|
136
|
+
The CLI asks for a name unless \`--name\` is passed. It does **not** ask about the scheme when
|
|
137
|
+
your reading already answered - a second question would only be a chance to contradict you.
|
|
138
|
+
|
|
139
|
+
If it refuses, it will tell you which of the two problems it is: no session, an expired one, or
|
|
140
|
+
a token issued by a different host than the one you are sending to. All three are recoverable
|
|
141
|
+
from the census already on disk; none of them need a re-measure.
|
|
142
|
+
|
|
143
|
+
### 4. Tell them exactly what they have, and what is theirs to decide
|
|
144
|
+
|
|
145
|
+
The import publishes **v1** as a faithful baseline and opens a **v2 draft** holding the
|
|
146
|
+
proposal. Say all of this in your own words - do not just print the link:
|
|
147
|
+
|
|
148
|
+
**What landed in v1**, and it is theirs, not ours:
|
|
149
|
+
|
|
150
|
+
- the scheme it opens in, and whether a second one was built
|
|
151
|
+
- their ramps under their own family names
|
|
152
|
+
- their exclusive components as contracts - **axes declared, every style block empty.** Say
|
|
153
|
+
this out loud, because a component list full of blank recipes looks like a failure until
|
|
154
|
+
someone explains it was deliberate. The platform shows them as *Not written yet*, not as a
|
|
155
|
+
zero.
|
|
156
|
+
|
|
157
|
+
**What is waiting in v2**, which only they can approve: near-duplicate colours collapsed,
|
|
158
|
+
unnamed heavy hitters given a place. Point at the link the CLI printed and name the two or
|
|
159
|
+
three biggest items so they know whether it is worth opening now.
|
|
160
|
+
|
|
161
|
+
**What the report said no to.** The notes carry every silent drop made loud - a value not
|
|
162
|
+
found in their code, a token that is not ramp-shaped, a name the document refused. Read them
|
|
163
|
+
and pass on the ones that matter.
|
|
164
|
+
|
|
165
|
+
### 5. Close the loop in their repo
|
|
166
|
+
|
|
167
|
+
The system is only worth something once it governs the code it came from:
|
|
168
|
+
|
|
169
|
+
\`\`\`
|
|
170
|
+
npx synthesisui upgrade <slug> [--registry …] # brings the contract in
|
|
171
|
+
npx synthesisui doctor [paths…] # the contract starts being enforced
|
|
172
|
+
\`\`\`
|
|
173
|
+
|
|
174
|
+
\`doctor\` is where the promise pays off: every design value written by hand, the token their
|
|
175
|
+
own system already has for it, and the components used outside the axes their contract
|
|
176
|
+
declares. In a monorepo, scope it per app - one system, N consumers.
|
|
177
|
+
|
|
178
|
+
## What good looks like
|
|
179
|
+
|
|
180
|
+
The user should end up with a system that reads like theirs and not like ours:
|
|
181
|
+
|
|
182
|
+
- **their names travel** - \`vivid-pink\`, \`darkgray-900\`, whatever they called it
|
|
183
|
+
- **their default face** - a dark product opens dark
|
|
184
|
+
- **no borrowed colour** - if it is in the system, it is in their code
|
|
185
|
+
- **their exclusive components arrive as contracts** - axes declared, every style block empty,
|
|
186
|
+
because a contract says what may vary and a recipe would be us redesigning their component
|
|
187
|
+
- **the duplicates are named out loud** - three components that all read as a badge, a brand
|
|
188
|
+
colour painted 275 times with no token
|
|
189
|
+
|
|
190
|
+
## What to resist
|
|
191
|
+
|
|
192
|
+
- **Do not run \`import\` without \`--dry\` to explore.** It creates a real system on their
|
|
193
|
+
account. Measure with \`--dry\`, read, then send once.
|
|
194
|
+
- **Do not redo the arithmetic.** If you find yourself counting colours, you are in the wrong
|
|
195
|
+
half of the pipeline.
|
|
196
|
+
- **Do not fill \`roles\` with what looks nice.** It is a reading of what their code already
|
|
197
|
+
does, not a redesign. The v2 proposal is where improvement belongs, and the user approves it.
|
|
198
|
+
- **Do not guess \`themes\` to be helpful.** Omitting it falls back to counting rungs, which is
|
|
199
|
+
a guess the product labels as a guess. A wrong confident answer does not get labelled.
|
|
200
|
+
- **Do not apologise for the empty contracts.** They are the design. Explain them.
|
|
201
|
+
|
|
202
|
+
## Not yet true
|
|
203
|
+
|
|
204
|
+
Say so if it comes up, rather than implying otherwise:
|
|
205
|
+
|
|
206
|
+
- **The v2 proposal covers colour only.** Near-duplicate collapse and unnamed heavy hitters.
|
|
207
|
+
It does not yet propose swapping a raw value in a recipe for the token that holds it, nor
|
|
208
|
+
retiring an option nothing passes, nor a categorical chart palette - all measured, none
|
|
209
|
+
offered.
|
|
210
|
+
- **You do not write recipes.** Exclusive components arrive as contracts and stay that way
|
|
211
|
+
until someone writes the look. When that lands, the rule will be **provenance, not
|
|
212
|
+
membership**: a value transcribed out of their file cites the file and line, and is
|
|
213
|
+
legitimate even if the census never saw it, because the census measures less than a project
|
|
214
|
+
contains.
|
|
215
|
+
- **Nothing confirms the import back.** You send, the CLI prints a link, and nobody asks the
|
|
216
|
+
platform whether what arrived is what you meant. Until that exists, tell them to open the
|
|
217
|
+
link and check.
|
|
218
|
+
`;
|
|
219
|
+
/** Where it lands in the consumer's repo. */
|
|
220
|
+
export const IMPORT_SKILL_PATH = ".claude/skills/import-design-system/SKILL.md";
|
package/package.json
CHANGED