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.
@@ -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 - both are read at startup."));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.59",
3
+ "version": "0.16.60",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {