synthesisui 0.1.12 → 0.1.14

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.
@@ -53,6 +53,17 @@ export async function add(slug, opts) {
53
53
  fetchedAt: new Date().toISOString(),
54
54
  };
55
55
  await writeFile(rootLockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
56
+ // 5b. governance rules (personal DS) → rules.md at the slug root (stable path,
57
+ // highest authority; the GUIDE tells the agent to read it first)
58
+ const rules = payload.rules ?? [];
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" +
62
+ `> Managed by synthesisui (edit in the studio). ${rules.length} rule(s).\n\n${rules
63
+ .map((r) => `- ${r}`)
64
+ .join("\n")}\n`;
65
+ await writeFile(join(slugDir, "rules.md"), body, "utf8");
66
+ }
56
67
  // 6. discovery by the agent
57
68
  const claudeMd = await syncClaudeMd(projectRoot);
58
69
  // outcome line
@@ -75,6 +86,9 @@ export async function add(slug, opts) {
75
86
  "GUIDE.md",
76
87
  ];
77
88
  console.log(` v${v}/: ${files.join(", ")}`);
89
+ if (rules.length > 0) {
90
+ console.log(` rules.md → ${rules.length} rule(s) (read these first)`);
91
+ }
78
92
  console.log(` CLAUDE.md ${claudeMd.created ? "created" : "updated"} (${claudeMd.count} system(s) installed)`);
79
93
  console.log("");
80
94
  console.log("Next steps:");
@@ -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
+ }
@@ -0,0 +1,32 @@
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 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.
10
+ */
11
+ export async function page(slug, template, opts) {
12
+ const base = resolveRegistry(opts.registry);
13
+ const root = opts.dir ?? process.cwd();
14
+ const config = await readProjectConfig(root);
15
+ const target = opts.target === "general" || opts.target === "next"
16
+ ? opts.target
17
+ : config.target;
18
+ console.log(`→ generating "${template}" from "${slug}" (${target}) …`);
19
+ 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})`);
26
+ console.log("");
27
+ console.log("Next steps:");
28
+ console.log(` • ensure the DS is installed: synthesisui add ${slug} (provides tokens.css)`);
29
+ console.log(` • @import "_synthesisui/ds/${slug}/tokens.css" in your global CSS`);
30
+ 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)`);
32
+ }
package/dist/config.js CHANGED
@@ -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
@@ -100,6 +100,38 @@ its typographic identity. Load them once (any one approach):
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 (default)
113
+ synthesisui page ${slug} ${layoutNames[0]} --target general # plain HTML
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.
119
+
120
+ ---
121
+ `
122
+ : "";
123
+ const hasRules = (payload.rules?.length ?? 0) > 0;
124
+ const rulesNote = hasRules
125
+ ? `
126
+ ## Rules — highest authority
127
+
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.
131
+
132
+ ---
133
+ `
134
+ : "";
103
135
  return `# Design System: ${name}
104
136
 
105
137
  > Generated by \`synthesisui add ${slug}\` (v${version}). **Do not edit by hand** —
@@ -114,7 +146,7 @@ ${meta.sourceUrl ? `**Reinterpretation of:** ${meta.sourceUrl}` : "**Original sy
114
146
  ${meta.narrative}
115
147
 
116
148
  ---
117
-
149
+ ${rulesNote}
118
150
  ## How to apply
119
151
 
120
152
  1. Import the tokens once in your project's global CSS:
@@ -180,7 +212,7 @@ those, not the versioned ones. The pinned files for this version — ${artifactL
180
212
  \`_synthesisui/ds/${slug}/v${version}/\`.
181
213
 
182
214
  ---
183
-
215
+ ${pagesSection}
184
216
  ## Building with the system
185
217
 
186
218
  **This system is for building real product UI** — pages, layouts, dashboards, whole flows.
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
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
  `;
@@ -92,6 +100,33 @@ 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) {
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Traz design systems do SynthesisUI para qualquer projeto (materializa em _local/ds/).",
5
5
  "type": "module",
6
6
  "bin": {