zyncat-ui 0.12.0 → 0.13.0
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 +2 -2
- package/dist/cli.js +61 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@ pnpm dlx zyncat-ui init
|
|
|
10
10
|
|
|
11
11
|
Run it inside a React project. One command installs `@zyncat/ui` (and React 19 if the project
|
|
12
12
|
needs it), imports the base stylesheet at your app root, installs the agent skill into
|
|
13
|
-
`.claude/skills/`, registers the MCP server in `.mcp.json
|
|
14
|
-
|
|
13
|
+
`.claude/skills/`, and registers the MCP server in `.mcp.json`. Re-run it after upgrading
|
|
14
|
+
`@zyncat/ui` to refresh everything.
|
|
15
15
|
|
|
16
16
|
This package is where the CLI lives, and it holds the unscoped name so the command works before
|
|
17
17
|
`@zyncat/ui` is installed. It ships one bundled file and declares zero runtime dependencies - the
|
package/dist/cli.js
CHANGED
|
@@ -212,6 +212,12 @@ function majorOf(range) {
|
|
|
212
212
|
const match = range?.match(/\d+/);
|
|
213
213
|
return match ? Number(match[0]) : null;
|
|
214
214
|
}
|
|
215
|
+
var release = (version2) => (version2.match(/\d+\.\d+\.\d+/)?.[0] ?? "0.0.0").split(".").map(Number);
|
|
216
|
+
function isOlder(version2, than) {
|
|
217
|
+
const [a2, b] = [release(version2), release(than)];
|
|
218
|
+
for (let i2 = 0; i2 < 3; i2++) if (a2[i2] !== b[i2]) return a2[i2] < b[i2];
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
215
221
|
function installedVersion(cwd, name) {
|
|
216
222
|
return readJson(join(cwd, "node_modules", name, "package.json"))?.version ?? null;
|
|
217
223
|
}
|
|
@@ -1531,21 +1537,14 @@ var PACKAGE = "@zyncat/ui";
|
|
|
1531
1537
|
var MCP_SERVER_ENTRY = { command: "node", args: ["./node_modules/@zyncat/ui/dist/mcp.js"] };
|
|
1532
1538
|
var STYLES_IMPORT = "import '@zyncat/ui/styles.css';";
|
|
1533
1539
|
var DOCS_URL = "https://ui.zyncat.app";
|
|
1534
|
-
var THEME_STARTER = `import { defineTheme } from '@zyncat/ui/theme';
|
|
1535
|
-
|
|
1536
|
-
export const base = defineTheme({
|
|
1537
|
-
color: { accent: 'oklch(0.63 0.118 198)' },
|
|
1538
|
-
});
|
|
1539
|
-
|
|
1540
|
-
export const dark = defineTheme({
|
|
1541
|
-
color: { bgApp: 'oklch(0.19 0.008 198)', textBody: 'oklch(0.92 0.004 198)' },
|
|
1542
|
-
});
|
|
1543
|
-
`;
|
|
1544
1540
|
var interactive = () => Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
1541
|
+
var installTouchedProject = false;
|
|
1545
1542
|
function bail(message, hint) {
|
|
1546
1543
|
log.error(message);
|
|
1547
1544
|
if (hint) log.message(dim(hint), { spacing: 0 });
|
|
1548
|
-
outro(
|
|
1545
|
+
outro(
|
|
1546
|
+
red(installTouchedProject ? "Stopped here - the install ran, nothing else was wired." : "Nothing was changed.")
|
|
1547
|
+
);
|
|
1549
1548
|
process.exit(1);
|
|
1550
1549
|
}
|
|
1551
1550
|
async function pickPm(cwd, targetPkg, flags) {
|
|
@@ -1563,13 +1562,20 @@ async function pickPm(cwd, targetPkg, flags) {
|
|
|
1563
1562
|
}
|
|
1564
1563
|
return choice;
|
|
1565
1564
|
}
|
|
1566
|
-
|
|
1565
|
+
var ADD_COMMAND = {
|
|
1566
|
+
pnpm: "pnpm add",
|
|
1567
|
+
npm: "npm install",
|
|
1568
|
+
yarn: "yarn add",
|
|
1569
|
+
bun: "bun add"
|
|
1570
|
+
};
|
|
1571
|
+
async function planInstall(cwd, targetPkg, flags, version2) {
|
|
1567
1572
|
const deps = { ...targetPkg.dependencies, ...targetPkg.devDependencies };
|
|
1568
1573
|
const specs = [];
|
|
1569
1574
|
let reactNote = null;
|
|
1570
1575
|
const hasPackage = PACKAGE in deps;
|
|
1571
|
-
const
|
|
1572
|
-
|
|
1576
|
+
const onDisk = installedVersion(cwd, PACKAGE);
|
|
1577
|
+
const outdated = onDisk !== null && isOlder(onDisk, version2);
|
|
1578
|
+
if (!hasPackage || outdated) specs.push(outdated ? `${PACKAGE}@^${version2}` : PACKAGE);
|
|
1573
1579
|
if (!("react" in deps)) {
|
|
1574
1580
|
specs.push("react", "react-dom");
|
|
1575
1581
|
} else {
|
|
@@ -1590,7 +1596,7 @@ async function planInstall(cwd, targetPkg, flags) {
|
|
|
1590
1596
|
else reactNote = `React ${major} stays as it is - ${PACKAGE} requires React 19, so upgrade before shipping.`;
|
|
1591
1597
|
}
|
|
1592
1598
|
}
|
|
1593
|
-
return { specs, restore: hasPackage &&
|
|
1599
|
+
return { specs, restore: hasPackage && onDisk === null, reactNote };
|
|
1594
1600
|
}
|
|
1595
1601
|
function renderProgress(pm, progress, tick) {
|
|
1596
1602
|
if (pm !== "pnpm") return shimmer(`Installing with ${pm}`, tick);
|
|
@@ -1668,20 +1674,36 @@ async function installPhase(pm, plan, cwd) {
|
|
|
1668
1674
|
process.exit(1);
|
|
1669
1675
|
}
|
|
1670
1676
|
}
|
|
1677
|
+
async function alignVersion(pm, cwd, version2) {
|
|
1678
|
+
const onDisk = installedVersion(cwd, PACKAGE);
|
|
1679
|
+
if (!onDisk || !isOlder(onDisk, version2)) return;
|
|
1680
|
+
log.warn(
|
|
1681
|
+
`${PACKAGE} ${onDisk} resolved from this project's range, older than the CLI (${version2}) - upgrading so the skill and MCP server match.`
|
|
1682
|
+
);
|
|
1683
|
+
const upgraded = await installPhase(pm, { specs: [`${PACKAGE}@^${version2}`], restore: false, reactNote: null }, cwd);
|
|
1684
|
+
if (upgraded) log.message(upgraded.line, { symbol: check });
|
|
1685
|
+
}
|
|
1671
1686
|
function sourceRoot(cwd, ownRoot) {
|
|
1672
1687
|
const installed = join3(cwd, "node_modules", PACKAGE);
|
|
1673
1688
|
if (existsSync3(join3(installed, "skills"))) return installed;
|
|
1674
1689
|
return ownRoot;
|
|
1675
1690
|
}
|
|
1676
|
-
function wireSkill(cwd, packageRoot) {
|
|
1691
|
+
function wireSkill(cwd, packageRoot, pm) {
|
|
1677
1692
|
const source = join3(packageRoot, "skills");
|
|
1678
|
-
if (!existsSync3(source))
|
|
1693
|
+
if (!existsSync3(source)) {
|
|
1694
|
+
const onDisk = installedVersion(cwd, PACKAGE);
|
|
1695
|
+
return {
|
|
1696
|
+
line: row("Agent skill", `skipped \xB7 ${PACKAGE}${onDisk ? ` ${onDisk}` : ""} ships no skills/`),
|
|
1697
|
+
done: false,
|
|
1698
|
+
hint: `Upgrade it with ${ADD_COMMAND[pm]} ${PACKAGE}@latest, then re-run init.`
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1679
1701
|
const dest = join3(cwd, ".claude/skills");
|
|
1680
1702
|
const existed = existsSync3(join3(dest, "zyncat-ui"));
|
|
1681
1703
|
mkdirSync(dest, { recursive: true });
|
|
1682
1704
|
for (const name of readdirSync2(source))
|
|
1683
1705
|
cpSync(join3(source, name), join3(dest, name), { recursive: true, force: true });
|
|
1684
|
-
return row("Agent skill", `.claude/skills/zyncat-ui \xB7 ${existed ? "refreshed" : "installed"}`);
|
|
1706
|
+
return { line: row("Agent skill", `.claude/skills/zyncat-ui \xB7 ${existed ? "refreshed" : "installed"}`), done: true };
|
|
1685
1707
|
}
|
|
1686
1708
|
function wireMcp(cwd) {
|
|
1687
1709
|
const path = join3(cwd, ".mcp.json");
|
|
@@ -1695,29 +1717,27 @@ function wireMcp(cwd) {
|
|
|
1695
1717
|
config.mcpServers = { ...config.mcpServers, "zyncat-ui": MCP_SERVER_ENTRY };
|
|
1696
1718
|
writeFileSync(path, `${JSON.stringify(config, null, 2)}
|
|
1697
1719
|
`);
|
|
1698
|
-
return row("MCP server", existed ? ".mcp.json \xB7 kept" : `.mcp.json ${arrow} zyncat-ui`);
|
|
1699
|
-
}
|
|
1700
|
-
function wireTheme(cwd) {
|
|
1701
|
-
const file = existsSync3(join3(cwd, "tsconfig.json")) ? "zyncat.theme.ts" : "zyncat.theme.js";
|
|
1702
|
-
const path = join3(cwd, file);
|
|
1703
|
-
if (existsSync3(path)) return row("Typed theme", `${file} \xB7 kept`);
|
|
1704
|
-
writeFileSync(path, THEME_STARTER);
|
|
1705
|
-
return row("Typed theme", `${file} \xB7 created`);
|
|
1720
|
+
return { line: row("MCP server", existed ? ".mcp.json \xB7 kept" : `.mcp.json ${arrow} zyncat-ui`), done: true };
|
|
1706
1721
|
}
|
|
1707
1722
|
function wireStyles(cwd) {
|
|
1708
1723
|
const entry = findAppEntry(cwd);
|
|
1709
|
-
if (!entry)
|
|
1724
|
+
if (!entry)
|
|
1725
|
+
return {
|
|
1726
|
+
line: row("Stylesheet", "no app entry found \xB7 add the import yourself"),
|
|
1727
|
+
done: false,
|
|
1728
|
+
hint: `Put ${STYLES_IMPORT} at your app root, above your own stylesheets.`
|
|
1729
|
+
};
|
|
1710
1730
|
const path = join3(cwd, entry);
|
|
1711
1731
|
const text = readFileSync2(path, "utf8");
|
|
1712
1732
|
if (text.includes("@zyncat/ui/styles.css"))
|
|
1713
|
-
return { line: row("Stylesheet", `${entry} \xB7 already imported`),
|
|
1733
|
+
return { line: row("Stylesheet", `${entry} \xB7 already imported`), done: true };
|
|
1714
1734
|
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
1715
1735
|
const lines = text.split(eol);
|
|
1716
1736
|
let at = 0;
|
|
1717
1737
|
while (at < lines.length && (lines[at].trim() === "" || /^(['"])use [\w-]+\1;?$/.test(lines[at].trim()))) at++;
|
|
1718
1738
|
lines.splice(at, 0, STYLES_IMPORT);
|
|
1719
1739
|
writeFileSync(path, lines.join(eol));
|
|
1720
|
-
return { line: row("Stylesheet", `${entry} \xB7 import added`),
|
|
1740
|
+
return { line: row("Stylesheet", `${entry} \xB7 import added`), done: true };
|
|
1721
1741
|
}
|
|
1722
1742
|
async function init(flags) {
|
|
1723
1743
|
const startedAt = performance.now();
|
|
@@ -1739,23 +1759,22 @@ async function init(flags) {
|
|
|
1739
1759
|
const pmVer = pmVersion(pm);
|
|
1740
1760
|
const project = targetPkg.name ?? "unnamed project";
|
|
1741
1761
|
log.message(dim(`${project} \xB7 ${pm}${pmVer ? ` ${pmVer}` : ""}`));
|
|
1742
|
-
const plan = await planInstall(cwd, targetPkg, flags);
|
|
1762
|
+
const plan = await planInstall(cwd, targetPkg, flags, version2);
|
|
1743
1763
|
const installed = await installPhase(pm, plan, cwd);
|
|
1764
|
+
installTouchedProject = true;
|
|
1744
1765
|
if (installed) log.message(installed.line, { symbol: check });
|
|
1745
1766
|
if (plan.reactNote) log.warn(plan.reactNote);
|
|
1767
|
+
await alignVersion(pm, cwd, version2);
|
|
1746
1768
|
const packageRoot = sourceRoot(cwd, ownRoot);
|
|
1747
|
-
const
|
|
1748
|
-
const
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
symbol: styles.manual && index === lines.length - 1 ? skip : check,
|
|
1753
|
-
spacing: index === 0 ? 1 : 0
|
|
1754
|
-
});
|
|
1755
|
-
if (styles.manual)
|
|
1756
|
-
log.message(dim(`Put ${STYLES_IMPORT} at your app root, above your own stylesheets.`), { spacing: 0 });
|
|
1769
|
+
const rows = [wireSkill(cwd, packageRoot, pm), wireMcp(cwd), wireStyles(cwd)];
|
|
1770
|
+
for (const [index, entry] of rows.entries()) {
|
|
1771
|
+
log.message(entry.line, { symbol: entry.done ? check : skip, spacing: index === 0 ? 1 : 0 });
|
|
1772
|
+
if (entry.hint) log.message(dim(entry.hint), { spacing: 0 });
|
|
1773
|
+
}
|
|
1757
1774
|
log.message(`${dim("Docs")} ${arrow} ${accentDeep(DOCS_URL)}`);
|
|
1758
|
-
|
|
1775
|
+
const skipped = rows.filter((entry) => !entry.done).length;
|
|
1776
|
+
const closing = skipped ? `${bold(`Done in ${seconds(performance.now() - startedAt)}.`)} ${skipped} step${skipped === 1 ? "" : "s"} left for you, above.` : `${bold(`Ready in ${seconds(performance.now() - startedAt)}.`)} Restart your agent session to load the skill.`;
|
|
1777
|
+
outro(closing);
|
|
1759
1778
|
}
|
|
1760
1779
|
|
|
1761
1780
|
// src/cli.ts
|
|
@@ -1778,7 +1797,7 @@ function help(version2) {
|
|
|
1778
1797
|
` ${wordmark(version2)}`,
|
|
1779
1798
|
"",
|
|
1780
1799
|
` ${dim("Sets up @zyncat/ui in a React project: installs the package,")}`,
|
|
1781
|
-
` ${dim("the agent skill, the MCP server
|
|
1800
|
+
` ${dim("the agent skill, the MCP server and the stylesheet.")}`,
|
|
1782
1801
|
"",
|
|
1783
1802
|
` ${bold("Usage")}`,
|
|
1784
1803
|
` zyncat-ui init ${dim("[flags]")}`,
|