synthesisui 0.1.2 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,54 +1,55 @@
1
1
  # synthesisui
2
2
 
3
- CLI para trazer design systems publicados no [SynthesisUI](https://www.synthesisui.com)
4
- para dentro de qualquer projeto. Materializa o sistema em `_synthesisui/ds/<slug>/` e injeta
5
- um bloco gerenciado no `CLAUDE.md` da raiz, de forma que o Claude Code construa
6
- componentes seguindo o design system.
3
+ CLI to bring design systems published on [SynthesisUI](https://www.synthesisui.com)
4
+ into any project. It materializes the system into `_synthesisui/ds/<slug>/` and injects
5
+ a managed block into the root `CLAUDE.md`, so Claude Code builds components following the
6
+ design system.
7
7
 
8
- ## Uso
8
+ ## Usage
9
9
 
10
- Sem instalar nada:
10
+ Without installing anything:
11
11
 
12
12
  ```bash
13
- npx synthesisui login # conecta o CLI à sua conta (device-flow no browser)
14
- npx synthesisui list # lista os design systems disponíveis
15
- npx synthesisui add <slug> # traz um DS para _synthesisui/ds/<slug>/
13
+ npx synthesisui login # connect the CLI to your account (device-flow in the browser)
14
+ npx synthesisui list # list the available design systems
15
+ npx synthesisui add <slug> # bring a DS into _synthesisui/ds/<slug>/
16
16
  ```
17
17
 
18
- Ou instale globalmente:
18
+ Or install globally:
19
19
 
20
20
  ```bash
21
21
  npm install -g synthesisui
22
22
  synthesisui add halogen
23
23
  ```
24
24
 
25
- ### O que o `add` materializa
25
+ ### What `add` materializes
26
26
 
27
- Em `_synthesisui/ds/<slug>/`:
27
+ In `_synthesisui/ds/<slug>/`:
28
28
 
29
- - `design-system.json` — a verdade canônica do design system
30
- - `tokens.css` — CSS custom properties escopadas por `data-ds`
31
- - `GUIDE.md` — instruções para o agente (papéis semânticos, mood, recipes, como adicionar componentes)
32
- - `.lock` — slug + versão pinada (reproduzível)
29
+ - `design-system.json` — the canonical source of truth of the design system
30
+ - `tokens.css` — CSS custom properties scoped by `data-ds`
31
+ - `theme.css` — optional Tailwind v4 `@theme` adapter (use `bg-primary`, `p-md`, backed by the tokens)
32
+ - `GUIDE.md`instructions for the agent (semantic roles, mood, recipes, how to add components)
33
+ - `.lock` — pinned slug + version (reproducible)
33
34
 
34
- E injeta um bloco idempotente `<!-- synthesisui:start/end -->` no `CLAUDE.md` da raiz,
35
- refletindo todos os DSs instalados.
35
+ And it injects an idempotent `<!-- synthesisui:start/end -->` block into the root `CLAUDE.md`,
36
+ reflecting every installed DS.
36
37
 
37
- ## Autenticação
38
+ ## Authentication
38
39
 
39
- `synthesisui login` usa device-flow (RFC 8628): abre o browser, você confirma um código,
40
- e o token é salvo em `~/.synthesisui/credentials.json` (por máquina). Logout = apagar esse arquivo.
40
+ `synthesisui login` uses device-flow (RFC 8628): it opens the browser, you confirm a code,
41
+ and the token is saved to `~/.synthesisui/credentials.json` (per machine). Logout = delete that file.
41
42
 
42
43
  ## Registry
43
44
 
44
- Por padrão aponta para `https://www.synthesisui.com`. Sobrescreva com:
45
+ By default it points to `https://www.synthesisui.com`. Override it with:
45
46
 
46
47
  ```bash
47
48
  synthesisui list --registry http://localhost:3000
48
- # ou
49
+ # or
49
50
  SYNTHESISUI_REGISTRY_URL=http://localhost:3000 synthesisui list
50
51
  ```
51
52
 
52
- ## Licença
53
+ ## License
53
54
 
54
55
  MIT
package/dist/claude-md.js CHANGED
@@ -2,7 +2,7 @@ import { readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  const START = "<!-- synthesisui:start -->";
4
4
  const END = "<!-- synthesisui:end -->";
5
- /** os DSs instalados a partir dos .lock em _synthesisui/ds/<slug>/. */
5
+ /** Reads the installed DSs from the .lock files in _synthesisui/ds/<slug>/. */
6
6
  async function readInstalled(projectRoot) {
7
7
  const dsDir = join(projectRoot, "_synthesisui", "ds");
8
8
  let entries = [];
@@ -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
- // pasta sem .lock válido ignora
24
+ // folder without a valid .lock — ignore
25
25
  }
26
26
  }
27
27
  return locks;
@@ -31,26 +31,26 @@ 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}) — guia: \`_synthesisui/ds/${ds.slug}/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
 
38
- Este projeto usa design system(s) trazido(s) pelo \`synthesisui\` CLI. **Ao criar ou editar
39
- componentes, leia o GUIDE.md do sistema e siga-o:** use apenas tokens semânticos
40
- (\`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`, etc.), escope a UI com \`data-ds="<slug>"\`,
41
- e reaproveite as classes \`.ds-*\`. Não use valores crus fora da escala do sistema. **Para revisar um
42
- componente, crie uma página de amostra isolada (ex.: \`app/synthesisui-samples/<componente>/\`) — não
43
- aplique a páginas reais de produção a menos que seja pedido.**
38
+ This project uses design system(s) brought in by the \`synthesisui\` CLI. **When creating or editing
39
+ components, read the system's GUIDE.md and follow it:** use only semantic tokens
40
+ (\`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`, etc.), scope the UI with \`data-ds="<slug>"\`,
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
43
+ apply it to real production pages unless asked.**
44
44
 
45
45
  ${lines}
46
46
 
47
- _Bloco gerenciado pelo CLI — não edite à mão; rode \`synthesisui add <slug>\` para atualizar._`;
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
  /**
51
- * Regenera o bloco gerenciado no CLAUDE.md da raiz refletindo todos os DSs
52
- * instalados. Idempotente: substitui o trecho entre os marcadores se existir,
53
- * senão cria o arquivo / anexa o bloco. Retorna se o arquivo foi criado.
51
+ * Regenerates the managed block in the root CLAUDE.md reflecting every installed
52
+ * DS. Idempotent: replaces the text between the markers if present, otherwise
53
+ * creates the file / appends the block. Returns whether the file was created.
54
54
  */
55
55
  export async function syncClaudeMd(projectRoot) {
56
56
  const installed = await readInstalled(projectRoot);
@@ -1,28 +1,50 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { syncClaudeMd } from "../claude-md.js";
4
4
  import { resolveRegistry } from "../config.js";
5
5
  import { buildGuide } from "../guide.js";
6
6
  import { fetchDesignSystem } from "../registry.js";
7
+ async function readRootLock(path) {
8
+ try {
9
+ return JSON.parse(await readFile(path, "utf8"));
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
7
15
  /**
8
- * Materializa um DS publicado em `_synthesisui/ds/<slug>/` e atualiza o CLAUDE.md.
16
+ * Materializes a published DS into `_synthesisui/ds/<slug>/v<version>/`, points
17
+ * stable root re-exports (tokens.css/theme.css) and a `.lock` at it, and updates
18
+ * CLAUDE.md. Older version folders are kept for rollback/diff.
9
19
  */
10
20
  export async function add(slug, opts) {
11
21
  const base = resolveRegistry(opts.registry);
12
22
  const projectRoot = opts.dir ?? process.cwd();
13
- console.log(`→ buscando "${slug}" em ${base} …`);
14
- const payload = await fetchDesignSystem(base, slug);
15
- const targetDir = join(projectRoot, "_synthesisui", "ds", payload.slug);
16
- await mkdir(targetDir, { recursive: true });
17
- // 1. artifacts compilados pelo servidor (tokens.css, e futuros theme.css…)
23
+ const label = opts.version != null ? `${slug}@v${opts.version}` : slug;
24
+ console.log(`→ fetching "${label}" from ${base} …`);
25
+ const payload = await fetchDesignSystem(base, slug, opts.version);
26
+ const slugDir = join(projectRoot, "_synthesisui", "ds", payload.slug);
27
+ const versionDir = join(slugDir, `v${payload.version}`);
28
+ const rootLockPath = join(slugDir, ".lock");
29
+ // know what was active before, to report install vs update vs switch
30
+ const prev = await readRootLock(rootLockPath);
31
+ await mkdir(versionDir, { recursive: true });
32
+ // 1. server artifacts (tokens.css, theme.css, …) → pinned version folder
18
33
  for (const [filename, content] of Object.entries(payload.artifacts)) {
19
- await writeFile(join(targetDir, filename), content, "utf8");
34
+ await writeFile(join(versionDir, filename), content, "utf8");
35
+ }
36
+ // 2. canonical source of truth
37
+ await writeFile(join(versionDir, "design-system.json"), `${JSON.stringify(payload.document, null, 2)}\n`, "utf8");
38
+ // 3. guide for the agent (generated client-side from the document)
39
+ await writeFile(join(versionDir, "GUIDE.md"), buildGuide(payload), "utf8");
40
+ // 4. stable root re-exports for each CSS artifact → always the active version,
41
+ // so the consumer's @import path never changes across updates
42
+ const cssArtifacts = Object.keys(payload.artifacts).filter((f) => f.endsWith(".css"));
43
+ for (const filename of cssArtifacts) {
44
+ await writeFile(join(slugDir, filename), `/* Active version (v${payload.version}). Managed by synthesisui — do not edit. */\n` +
45
+ `@import "./v${payload.version}/${filename}";\n`, "utf8");
20
46
  }
21
- // 2. verdade canônica
22
- await writeFile(join(targetDir, "design-system.json"), `${JSON.stringify(payload.document, null, 2)}\n`, "utf8");
23
- // 3. guia para o agente (gerado client-side a partir do document)
24
- await writeFile(join(targetDir, "GUIDE.md"), buildGuide(payload), "utf8");
25
- // 4. lock reproduzível
47
+ // 5. root pointer
26
48
  const lock = {
27
49
  slug: payload.slug,
28
50
  name: payload.name,
@@ -30,21 +52,33 @@ export async function add(slug, opts) {
30
52
  registry: base,
31
53
  fetchedAt: new Date().toISOString(),
32
54
  };
33
- await writeFile(join(targetDir, ".lock"), `${JSON.stringify(lock, null, 2)}\n`, "utf8");
34
- // 5. descoberta pelo agente
55
+ await writeFile(rootLockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
56
+ // 6. discovery by the agent
35
57
  const claudeMd = await syncClaudeMd(projectRoot);
58
+ // outcome line
59
+ const v = payload.version;
60
+ if (!prev) {
61
+ console.log(`✓ ${payload.name} v${v} installed → _synthesisui/ds/${payload.slug}/`);
62
+ }
63
+ else if (prev.version === v) {
64
+ console.log(`✓ ${payload.name} v${v} already installed${opts.version == null ? " (latest)" : ""} — refreshed`);
65
+ }
66
+ else if (v > prev.version) {
67
+ console.log(`↑ ${payload.name} v${prev.version} → v${v} (kept v${prev.version}/ for rollback)`);
68
+ }
69
+ else {
70
+ console.log(`↺ ${payload.name} active version set to v${v} (was v${prev.version})`);
71
+ }
36
72
  const files = [
37
73
  ...Object.keys(payload.artifacts),
38
74
  "design-system.json",
39
75
  "GUIDE.md",
40
- ".lock",
41
76
  ];
42
- console.log(`✓ ${payload.name} v${payload.version} → _synthesisui/ds/${payload.slug}/`);
43
- console.log(` ${files.join(", ")}`);
44
- console.log(` CLAUDE.md ${claudeMd.created ? "criado" : "atualizado"} (${claudeMd.count} sistema(s) instalado(s))`);
77
+ console.log(` v${v}/: ${files.join(", ")}`);
78
+ console.log(` CLAUDE.md ${claudeMd.created ? "created" : "updated"} (${claudeMd.count} system(s) installed)`);
45
79
  console.log("");
46
- console.log("Próximos passos:");
47
- console.log(` • @import "_synthesisui/ds/${payload.slug}/tokens.css" no seu CSS global`);
48
- console.log(` • escope sua UI com data-ds="${payload.slug}"`);
49
- console.log(` • detalhes e regras em _synthesisui/ds/${payload.slug}/GUIDE.md`);
80
+ console.log("Next steps:");
81
+ console.log(` • @import "_synthesisui/ds/${payload.slug}/tokens.css" in your global CSS (stable path)`);
82
+ console.log(` • scope your UI with data-ds="${payload.slug}"`);
83
+ console.log(` • details and rules in _synthesisui/ds/${payload.slug}/v${v}/GUIDE.md`);
50
84
  }
@@ -1,17 +1,17 @@
1
1
  import { resolveRegistry } from "../config.js";
2
2
  import { fetchList } from "../registry.js";
3
- /** Lista os design systems publicados disponíveis no registry. */
3
+ /** Lists the published design systems available in the registry. */
4
4
  export async function list(opts) {
5
5
  const base = resolveRegistry(opts.registry);
6
6
  const systems = await fetchList(base);
7
7
  if (systems.length === 0) {
8
- console.log(`Nenhum design system publicado em ${base}.`);
8
+ console.log(`No design systems published at ${base}.`);
9
9
  return;
10
10
  }
11
- console.log(`Design systems disponíveis (${base}):\n`);
11
+ console.log(`Available design systems (${base}):\n`);
12
12
  const width = Math.max(...systems.map((s) => s.slug.length));
13
13
  for (const s of systems) {
14
14
  console.log(` ${s.slug.padEnd(width)} ${s.name} (v${s.version})`);
15
15
  }
16
- console.log(`\nTraga um com: synthesisui add <slug>`);
16
+ console.log(`\nBring one in with: synthesisui add <slug>`);
17
17
  }
@@ -4,7 +4,7 @@ import { RegistryError } from "../registry.js";
4
4
  const CLIENT_ID = "synthesisui-cli";
5
5
  const GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
6
6
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
7
- /** Tenta abrir o browser no SO; silencioso se não der. */
7
+ /** Tries to open the OS browser; silent if it fails. */
8
8
  function openBrowser(url) {
9
9
  const [cmd, args] = process.platform === "darwin"
10
10
  ? ["open", [url]]
@@ -20,10 +20,10 @@ function openBrowser(url) {
20
20
  child.unref();
21
21
  }
22
22
  catch {
23
- // sem browser disponívelo usuário abre manualmente
23
+ // no browser availablethe user opens it manually
24
24
  }
25
25
  }
26
- /** Device authorization (RFC 8628): abre o browser, espera a aprovação. */
26
+ /** Device authorization (RFC 8628): opens the browser, waits for approval. */
27
27
  export async function login(opts) {
28
28
  const base = resolveRegistry(opts.registry);
29
29
  const codeRes = await fetch(`${base}/api/auth/device/code`, {
@@ -32,20 +32,20 @@ export async function login(opts) {
32
32
  body: JSON.stringify({ client_id: CLIENT_ID }),
33
33
  }).catch(() => null);
34
34
  if (!codeRes || !codeRes.ok) {
35
- throw new RegistryError(`Não consegui iniciar o login em ${base}` +
35
+ throw new RegistryError(`Could not start login at ${base}` +
36
36
  (codeRes
37
37
  ? ` (HTTP ${codeRes.status}).`
38
- : ". Confira a URL e a conexão."));
38
+ : ". Check the URL and your connection."));
39
39
  }
40
40
  const code = (await codeRes.json());
41
- console.log("\nPara conectar o CLI à sua conta:");
42
- console.log(` 1. abra: ${code.verification_uri}`);
43
- console.log(` 2. confirme o código: ${code.user_code}\n`);
44
- console.log("(tentando abrir o navegador…)");
41
+ console.log("\nTo connect the CLI to your account:");
42
+ console.log(` 1. open: ${code.verification_uri}`);
43
+ console.log(` 2. confirm the code: ${code.user_code}\n`);
44
+ console.log("(trying to open the browser…)");
45
45
  openBrowser(code.verification_uri_complete);
46
46
  let interval = (code.interval || 5) * 1000;
47
47
  const deadline = Date.now() + (code.expires_in || 900) * 1000;
48
- process.stdout.write("aguardando aprovação");
48
+ process.stdout.write("waiting for approval");
49
49
  while (Date.now() < deadline) {
50
50
  await sleep(interval);
51
51
  process.stdout.write(".");
@@ -63,7 +63,7 @@ export async function login(opts) {
63
63
  : {};
64
64
  if (tokenRes?.ok && typeof data.access_token === "string") {
65
65
  await writeToken(data.access_token, base);
66
- console.log("\n✓ Login concluído. Token salvo em ~/.synthesisui/credentials.json");
66
+ console.log("\n✓ Login complete. Token saved to ~/.synthesisui/credentials.json");
67
67
  return;
68
68
  }
69
69
  const err = data.error;
@@ -73,7 +73,7 @@ export async function login(opts) {
73
73
  interval += 5000;
74
74
  continue;
75
75
  }
76
- throw new RegistryError(`\nLogin falhou: ${data.error_description ?? err ?? tokenRes?.status ?? "erro desconhecido"}`);
76
+ throw new RegistryError(`\nLogin failed: ${data.error_description ?? err ?? tokenRes?.status ?? "unknown error"}`);
77
77
  }
78
- throw new RegistryError("\nO código expirou antes da aprovação. Rode `synthesisui login` de novo.");
78
+ throw new RegistryError("\nThe code expired before approval. Run `synthesisui login` again.");
79
79
  }
package/dist/config.js CHANGED
@@ -2,20 +2,20 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
4
  /**
5
- * Registry padrão (domínio canônico de produção). Sobrescrevível por
6
- * `--registry <url>` ou `SYNTHESISUI_REGISTRY_URL` (ex.: http://localhost:3000
7
- * em dev).
5
+ * Default registry (canonical production domain). Overridable via
6
+ * `--registry <url>` or `SYNTHESISUI_REGISTRY_URL` (e.g. http://localhost:3000
7
+ * in dev).
8
8
  */
9
9
  export const DEFAULT_REGISTRY = "https://www.synthesisui.com";
10
10
  export function resolveRegistry(flag) {
11
11
  const base = flag || process.env.SYNTHESISUI_REGISTRY_URL || DEFAULT_REGISTRY;
12
- return base.replace(/\/+$/, ""); // sem barra final
12
+ return base.replace(/\/+$/, ""); // no trailing slash
13
13
  }
14
- /** Onde o token do device-flow (passo 3) vive por máquina, na home. */
14
+ /** Where the device-flow token livesper machine, in the home dir. */
15
15
  export const credentialsPath = join(homedir(), ".synthesisui", "credentials.json");
16
16
  /**
17
- * o token salvo, se existir. Hoje opcional (portão aberto); o device-flow
18
- * (passo 3) é quem vai gravá-lo. Mandamos como Bearer quando presente.
17
+ * Reads the saved token, if any. Optional for now (open gate); the device-flow
18
+ * is what writes it. Sent as a Bearer header when present.
19
19
  */
20
20
  export async function readToken() {
21
21
  try {
@@ -27,7 +27,7 @@ export async function readToken() {
27
27
  return null;
28
28
  }
29
29
  }
30
- /** Persiste o token do device-flow (chmod 600, dir só do usuário). */
30
+ /** Persists the device-flow token (chmod 600, user-only dir). */
31
31
  export async function writeToken(token, registry) {
32
32
  await mkdir(dirname(credentialsPath), { recursive: true, mode: 0o700 });
33
33
  const payload = { token, registry, savedAt: new Date().toISOString() };
package/dist/guide.js CHANGED
@@ -1,10 +1,10 @@
1
1
  const kebab = (v) => v.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
2
- const list = (items) => items.length ? items.map((i) => `\`${i}\``).join(", ") : "_(nenhum)_";
2
+ const list = (items) => items.length ? items.map((i) => `\`${i}\``).join(", ") : "_(none)_";
3
3
  /**
4
- * Gera o GUIDE.md — instruções *para o agente* sobre como construir
5
- * componentes seguindo o DS. É a peça que faz "com claude-code eu crio os
6
- * componentes" funcionar: não basta os tokens, o agente precisa das regras
7
- * e do vocabulário real (nomes de tokens semânticos e de recipes).
4
+ * Builds GUIDE.md — instructions *for the agent* on how to build components
5
+ * that follow the design system. This is the piece that makes "I create the
6
+ * components with claude-code" work: the tokens alone are not enough, the agent
7
+ * needs the rules and the real vocabulary (semantic token names and recipes).
8
8
  */
9
9
  export function buildGuide(payload) {
10
10
  const { document: doc, slug, name, version } = payload;
@@ -13,13 +13,14 @@ export function buildGuide(payload) {
13
13
  const hasAlt = foundations.color.semanticAlt &&
14
14
  Object.keys(foundations.color.semanticAlt).length > 0;
15
15
  const altScheme = meta.scheme === "light" ? "dark" : "light";
16
+ const hasTailwind = "theme.css" in payload.artifacts;
16
17
  const componentLines = Object.entries(components).map(([cname, recipe]) => {
17
18
  const cls = `.ds-${kebab(cname)}`;
18
19
  const axes = Object.entries(recipe.variants).map(([axis, opts]) => {
19
20
  const options = Object.keys(opts);
20
21
  return `\`data-${kebab(axis)}="${options.join("|")}"\``;
21
22
  });
22
- const variantsText = axes.length ? ` — variantes: ${axes.join(", ")}` : "";
23
+ const variantsText = axes.length ? ` — variants: ${axes.join(", ")}` : "";
23
24
  return `- **${cname}** (\`${cls}\`)${variantsText}\n ${recipe.description}`;
24
25
  });
25
26
  const artifactList = Object.keys(payload.artifacts)
@@ -27,81 +28,120 @@ export function buildGuide(payload) {
27
28
  .join(", ");
28
29
  return `# Design System: ${name}
29
30
 
30
- > Gerado por \`synthesisui add ${slug}\` (v${version}). **Não edite à mão** —
31
- > rode \`synthesisui add ${slug}\` de novo para atualizar.
31
+ > Generated by \`synthesisui add ${slug}\` (v${version}). **Do not edit by hand** —
32
+ > run \`synthesisui add ${slug}\` again to update.
32
33
 
33
34
  ${meta.tagline}
34
35
 
35
36
  **Mood:** ${meta.mood.join(" · ")}
36
- **Modo padrão:** ${meta.scheme}${hasAlt ? ` (suporta toggle para ${altScheme})` : ""}
37
- ${meta.sourceUrl ? `**Releitura de:** ${meta.sourceUrl}` : "**Sistema autoral.**"}
37
+ **Default scheme:** ${meta.scheme}${hasAlt ? ` (supports a toggle to ${altScheme})` : ""}
38
+ ${meta.sourceUrl ? `**Reinterpretation of:** ${meta.sourceUrl}` : "**Original system.**"}
38
39
 
39
40
  ${meta.narrative}
40
41
 
41
42
  ---
42
43
 
43
- ## Como aplicar
44
+ ## How to apply
44
45
 
45
- 1. Importe os tokens uma vez no CSS global do projeto:
46
+ 1. Import the tokens once in your project's global CSS:
46
47
  \`\`\`css
47
48
  @import "./_synthesisui/ds/${slug}/tokens.css";
48
49
  \`\`\`
49
- (ajuste o caminho relativo conforme a localização do seu CSS.)
50
+ (adjust the relative path to where your CSS lives.)
50
51
 
51
- 2. Envolva a árvore que deve usar o sistema com o atributo de escopo:
52
+ 2. Wrap the tree that should use the system with the scope attribute:
52
53
  \`\`\`html
53
- <div data-ds="${slug}">…sua UI aqui…</div>
54
+ <div data-ds="${slug}">…your UI here…</div>
54
55
  \`\`\`
55
- Todas as custom properties \`--ds-*\` e as classes \`.ds-*\` valem dentro desse escopo.
56
+ All \`--ds-*\` custom properties and \`.ds-*\` classes only apply inside that scope.
56
57
  ${hasAlt
57
58
  ? `
58
- 3. Light/dark: um ancestral com \`data-scheme="${altScheme}"\` troca os papéis neutros para o modo oposto.
59
+ 3. Light/dark: an ancestor with \`data-scheme="${altScheme}"\` switches the neutral roles to the opposite mode.
59
60
  \`\`\`html
60
61
  <div data-scheme="${altScheme}"><div data-ds="${slug}">…</div></div>
61
62
  \`\`\`
63
+ `
64
+ : ""}${hasTailwind
65
+ ? `
66
+ ## Styling with Tailwind v4 (preferred in this project)
67
+
68
+ Import \`theme.css\` after \`tailwindcss\` and \`tokens.css\`:
69
+ \`\`\`css
70
+ @import "tailwindcss";
71
+ @import "./_synthesisui/ds/${slug}/tokens.css";
72
+ @import "./_synthesisui/ds/${slug}/theme.css";
73
+ \`\`\`
74
+ This maps the DS tokens onto Tailwind's theme, so inside \`[data-ds="${slug}"]\` you get utilities
75
+ backed by the design system: \`bg-*\`/\`text-*\`/\`border-*\` (semantic colors), \`p-*\`/\`m-*\`/\`gap-*\`
76
+ (spacing), \`rounded-*\`, \`shadow-*\`, \`font-*\`, \`ease-*\`.
77
+
78
+ **Prefer these utilities for layout and new composition** — they are this project's idiom and read
79
+ far better than inline \`style\`. Reach for inline \`var(--ds-*)\` only when no utility fits.
80
+
81
+ \`\`\`tsx
82
+ // ✅ preferred — Tailwind utilities backed by the DS
83
+ <main className="bg-canvas text-foreground p-2xl flex flex-col gap-md">
84
+ <button className="ds-button" data-intent="primary">Save</button>
85
+ </main>
86
+
87
+ // ❌ avoid — inline styles with raw var() when a utility exists
88
+ <main style={{ background: "var(--ds-color-semantic-canvas)", padding: "var(--ds-spacing-2xl)" }}>
89
+ \`\`\`
90
+
91
+ ---
62
92
  `
63
93
  : ""}
64
- Artefatos disponíveis em \`_synthesisui/ds/${slug}/\`: ${artifactList}, \`design-system.json\` (verdade canônica), \`GUIDE.md\` (este arquivo).
94
+ This is **v${version}**. The stable entrypoints at \`_synthesisui/ds/${slug}/\` (the
95
+ \`tokens.css\`/\`theme.css\` re-exports, plus \`.lock\`) always point at the active version — import
96
+ those, not the versioned ones. The pinned files for this version — ${artifactList},
97
+ \`design-system.json\` (canonical source of truth), \`GUIDE.md\` (this file) — live in
98
+ \`_synthesisui/ds/${slug}/v${version}/\`.
65
99
 
66
100
  ---
67
101
 
68
- ## Regras (siga ao criar componentes)
69
-
70
- - **Use SEMPRE tokens semânticos**, nunca valores crus nem primitivas diretas.
71
- Cor: \`var(--ds-color-semantic-<papel>)\`. Os papéis são: ${list(semanticRoles)}.
72
- - Primitivas (\`--ds-color-<paleta>-<step>\`) existem mas **não** devem ser referenciadas direto
73
- elas alimentam os papéis semânticos.
74
- - Espaçamento \`var(--ds-spacing-<key>)\`: ${list(Object.keys(foundations.spacing))}.
75
- - Raio → \`var(--ds-radius-<key>)\`: ${list(Object.keys(foundations.radius))}.
76
- - Sombra \`var(--ds-shadow-<key>)\`: ${list(Object.keys(foundations.shadow))}.
77
- - Tipografia: famílias \`--ds-typography-families-{display,body,mono}\` (${foundations.typography.families.display}, ${foundations.typography.families.body}, ${foundations.typography.families.mono});
78
- escala \`--ds-typography-scale-<key>-font-size\` etc.: ${list(Object.keys(foundations.typography.scale))}.
79
- - Motion: durações \`--ds-motion-durations-<key>\` (${list(Object.keys(motion.durations))}) e
102
+ ## Rules (follow them when creating components)
103
+ ${hasTailwind
104
+ ? `
105
+ - **Styling mechanism:** prefer Tailwind utilities backed by the DS (\`bg-primary\`, \`p-md\`,
106
+ \`font-display\`, ) for layout and new composition, and reuse the \`.ds-*\` recipes for components
107
+ the DS already covers. Use inline \`style\` with \`var(--ds-*)\` only as a last resort. The token
108
+ names below are the source vocabulary — every utility derives from them.`
109
+ : ""}
110
+ - **Always use semantic tokens**, never raw values nor primitives directly.
111
+ Color: \`var(--ds-color-semantic-<role>)\`${hasTailwind ? " (utility: `bg-<role>`/`text-<role>`)" : ""}. The roles are: ${list(semanticRoles)}.
112
+ - Primitives (\`--ds-color-<palette>-<step>\`) exist but should **not** be referenced directly —
113
+ they feed the semantic roles.
114
+ - Spacing → \`var(--ds-spacing-<key>)\`: ${list(Object.keys(foundations.spacing))}.
115
+ - Radius → \`var(--ds-radius-<key>)\`: ${list(Object.keys(foundations.radius))}.
116
+ - Shadow → \`var(--ds-shadow-<key>)\`: ${list(Object.keys(foundations.shadow))}.
117
+ - Typography: families \`--ds-typography-families-{display,body,mono}\` (${foundations.typography.families.display}, ${foundations.typography.families.body}, ${foundations.typography.families.mono});
118
+ scale \`--ds-typography-scale-<key>-font-size\` etc.: ${list(Object.keys(foundations.typography.scale))}.
119
+ - Motion: durations \`--ds-motion-durations-<key>\` (${list(Object.keys(motion.durations))}) and
80
120
  easings \`--ds-motion-easings-<key>\` (${list(Object.keys(motion.easings))}).
81
- - Ao **criar um componente novo** que o DS ainda não cobre: componha a partir desses tokens
82
- semânticos para herdar a identidade do sistema; não invente cores/medidas fora da escala.
121
+ - When **creating a new component** the DS does not cover yet: compose it from these semantic
122
+ tokens to inherit the system's identity; do not invent colors/measures outside the scale.
83
123
 
84
124
  ---
85
125
 
86
- ## Onde testar/preview
126
+ ## Where to preview
87
127
 
88
- **Preview isolado, nunca em página real.** Ao criar ou demonstrar um componente, gere uma página de
89
- amostra dedicada — \`app/synthesisui-samples/<componente>/\` no Next.js App Router (ou a rota/pasta de
90
- samples equivalente na stack do projeto). **Não** aplique o componente a páginas reais de produção
91
- (home, layout, rotas existentes) a menos que seja explicitamente pedido. As amostras deixam revisar o
92
- componente no contexto do design system sem tocar no app.
128
+ **Preview in isolation, never on a real page.** When creating or demoing a component, generate a
129
+ dedicated sample page — \`app/synthesisui-samples/<component>/\` in the Next.js App Router (or the
130
+ equivalent samples route/folder in the project's stack). **Do not** apply the component to real
131
+ production pages (home, layout, existing routes) unless explicitly asked. Samples let you review the
132
+ component in the context of the design system without touching the app.
93
133
 
94
134
  ---
95
135
 
96
- ## Componentes prontos
136
+ ## Ready-made components
97
137
 
98
- Cada recipe vira uma classe \`.ds-<nome>\` (dentro do escopo \`[data-ds="${slug}"]\`).
99
- Variantes são atributos \`data-<eixo>="<opção>"\`; estados (hover/focus/active/disabled) vêm no CSS.
138
+ Each recipe becomes a \`.ds-<name>\` class (inside the \`[data-ds="${slug}"]\` scope).
139
+ Variants are \`data-<axis>="<option>"\` attributes; states (hover/focus/active/disabled) ship in the CSS.
100
140
 
101
141
  ${componentLines.join("\n\n")}
102
142
 
103
143
  ---
104
144
 
105
- _Verdade canônica completa (incluindo valores e keyframes) em \`design-system.json\`._
145
+ _Full canonical source of truth (including values and keyframes) in \`design-system.json\`._
106
146
  `;
107
147
  }
package/dist/index.js CHANGED
@@ -3,25 +3,27 @@ import { add } from "./commands/add.js";
3
3
  import { list } from "./commands/list.js";
4
4
  import { login } from "./commands/login.js";
5
5
  import { RegistryError } from "./registry.js";
6
- const HELP = `synthesisui — traz design systems do SynthesisUI para o seu projeto
6
+ const HELP = `synthesisui — bring SynthesisUI design systems into your project
7
7
 
8
- Uso:
9
- synthesisui login [opções] conecta o CLI à sua conta (device-flow)
10
- synthesisui list [opções] lista os DSs publicados
11
- synthesisui add <slug> [opções] materializa um DS em _synthesisui/ds/<slug>/
8
+ Usage:
9
+ synthesisui login [options] connect the CLI to your account (device-flow)
10
+ synthesisui list [options] list the published design systems
11
+ synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
12
12
 
13
- Opções:
14
- --registry <url> URL do registry (ou env SYNTHESISUI_REGISTRY_URL)
15
- --dir <path> raiz do projeto consumidor (default: diretório atual)
16
- -h, --help esta ajuda
13
+ Options:
14
+ --registry <url> registry URL (or env SYNTHESISUI_REGISTRY_URL)
15
+ --dir <path> consumer project root (default: current directory)
16
+ --version <n> install a specific version (default: latest)
17
+ -h, --help this help
17
18
 
18
- Exemplos:
19
+ Examples:
19
20
  synthesisui login
20
21
  synthesisui list
21
22
  synthesisui add halogen
23
+ synthesisui add halogen --version 3
22
24
  synthesisui add halogen --registry http://localhost:3737
23
25
  `;
24
- /** Extrai `--flag value` simples e os posicionais restantes. */
26
+ /** Extracts simple `--flag value` pairs and the remaining positionals. */
25
27
  function parseFlags(argv) {
26
28
  const positionals = [];
27
29
  const flags = {};
@@ -63,25 +65,34 @@ async function main() {
63
65
  case "add": {
64
66
  const slug = args[0];
65
67
  if (!slug) {
66
- console.error("erro: informe o slug — `synthesisui add <slug>`");
68
+ console.error("error: provide the slug — `synthesisui add <slug>`");
67
69
  process.exitCode = 1;
68
70
  return;
69
71
  }
70
- await add(slug, { registry, dir });
72
+ let version;
73
+ if (typeof flags.version === "string") {
74
+ version = Number.parseInt(flags.version.replace(/^v/i, ""), 10);
75
+ if (!Number.isInteger(version) || version < 1) {
76
+ console.error(`error: invalid --version "${flags.version}" — use an integer ≥ 1`);
77
+ process.exitCode = 1;
78
+ return;
79
+ }
80
+ }
81
+ await add(slug, { registry, dir, version });
71
82
  break;
72
83
  }
73
84
  case "login":
74
85
  await login({ registry });
75
86
  break;
76
87
  default:
77
- console.error(`comando desconhecido: "${command}"\n`);
88
+ console.error(`unknown command: "${command}"\n`);
78
89
  console.log(HELP);
79
90
  process.exitCode = 1;
80
91
  }
81
92
  }
82
93
  main().catch((err) => {
83
94
  if (err instanceof RegistryError) {
84
- console.error(`erro: ${err.message}`);
95
+ console.error(`error: ${err.message}`);
85
96
  }
86
97
  else {
87
98
  console.error(err);
package/dist/registry.js CHANGED
@@ -11,29 +11,34 @@ async function request(url) {
11
11
  res = await fetch(url, { headers: await authHeaders() });
12
12
  }
13
13
  catch {
14
- throw new RegistryError(`Não consegui falar com o registry em ${url}. ` +
15
- `Confira a URL (--registry / SYNTHESISUI_REGISTRY_URL) e a conexão.`);
14
+ throw new RegistryError(`Could not reach the registry at ${url}. ` +
15
+ `Check the URL (--registry / SYNTHESISUI_REGISTRY_URL) and your connection.`);
16
16
  }
17
17
  return res;
18
18
  }
19
- /** Lista os design systems publicados disponíveis. */
19
+ /** Lists the available published design systems. */
20
20
  export async function fetchList(base) {
21
21
  const res = await request(`${base}/api/registry/ds`);
22
22
  if (!res.ok) {
23
- throw new RegistryError(`Registry respondeu ${res.status} ao listar.`);
23
+ throw new RegistryError(`Registry responded ${res.status} while listing.`);
24
24
  }
25
25
  const body = (await res.json());
26
26
  return body.designSystems ?? [];
27
27
  }
28
- /** Busca um DS publicado já compilado (document + artifacts). */
29
- export async function fetchDesignSystem(base, slug) {
30
- const res = await request(`${base}/api/registry/ds/${encodeURIComponent(slug)}`);
28
+ /**
29
+ * Fetches a published DS already compiled (document + artifacts). Without
30
+ * `version` it returns the latest; with `version` it returns that one.
31
+ */
32
+ export async function fetchDesignSystem(base, slug, version) {
33
+ const url = new URL(`${base}/api/registry/ds/${encodeURIComponent(slug)}`);
34
+ if (version != null)
35
+ url.searchParams.set("version", String(version));
36
+ const res = await request(url.toString());
31
37
  if (res.status === 404) {
32
- throw new RegistryError(`Nenhum design system publicado com slug "${slug}". ` +
33
- `Rode \`synthesisui list\` para ver os disponíveis.`);
38
+ throw new RegistryError(`No design system published with slug "${slug}"${version != null ? ` at version v${version}` : ""}. Run \`synthesisui list\` to see what's available.`);
34
39
  }
35
40
  if (!res.ok) {
36
- throw new RegistryError(`Registry respondeu ${res.status} ao buscar "${slug}".`);
41
+ throw new RegistryError(`Registry responded ${res.status} while fetching "${slug}".`);
37
42
  }
38
43
  return (await res.json());
39
44
  }
package/dist/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Espelho mínimo do contrato do registry — o CLI é standalone e NÃO importa
3
- * `@synthesisui-hub/ds-contracts` ( consome o JSON do endpoint). Tipamos
4
- * apenas o que o CLI para gerar o GUIDE.md e o .lock.
2
+ * Minimal mirror of the registry contract the CLI is standalone and does NOT
3
+ * import `@synthesisui-hub/ds-contracts` (it only consumes the endpoint JSON).
4
+ * We type only what the CLI reads to generate GUIDE.md and the .lock.
5
5
  */
6
6
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "Traz design systems do SynthesisUI para qualquer projeto (materializa em _local/ds/).",
5
5
  "type": "module",
6
6
  "bin": {