synthesisui 0.1.14 → 0.1.16

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,13 +57,29 @@ 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`;
65
65
  await writeFile(join(slugDir, "rules.md"), body, "utf8");
66
66
  }
67
+ // 5c. structured philosophy (personal DS) → philosophy.md at the slug root.
68
+ // Narrative guidance (mission, principles, voice, motion doctrine…); the
69
+ // GUIDE points the agent here right after rules.md.
70
+ const philosophy = payload.document.philosophy;
71
+ const sections = philosophy?.sections ?? [];
72
+ if (sections.length > 0 || philosophy?.context) {
73
+ const parts = [`# ${payload.name} - Philosophy`, ""];
74
+ parts.push("> The voice and principles behind this system. Read after rules.md;", "> let it shape every screen. Managed by synthesisui (edit in the studio).", "");
75
+ if (philosophy?.context) {
76
+ parts.push("## What this product is", "", philosophy.context, "");
77
+ }
78
+ for (const s of sections) {
79
+ parts.push(`## ${s.title}`, "", s.body, "");
80
+ }
81
+ await writeFile(join(slugDir, "philosophy.md"), `${parts.join("\n")}\n`, "utf8");
82
+ }
67
83
  // 6. discovery by the agent
68
84
  const claudeMd = await syncClaudeMd(projectRoot);
69
85
  // outcome line
@@ -72,7 +88,7 @@ export async function add(slug, opts) {
72
88
  console.log(`✓ ${payload.name} v${v} installed → _synthesisui/ds/${payload.slug}/`);
73
89
  }
74
90
  else if (prev.version === v) {
75
- console.log(`✓ ${payload.name} v${v} already installed${opts.version == null ? " (latest)" : ""} refreshed`);
91
+ console.log(`✓ ${payload.name} v${v} already installed${opts.version == null ? " (latest)" : ""} - refreshed`);
76
92
  }
77
93
  else if (v > prev.version) {
78
94
  console.log(`↑ ${payload.name} v${prev.version} → v${v} (kept v${prev.version}/ for rollback)`);
@@ -89,6 +105,9 @@ export async function add(slug, opts) {
89
105
  if (rules.length > 0) {
90
106
  console.log(` rules.md → ${rules.length} rule(s) (read these first)`);
91
107
  }
108
+ if (sections.length > 0 || philosophy?.context) {
109
+ console.log(` philosophy.md → ${sections.length} section(s) (read after rules)`);
110
+ }
92
111
  console.log(` CLAUDE.md ${claudeMd.created ? "created" : "updated"} (${claudeMd.count} system(s) installed)`);
93
112
  console.log("");
94
113
  console.log("Next steps:");
@@ -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
  }
@@ -1,7 +1,11 @@
1
1
  import { DEFAULT_CONFIG, writeProjectConfig } from "../config.js";
2
+ import { add } from "./add.js";
2
3
  /**
3
- * Writes `_synthesisui/config.json` — where `synthesisui page` materializes
4
- * pages and which framework to target. Committable; safe to re-run.
4
+ * Bootstraps a project for SynthesisUI: writes `_synthesisui/config.json`
5
+ * (where `page` materializes pages, where components live, which framework to
6
+ * target) and - with `--ds <slug>` - immediately brings that system in, so the
7
+ * project lands with tokens, philosophy, rules and a CLAUDE.md in one step.
8
+ * Committable; safe to re-run.
5
9
  */
6
10
  export async function init(opts) {
7
11
  const root = opts.dir ?? process.cwd();
@@ -9,13 +13,22 @@ export async function init(opts) {
9
13
  const config = {
10
14
  target,
11
15
  pagesDir: opts.pagesDir ?? (target === "next" ? "app" : DEFAULT_CONFIG.pagesDir),
16
+ componentsDir: opts.componentsDir ?? DEFAULT_CONFIG.componentsDir,
12
17
  };
13
18
  await writeProjectConfig(root, config);
14
19
  console.log("✓ wrote _synthesisui/config.json");
15
- console.log(` target: ${config.target}`);
16
- console.log(` pagesDir: ${config.pagesDir}`);
20
+ console.log(` target: ${config.target}`);
21
+ console.log(` pagesDir: ${config.pagesDir}`);
22
+ console.log(` componentsDir: ${config.componentsDir}`);
23
+ // --ds bootstraps the project with a system in one step (tokens + philosophy
24
+ // + rules + CLAUDE.md all arrive via `add`).
25
+ if (opts.ds) {
26
+ console.log("");
27
+ await add(opts.ds, { registry: opts.registry, dir: root });
28
+ return;
29
+ }
17
30
  console.log("");
18
31
  console.log("Next steps:");
19
- console.log(" • synthesisui add <slug> bring a design system in");
32
+ console.log(" • synthesisui add <slug> bring a design system in");
20
33
  console.log(" • synthesisui page <slug> <template> materialize a full page");
21
34
  }
@@ -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. */
@@ -4,9 +4,10 @@ import { readProjectConfig, resolveRegistry } from "../config.js";
4
4
  import { fetchPage } from "../registry.js";
5
5
  /**
6
6
  * Materializes a whole page from a DS template into the project (hybrid
7
- * codegen-first): the server codegens a deterministic file, we write it, and
8
- * the agent refines it in place. The page uses the DS's `.ds-*` classes +
9
- * inline token vars, so it re-vests once `tokens.css` is imported.
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.
10
11
  */
11
12
  export async function page(slug, template, opts) {
12
13
  const base = resolveRegistry(opts.registry);
@@ -17,16 +18,25 @@ export async function page(slug, template, opts) {
17
18
  : config.target;
18
19
  console.log(`→ generating "${template}" from "${slug}" (${target}) …`);
19
20
  const generated = await fetchPage(base, slug, template, target, opts.version);
20
- // --out wins; otherwise <pagesDir>/<filename> (e.g. app/dashboard.tsx).
21
- const relPath = opts.out ?? join(config.pagesDir, generated.filename);
22
- const outPath = join(root, relPath);
23
- await mkdir(dirname(outPath), { recursive: true });
24
- await writeFile(outPath, generated.code, "utf8");
25
- console.log(`✓ wrote ${relPath} (${slug} v${generated.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
+ }
26
36
  console.log("");
27
37
  console.log("Next steps:");
28
38
  console.log(` • ensure the DS is installed: synthesisui add ${slug} (provides tokens.css)`);
29
39
  console.log(` • @import "_synthesisui/ds/${slug}/tokens.css" in your global CSS`);
30
40
  console.log(" • refine the file: wire real data, split into components, swap placeholders");
31
- console.log(` • keep the data-ds="${slug}" wrapper and the ds-* classes (stays on-system)`);
41
+ console.log(` • keep the data-ds="${slug}" wrapper and the ds-* / layout classes (stays on-system)`);
32
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
@@ -39,6 +39,7 @@ export async function writeToken(token, registry) {
39
39
  export const DEFAULT_CONFIG = {
40
40
  target: "next",
41
41
  pagesDir: "app",
42
+ componentsDir: "components",
42
43
  };
43
44
  const projectConfigPath = (root) => join(root, "_synthesisui", "config.json");
44
45
  /** Reads the project config, falling back to defaults when absent/invalid. */
@@ -51,6 +52,9 @@ export async function readProjectConfig(root) {
51
52
  pagesDir: typeof parsed.pagesDir === "string" && parsed.pagesDir
52
53
  ? parsed.pagesDir
53
54
  : DEFAULT_CONFIG.pagesDir,
55
+ componentsDir: typeof parsed.componentsDir === "string" && parsed.componentsDir
56
+ ? parsed.componentsDir
57
+ : DEFAULT_CONFIG.componentsDir,
54
58
  };
55
59
  }
56
60
  catch {
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,7 +94,7 @@ 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)
@@ -109,32 +109,42 @@ This system ships whole-page templates: ${layoutNames.map((n) => `\`${n}\``).joi
109
109
 
110
110
  Materialize one as a real file:
111
111
  \`\`\`bash
112
- synthesisui page ${slug} ${layoutNames[0]} # Next .tsx (default)
113
- synthesisui page ${slug} ${layoutNames[0]} --target general # plain HTML
112
+ synthesisui page ${slug} ${layoutNames[0]} # Next .tsx + .css (default)
113
+ synthesisui page ${slug} ${layoutNames[0]} --target general # single self-contained HTML
114
114
  \`\`\`
115
- It writes a **deterministic scaffold** using this DS's \`.ds-*\` classes + inline token vars. **Refine it
116
- in place** wire real data, split into components, swap the chart/icon/media placeholders — but keep the
117
- \`data-ds="${slug}"\` wrapper and the \`.ds-*\` classes so it stays on-system. Run \`synthesisui init\` once
118
- to set the target (next/general) and the output folder.
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.
119
121
 
120
122
  ---
121
123
  `
122
124
  : "";
123
125
  const hasRules = (payload.rules?.length ?? 0) > 0;
124
- const rulesNote = hasRules
126
+ const philosophy = payload.document.philosophy;
127
+ const hasPhilosophy = (philosophy?.sections?.length ?? 0) > 0 || !!philosophy?.context;
128
+ const readFirst = [
129
+ hasRules
130
+ ? `**Read \`_synthesisui/ds/${slug}/rules.md\` FIRST and obey it above everything else** - it carries this system's accumulated, project-specific rules; on any conflict they win.`
131
+ : "",
132
+ hasPhilosophy
133
+ ? `**Then read \`_synthesisui/ds/${slug}/philosophy.md\`** - the mission, principles, voice and motion doctrine. Let it shape every screen you build.`
134
+ : "",
135
+ ].filter(Boolean);
136
+ const rulesNote = readFirst.length > 0
125
137
  ? `
126
- ## Rules highest authority
138
+ ## Read first - highest authority
127
139
 
128
- **Read \`_synthesisui/ds/${slug}/rules.md\` FIRST and obey it above everything else in this guide.**
129
- It carries this system's accumulated, project-specific rules; on any conflict they win over the
130
- generic guidance below.
140
+ ${readFirst.map((l) => `- ${l}`).join("\n")}
131
141
 
132
142
  ---
133
143
  `
134
144
  : "";
135
145
  return `# Design System: ${name}
136
146
 
137
- > Generated by \`synthesisui add ${slug}\` (v${version}). **Do not edit by hand**
147
+ > Generated by \`synthesisui add ${slug}\` (v${version}). **Do not edit by hand** -
138
148
  > run \`synthesisui add ${slug}\` again to update.
139
149
 
140
150
  ${meta.tagline}
@@ -161,7 +171,7 @@ ${rulesNote}
161
171
  \`\`\`
162
172
  All \`--ds-*\` custom properties and \`.ds-*\` classes only apply inside that scope.
163
173
  Applying \`data-ds="${slug}"\` at the app root (e.g. \`<body>\` or the root layout)
164
- is the simplest choice the whole app then wears the system.
174
+ is the simplest choice - the whole app then wears the system.
165
175
  ${hasAlt
166
176
  ? `
167
177
  3. Light/dark: an ancestor with \`data-scheme="${altScheme}"\` switches the neutral roles to the opposite mode.
@@ -189,16 +199,16 @@ backed by the design system: \`bg-*\`/\`text-*\`/\`border-*\` (semantic colors),
189
199
  ? `, \`bg-series-*\`/\`text-series-*\`/\`fill-series-*\` (data-viz series)`
190
200
  : ""}.
191
201
 
192
- **Prefer these utilities for layout and new composition** they are this project's idiom and read
202
+ **Prefer these utilities for layout and new composition** - they are this project's idiom and read
193
203
  far better than inline \`style\`. Reach for inline \`var(--ds-*)\` only when no utility fits.
194
204
 
195
205
  \`\`\`tsx
196
- // ✅ preferred Tailwind utilities backed by the DS
206
+ // ✅ preferred - Tailwind utilities backed by the DS
197
207
  <main className="bg-canvas text-foreground p-2xl flex flex-col gap-md">
198
208
  <button className="ds-button" data-intent="primary">Save</button>
199
209
  </main>
200
210
 
201
- // ❌ avoid inline styles with raw var() when a utility exists
211
+ // ❌ avoid - inline styles with raw var() when a utility exists
202
212
  <main style={{ background: "var(--ds-color-semantic-canvas)", padding: "var(--ds-spacing-2xl)" }}>
203
213
  \`\`\`
204
214
 
@@ -206,16 +216,16 @@ far better than inline \`style\`. Reach for inline \`var(--ds-*)\` only when no
206
216
  `
207
217
  : ""}
208
218
  This is **v${version}**. The stable entrypoints at \`_synthesisui/ds/${slug}/\` (the
209
- \`tokens.css\`/\`theme.css\` re-exports, plus \`.lock\`) always point at the active version import
210
- those, not the versioned ones. The pinned files for this version ${artifactList},
211
- \`design-system.json\` (canonical source of truth), \`GUIDE.md\` (this file) live in
219
+ \`tokens.css\`/\`theme.css\` re-exports, plus \`.lock\`) always point at the active version - import
220
+ those, not the versioned ones. The pinned files for this version - ${artifactList},
221
+ \`design-system.json\` (canonical source of truth), \`GUIDE.md\` (this file) - live in
212
222
  \`_synthesisui/ds/${slug}/v${version}/\`.
213
223
 
214
224
  ---
215
225
  ${pagesSection}
216
226
  ## Building with the system
217
227
 
218
- **This system is for building real product UI** pages, layouts, dashboards, whole flows.
228
+ **This system is for building real product UI** - pages, layouts, dashboards, whole flows.
219
229
  Compose the \`.ds-*\` recipes (and their parts) together with the DS-backed utilities to assemble
220
230
  actual screens. There is **no "samples only" rule**: build the real app. An
221
231
  \`app/synthesisui-samples/<component>/\` page is a fine *optional* scratch space to eyeball a single
@@ -223,15 +233,15 @@ component, but it is never required.
223
233
 
224
234
  ### Layout & composition
225
235
  The system defines the scale; these are sensible defaults for spending it:
226
- - **Page gutter / container padding:** a large spacing step ${list(Object.keys(foundations.spacing).filter((k) => /xl/.test(k)))}.
236
+ - **Page gutter / container padding:** a large spacing step - ${list(Object.keys(foundations.spacing).filter((k) => /xl/.test(k)))}.
227
237
  - **Section gaps:** \`lg\` (or the nearest large step). **Card/panel padding:** \`md\`.
228
238
  - **Field / tight gaps:** \`2xs\`/\`3xs\`.
229
- - The system imposes no content max-width cap long-form/text columns yourself for readability.
239
+ - The system imposes no content max-width - cap long-form/text columns yourself for readability.
230
240
  ${hasParts
231
241
  ? `
232
242
  ### Multi-part components
233
243
  Components that have **parts** compile to \`.ds-<name>-<part>\` classes you nest yourself; the exact
234
- part classes and their \`data-*\` are listed per component below. Example a table:
244
+ part classes and their \`data-*\` are listed per component below. Example - a table:
235
245
  \`\`\`tsx
236
246
  <table className="ds-table">
237
247
  <thead className="ds-table-head">
@@ -251,27 +261,27 @@ part classes and their \`data-*\` are listed per component below. Example — a
251
261
  `
252
262
  : ""}
253
263
  ### Overlays & portals
254
- Dialogs, menus and toasts are often rendered through a portal at the end of \`<body>\` **outside**
264
+ Dialogs, menus and toasts are often rendered through a portal at the end of \`<body>\` - **outside**
255
265
  your \`data-ds\` scope. Since \`.ds-*\`/\`--ds-*\` only resolve inside the scope, wrap any portalled UI
256
266
  in its own \`<div data-ds="${slug}"${hasAlt ? ` data-scheme="…"` : ""}>\`, or apply \`data-ds\` at the
257
267
  app root so everything (portals included) inherits it. Behavior (open/close, focus trap, positioning,
258
- keyboard) is yours to wire the system ships the **looks**, not the JavaScript.
268
+ keyboard) is yours to wire - the system ships the **looks**, not the JavaScript.
259
269
 
260
- ### Interactive recipes the behavior contract
270
+ ### Interactive recipes - the behavior contract
261
271
  Several recipes are **static surfaces**: they ship the styling for every state, but never any
262
272
  JavaScript. You own the interaction and drive each state by toggling the documented \`data-*\`
263
273
  attributes (listed per component below). The recipe restyles itself; you wire the logic.
264
274
  - **Open / close** (menu, select, modal, tooltip, popover): render the surface, then handle show/hide,
265
275
  outside-click, focus trap, positioning and \`Esc\` yourself (or with a headless lib).
266
276
  - **Selection / active** (tabs, sidebar, pagination): set \`data-active="true"\` on the chosen item from
267
- your own state/router the recipe lifts it onto a surface.
277
+ your own state/router - the recipe lifts it onto a surface.
268
278
  - **On / off** (switch): toggle \`data-state="on"\` on the track **and** its thumb together.
269
- - **Command bar / ⌘K** (if your system ships one): the recipe is only the styled input row wire the
279
+ - **Command bar / ⌘K** (if your system ships one): the recipe is only the styled input row - wire the
270
280
  shortcut, the palette list and filtering yourself.
271
281
  - **Select** (native vs custom): \`.ds-select\` strips native chrome (\`appearance:none\`). On a real
272
282
  \`<select>\`, wrap it and overlay your own chevron; on a custom trigger, nest \`.ds-select-chevron\`.
273
283
 
274
- Pair these with the right ARIA (\`aria-expanded\`, \`role="dialog"\`, \`aria-current\`, …) the system
284
+ Pair these with the right ARIA (\`aria-expanded\`, \`role="dialog"\`, \`aria-current\`, …) - the system
275
285
  styles it, you make it work.
276
286
 
277
287
  ---
@@ -282,11 +292,11 @@ ${hasTailwind
282
292
  - **Styling mechanism:** prefer Tailwind utilities backed by the DS (\`bg-primary\`, \`p-md\`,
283
293
  \`font-display\`, \`font-medium\`, …) for layout and new composition, and reuse the \`.ds-*\` recipes
284
294
  for components the DS already covers. Use inline \`style\` with \`var(--ds-*)\` only as a last resort.
285
- The token names below are the source vocabulary every utility derives from them.`
295
+ The token names below are the source vocabulary - every utility derives from them.`
286
296
  : ""}
287
297
  - **Always use semantic tokens**, never raw values nor primitives directly.
288
298
  Color: \`var(--ds-color-semantic-<role>)\`${hasTailwind ? " (utility: `bg-<role>`/`text-<role>`)" : ""}. The roles are: ${list(semanticRoles)}.
289
- - Primitives (\`--ds-color-<palette>-<step>\`) exist but should **not** be referenced directly
299
+ - Primitives (\`--ds-color-<palette>-<step>\`) exist but should **not** be referenced directly -
290
300
  they feed the semantic roles.${seriesKeys.length > 0
291
301
  ? `\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.`
292
302
  : ""}
@@ -299,7 +309,7 @@ ${hasTailwind
299
309
  - Motion: durations \`--ds-motion-durations-<key>\` (${list(Object.keys(motion.durations))}) and
300
310
  easings \`--ds-motion-easings-<key>\` (${list(Object.keys(motion.easings))}). Use them on
301
311
  \`transition\`/\`animation\` (e.g. \`transition: color var(--ds-motion-durations-fast) var(--ds-motion-easings-standard)\`)
302
- so timing stays on-brand. The DS ships timing tokens, **not** a runtime for entrance/reveal/stagger
312
+ so timing stays on-brand. The DS ships timing tokens, **not** a runtime - for entrance/reveal/stagger
303
313
  pair them with a motion lib (e.g. \`motion\`/Framer) or CSS \`@keyframes\`.
304
314
  - When **creating a new component** the DS does not cover yet: compose it from these semantic
305
315
  tokens to inherit the system's identity; do not invent colors/measures outside the scale.
@@ -319,10 +329,10 @@ ${blockEntries.length
319
329
 
320
330
  ## Engagement blocks (optional)
321
331
 
322
- A small gamification library the AI advisor (\`synthesisui advise\`) can propose same
332
+ A small gamification library the AI advisor (\`synthesisui advise\`) can propose - same
323
333
  \`.ds-<name>\` recipe shape as the components above, token-only so they wear the system. Use them
324
334
  **only where they fit the product** (progress, retention, recognition); they're a library to compose
325
- from, not a default and lean against over-gamifying a serious B2B product. Each is a \`.ds-<name>\`
335
+ from, not a default - and lean against over-gamifying a serious B2B product. Each is a \`.ds-<name>\`
326
336
  class inside the \`[data-ds="${slug}"]\` scope; multi-part ones expose \`.ds-<name>-<part>\`.
327
337
 
328
338
  ${blockLines.join("\n\n")}
package/dist/index.js CHANGED
@@ -7,11 +7,11 @@ import { list } from "./commands/list.js";
7
7
  import { login } from "./commands/login.js";
8
8
  import { page } from "./commands/page.js";
9
9
  import { RegistryError } from "./registry.js";
10
- const HELP = `synthesisui bring SynthesisUI design systems into your project
10
+ const HELP = `synthesisui - bring SynthesisUI design systems into your project
11
11
 
12
12
  Usage:
13
13
  synthesisui login [options] connect the CLI to your account (device-flow)
14
- synthesisui init [options] write _synthesisui/config.json (target + pages dir)
14
+ synthesisui init [options] write _synthesisui/config.json (target, dirs); --ds to bring one in
15
15
  synthesisui list [options] list the published design systems
16
16
  synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
17
17
  synthesisui page <slug> <template> materialize a whole page from a DS template
@@ -22,15 +22,18 @@ Options:
22
22
  --registry <url> registry URL (or env SYNTHESISUI_REGISTRY_URL)
23
23
  --dir <path> consumer project root (default: current directory)
24
24
  --version <n> install a specific version (default: latest)
25
- --ds <slug> target design system for generate (default: the installed one)
25
+ --ds <slug> init: bring this DS in right away · generate: target DS (default: installed)
26
26
  --name <name> preferred component name for generate
27
27
  --target <t> page/init target: next | general (default: next)
28
+ --pages-dir <dir> init: folder for generated pages (default: app)
29
+ --components-dir <dir> init: folder where components live (default: components)
28
30
  --out <path> output path for the generated page (default: <pagesDir>/<file>)
29
31
  -h, --help this help
30
32
 
31
33
  Examples:
32
34
  synthesisui login
33
35
  synthesisui init --target next
36
+ synthesisui init --target next --ds halogen bootstrap + bring a system in
34
37
  synthesisui list
35
38
  synthesisui add halogen
36
39
  synthesisui add halogen --version 3
@@ -81,7 +84,7 @@ async function main() {
81
84
  case "add": {
82
85
  const slug = args[0];
83
86
  if (!slug) {
84
- console.error("error: provide the slug `synthesisui add <slug>`");
87
+ console.error("error: provide the slug - `synthesisui add <slug>`");
85
88
  process.exitCode = 1;
86
89
  return;
87
90
  }
@@ -89,7 +92,7 @@ async function main() {
89
92
  if (typeof flags.version === "string") {
90
93
  version = Number.parseInt(flags.version.replace(/^v/i, ""), 10);
91
94
  if (!Number.isInteger(version) || version < 1) {
92
- console.error(`error: invalid --version "${flags.version}" use an integer ≥ 1`);
95
+ console.error(`error: invalid --version "${flags.version}" - use an integer ≥ 1`);
93
96
  process.exitCode = 1;
94
97
  return;
95
98
  }
@@ -102,14 +105,19 @@ async function main() {
102
105
  break;
103
106
  case "init": {
104
107
  const target = typeof flags.target === "string" ? flags.target : undefined;
105
- await init({ dir, target });
108
+ const pagesDir = typeof flags["pages-dir"] === "string" ? flags["pages-dir"] : undefined;
109
+ const componentsDir = typeof flags["components-dir"] === "string"
110
+ ? flags["components-dir"]
111
+ : undefined;
112
+ const ds = typeof flags.ds === "string" ? flags.ds : undefined;
113
+ await init({ dir, registry, target, pagesDir, componentsDir, ds });
106
114
  break;
107
115
  }
108
116
  case "page": {
109
117
  const slug = args[0];
110
118
  const template = args[1];
111
119
  if (!slug || !template) {
112
- console.error("error: provide slug and template `synthesisui page <slug> <template>`");
120
+ console.error("error: provide slug and template - `synthesisui page <slug> <template>`");
113
121
  process.exitCode = 1;
114
122
  return;
115
123
  }
@@ -117,7 +125,7 @@ async function main() {
117
125
  if (typeof flags.version === "string") {
118
126
  version = Number.parseInt(flags.version.replace(/^v/i, ""), 10);
119
127
  if (!Number.isInteger(version) || version < 1) {
120
- console.error(`error: invalid --version "${flags.version}" use an integer ≥ 1`);
128
+ console.error(`error: invalid --version "${flags.version}" - use an integer ≥ 1`);
121
129
  process.exitCode = 1;
122
130
  return;
123
131
  }
@@ -130,7 +138,7 @@ async function main() {
130
138
  case "advise": {
131
139
  const valueProp = args.join(" ").trim();
132
140
  if (!valueProp) {
133
- console.error('error: describe your product `synthesisui advise "<value proposition>"`');
141
+ console.error('error: describe your product - `synthesisui advise "<value proposition>"`');
134
142
  process.exitCode = 1;
135
143
  return;
136
144
  }
@@ -140,7 +148,7 @@ async function main() {
140
148
  case "generate": {
141
149
  const description = args.join(" ").trim();
142
150
  if (!description) {
143
- console.error('error: describe the component `synthesisui generate "<description>"`');
151
+ console.error('error: describe the component - `synthesisui generate "<description>"`');
144
152
  process.exitCode = 1;
145
153
  return;
146
154
  }
@@ -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.14",
3
+ "version": "0.1.16",
4
4
  "description": "Traz design systems do SynthesisUI para qualquer projeto (materializa em _local/ds/).",
5
5
  "type": "module",
6
6
  "bin": {