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.
- package/dist/commands/add.js +14 -0
- package/dist/commands/init.js +21 -0
- package/dist/commands/page.js +32 -0
- package/dist/config.js +28 -0
- package/dist/guide.js +34 -2
- package/dist/index.js +41 -6
- package/dist/registry.js +21 -0
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -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]
|
|
12
|
-
synthesisui
|
|
13
|
-
synthesisui
|
|
14
|
-
synthesisui
|
|
15
|
-
synthesisui
|
|
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
|
|
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.
|