synthesisui 0.1.13 → 0.1.15

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/claude-md.js CHANGED
@@ -21,7 +21,7 @@ async function readInstalled(projectRoot) {
21
21
  locks.push({ slug: lock.slug, name: lock.name, version: lock.version });
22
22
  }
23
23
  catch {
24
- // folder without a valid .lock ignore
24
+ // folder without a valid .lock - ignore
25
25
  }
26
26
  }
27
27
  return locks;
@@ -31,7 +31,7 @@ function renderRegion(installed) {
31
31
  return `${START}\n${END}`;
32
32
  }
33
33
  const lines = installed
34
- .map((ds) => `- **${ds.name}** (\`${ds.slug}\`, v${ds.version}) guide: \`_synthesisui/ds/${ds.slug}/v${ds.version}/GUIDE.md\``)
34
+ .map((ds) => `- **${ds.name}** (\`${ds.slug}\`, v${ds.version}) - guide: \`_synthesisui/ds/${ds.slug}/v${ds.version}/GUIDE.md\``)
35
35
  .join("\n");
36
36
  const body = `## Design Systems (via SynthesisUI)
37
37
 
@@ -39,12 +39,12 @@ This project uses design system(s) brought in by the \`synthesisui\` CLI. **When
39
39
  components, read the system's GUIDE.md and follow it:** use only semantic tokens
40
40
  (\`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`, etc.), scope the UI with \`data-ds="<slug>"\`,
41
41
  and reuse the \`.ds-*\` classes. Do not use raw values outside the system's scale. **To review a
42
- component, create an isolated sample page (e.g. \`app/synthesisui-samples/<component>/\`) do not
42
+ component, create an isolated sample page (e.g. \`app/synthesisui-samples/<component>/\`) - do not
43
43
  apply it to real production pages unless asked.**
44
44
 
45
45
  ${lines}
46
46
 
47
- _Block managed by the CLI do not edit by hand; run \`synthesisui add <slug>\` to update._`;
47
+ _Block managed by the CLI - do not edit by hand; run \`synthesisui add <slug>\` to update._`;
48
48
  return `${START}\n${body}\n${END}`;
49
49
  }
50
50
  /**
@@ -41,7 +41,7 @@ export async function add(slug, opts) {
41
41
  // so the consumer's @import path never changes across updates
42
42
  const cssArtifacts = Object.keys(payload.artifacts).filter((f) => f.endsWith(".css"));
43
43
  for (const filename of cssArtifacts) {
44
- await writeFile(join(slugDir, filename), `/* Active version (v${payload.version}). Managed by synthesisui do not edit. */\n` +
44
+ await writeFile(join(slugDir, filename), `/* Active version (v${payload.version}). Managed by synthesisui - do not edit. */\n` +
45
45
  `@import "./v${payload.version}/${filename}";\n`, "utf8");
46
46
  }
47
47
  // 5. root pointer
@@ -57,8 +57,8 @@ export async function add(slug, opts) {
57
57
  // highest authority; the GUIDE tells the agent to read it first)
58
58
  const rules = payload.rules ?? [];
59
59
  if (rules.length > 0) {
60
- const body = `# ${payload.name} Rules\n\n` +
61
- "> Accumulated rules for this design system. **Max authority follow these first.**\n" +
60
+ const body = `# ${payload.name} - Rules\n\n` +
61
+ "> Accumulated rules for this design system. **Max authority - follow these first.**\n" +
62
62
  `> Managed by synthesisui (edit in the studio). ${rules.length} rule(s).\n\n${rules
63
63
  .map((r) => `- ${r}`)
64
64
  .join("\n")}\n`;
@@ -72,7 +72,7 @@ export async function add(slug, opts) {
72
72
  console.log(`✓ ${payload.name} v${v} installed → _synthesisui/ds/${payload.slug}/`);
73
73
  }
74
74
  else if (prev.version === v) {
75
- console.log(`✓ ${payload.name} v${v} already installed${opts.version == null ? " (latest)" : ""} refreshed`);
75
+ console.log(`✓ ${payload.name} v${v} already installed${opts.version == null ? " (latest)" : ""} - refreshed`);
76
76
  }
77
77
  else if (v > prev.version) {
78
78
  console.log(`↑ ${payload.name} v${prev.version} → v${v} (kept v${prev.version}/ for rollback)`);
@@ -4,7 +4,7 @@ import { buildRepoContext } from "../repo-context.js";
4
4
  /**
5
5
  * Asks the hosted advisor for engagement-pattern proposals, grounded in THIS
6
6
  * project (the CLI gathers a compact repo summary) + the value proposition you
7
- * pass. The advisor proposes only it changes nothing in your project.
7
+ * pass. The advisor proposes only - it changes nothing in your project.
8
8
  */
9
9
  export async function advise(valueProp, opts) {
10
10
  const base = resolveRegistry(opts.registry);
@@ -26,6 +26,6 @@ export async function advise(valueProp, opts) {
26
26
  }
27
27
  console.log("");
28
28
  });
29
- console.log(`(${res.usage.inputTokens} in / ${res.usage.outputTokens} out tokens ` +
29
+ console.log(`(${res.usage.inputTokens} in / ${res.usage.outputTokens} out tokens - ` +
30
30
  `proposals only; nothing in your project was changed)`);
31
31
  }
@@ -49,5 +49,5 @@ export async function generate(description, opts) {
49
49
  console.log("Use it:");
50
50
  console.log(` • @import "_synthesisui/ds/${slug}/generated/${res.name}.css" in your CSS`);
51
51
  console.log(` • <div data-ds="${slug}"><div class="ds-${res.name}">…</div></div>`);
52
- console.log(` (${res.usage.inputTokens} in / ${res.usage.outputTokens} out tokens recipe is additive; nothing else changed)`);
52
+ console.log(` (${res.usage.inputTokens} in / ${res.usage.outputTokens} out tokens - recipe is additive; nothing else changed)`);
53
53
  }
@@ -0,0 +1,21 @@
1
+ import { DEFAULT_CONFIG, writeProjectConfig } from "../config.js";
2
+ /**
3
+ * Writes `_synthesisui/config.json` - where `synthesisui page` materializes
4
+ * pages and which framework to target. Committable; safe to re-run.
5
+ */
6
+ export async function init(opts) {
7
+ const root = opts.dir ?? process.cwd();
8
+ const target = opts.target === "general" ? "general" : "next";
9
+ const config = {
10
+ target,
11
+ pagesDir: opts.pagesDir ?? (target === "next" ? "app" : DEFAULT_CONFIG.pagesDir),
12
+ };
13
+ await writeProjectConfig(root, config);
14
+ console.log("✓ wrote _synthesisui/config.json");
15
+ console.log(` target: ${config.target}`);
16
+ console.log(` pagesDir: ${config.pagesDir}`);
17
+ console.log("");
18
+ console.log("Next steps:");
19
+ console.log(" • synthesisui add <slug> bring a design system in");
20
+ console.log(" • synthesisui page <slug> <template> materialize a full page");
21
+ }
@@ -20,7 +20,7 @@ function openBrowser(url) {
20
20
  child.unref();
21
21
  }
22
22
  catch {
23
- // no browser available the user opens it manually
23
+ // no browser available - the user opens it manually
24
24
  }
25
25
  }
26
26
  /** Device authorization (RFC 8628): opens the browser, waits for approval. */
@@ -0,0 +1,42 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { readProjectConfig, resolveRegistry } from "../config.js";
4
+ import { fetchPage } from "../registry.js";
5
+ /**
6
+ * Materializes a whole page from a DS template into the project (hybrid
7
+ * codegen-first): the server codegens deterministic files, we write them, and
8
+ * the agent refines them in place. The page uses the DS's `.ds-*` classes +
9
+ * path classes; the co-located CSS (Next target) carries the responsive media
10
+ * queries + the CSS-only hamburger, so it re-vests once `tokens.css` is in.
11
+ */
12
+ export async function page(slug, template, opts) {
13
+ const base = resolveRegistry(opts.registry);
14
+ const root = opts.dir ?? process.cwd();
15
+ const config = await readProjectConfig(root);
16
+ const target = opts.target === "general" || opts.target === "next"
17
+ ? opts.target
18
+ : config.target;
19
+ console.log(`→ generating "${template}" from "${slug}" (${target}) …`);
20
+ const generated = await fetchPage(base, slug, template, target, opts.version);
21
+ // --out targets the page (1st file); sibling files (e.g. the CSS) land in the
22
+ // same directory. Without --out, everything goes under <pagesDir>.
23
+ const [pageFile, ...siblings] = generated.files;
24
+ const pageRel = opts.out ?? join(config.pagesDir, pageFile.filename);
25
+ const pageDir = dirname(join(root, pageRel));
26
+ await mkdir(pageDir, { recursive: true });
27
+ await writeFile(join(root, pageRel), pageFile.code, "utf8");
28
+ console.log(`✓ wrote ${pageRel} (${slug} v${generated.version})`);
29
+ for (const f of siblings) {
30
+ const rel = opts.out
31
+ ? join(dirname(pageRel), f.filename)
32
+ : join(config.pagesDir, f.filename);
33
+ await writeFile(join(root, rel), f.code, "utf8");
34
+ console.log(`✓ wrote ${rel}`);
35
+ }
36
+ console.log("");
37
+ console.log("Next steps:");
38
+ console.log(` • ensure the DS is installed: synthesisui add ${slug} (provides tokens.css)`);
39
+ console.log(` • @import "_synthesisui/ds/${slug}/tokens.css" in your global CSS`);
40
+ console.log(" • refine the file: wire real data, split into components, swap placeholders");
41
+ console.log(` • keep the data-ds="${slug}" wrapper and the ds-* / layout classes (stays on-system)`);
42
+ }
package/dist/config.js CHANGED
@@ -11,7 +11,7 @@ export function resolveRegistry(flag) {
11
11
  const base = flag || process.env.SYNTHESISUI_REGISTRY_URL || DEFAULT_REGISTRY;
12
12
  return base.replace(/\/+$/, ""); // no trailing slash
13
13
  }
14
- /** Where the device-flow token lives per machine, in the home dir. */
14
+ /** Where the device-flow token lives - per machine, in the home dir. */
15
15
  export const credentialsPath = join(homedir(), ".synthesisui", "credentials.json");
16
16
  /**
17
17
  * Reads the saved token, if any. Optional for now (open gate); the device-flow
@@ -35,3 +35,31 @@ export async function writeToken(token, registry) {
35
35
  mode: 0o600,
36
36
  });
37
37
  }
38
+ /** Project-level config (committed): `<root>/_synthesisui/config.json`. */
39
+ export const DEFAULT_CONFIG = {
40
+ target: "next",
41
+ pagesDir: "app",
42
+ };
43
+ const projectConfigPath = (root) => join(root, "_synthesisui", "config.json");
44
+ /** Reads the project config, falling back to defaults when absent/invalid. */
45
+ export async function readProjectConfig(root) {
46
+ try {
47
+ const raw = await readFile(projectConfigPath(root), "utf8");
48
+ const parsed = JSON.parse(raw);
49
+ return {
50
+ target: parsed.target === "general" ? "general" : "next",
51
+ pagesDir: typeof parsed.pagesDir === "string" && parsed.pagesDir
52
+ ? parsed.pagesDir
53
+ : DEFAULT_CONFIG.pagesDir,
54
+ };
55
+ }
56
+ catch {
57
+ return DEFAULT_CONFIG;
58
+ }
59
+ }
60
+ /** Writes the project config (committable, plain JSON). */
61
+ export async function writeProjectConfig(root, config) {
62
+ const path = projectConfigPath(root);
63
+ await mkdir(dirname(path), { recursive: true });
64
+ await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, "utf8");
65
+ }
package/dist/guide.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const kebab = (v) => v.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
2
2
  const list = (items) => items.length ? items.map((i) => `\`${i}\``).join(", ") : "_(none)_";
3
3
  const dataAttrs = (variants) => Object.entries(variants).map(([axis, opts]) => `data-${kebab(axis)}="${Object.keys(opts).join("|")}"`);
4
- // Famílias genéricas do CSS fallbacks, não webfonts a carregar.
4
+ // Famílias genéricas do CSS - fallbacks, não webfonts a carregar.
5
5
  const GENERIC_FAMILIES = new Set([
6
6
  "sans-serif",
7
7
  "serif",
@@ -35,7 +35,7 @@ function customFontFamilies(families) {
35
35
  function componentEntry(cname, recipe) {
36
36
  const cls = `.ds-${kebab(cname)}`;
37
37
  const axes = dataAttrs(recipe.variants).map((a) => `\`${a}\``);
38
- const variantsText = axes.length ? ` variants: ${axes.join(", ")}` : "";
38
+ const variantsText = axes.length ? ` - variants: ${axes.join(", ")}` : "";
39
39
  const states = Object.keys(recipe.states ?? {});
40
40
  const statesText = states.length ? `\n states: ${list(states)}` : "";
41
41
  const partEntries = Object.entries(recipe.parts ?? {});
@@ -51,7 +51,7 @@ function componentEntry(cname, recipe) {
51
51
  return `- **${cname}** (\`${cls}\`)${variantsText}\n ${recipe.description}${partsText}${statesText}`;
52
52
  }
53
53
  /**
54
- * Builds GUIDE.md instructions *for the agent* on how to build components
54
+ * Builds GUIDE.md - instructions *for the agent* on how to build components
55
55
  * that follow the design system. This is the piece that makes "I create the
56
56
  * components with claude-code" work: the tokens alone are not enough, the agent
57
57
  * needs the rules and the real vocabulary (semantic token names and recipes).
@@ -71,17 +71,17 @@ export function buildGuide(payload) {
71
71
  ? `
72
72
  ## Fonts
73
73
 
74
- This system's type relies on ${list(fontFamilies)} **the DS ships token names, not the
74
+ This system's type relies on ${list(fontFamilies)} - **the DS ships token names, not the
75
75
  fonts themselves.** If you don't load them they fall back to a generic family and the system loses
76
76
  its typographic identity. Load them once (any one approach):
77
77
 
78
- - **Google Fonts** drop in your \`<head>\` (or root layout):
78
+ - **Google Fonts** - drop in your \`<head>\` (or root layout):
79
79
  \`\`\`html
80
80
  <link rel="preconnect" href="https://fonts.googleapis.com" />
81
81
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
82
82
  <link rel="stylesheet" href="${fontsHref}" />
83
83
  \`\`\`
84
- - **Next.js** (\`next/font/google\`), **Fontsource**, or self-hosted \`@font-face\` work too just
84
+ - **Next.js** (\`next/font/google\`), **Fontsource**, or self-hosted \`@font-face\` work too - just
85
85
  register the families above. If a family isn't on Google Fonts, self-host it.
86
86
 
87
87
  ---
@@ -94,16 +94,38 @@ its typographic identity. Load them once (any one approach):
94
94
  const hasTailwind = "theme.css" in payload.artifacts;
95
95
  const hasParts = Object.values(components).some((r) => r.parts && Object.keys(r.parts).length > 0);
96
96
  const componentLines = Object.entries(components).map(([cname, recipe]) => componentEntry(cname, recipe));
97
- // Engagement blocks (gamification library) category apart from core components.
97
+ // Engagement blocks (gamification library) - category apart from core components.
98
98
  const blockEntries = Object.entries(doc.blocks ?? {});
99
99
  const blockLines = blockEntries.map(([bname, recipe]) => componentEntry(bname, recipe));
100
100
  const artifactList = Object.keys(payload.artifacts)
101
101
  .map((f) => `\`${f}\``)
102
102
  .join(", ");
103
+ const layoutNames = Object.keys(doc.layouts ?? {});
104
+ const pagesSection = layoutNames.length > 0
105
+ ? `
106
+ ## Full pages (templates)
107
+
108
+ This system ships whole-page templates: ${layoutNames.map((n) => `\`${n}\``).join(", ")}.
109
+
110
+ Materialize one as a real file:
111
+ \`\`\`bash
112
+ synthesisui page ${slug} ${layoutNames[0]} # Next .tsx + .css (default)
113
+ synthesisui page ${slug} ${layoutNames[0]} --target general # single self-contained HTML
114
+ \`\`\`
115
+ It writes a **deterministic scaffold**: the page uses this DS's \`.ds-*\` recipe classes + layout
116
+ path-classes, paired with a co-located scoped CSS (Next target) that carries the **responsive** media
117
+ queries and the **CSS-only hamburger** - so the page is mobile-ready out of the box. **Refine it in
118
+ place** - wire real data, split into components, swap the chart/icon/media placeholders - but keep the
119
+ \`data-ds="${slug}"\` wrapper and the \`.ds-*\` / layout classes so it stays on-system. Run
120
+ \`synthesisui init\` once to set the target (next/general) and the output folder.
121
+
122
+ ---
123
+ `
124
+ : "";
103
125
  const hasRules = (payload.rules?.length ?? 0) > 0;
104
126
  const rulesNote = hasRules
105
127
  ? `
106
- ## Rules highest authority
128
+ ## Rules - highest authority
107
129
 
108
130
  **Read \`_synthesisui/ds/${slug}/rules.md\` FIRST and obey it above everything else in this guide.**
109
131
  It carries this system's accumulated, project-specific rules; on any conflict they win over the
@@ -114,7 +136,7 @@ generic guidance below.
114
136
  : "";
115
137
  return `# Design System: ${name}
116
138
 
117
- > Generated by \`synthesisui add ${slug}\` (v${version}). **Do not edit by hand**
139
+ > Generated by \`synthesisui add ${slug}\` (v${version}). **Do not edit by hand** -
118
140
  > run \`synthesisui add ${slug}\` again to update.
119
141
 
120
142
  ${meta.tagline}
@@ -141,7 +163,7 @@ ${rulesNote}
141
163
  \`\`\`
142
164
  All \`--ds-*\` custom properties and \`.ds-*\` classes only apply inside that scope.
143
165
  Applying \`data-ds="${slug}"\` at the app root (e.g. \`<body>\` or the root layout)
144
- is the simplest choice the whole app then wears the system.
166
+ is the simplest choice - the whole app then wears the system.
145
167
  ${hasAlt
146
168
  ? `
147
169
  3. Light/dark: an ancestor with \`data-scheme="${altScheme}"\` switches the neutral roles to the opposite mode.
@@ -169,16 +191,16 @@ backed by the design system: \`bg-*\`/\`text-*\`/\`border-*\` (semantic colors),
169
191
  ? `, \`bg-series-*\`/\`text-series-*\`/\`fill-series-*\` (data-viz series)`
170
192
  : ""}.
171
193
 
172
- **Prefer these utilities for layout and new composition** they are this project's idiom and read
194
+ **Prefer these utilities for layout and new composition** - they are this project's idiom and read
173
195
  far better than inline \`style\`. Reach for inline \`var(--ds-*)\` only when no utility fits.
174
196
 
175
197
  \`\`\`tsx
176
- // ✅ preferred Tailwind utilities backed by the DS
198
+ // ✅ preferred - Tailwind utilities backed by the DS
177
199
  <main className="bg-canvas text-foreground p-2xl flex flex-col gap-md">
178
200
  <button className="ds-button" data-intent="primary">Save</button>
179
201
  </main>
180
202
 
181
- // ❌ avoid inline styles with raw var() when a utility exists
203
+ // ❌ avoid - inline styles with raw var() when a utility exists
182
204
  <main style={{ background: "var(--ds-color-semantic-canvas)", padding: "var(--ds-spacing-2xl)" }}>
183
205
  \`\`\`
184
206
 
@@ -186,16 +208,16 @@ far better than inline \`style\`. Reach for inline \`var(--ds-*)\` only when no
186
208
  `
187
209
  : ""}
188
210
  This is **v${version}**. The stable entrypoints at \`_synthesisui/ds/${slug}/\` (the
189
- \`tokens.css\`/\`theme.css\` re-exports, plus \`.lock\`) always point at the active version import
190
- those, not the versioned ones. The pinned files for this version ${artifactList},
191
- \`design-system.json\` (canonical source of truth), \`GUIDE.md\` (this file) live in
211
+ \`tokens.css\`/\`theme.css\` re-exports, plus \`.lock\`) always point at the active version - import
212
+ those, not the versioned ones. The pinned files for this version - ${artifactList},
213
+ \`design-system.json\` (canonical source of truth), \`GUIDE.md\` (this file) - live in
192
214
  \`_synthesisui/ds/${slug}/v${version}/\`.
193
215
 
194
216
  ---
195
-
217
+ ${pagesSection}
196
218
  ## Building with the system
197
219
 
198
- **This system is for building real product UI** pages, layouts, dashboards, whole flows.
220
+ **This system is for building real product UI** - pages, layouts, dashboards, whole flows.
199
221
  Compose the \`.ds-*\` recipes (and their parts) together with the DS-backed utilities to assemble
200
222
  actual screens. There is **no "samples only" rule**: build the real app. An
201
223
  \`app/synthesisui-samples/<component>/\` page is a fine *optional* scratch space to eyeball a single
@@ -203,15 +225,15 @@ component, but it is never required.
203
225
 
204
226
  ### Layout & composition
205
227
  The system defines the scale; these are sensible defaults for spending it:
206
- - **Page gutter / container padding:** a large spacing step ${list(Object.keys(foundations.spacing).filter((k) => /xl/.test(k)))}.
228
+ - **Page gutter / container padding:** a large spacing step - ${list(Object.keys(foundations.spacing).filter((k) => /xl/.test(k)))}.
207
229
  - **Section gaps:** \`lg\` (or the nearest large step). **Card/panel padding:** \`md\`.
208
230
  - **Field / tight gaps:** \`2xs\`/\`3xs\`.
209
- - The system imposes no content max-width cap long-form/text columns yourself for readability.
231
+ - The system imposes no content max-width - cap long-form/text columns yourself for readability.
210
232
  ${hasParts
211
233
  ? `
212
234
  ### Multi-part components
213
235
  Components that have **parts** compile to \`.ds-<name>-<part>\` classes you nest yourself; the exact
214
- part classes and their \`data-*\` are listed per component below. Example a table:
236
+ part classes and their \`data-*\` are listed per component below. Example - a table:
215
237
  \`\`\`tsx
216
238
  <table className="ds-table">
217
239
  <thead className="ds-table-head">
@@ -231,27 +253,27 @@ part classes and their \`data-*\` are listed per component below. Example — a
231
253
  `
232
254
  : ""}
233
255
  ### Overlays & portals
234
- Dialogs, menus and toasts are often rendered through a portal at the end of \`<body>\` **outside**
256
+ Dialogs, menus and toasts are often rendered through a portal at the end of \`<body>\` - **outside**
235
257
  your \`data-ds\` scope. Since \`.ds-*\`/\`--ds-*\` only resolve inside the scope, wrap any portalled UI
236
258
  in its own \`<div data-ds="${slug}"${hasAlt ? ` data-scheme="…"` : ""}>\`, or apply \`data-ds\` at the
237
259
  app root so everything (portals included) inherits it. Behavior (open/close, focus trap, positioning,
238
- keyboard) is yours to wire the system ships the **looks**, not the JavaScript.
260
+ keyboard) is yours to wire - the system ships the **looks**, not the JavaScript.
239
261
 
240
- ### Interactive recipes the behavior contract
262
+ ### Interactive recipes - the behavior contract
241
263
  Several recipes are **static surfaces**: they ship the styling for every state, but never any
242
264
  JavaScript. You own the interaction and drive each state by toggling the documented \`data-*\`
243
265
  attributes (listed per component below). The recipe restyles itself; you wire the logic.
244
266
  - **Open / close** (menu, select, modal, tooltip, popover): render the surface, then handle show/hide,
245
267
  outside-click, focus trap, positioning and \`Esc\` yourself (or with a headless lib).
246
268
  - **Selection / active** (tabs, sidebar, pagination): set \`data-active="true"\` on the chosen item from
247
- your own state/router the recipe lifts it onto a surface.
269
+ your own state/router - the recipe lifts it onto a surface.
248
270
  - **On / off** (switch): toggle \`data-state="on"\` on the track **and** its thumb together.
249
- - **Command bar / ⌘K** (if your system ships one): the recipe is only the styled input row wire the
271
+ - **Command bar / ⌘K** (if your system ships one): the recipe is only the styled input row - wire the
250
272
  shortcut, the palette list and filtering yourself.
251
273
  - **Select** (native vs custom): \`.ds-select\` strips native chrome (\`appearance:none\`). On a real
252
274
  \`<select>\`, wrap it and overlay your own chevron; on a custom trigger, nest \`.ds-select-chevron\`.
253
275
 
254
- Pair these with the right ARIA (\`aria-expanded\`, \`role="dialog"\`, \`aria-current\`, …) the system
276
+ Pair these with the right ARIA (\`aria-expanded\`, \`role="dialog"\`, \`aria-current\`, …) - the system
255
277
  styles it, you make it work.
256
278
 
257
279
  ---
@@ -262,11 +284,11 @@ ${hasTailwind
262
284
  - **Styling mechanism:** prefer Tailwind utilities backed by the DS (\`bg-primary\`, \`p-md\`,
263
285
  \`font-display\`, \`font-medium\`, …) for layout and new composition, and reuse the \`.ds-*\` recipes
264
286
  for components the DS already covers. Use inline \`style\` with \`var(--ds-*)\` only as a last resort.
265
- The token names below are the source vocabulary every utility derives from them.`
287
+ The token names below are the source vocabulary - every utility derives from them.`
266
288
  : ""}
267
289
  - **Always use semantic tokens**, never raw values nor primitives directly.
268
290
  Color: \`var(--ds-color-semantic-<role>)\`${hasTailwind ? " (utility: `bg-<role>`/`text-<role>`)" : ""}. The roles are: ${list(semanticRoles)}.
269
- - Primitives (\`--ds-color-<palette>-<step>\`) exist but should **not** be referenced directly
291
+ - Primitives (\`--ds-color-<palette>-<step>\`) exist but should **not** be referenced directly -
270
292
  they feed the semantic roles.${seriesKeys.length > 0
271
293
  ? `\n- Data-viz → \`var(--ds-color-series-<n>)\`${hasTailwind ? " (utility: `bg-series-<n>`/`text-series-<n>`/`fill-series-<n>`)" : ""}: categorical chart/series colors, ${seriesKeys.length} of them (${list(seriesKeys)}). Use them in order for multi-series charts; they re-paint with the system.`
272
294
  : ""}
@@ -279,7 +301,7 @@ ${hasTailwind
279
301
  - Motion: durations \`--ds-motion-durations-<key>\` (${list(Object.keys(motion.durations))}) and
280
302
  easings \`--ds-motion-easings-<key>\` (${list(Object.keys(motion.easings))}). Use them on
281
303
  \`transition\`/\`animation\` (e.g. \`transition: color var(--ds-motion-durations-fast) var(--ds-motion-easings-standard)\`)
282
- so timing stays on-brand. The DS ships timing tokens, **not** a runtime for entrance/reveal/stagger
304
+ so timing stays on-brand. The DS ships timing tokens, **not** a runtime - for entrance/reveal/stagger
283
305
  pair them with a motion lib (e.g. \`motion\`/Framer) or CSS \`@keyframes\`.
284
306
  - When **creating a new component** the DS does not cover yet: compose it from these semantic
285
307
  tokens to inherit the system's identity; do not invent colors/measures outside the scale.
@@ -299,10 +321,10 @@ ${blockEntries.length
299
321
 
300
322
  ## Engagement blocks (optional)
301
323
 
302
- A small gamification library the AI advisor (\`synthesisui advise\`) can propose same
324
+ A small gamification library the AI advisor (\`synthesisui advise\`) can propose - same
303
325
  \`.ds-<name>\` recipe shape as the components above, token-only so they wear the system. Use them
304
326
  **only where they fit the product** (progress, retention, recognition); they're a library to compose
305
- from, not a default and lean against over-gamifying a serious B2B product. Each is a \`.ds-<name>\`
327
+ from, not a default - and lean against over-gamifying a serious B2B product. Each is a \`.ds-<name>\`
306
328
  class inside the \`[data-ds="${slug}"]\` scope; multi-part ones expose \`.ds-<name>-<part>\`.
307
329
 
308
330
  ${blockLines.join("\n\n")}
package/dist/index.js CHANGED
@@ -2,17 +2,21 @@
2
2
  import { add } from "./commands/add.js";
3
3
  import { advise } from "./commands/advise.js";
4
4
  import { generate } from "./commands/generate.js";
5
+ import { init } from "./commands/init.js";
5
6
  import { list } from "./commands/list.js";
6
7
  import { login } from "./commands/login.js";
8
+ import { page } from "./commands/page.js";
7
9
  import { RegistryError } from "./registry.js";
8
- const HELP = `synthesisui bring SynthesisUI design systems into your project
10
+ const HELP = `synthesisui - bring SynthesisUI design systems into your project
9
11
 
10
12
  Usage:
11
- synthesisui login [options] connect the CLI to your account (device-flow)
12
- synthesisui list [options] list the published design systems
13
- synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
14
- synthesisui advise "<value prop>" engagement-pattern proposals for this project (login required)
15
- synthesisui generate "<desc>" generate a token-only component recipe for your DS (login required)
13
+ synthesisui login [options] connect the CLI to your account (device-flow)
14
+ synthesisui init [options] write _synthesisui/config.json (target + pages dir)
15
+ synthesisui list [options] list the published design systems
16
+ synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
17
+ synthesisui page <slug> <template> materialize a whole page from a DS template
18
+ synthesisui advise "<value prop>" engagement-pattern proposals for this project (login required)
19
+ synthesisui generate "<desc>" generate a token-only component recipe for your DS (login required)
16
20
 
17
21
  Options:
18
22
  --registry <url> registry URL (or env SYNTHESISUI_REGISTRY_URL)
@@ -20,14 +24,18 @@ Options:
20
24
  --version <n> install a specific version (default: latest)
21
25
  --ds <slug> target design system for generate (default: the installed one)
22
26
  --name <name> preferred component name for generate
27
+ --target <t> page/init target: next | general (default: next)
28
+ --out <path> output path for the generated page (default: <pagesDir>/<file>)
23
29
  -h, --help this help
24
30
 
25
31
  Examples:
26
32
  synthesisui login
33
+ synthesisui init --target next
27
34
  synthesisui list
28
35
  synthesisui add halogen
29
36
  synthesisui add halogen --version 3
30
- synthesisui add halogen --registry http://localhost:3737
37
+ synthesisui page halogen dashboard-sidebar
38
+ synthesisui page halogen landing --out app/page.tsx
31
39
  synthesisui advise "habit-building app for tracking personal finances"
32
40
  synthesisui generate "an upgrade banner with a title, message and a primary CTA"
33
41
  `;
@@ -73,7 +81,7 @@ async function main() {
73
81
  case "add": {
74
82
  const slug = args[0];
75
83
  if (!slug) {
76
- console.error("error: provide the slug `synthesisui add <slug>`");
84
+ console.error("error: provide the slug - `synthesisui add <slug>`");
77
85
  process.exitCode = 1;
78
86
  return;
79
87
  }
@@ -81,7 +89,7 @@ async function main() {
81
89
  if (typeof flags.version === "string") {
82
90
  version = Number.parseInt(flags.version.replace(/^v/i, ""), 10);
83
91
  if (!Number.isInteger(version) || version < 1) {
84
- console.error(`error: invalid --version "${flags.version}" use an integer ≥ 1`);
92
+ console.error(`error: invalid --version "${flags.version}" - use an integer ≥ 1`);
85
93
  process.exitCode = 1;
86
94
  return;
87
95
  }
@@ -92,10 +100,37 @@ async function main() {
92
100
  case "login":
93
101
  await login({ registry });
94
102
  break;
103
+ case "init": {
104
+ const target = typeof flags.target === "string" ? flags.target : undefined;
105
+ await init({ dir, target });
106
+ break;
107
+ }
108
+ case "page": {
109
+ const slug = args[0];
110
+ const template = args[1];
111
+ if (!slug || !template) {
112
+ console.error("error: provide slug and template - `synthesisui page <slug> <template>`");
113
+ process.exitCode = 1;
114
+ return;
115
+ }
116
+ let version;
117
+ if (typeof flags.version === "string") {
118
+ version = Number.parseInt(flags.version.replace(/^v/i, ""), 10);
119
+ if (!Number.isInteger(version) || version < 1) {
120
+ console.error(`error: invalid --version "${flags.version}" - use an integer ≥ 1`);
121
+ process.exitCode = 1;
122
+ return;
123
+ }
124
+ }
125
+ const target = typeof flags.target === "string" ? flags.target : undefined;
126
+ const out = typeof flags.out === "string" ? flags.out : undefined;
127
+ await page(slug, template, { registry, dir, out, target, version });
128
+ break;
129
+ }
95
130
  case "advise": {
96
131
  const valueProp = args.join(" ").trim();
97
132
  if (!valueProp) {
98
- console.error('error: describe your product `synthesisui advise "<value proposition>"`');
133
+ console.error('error: describe your product - `synthesisui advise "<value proposition>"`');
99
134
  process.exitCode = 1;
100
135
  return;
101
136
  }
@@ -105,7 +140,7 @@ async function main() {
105
140
  case "generate": {
106
141
  const description = args.join(" ").trim();
107
142
  if (!description) {
108
- console.error('error: describe the component `synthesisui generate "<description>"`');
143
+ console.error('error: describe the component - `synthesisui generate "<description>"`');
109
144
  process.exitCode = 1;
110
145
  return;
111
146
  }
package/dist/registry.js CHANGED
@@ -42,6 +42,27 @@ export async function fetchDesignSystem(base, slug, version) {
42
42
  }
43
43
  return (await res.json());
44
44
  }
45
+ /**
46
+ * Fetches a whole page generated from a DS template (`?page=&target=`). The
47
+ * server codegens it from `document.layouts[<template>]`; the CLI just writes it.
48
+ */
49
+ export async function fetchPage(base, slug, template, target, version) {
50
+ const url = new URL(`${base}/api/registry/ds/${encodeURIComponent(slug)}`);
51
+ url.searchParams.set("page", template);
52
+ url.searchParams.set("target", target);
53
+ if (version != null)
54
+ url.searchParams.set("version", String(version));
55
+ const res = await request(url.toString());
56
+ if (res.status === 404) {
57
+ const body = (await res.json().catch(() => ({})));
58
+ throw new RegistryError(body.message ??
59
+ `No template "${template}" in "${slug}". Run \`synthesisui list\` and check the DS templates.`);
60
+ }
61
+ if (!res.ok) {
62
+ throw new RegistryError(`Registry responded ${res.status} while generating "${template}".`);
63
+ }
64
+ return (await res.json());
65
+ }
45
66
  /**
46
67
  * Calls the hosted advisor (`POST /api/ai/advisor`). Gated + metered server-side:
47
68
  * 401 = not logged in, 429 = daily quota reached. Sends the Bearer token if present.
@@ -1,7 +1,7 @@
1
1
  import { readdir, readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  /**
4
- * Monta um resumo COMPACTO e aterrado do projeto pro advisor barato em tokens
4
+ * Monta um resumo COMPACTO e aterrado do projeto pro advisor - barato em tokens
5
5
  * e suficiente pra propostas específicas: stack (package.json), forma do repo
6
6
  * (árvore nível 1), DS SynthesisUI instalado(s) e o começo do README. Sem dump
7
7
  * de código (custo/ruído): o advisor propõe padrões, não lê implementação.
@@ -52,7 +52,7 @@ async function installedDesignSystems(root) {
52
52
  }
53
53
  }
54
54
  catch {
55
- // nenhum DS instalado tudo bem
55
+ // nenhum DS instalado - tudo bem
56
56
  }
57
57
  return out;
58
58
  }
@@ -72,7 +72,7 @@ export async function buildRepoContext(root) {
72
72
  const pkg = await readJson(join(root, "package.json"));
73
73
  const parts = [];
74
74
  if (pkg) {
75
- parts.push(`Projeto: ${pkg.name ?? "(sem nome)"}${pkg.description ? ` ${pkg.description}` : ""}`);
75
+ parts.push(`Projeto: ${pkg.name ?? "(sem nome)"}${pkg.description ? ` - ${pkg.description}` : ""}`);
76
76
  const deps = [
77
77
  ...Object.keys(pkg.dependencies ?? {}),
78
78
  ...Object.keys(pkg.devDependencies ?? {}),
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Minimal mirror of the registry contract the CLI is standalone and does NOT
2
+ * Minimal mirror of the registry contract - the CLI is standalone and does NOT
3
3
  * import `@synthesisui-hub/ds-contracts` (it only consumes the endpoint JSON).
4
4
  * We type only what the CLI reads to generate GUIDE.md and the .lock.
5
5
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Traz design systems do SynthesisUI para qualquer projeto (materializa em _local/ds/).",
5
5
  "type": "module",
6
6
  "bin": {