@buoy-design/cli 0.6.1 → 0.7.1

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.
@@ -0,0 +1,98 @@
1
+ /**
2
+ * `buoy mcp serve`: a stdio MCP server that gives coding agents this repo's
3
+ * design tokens and drift checks, so hardcoded values are caught while the
4
+ * agent writes instead of on the PR.
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { z } from "zod";
9
+ import { generateContext } from "../services/context-generator.js";
10
+ import { ScanOrchestrator } from "../scan/orchestrator.js";
11
+ import { checkDrift, findTokensByValue, loadProject, scanTokens, summarizeToken, } from "./project.js";
12
+ const TOKEN_CACHE_MS = 30_000;
13
+ export function createBuoyMcpServer(options) {
14
+ const server = new McpServer({ name: "buoy", version: options.version });
15
+ let project = null;
16
+ let tokenCache = null;
17
+ const getProject = async () => (project ??= await loadProject(options.cwd));
18
+ const getTokens = async () => {
19
+ if (tokenCache && Date.now() - tokenCache.at < TOKEN_CACHE_MS)
20
+ return tokenCache.tokens;
21
+ const tokens = await scanTokens(await getProject());
22
+ tokenCache = { at: Date.now(), tokens };
23
+ return tokens;
24
+ };
25
+ const json = (value) => ({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }] });
26
+ server.registerTool("list_design_tokens", {
27
+ title: "List design tokens",
28
+ description: "The design tokens defined in this repository (CSS custom properties, Tailwind theme, tokens.json, SCSS variables). Call this before writing any CSS, inline style or Tailwind class so you use an existing token instead of a literal value. Filter by category (color, spacing, typography, shadow, border, sizing, motion, zIndex) or a name substring.",
29
+ inputSchema: {
30
+ category: z.string().optional().describe("Only tokens in this category"),
31
+ query: z.string().optional().describe("Case-insensitive substring of the token name"),
32
+ limit: z.number().int().positive().max(500).optional().describe("Max tokens to return (default 200)"),
33
+ },
34
+ }, async ({ category, query, limit }) => {
35
+ const tokens = await getTokens();
36
+ const q = query?.toLowerCase();
37
+ const matched = tokens
38
+ .filter((t) => !category || t.category === category)
39
+ .filter((t) => !q || t.name.toLowerCase().includes(q));
40
+ const byCategory = {};
41
+ for (const t of tokens)
42
+ byCategory[t.category] = (byCategory[t.category] ?? 0) + 1;
43
+ return json({
44
+ total: tokens.length,
45
+ byCategory,
46
+ matched: matched.length,
47
+ tokens: matched.slice(0, limit ?? 200).map(summarizeToken),
48
+ ...(tokens.length === 0 ? { note: "No tokens found. Run `buoy dock tokens` to extract a token set from the values this codebase already uses." } : {}),
49
+ });
50
+ });
51
+ server.registerTool("find_token_for_value", {
52
+ title: "Find the token for a literal value",
53
+ description: "Given a literal CSS value you are about to write (for example '#1a73e8', '16px', '0.5rem'), return the design tokens in this repository that have exactly that value. Use the token instead of the literal. An empty result means there is no token for it; prefer the nearest token from list_design_tokens over inventing a new value.",
54
+ inputSchema: {
55
+ value: z.string().describe("The literal value, e.g. '#ffffff' or '8px'"),
56
+ category: z.string().optional().describe("Restrict to a category such as color or spacing"),
57
+ },
58
+ }, async ({ value, category }) => {
59
+ const matches = findTokensByValue(await getTokens(), value, category);
60
+ return json({ value, matches, suggestion: matches[0] ? `Use ${matches[0].name} instead of ${value}` : null });
61
+ });
62
+ server.registerTool("check_design_drift", {
63
+ title: "Check files for design drift",
64
+ description: "Run Buoy's drift analysis and return hardcoded values, unknown tokens and inconsistent components, each with the token to use instead when one exists. Pass the files you just edited to check only those; omit files for the whole repository. Run this after editing styles and fix every issue that has a suggestion.",
65
+ inputSchema: {
66
+ files: z.array(z.string()).optional().describe("Repository-relative or absolute paths to limit the check to"),
67
+ },
68
+ }, async ({ files }) => {
69
+ const result = await checkDrift(await getProject(), files, files?.length ? await getTokens() : undefined);
70
+ return json({
71
+ ...result,
72
+ instructions: result.issues.length === 0
73
+ ? "No design drift in the checked files."
74
+ : "Replace each `current` value with `suggested` (or one of `tokenSuggestions`), then run check_design_drift again.",
75
+ });
76
+ });
77
+ server.registerTool("design_system_context", {
78
+ title: "Design system overview",
79
+ description: "A Markdown summary of this repository's design system: token categories with example names, the component library, and the anti-patterns Buoy flags. Read it once at the start of a task that touches UI.",
80
+ inputSchema: {
81
+ detail: z.enum(["minimal", "standard", "comprehensive"]).optional().describe("How much to include (default standard)"),
82
+ },
83
+ }, async ({ detail }) => {
84
+ const proj = await getProject();
85
+ const orchestrator = new ScanOrchestrator(proj.config, proj.projectRoot);
86
+ const scan = await orchestrator.scan();
87
+ const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
88
+ const diff = new SemanticDiffEngine().analyzeComponents(scan.components, { availableTokens: scan.tokens });
89
+ const result = generateContext({ tokens: scan.tokens, components: scan.components, drifts: diff.drifts, projectName: proj.projectRoot.split("/").pop() || "project" }, { detailLevel: detail ?? "standard" });
90
+ return { content: [{ type: "text", text: result.content }] };
91
+ });
92
+ return server;
93
+ }
94
+ export async function serveStdio(options) {
95
+ const server = createBuoyMcpServer(options);
96
+ await server.connect(new StdioServerTransport());
97
+ }
98
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@buoy-design/cli",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "Catch design drift before it ships",
5
+ "mcpName": "io.github.ahoybuoy/buoy",
5
6
  "type": "module",
6
7
  "license": "MIT",
7
8
  "author": "Buoy <hello@buoy.design>",
@@ -35,6 +36,7 @@
35
36
  "@anthropic-ai/sdk": "^0.71.2",
36
37
  "@google/design.md": "0.1.1",
37
38
  "@inquirer/prompts": "^8.1.0",
39
+ "@modelcontextprotocol/sdk": "^1.30.0",
38
40
  "@octokit/rest": "^21.0.0",
39
41
  "chalk": "^5.3.0",
40
42
  "cli-table3": "^0.6.5",
@@ -47,8 +49,8 @@
47
49
  "yaml": "^2.8.2",
48
50
  "zod": "^3.24.1",
49
51
  "zod-validation-error": "^5.0.0",
50
- "@buoy-design/core": "0.6.1",
51
- "@buoy-design/scanners": "0.6.1"
52
+ "@buoy-design/core": "0.7.1",
53
+ "@buoy-design/scanners": "0.7.1"
52
54
  },
53
55
  "devDependencies": {
54
56
  "@types/node": "^22.10.2",