@bezel-labs/bezel-kit 0.7.0 → 0.8.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 +24 -4
- package/dist/agent-skill.cjs +41 -0
- package/dist/agent-skill.cjs.map +1 -0
- package/dist/agent-skill.d.cts +16 -0
- package/dist/agent-skill.d.ts +16 -0
- package/dist/agent-skill.js +7 -0
- package/dist/agent-skill.js.map +1 -0
- package/dist/chunk-3XU5AUYB.js +17 -0
- package/dist/chunk-3XU5AUYB.js.map +1 -0
- package/dist/{chunk-Z7IFWPRA.js → chunk-DXZXDPQ6.js} +1 -1
- package/dist/{chunk-Z7IFWPRA.js.map → chunk-DXZXDPQ6.js.map} +1 -1
- package/dist/{chunk-KCAFXZVO.js → chunk-WHESGLCA.js} +16 -10
- package/dist/chunk-WHESGLCA.js.map +1 -0
- package/dist/cli.cjs +204 -23
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +185 -18
- package/dist/cli.js.map +1 -1
- package/dist/{index-CxeyVs4k.d.cts → index-BF0AyEbO.d.ts} +2 -0
- package/dist/{index-CxeyVs4k.d.ts → index-Bme8IB4Z.d.cts} +2 -0
- package/dist/index.cjs +15 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/node.cjs +28 -8
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +3 -2
- package/dist/node.d.ts +3 -2
- package/dist/node.js +6 -2
- package/package.json +9 -2
- package/dist/chunk-KCAFXZVO.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node.ts","../src/config.ts","../src/managed-block.ts","../src/gitignore.ts","../src/init.ts"],"sourcesContent":["/**\n * bezel/node — Node entry.\n *\n * Re-exports the full isomorphic core, plus the filesystem conveniences:\n * read a DTCG tokens file and write the generated `variables.css` (and optional\n * `contexts.ts`/`fonts.ts`) to disk. This module imports `node:*` and is not\n * browser-safe; for the pure transform use `bezel`.\n */\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\"\nimport { dirname, relative, sep } from \"node:path\"\nimport { resolveOptions, type BezelOptions, type ResolvedOptions } from \"./config\"\nimport { emitCss } from \"./emit\"\nimport { formatContextsModule, getContexts } from \"./contexts\"\nimport { formatFontsModule, getFonts } from \"./fonts\"\nimport { mergeGitignore } from \"./gitignore\"\nimport type { DtcgNode } from \"./types\"\n\nexport * from \"./index\"\nexport { resolveOptions } from \"./config\"\nexport type { BezelOptions, ResolvedOptions } from \"./config\"\nexport { initConfig, readPackageScripts } from \"./init\"\nexport type { InitOptions, InitResult } from \"./init\"\nexport { DEFAULT_CONFIG_FILE, DEFAULT_OUTPUT_DIR, FALLBACK_OUTPUT_DIR } from \"./defaults\"\n\n/**\n * Generate a scoped `variables.css` from a W3C DTCG tokens file.\n *\n * Reads the tokens file, resolves each configured context (with `$root` fallback\n * and reference resolution), and writes one CSS scope per context — the base\n * context in full, others as override-only blocks. Returns the generated CSS.\n *\n * The tokens file is always read from `design-tokens.json` at the project root\n * (resolved against `cwd`); the name and location are fixed and not configurable.\n *\n * @example\n * // zero-config: ./design-tokens.json → ./src/bezel/variables.css\n * await generateVariablesCss()\n *\n * @example\n * await generateVariablesCss({\n * variablesOutput: \"packages/ui/src/bezel/variables.css\",\n * })\n */\nexport async function generateVariablesCss(options: BezelOptions = {}): Promise<string> {\n const resolved = resolveOptions(options)\n\n let raw: string\n try {\n raw = await readFile(resolved.inputPath, \"utf8\")\n } catch (err) {\n throw new Error(\n `bezel: could not read tokens file at \"${resolved.inputPath}\". ` +\n `A \"design-tokens.json\" file must exist at the project root.\\n${(err as Error).message}`,\n )\n }\n\n let tokens: DtcgNode\n try {\n tokens = JSON.parse(raw) as DtcgNode\n } catch (err) {\n throw new Error(`bezel: \"${resolved.inputPath}\" is not valid JSON.\\n${(err as Error).message}`)\n }\n\n const css = emitCss(tokens, resolved)\n\n if (resolved.write) {\n await mkdir(dirname(resolved.variablesOutputPath), { recursive: true })\n await writeFile(resolved.variablesOutputPath, css, \"utf8\")\n\n if (resolved.contextsOutput) {\n const contextsModule = formatContextsModule(getContexts(tokens))\n await mkdir(dirname(resolved.contextsOutput), { recursive: true })\n await writeFile(resolved.contextsOutput, contextsModule, \"utf8\")\n }\n\n if (resolved.fontsOutput) {\n const fontsModule = formatFontsModule(getFonts(tokens))\n await mkdir(dirname(resolved.fontsOutput), { recursive: true })\n await writeFile(resolved.fontsOutput, fontsModule, \"utf8\")\n }\n\n if (resolved.gitignore) {\n await updateGitignore(resolved)\n }\n }\n\n return css\n}\n\n/**\n * Create or update the project `.gitignore` so the generated outputs are ignored.\n * Outputs are written as paths relative to the `.gitignore`, anchored with a leading\n * `/`. Outputs outside the `.gitignore`'s directory can't be anchored from it and are\n * skipped with a warning. The file is only rewritten when its contents actually change.\n */\nasync function updateGitignore(resolved: ResolvedOptions): Promise<void> {\n const dir = dirname(resolved.gitignorePath)\n const outputs = [\n resolved.variablesOutputPath,\n resolved.contextsOutput,\n resolved.fontsOutput,\n ].filter((p): p is string => p !== null)\n\n const entries: string[] = []\n for (const output of outputs) {\n const rel = relative(dir, output)\n if (rel === \"\" || rel.startsWith(\"..\")) {\n process.stderr.write(\n `bezel: skipping .gitignore entry for \"${output}\" — outside ${resolved.gitignorePath}\\n`,\n )\n continue\n }\n entries.push(`/${rel.split(sep).join(\"/\")}`)\n }\n\n if (entries.length === 0) return\n\n let existing = \"\"\n try {\n existing = await readFile(resolved.gitignorePath, \"utf8\")\n } catch {\n // No existing .gitignore — create one.\n }\n\n const next = mergeGitignore(existing, entries)\n if (next !== existing) {\n await mkdir(dir, { recursive: true })\n await writeFile(resolved.gitignorePath, next, \"utf8\")\n }\n}\n","import { isAbsolute, resolve } from \"node:path\"\nimport { DEFAULT_INPUT, DEFAULT_VARIABLES_OUTPUT } from \"./defaults\"\nimport { resolveCssOptions, type CssOptions } from \"./options\"\n\nexport {\n DEFAULT_CONFIG_FILE,\n DEFAULT_CONTEXTS,\n DEFAULT_INPUT,\n DEFAULT_OUTPUT_DIR,\n DEFAULT_VARIABLES_OUTPUT,\n DEFAULT_NAME_EXTENSION,\n FALLBACK_OUTPUT_DIR,\n} from \"./defaults\"\n\n/**\n * Options for {@link generateVariablesCss} — the Node, file-oriented surface.\n * Extends the pure {@link CssOptions} with input/output paths. All optional;\n * defaults make the zero-config case work.\n */\nexport interface BezelOptions extends CssOptions {\n /**\n * The Bezel project this repo is linked to (a UUID). Read only by the Bezel MCP to\n * know which project to pull `design-tokens.json` from — ignored by `build`.\n */\n projectId?: string\n /**\n * Which tokens the Bezel MCP fetches: `\"latest\"` (the project's latest snapshot) or a\n * published semver like `\"1.4.0\"`. Missing means `\"latest\"`. Ignored by `build`.\n */\n version?: string\n /** Path to write the generated CSS. Default: `\"src/bezel/variables.css\"` (resolved from `cwd`). */\n variablesOutput?: string\n /** Base directory paths are resolved against. Default: `process.cwd()`. */\n cwd?: string\n /**\n * When set, also write a generated TypeScript module exporting `CONTEXTS` (the list of\n * context names derived from the tokens). Path resolved against `cwd`. Default: none.\n */\n contextsOutput?: string\n /**\n * When set, also write a generated TypeScript module exporting `FONTS` (the web fonts\n * derived from the tokens). Path resolved against `cwd`. Default: none.\n */\n fontsOutput?: string\n /** When `false`, return the CSS string without touching the filesystem. Default: `true`. */\n write?: boolean\n /**\n * Create/update a project `.gitignore` so the generated outputs are ignored. The\n * entries live in a marked block, rewritten on each run so renamed outputs are\n * cleaned up. Default: `true`.\n */\n gitignore?: boolean\n /** Path to the `.gitignore` to manage. Default: `\".gitignore\"` (resolved from `cwd`). */\n gitignorePath?: string\n}\n\n/** Fully-resolved options (no `undefined`), produced by {@link resolveOptions}. */\nexport interface ResolvedOptions {\n inputPath: string\n variablesOutputPath: string\n contexts: Record<string, string>\n colorFormat: \"oklch\" | \"hex\"\n dimensionUnit: \"preserve\" | \"rem\"\n nameExtension: string\n includeAll: boolean\n contextsOutput: string | null\n fontsOutput: string | null\n write: boolean\n gitignore: boolean\n gitignorePath: string\n}\n\n/** Apply defaults and resolve input/output paths against `cwd`. */\nexport function resolveOptions(options: BezelOptions = {}): ResolvedOptions {\n const cwd = options.cwd ?? process.cwd()\n const toAbs = (p: string) => (isAbsolute(p) ? p : resolve(cwd, p))\n\n const css = resolveCssOptions(options)\n\n return {\n // The tokens file is always `design-tokens.json` at the project root — not configurable.\n inputPath: toAbs(DEFAULT_INPUT),\n variablesOutputPath: toAbs(options.variablesOutput ?? DEFAULT_VARIABLES_OUTPUT),\n ...css,\n contextsOutput: options.contextsOutput ? toAbs(options.contextsOutput) : null,\n fontsOutput: options.fontsOutput ? toAbs(options.fontsOutput) : null,\n write: options.write ?? true,\n gitignore: options.gitignore ?? true,\n gitignorePath: toAbs(options.gitignorePath ?? \".gitignore\"),\n }\n}\n","/**\n * bezel — managed-block merging for text files Bezel shares with the user.\n *\n * Pure string transforms (no `node:fs`): given a file's existing contents and the\n * lines Bezel wants to own, produce the new contents. Bezel's lines live between a\n * start and end marker so repeated runs rewrite just that span — stale lines drop out,\n * nothing duplicates, and everything the user wrote outside the markers is preserved.\n * `.gitignore` uses `#` comment markers; `AGENTS.md` uses HTML comment markers.\n */\n\nexport interface BlockMarkers {\n /** Opening marker line. */\n start: string\n /** Closing marker line. */\n end: string\n}\n\n/**\n * Merge a managed block into existing file contents.\n *\n * - Empty `existing` → just the block.\n * - An existing block (delimited by the markers) → replaced in place, preserving\n * everything before and after.\n * - No block → appended after the existing content, separated by a blank line.\n *\n * The result always ends with a single trailing newline.\n */\nexport function mergeManagedBlock(\n existing: string,\n lines: string[],\n markers: BlockMarkers,\n): string {\n const block = [markers.start, ...lines, markers.end].join(\"\\n\")\n\n if (existing.trim() === \"\") return `${block}\\n`\n\n const start = existing.indexOf(markers.start)\n if (start !== -1) {\n const endMarker = existing.indexOf(markers.end, start)\n if (endMarker !== -1) {\n const before = existing.slice(0, start).replace(/\\n+$/, \"\")\n const after = existing.slice(endMarker + markers.end.length).replace(/^\\n+/, \"\")\n const head = before === \"\" ? \"\" : `${before}\\n\\n`\n const tail = after === \"\" ? \"\" : `\\n\\n${after.replace(/\\n+$/, \"\")}`\n return `${head}${block}${tail}\\n`\n }\n }\n\n return `${existing.replace(/\\n+$/, \"\")}\\n\\n${block}\\n`\n}\n","/**\n * bezel — `.gitignore` managed-block merging.\n *\n * Given the existing `.gitignore` contents and the list of generated outputs to\n * ignore, produce the new contents. Bezel's entries live inside a marked block so\n * repeated runs rewrite just that span — renamed/removed outputs drop out\n * automatically and entries never duplicate, while the user's own rules outside the\n * markers are preserved. See {@link mergeManagedBlock} for the shared mechanics.\n */\nimport { mergeManagedBlock } from \"./managed-block\"\n\n/** Opening marker for the Bezel-managed block. */\nexport const START = \"# bezel:start (generated — do not edit)\"\n/** Closing marker for the Bezel-managed block. */\nexport const END = \"# bezel:end\"\n\n/**\n * Merge Bezel's managed block into existing `.gitignore` contents.\n *\n * - Empty `existing` → just the block.\n * - An existing block (delimited by {@link START}…{@link END}) → replaced in place,\n * preserving everything before and after.\n * - No block → appended after the existing content, separated by a blank line.\n *\n * The result always ends with a single trailing newline.\n */\nexport function mergeGitignore(existing: string, entries: string[]): string {\n return mergeManagedBlock(existing, entries, { start: START, end: END })\n}\n","/**\n * bezel — `bezel.json` scaffolding.\n *\n * Writes the config file `bezel build` auto-loads, so a project doesn't have to\n * hand-author it. Defaults are derived from the project itself: outputs land in\n * `src/bezel/` when there's a `src/` (otherwise `bezel/`), and the TypeScript\n * modules are only scaffolded for a TypeScript project — `contexts.ts`/`fonts.ts`\n * are emitted as TS (`as const`, `export type`) and would be dead weight in a\n * JavaScript one.\n *\n * Non-destructive: an existing `bezel.json` is never overwritten without `force`.\n * The one exception is the project link: passing `projectId` and/or `version` to an\n * existing config updates just those keys in place, leaving everything else as-is.\n */\nimport { existsSync } from \"node:fs\"\nimport { readFile, writeFile } from \"node:fs/promises\"\nimport { isAbsolute, resolve } from \"node:path\"\nimport type { BezelOptions } from \"./config\"\nimport {\n CONTEXTS_FILE,\n DEFAULT_CONFIG_FILE,\n DEFAULT_INPUT,\n DEFAULT_OUTPUT_DIR,\n FALLBACK_OUTPUT_DIR,\n FONTS_FILE,\n VARIABLES_FILE,\n} from \"./defaults\"\n\n/** Options for {@link initConfig}. All optional — the zero-arg call is the happy path. */\nexport interface InitOptions {\n /** Project root to scaffold into. Default: `process.cwd()`. */\n cwd?: string\n /**\n * Directory for all three outputs, relative to `cwd`. Default: `\"src/bezel\"` when\n * the project has a `src/`, otherwise `\"bezel\"`. Ignored for any output whose path\n * is set explicitly.\n */\n outputDir?: string\n /** Explicit path for the generated CSS. Overrides {@link InitOptions.outputDir}. */\n variablesOutput?: string\n /** Explicit path for the generated contexts module. Implies `contexts: true`. */\n contextsOutput?: string\n /** Explicit path for the generated fonts module. Implies `fonts: true`. */\n fontsOutput?: string\n /** Include `contextsOutput`. Default: true for a TypeScript project. */\n contexts?: boolean\n /** Include `fontsOutput`. Default: true for a TypeScript project. */\n fonts?: boolean\n /** Written to the config only when set — otherwise the build default applies. */\n colorFormat?: \"oklch\" | \"hex\"\n /** Written to the config only when set — otherwise the build default applies. */\n dimensionUnit?: \"preserve\" | \"rem\"\n /** Written to the config only when `true` — otherwise the build default (`false`) applies. */\n includeAll?: boolean\n /** Overwrite an existing `bezel.json`. Default: `false`. */\n force?: boolean\n /**\n * The Bezel project to link this repo to (a UUID). Written as `projectId`; on an\n * existing config it updates just that key without needing `force`.\n */\n projectId?: string\n /**\n * Tokens version the Bezel MCP fetches: `\"latest\"` or a semver like `\"1.4.0\"`.\n * Always written — defaults to `\"latest\"` on a fresh init; on an existing config it\n * updates just that key without needing `force`.\n */\n version?: string\n}\n\n/** Outcome of {@link initConfig}. */\nexport interface InitResult {\n /** Absolute path of the config file. */\n configPath: string\n /** The config that was written — or the one that would have been, when `written` is false. */\n config: BezelOptions\n /** False when an existing `bezel.json` was left untouched (no `force`). */\n written: boolean\n /** Whether a `design-tokens.json` is already present at the project root. */\n hasTokensFile: boolean\n /** Whether the project looks like TypeScript (drives the contexts/fonts defaults). */\n isTypeScript: boolean\n /** True when only `projectId`/`version` were merged into an existing `bezel.json`. */\n updated: boolean\n /** The `projectId` that was in the file before an update, if any. */\n previousProjectId?: string\n /** The `version` that was in the file before an update, if any. */\n previousVersion?: string\n}\n\n/** Whether `value` is a UUID — the shape of a Bezel project id. */\nexport const isUuid = (value: string): boolean =>\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)\n\n/** Whether `value` is a valid tokens version: `\"latest\"` or a semver like `1.4.0`. */\nexport const isTokensVersion = (value: string): boolean =>\n value === \"latest\" || /^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?$/.test(value)\n\n/**\n * Join config path segments with forward slashes.\n *\n * Config values are portable JSON, so they always use `/` — `node:path.join` would\n * emit backslashes on Windows and bake a platform into a committed file.\n */\nconst joinPath = (dir: string, file: string): string =>\n dir === \"\" ? file : `${dir.replace(/\\/+$/, \"\")}/${file}`\n\n/**\n * Create a `bezel.json` in the project root, inferring sensible defaults from the\n * project layout.\n *\n * @example\n * // src/bezel/{variables.css,contexts.ts,fonts.ts} for a TS project with a src/\n * await initConfig()\n *\n * @example\n * await initConfig({ outputDir: \"packages/ui/src/bezel\", fonts: false })\n */\nexport async function initConfig(options: InitOptions = {}): Promise<InitResult> {\n if (options.projectId !== undefined && !isUuid(options.projectId)) {\n throw new Error(`bezel: --project must be a UUID, got \"${options.projectId}\"`)\n }\n if (options.version !== undefined && !isTokensVersion(options.version)) {\n throw new Error(\n `bezel: --tokens-version must be \"latest\" or a semver like 1.4.0, got \"${options.version}\"`,\n )\n }\n\n const cwd = options.cwd ?? process.cwd()\n const toAbs = (p: string) => (isAbsolute(p) ? p : resolve(cwd, p))\n\n const configPath = toAbs(DEFAULT_CONFIG_FILE)\n const hasTokensFile = existsSync(toAbs(DEFAULT_INPUT))\n const isTypeScript = existsSync(toAbs(\"tsconfig.json\"))\n\n const hasSrc = existsSync(toAbs(\"src\"))\n const outputDir = options.outputDir ?? (hasSrc ? DEFAULT_OUTPUT_DIR : FALLBACK_OUTPUT_DIR)\n\n // An explicit path is itself an opt-in; otherwise TypeScript projects get the\n // generated modules and JavaScript ones don't.\n const wantsContexts = options.contextsOutput !== undefined || (options.contexts ?? isTypeScript)\n const wantsFonts = options.fontsOutput !== undefined || (options.fonts ?? isTypeScript)\n\n // Link keys come first so the file reads project → version → outputs.\n const config: BezelOptions = {}\n if (options.projectId !== undefined) config.projectId = options.projectId\n config.version = options.version ?? \"latest\"\n config.variablesOutput = options.variablesOutput ?? joinPath(outputDir, VARIABLES_FILE)\n if (wantsContexts) {\n config.contextsOutput = options.contextsOutput ?? joinPath(outputDir, CONTEXTS_FILE)\n }\n if (wantsFonts) {\n config.fontsOutput = options.fontsOutput ?? joinPath(outputDir, FONTS_FILE)\n }\n // Only persist the transform options that were asked for, so the file stays minimal\n // and keeps tracking the library defaults for everything else.\n if (options.colorFormat) config.colorFormat = options.colorFormat\n if (options.dimensionUnit) config.dimensionUnit = options.dimensionUnit\n if (options.includeAll) config.includeAll = true\n\n if (existsSync(configPath) && !options.force) {\n const wantsLinkUpdate = options.projectId !== undefined || options.version !== undefined\n if (!wantsLinkUpdate) {\n return { configPath, config, written: false, updated: false, hasTokensFile, isTypeScript }\n }\n\n // Link-only update: replace just the two link keys, keep every other key in place.\n const existing = JSON.parse(await readFile(configPath, \"utf8\")) as BezelOptions\n const { projectId: previousProjectId, version: previousVersion, ...rest } = existing\n const merged: BezelOptions = {}\n const projectId = options.projectId ?? previousProjectId\n if (projectId !== undefined) merged.projectId = projectId\n merged.version = options.version ?? previousVersion ?? \"latest\"\n Object.assign(merged, rest)\n\n await writeFile(configPath, `${JSON.stringify(merged, null, 2)}\\n`, \"utf8\")\n\n return {\n configPath,\n config: merged,\n written: true,\n updated: true,\n previousProjectId,\n previousVersion,\n hasTokensFile,\n isTypeScript,\n }\n }\n\n await writeFile(configPath, `${JSON.stringify(config, null, 2)}\\n`, \"utf8\")\n\n return { configPath, config, written: true, updated: false, hasTokensFile, isTypeScript }\n}\n\n/**\n * Read the `\"scripts\"` block of the project's `package.json`, if there is one.\n * Used only to tailor the post-init hint — a missing or unparsable file is not an error.\n */\nexport async function readPackageScripts(cwd: string): Promise<Record<string, string>> {\n try {\n const raw = await readFile(resolve(cwd, \"package.json\"), \"utf8\")\n const parsed = JSON.parse(raw) as { scripts?: Record<string, string> }\n return parsed.scripts ?? {}\n } catch {\n return {}\n }\n}\n"],"mappings":"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","names":["readFile","writeFile","isAbsolute","resolve","isAbsolute","resolve","readFile","writeFile"]}
|
package/dist/cli.cjs
CHANGED
|
@@ -25,10 +25,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
27
|
var import_node_fs2 = require("fs");
|
|
28
|
-
var
|
|
28
|
+
var import_promises5 = require("fs/promises");
|
|
29
29
|
var import_node_url = require("url");
|
|
30
30
|
var import_node_util = require("util");
|
|
31
|
-
var
|
|
31
|
+
var import_node_path6 = require("path");
|
|
32
32
|
|
|
33
33
|
// src/node.ts
|
|
34
34
|
var import_promises2 = require("fs/promises");
|
|
@@ -428,19 +428,17 @@ export type FontDef = { family: string; weights: number[] }
|
|
|
428
428
|
`;
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
-
// src/
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
function mergeGitignore(existing, entries) {
|
|
435
|
-
const block = [START, ...entries, END].join("\n");
|
|
431
|
+
// src/managed-block.ts
|
|
432
|
+
function mergeManagedBlock(existing, lines, markers) {
|
|
433
|
+
const block = [markers.start, ...lines, markers.end].join("\n");
|
|
436
434
|
if (existing.trim() === "") return `${block}
|
|
437
435
|
`;
|
|
438
|
-
const start = existing.indexOf(
|
|
436
|
+
const start = existing.indexOf(markers.start);
|
|
439
437
|
if (start !== -1) {
|
|
440
|
-
const endMarker = existing.indexOf(
|
|
438
|
+
const endMarker = existing.indexOf(markers.end, start);
|
|
441
439
|
if (endMarker !== -1) {
|
|
442
440
|
const before = existing.slice(0, start).replace(/\n+$/, "");
|
|
443
|
-
const after = existing.slice(endMarker +
|
|
441
|
+
const after = existing.slice(endMarker + markers.end.length).replace(/^\n+/, "");
|
|
444
442
|
const head = before === "" ? "" : `${before}
|
|
445
443
|
|
|
446
444
|
`;
|
|
@@ -457,6 +455,26 @@ ${block}
|
|
|
457
455
|
`;
|
|
458
456
|
}
|
|
459
457
|
|
|
458
|
+
// src/gitignore.ts
|
|
459
|
+
var START = "# bezel:start (generated \u2014 do not edit)";
|
|
460
|
+
var END = "# bezel:end";
|
|
461
|
+
function mergeGitignore(existing, entries) {
|
|
462
|
+
return mergeManagedBlock(existing, entries, { start: START, end: END });
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/agent-skill.ts
|
|
466
|
+
var AGENT_SKILL = {
|
|
467
|
+
name: "bezel",
|
|
468
|
+
description: "Use when the repo has bezel.json or design-tokens.json and the task touches design tokens, theme colors, spacing, typography, radii, CSS variables, dark mode, or files under the Bezel output folder (src/bezel by default). Edit design-tokens.json, never the generated variables.css / contexts.ts / fonts.ts, then run the tokens build. Also covers pulling the latest tokens from the Bezel app and using the Bezel MCP tools.",
|
|
469
|
+
files: {
|
|
470
|
+
"SKILL.md": '---\nname: bezel\ndescription: Use when the repo has bezel.json or design-tokens.json and the task touches design tokens, theme colors, spacing, typography, radii, CSS variables, dark mode, or files under the Bezel output folder (src/bezel by default). Edit design-tokens.json, never the generated variables.css / contexts.ts / fonts.ts, then run the tokens build. Also covers pulling the latest tokens from the Bezel app and using the Bezel MCP tools.\n---\n\n# Bezel in this repository\n\nBezel is a design-token platform. Designers maintain the tokens in the Bezel app; this repo holds an exported copy and generates CSS from it with `@bezel-labs/bezel-kit`.\n\n```\nBezel app (cloud, source of truth)\n -> design-tokens.json DTCG file at the repo root, exported from Bezel\n -> bezel build reads bezel.json for options and output paths\n -> variables.css one CSS scope per context (:root, .dark, .light, ...)\n contexts.ts, fonts.ts optional generated TypeScript modules\n```\n\nNothing in this repo pushes back to Bezel. A change that must survive the next sync belongs in the Bezel app.\n\n## Hard rules\n\n1. Never edit a generated file. They start with `/* Generated by bezel \u2014 do not edit by hand. */` and live at the paths named in `bezel.json` (`variablesOutput`, `contextsOutput`, `fontsOutput`; default folder `src/bezel/`). `bezel build` overwrites them without asking and they are gitignored.\n2. To change a token value or add a variable, edit `design-tokens.json`, then rebuild. Never hand-write the CSS variable.\n3. After any change to `design-tokens.json` or `bezel.json`, run the tokens build: `npm run tokens` when that script exists, otherwise `npx bezel build` (use the repo\'s package manager runner). Confirm the outputs were rewritten before finishing.\n4. Only tokens with an `exportName` become CSS variables unless `bezel.json` sets `"includeAll": true`. Adding a token without an `exportName` produces no variable.\n5. Never pass `--force` to `bezel init`; it discards the existing `bezel.json`. `--project` and `--tokens-version` update their own keys without it.\n6. Do not start dev servers, preview servers, or watchers to verify a change. Rebuild and inspect the generated CSS instead.\n7. Local edits to `design-tokens.json` are overwritten by the next sync from Bezel. Tell the user when a change should be made in the Bezel app instead, and never silently sync over their local edits.\n\n## The tokens file\n\n`design-tokens.json` follows the W3C Design Tokens (DTCG) format. Every token is an object with a `$type` and one `$value` per context:\n\n```json\n"semantic": {\n "color": {\n "primary": {\n "$type": "color",\n "$root": { "$value": "{base.color.green.500}" },\n "dark": { "$value": { "colorSpace": "oklch", "components": [0.85, 0.16, 152] } },\n "$extensions": { "com.bezel.app": { "exportName": ["primary", "ring"] } }\n }\n }\n}\n```\n\n- Context keys (`$root`, `dark`, `light`, or whatever `bezel.json` `contexts` lists) sit inside the token. `$root` is the base; other contexts only need the values that differ.\n- `$value` is either a literal (DTCG color, dimension, font family, ...) or a reference like `{base.color.green.500}`. References are resolved to literal values in the generated CSS.\n- `$extensions["com.bezel.app"].exportName` is the list of CSS variable names the token emits. One token can emit several (`foreground`, `card-foreground`). An empty list means no variable.\n- Groups (`base`, `semantic`, `color`, ...) are plain nesting with no `$type`.\n\n## What to do for common requests\n\n| Request | Edit | Then |\n|---|---|---|\n| Change a color, size, radius, or font value | The token\'s `$value` in the right context key (`$root` for every mode, `dark` for dark mode only) | Rebuild |\n| A value should differ in dark mode | Add or edit the `dark` key on that token | Rebuild |\n| New CSS variable | Add a token with `$type`, `$root.$value`, and an `exportName` (or reference an existing token) | Rebuild |\n| Rename a CSS variable | Change the `exportName` entry; update every consumer of the old `--name` | Rebuild |\n| New theme or brand context | Add the context to `bezel.json` `contexts` (name -> selector) and add that key to the tokens that differ | Rebuild |\n| Change where files are generated | Edit `variablesOutput` / `contextsOutput` / `fontsOutput` in `bezel.json`; update the CSS import | Rebuild, delete the old outputs |\n| "Pull the latest tokens" / "sync from Bezel" | See the next section | Rebuild |\n\nFind the token for an existing variable by searching `design-tokens.json` for its name inside an `exportName` array.\n\n## Pulling the latest tokens from Bezel\n\n`bezel.json` `projectId` and `version` (`"latest"` or a published semver) say which Bezel project and snapshot this repo tracks.\n\nWith the Bezel MCP connected: call `get-design-tokens-file` with that `projectId` and `version`, write the returned `content` string to `design-tokens.json` verbatim (no reformatting), then rebuild. The `sync-project` prompt walks through this, including linking a project when `projectId` is missing. Never pick a project or version yourself; ask when it is ambiguous.\n\nWithout the MCP: ask the user to download `design-tokens.json` from the project in the Bezel app and replace the file at the repo root, then rebuild.\n\nWarn before overwriting a `design-tokens.json` that has local edits.\n\n## With the Bezel MCP connected\n\nPrefer small, targeted calls over reading the whole tokens file:\n\n- `get-project-manifest` first: groups, counts, types, contexts, no values.\n- `query-tokens` to find tokens by group, type, name, or `exportName`; `get-token-by-path` for one token with every context value.\n- `get-token-bundle` for a curated set per task (`theming-setup`, `button`, `card`, `form`, ...).\n- `export-tokens` and `get-framework-overrides` to generate CSS, JS/TS objects, or Tailwind / shadcn / Mantine config from tokens.\n- `contrast-check`, `color-convert`, `color-ramp`, `unit-convert` for deterministic math instead of estimating.\n- `design-review` to check a component description against the tokens.\n\nSee `references/mcp-tools.md` for the full list and `references/bezel-json.md` for every config field.\n\n## Setting up a repo that is not on Bezel yet\n\nRun the `setup-project` tool (or the `/mcp__bezel__setup-project` prompt) and follow it; it installs `@bezel-labs/bezel-kit`, writes `bezel.json`, fetches the tokens, builds, and wires the CSS import. Do not improvise the steps.\n',
|
|
471
|
+
"references/bezel-json.md": '# bezel.json reference\n\n`bezel.json` lives at the repo root (or the working directory `bezel build` runs in). `bezel build` loads it automatically; `bezel init` writes it. The tokens input is always `design-tokens.json` in that same directory and is not configurable.\n\n| Field | Default | Used by | Meaning |\n|---|---|---|---|\n| `projectId` | none | MCP | UUID of the Bezel project this repo tracks. Set with `npx bezel init --project <uuid>`. |\n| `version` | `"latest"` | MCP | Which tokens snapshot to fetch: `"latest"` or a published semver such as `"1.4.0"`. Set with `--tokens-version`. |\n| `variablesOutput` | `"src/bezel/variables.css"` | build | Generated CSS file. Import this from the app entry. |\n| `contextsOutput` | none | build | When set, writes a TypeScript module exporting `CONTEXTS` (the context names). |\n| `fontsOutput` | none | build | When set, writes a TypeScript module exporting `FONTS` (web fonts derived from the tokens). |\n| `contexts` | `{ "$root": ":root", "dark": ".dark", "light": ".light" }` | build | Context name -> CSS selector. The first entry is the base context and is emitted in full; the rest are override-only blocks. |\n| `colorFormat` | `"oklch"` | build | `"oklch"` or `"hex"`. |\n| `dimensionUnit` | `"preserve"` | build | `"preserve"` keeps authored units; `"rem"` converts `px` to `rem`. |\n| `nameExtension` | `"com.bezel.app"` | build | The `$extensions` key that holds `exportName`. Only change for files exported before the 0.3.0 rename. |\n| `includeAll` | `false` | build | Also emit a variable for every token without an `exportName`, named by its kebab-cased path (`--base-color-primary-500`). |\n| `write` | `true` | build | `false` returns the CSS without writing files (programmatic use). |\n| `gitignore` | `true` | build | Maintain a `# bezel:start` ... `# bezel:end` block in `.gitignore` listing the generated outputs. |\n| `gitignorePath` | `".gitignore"` | build | Which `.gitignore` to manage. |\n| `cwd` | process cwd | build | Base directory paths resolve against. |\n\n`projectId` and `version` are ignored by `bezel build`; everything else is ignored by the MCP.\n\n## CLI\n\n```\nbezel init [--project <uuid>] [--tokens-version <v>] [--dir <path>] ... create or update bezel.json\nbezel build [--stdout] [--no-gitignore] ... generate the outputs (default command)\nbezel install-mcp [--client <name>] connect an AI coding tool to the Bezel MCP\nbezel install-skills [--client <name>] [--no-agents-md] install this skill into the repo\n```\n\n`bezel init` on an existing `bezel.json` with `--project` or `--tokens-version` updates only those keys. Any other re-run leaves the file alone unless `--force` is passed, which agents must not do.\n',
|
|
472
|
+
"references/mcp-tools.md": "# Bezel MCP quick reference\n\nRemote server at `https://mcp.bezel.new/mcp` (Streamable HTTP, OAuth). The server returns data and instructions; the coding agent performs every local file edit and command. Project-scoped tools take the `projectId` from `bezel.json` or from `find-project`.\n\n## Guided workflows\n\n| Tool / prompt | Use |\n|---|---|\n| `setup-project` (tool and prompt) | Resumable setup: install bezel-kit, link a project, write `bezel.json`, fetch tokens, build, wire the CSS import, optional Crystal live preview. |\n| `sync-project` (prompt) | Refresh `design-tokens.json` from the linked project and version, then rebuild. |\n| `safe-usage-guide` (prompt) | Context-budget rules: manifest first, targeted queries, cite token paths. |\n| `manifest-first-workflow`, `context-resolution` (prompts) | Recommended reading order and how contexts resolve. |\n| `get-agent-guide` (tool) | Returns this skill's text when the repo has no local copy. |\n\n## Discovery\n\n| Tool | Parameters | Returns |\n|---|---|---|\n| `find-project` | `query?` | All owned projects (name + id), or one project resolved from a name or UUID. |\n| `get-project-manifest` | `projectId` | Groups, token counts, types, contexts. No values. Call this first. |\n\n## Repo sync\n\n| Tool | Parameters | Returns |\n|---|---|---|\n| `get-design-tokens-file` | `projectId`, `version?` | The exact `design-tokens.json` content to write to disk, plus project name and version. Unknown version -> list of available versions. |\n\n## Reading tokens\n\n| Tool | Parameters | Returns |\n|---|---|---|\n| `query-tokens` | `projectId`, `groupPath?`, `tokenType?`, `search?`, `exportName?`, `contextKey?`, `resolveAliases?` | Filtered tokens. |\n| `get-token-by-path` | `projectId`, `tokenPath`, `contextKey?`, `resolveAliases?` | One token with every context value and alias detail. |\n| `get-token-bundle` | `projectId`, `intent` | Curated set: `theming-setup`, `button`, `card`, `input`, `form`, `auth-page`, `dashboard-shell`. |\n| `get-project-contexts` | `projectId` | Context groups and values (theme modes, platforms, brands). |\n| `resolve-context` | `projectId`, `contextGroup`, `contextValue` | Canonical context value or a structured error. |\n\n## Generating code\n\n| Tool | Parameters | Returns |\n|---|---|---|\n| `export-tokens` | `projectId`, `format` (`css-variables`, `js-object`, `ts-object`), filters, `prefix?`, `includeAll?` | Generated code with aliases resolved. |\n| `get-framework-overrides` | `projectId`, `framework` (`tailwind`, `shadcn`, `mantine`), `version?`, filters | Framework config derived from the tokens. |\n\n## Color and unit math (deterministic)\n\n| Tool | Parameters |\n|---|---|\n| `contrast-check` | `foreground`, `background` -> WCAG ratio, grade, recommendations |\n| `color-convert` | `color`, `targetFormat`, `manipulation?`, `mixColor?` |\n| `color-ramp` | `baseColor`, `steps`, `endColor?`, `colorSpace?` |\n| `unit-convert` | `value`, `fromUnit`, `toUnit`, `rootFontSize?`, `parentFontSize?` |\n\n## Review and history\n\n| Tool | Parameters |\n|---|---|\n| `design-review` | `projectId`, `componentDescription`, `tokenPathsUsed?`, `contextKey?` |\n| `get-chat-history` | `projectId` |\n| `search-chat-history` | `projectId`, `query` |\n\n## Resources\n\n- `bezel://projects`: your projects with ids and names.\n- `bezel://agent-guide`: this skill's text.\n"
|
|
473
|
+
},
|
|
474
|
+
agentsMdBlock: '## Bezel design tokens\n\n- `design-tokens.json` at the repo root is the source for every design token. Edit it to change a color, size, font, or radius, or to add a CSS variable (add an `exportName` under `$extensions["com.bezel.app"]`).\n- Files under the Bezel output folder (`variablesOutput`, `contextsOutput`, `fontsOutput` in `bezel.json`; `src/bezel/` by default) are generated. Never edit them; they are overwritten on every build.\n- After changing `design-tokens.json` or `bezel.json`, run `npm run tokens` (or `npx bezel build`) and check the outputs were rewritten.\n- Dark mode and other contexts are keys inside each token (`$root`, `dark`, `light`). Add a value under the context key instead of writing a CSS override.\n- The Bezel app is the source of truth. Local token edits are replaced by the next sync (`get-design-tokens-file` via the Bezel MCP, or a download from the app), so tell the user when a change should be made in Bezel.\n- Never run `bezel init --force`. Never start dev servers to verify token changes.\n- Full guidance: the `bezel` skill in `.claude/skills/bezel/SKILL.md` or `.agents/skills/bezel/SKILL.md`.',
|
|
475
|
+
summary: 'Use when the repo has bezel.json or design-tokens.json and the task touches design tokens, theme colors, spacing, typography, radii, CSS variables, dark mode, or files under the Bezel output folder (src/bezel by default). Edit design-tokens.json, never the generated variables.css / contexts.ts / fonts.ts, then run the tokens build. Also covers pulling the latest tokens from the Bezel app and using the Bezel MCP tools.\n\n## Hard rules\n\n1. Never edit a generated file. They start with `/* Generated by bezel \u2014 do not edit by hand. */` and live at the paths named in `bezel.json` (`variablesOutput`, `contextsOutput`, `fontsOutput`; default folder `src/bezel/`). `bezel build` overwrites them without asking and they are gitignored.\n2. To change a token value or add a variable, edit `design-tokens.json`, then rebuild. Never hand-write the CSS variable.\n3. After any change to `design-tokens.json` or `bezel.json`, run the tokens build: `npm run tokens` when that script exists, otherwise `npx bezel build` (use the repo\'s package manager runner). Confirm the outputs were rewritten before finishing.\n4. Only tokens with an `exportName` become CSS variables unless `bezel.json` sets `"includeAll": true`. Adding a token without an `exportName` produces no variable.\n5. Never pass `--force` to `bezel init`; it discards the existing `bezel.json`. `--project` and `--tokens-version` update their own keys without it.\n6. Do not start dev servers, preview servers, or watchers to verify a change. Rebuild and inspect the generated CSS instead.\n7. Local edits to `design-tokens.json` are overwritten by the next sync from Bezel. Tell the user when a change should be made in the Bezel app instead, and never silently sync over their local edits.'
|
|
476
|
+
};
|
|
477
|
+
|
|
460
478
|
// src/init.ts
|
|
461
479
|
var import_node_fs = require("fs");
|
|
462
480
|
var import_promises = require("fs/promises");
|
|
@@ -607,8 +625,131 @@ async function updateGitignore(resolved) {
|
|
|
607
625
|
|
|
608
626
|
// src/install-mcp.ts
|
|
609
627
|
var import_node_child_process = require("child_process");
|
|
628
|
+
var import_promises4 = require("fs/promises");
|
|
629
|
+
var import_node_path5 = require("path");
|
|
630
|
+
|
|
631
|
+
// src/install-skills.ts
|
|
610
632
|
var import_promises3 = require("fs/promises");
|
|
611
633
|
var import_node_path4 = require("path");
|
|
634
|
+
var SKILL_CLIENTS = ["claude-code", "codex", "cursor", "all"];
|
|
635
|
+
var AGENTS_MD_START = "<!-- bezel:start (generated by bezel install-skills \u2014 do not edit) -->";
|
|
636
|
+
var AGENTS_MD_END = "<!-- bezel:end -->";
|
|
637
|
+
var CLAUDE_SKILLS_DIR = ".claude/skills";
|
|
638
|
+
var AGENTS_SKILLS_DIR = ".agents/skills";
|
|
639
|
+
function skillDirectories(client) {
|
|
640
|
+
if (client === "claude-code") return [CLAUDE_SKILLS_DIR];
|
|
641
|
+
if (client === "codex" || client === "cursor") return [AGENTS_SKILLS_DIR];
|
|
642
|
+
return [CLAUDE_SKILLS_DIR, AGENTS_SKILLS_DIR];
|
|
643
|
+
}
|
|
644
|
+
function mergeAgentsMd(existing) {
|
|
645
|
+
return mergeManagedBlock(existing, AGENT_SKILL.agentsMdBlock.split("\n"), {
|
|
646
|
+
start: AGENTS_MD_START,
|
|
647
|
+
end: AGENTS_MD_END
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
async function readIfExists(path) {
|
|
651
|
+
try {
|
|
652
|
+
return await (0, import_promises3.readFile)(path, "utf8");
|
|
653
|
+
} catch (error) {
|
|
654
|
+
if (error.code === "ENOENT") return void 0;
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async function writeManaged(cwd, path, content, result) {
|
|
659
|
+
const rel = (0, import_node_path4.relative)(cwd, path);
|
|
660
|
+
const existing = await readIfExists(path);
|
|
661
|
+
if (existing === content) {
|
|
662
|
+
result.unchanged.push(rel);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(path), { recursive: true });
|
|
666
|
+
if (existing !== void 0) {
|
|
667
|
+
try {
|
|
668
|
+
await (0, import_promises3.writeFile)(`${path}.bezel-backup`, existing, { flag: "wx", mode: 384 });
|
|
669
|
+
result.backedUp.push(rel);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
if (error.code !== "EEXIST") throw error;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
await (0, import_promises3.writeFile)(path, content, "utf8");
|
|
675
|
+
result.written.push(rel);
|
|
676
|
+
}
|
|
677
|
+
async function installSkills(options) {
|
|
678
|
+
const cwd = (0, import_node_path4.resolve)(options.cwd ?? process.cwd());
|
|
679
|
+
const result = { written: [], unchanged: [], backedUp: [], hints: [] };
|
|
680
|
+
for (const dir of skillDirectories(options.client)) {
|
|
681
|
+
for (const [file, content] of Object.entries(AGENT_SKILL.files)) {
|
|
682
|
+
await writeManaged(cwd, (0, import_node_path4.join)(cwd, dir, AGENT_SKILL.name, file), content, result);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (options.agentsMd ?? true) {
|
|
686
|
+
const agentsPath = (0, import_node_path4.join)(cwd, "AGENTS.md");
|
|
687
|
+
const existing = await readIfExists(agentsPath) ?? "";
|
|
688
|
+
const merged = mergeAgentsMd(existing);
|
|
689
|
+
if (merged === existing) {
|
|
690
|
+
result.unchanged.push("AGENTS.md");
|
|
691
|
+
} else {
|
|
692
|
+
await (0, import_promises3.writeFile)(agentsPath, merged, "utf8");
|
|
693
|
+
result.written.push("AGENTS.md");
|
|
694
|
+
}
|
|
695
|
+
const claudeMd = await readIfExists((0, import_node_path4.join)(cwd, "CLAUDE.md"));
|
|
696
|
+
if (claudeMd !== void 0 && !claudeMd.includes("@AGENTS.md")) {
|
|
697
|
+
result.hints.push(
|
|
698
|
+
"CLAUDE.md exists and does not include AGENTS.md. Add a line containing `@AGENTS.md` to CLAUDE.md so Claude Code loads the Bezel rules."
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return result;
|
|
703
|
+
}
|
|
704
|
+
function skillClientForMcpClient(client) {
|
|
705
|
+
if (client === "claude-code" || client === "codex" || client === "cursor") return client;
|
|
706
|
+
return "all";
|
|
707
|
+
}
|
|
708
|
+
async function installSkillsCommand(clientName, options = {}) {
|
|
709
|
+
let choice = clientName;
|
|
710
|
+
if (!choice) {
|
|
711
|
+
if (!process.stdin.isTTY) {
|
|
712
|
+
throw new Error(`Choose a client with --client ${SKILL_CLIENTS.join("|")}.`);
|
|
713
|
+
}
|
|
714
|
+
const { default: select } = await import("@inquirer/select");
|
|
715
|
+
try {
|
|
716
|
+
choice = await select({
|
|
717
|
+
message: "Install the Bezel skill for:",
|
|
718
|
+
choices: [
|
|
719
|
+
{ name: "Claude Code (.claude/skills)", value: "claude-code" },
|
|
720
|
+
{ name: "Codex (.agents/skills)", value: "codex" },
|
|
721
|
+
{ name: "Cursor (.agents/skills)", value: "cursor" },
|
|
722
|
+
{ name: "All of the above", value: "all" }
|
|
723
|
+
]
|
|
724
|
+
});
|
|
725
|
+
} catch (error) {
|
|
726
|
+
if (error instanceof Error && error.name === "ExitPromptError") {
|
|
727
|
+
console.log("Setup cancelled.");
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
throw error;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (!SKILL_CLIENTS.includes(choice))
|
|
734
|
+
throw new Error(`Unknown client: ${choice}. Choose ${SKILL_CLIENTS.join(", ")}.`);
|
|
735
|
+
const result = await installSkills({
|
|
736
|
+
cwd: options.cwd,
|
|
737
|
+
client: choice,
|
|
738
|
+
agentsMd: options.agentsMd
|
|
739
|
+
});
|
|
740
|
+
reportInstallSkills(result);
|
|
741
|
+
}
|
|
742
|
+
function reportInstallSkills(result) {
|
|
743
|
+
for (const file of result.written) console.log(`Bezel skill: wrote ${file}`);
|
|
744
|
+
for (const file of result.backedUp) console.log(`Bezel skill: backed up ${file}.bezel-backup`);
|
|
745
|
+
if (result.written.length === 0) console.log("Bezel skill: already up to date.");
|
|
746
|
+
for (const hint of result.hints) console.log(`Note: ${hint}`);
|
|
747
|
+
console.log(
|
|
748
|
+
"Next: open this repo in your assistant. The Bezel skill loads automatically when a task touches design tokens."
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/install-mcp.ts
|
|
612
753
|
var MCP_URL = "https://mcp.bezel.new/mcp";
|
|
613
754
|
var CLIENTS = ["claude-code", "codex", "cursor", "vscode", "other"];
|
|
614
755
|
function clientCommand(client) {
|
|
@@ -657,7 +798,7 @@ function mergeMcpConfig(raw, client) {
|
|
|
657
798
|
return `${JSON.stringify({ ...config, [key]: { ...servers, bezel: entry } }, null, 2)}
|
|
658
799
|
`;
|
|
659
800
|
}
|
|
660
|
-
async function installMcp(clientName) {
|
|
801
|
+
async function installMcp(clientName, options = {}) {
|
|
661
802
|
let choice = clientName;
|
|
662
803
|
if (!choice) {
|
|
663
804
|
if (!process.stdin.isTTY) {
|
|
@@ -713,11 +854,11 @@ async function installMcp(clientName) {
|
|
|
713
854
|
);
|
|
714
855
|
}
|
|
715
856
|
} else if (client === "cursor" || client === "vscode") {
|
|
716
|
-
const path = (0,
|
|
857
|
+
const path = (0, import_node_path5.resolve)(client === "cursor" ? ".cursor/mcp.json" : ".vscode/mcp.json");
|
|
717
858
|
let raw = "{}";
|
|
718
859
|
let exists = false;
|
|
719
860
|
try {
|
|
720
|
-
raw = await (0,
|
|
861
|
+
raw = await (0, import_promises4.readFile)(path, "utf8");
|
|
721
862
|
exists = true;
|
|
722
863
|
} catch (error) {
|
|
723
864
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -731,9 +872,9 @@ async function installMcp(clientName) {
|
|
|
731
872
|
);
|
|
732
873
|
}
|
|
733
874
|
if (merged !== raw) {
|
|
734
|
-
await (0,
|
|
735
|
-
if (exists) await (0,
|
|
736
|
-
await (0,
|
|
875
|
+
await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
|
|
876
|
+
if (exists) await (0, import_promises4.writeFile)(`${path}.bezel-backup`, raw, { flag: "wx", mode: 384 });
|
|
877
|
+
await (0, import_promises4.writeFile)(path, merged, { flag: exists ? "w" : "wx" });
|
|
737
878
|
}
|
|
738
879
|
console.log(`Bezel configured in ${path}`);
|
|
739
880
|
} else {
|
|
@@ -746,20 +887,51 @@ Client instructions: https://docs.bezel.new/docs/mcp-server/setup`
|
|
|
746
887
|
client === "codex" ? "Next: run `codex mcp login bezel` and complete browser sign-in." : client === "claude-code" ? "Next: open Claude Code, run /mcp, select bezel, and sign in." : "Next: open or reload your coding tool and sign in to Bezel when prompted."
|
|
747
888
|
);
|
|
748
889
|
console.log(
|
|
749
|
-
'\nThen open your current project in the assistant and ask:\n"Set up Bezel in this project.
|
|
890
|
+
'\nThen open your current project in the assistant and ask:\n"Set up Bezel in this project."'
|
|
750
891
|
);
|
|
892
|
+
if (await wantsSkills(options.skills)) {
|
|
893
|
+
console.log("");
|
|
894
|
+
reportInstallSkills(await installSkills({ client: skillClientForMcpClient(client) }));
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
async function wantsSkills(flag) {
|
|
898
|
+
if (flag !== void 0) return flag;
|
|
899
|
+
if (!process.stdin.isTTY) return false;
|
|
900
|
+
const { default: select } = await import("@inquirer/select");
|
|
901
|
+
try {
|
|
902
|
+
return await select({
|
|
903
|
+
message: "Also install the Bezel agent skill into this repo (SKILL.md + AGENTS.md rules)?",
|
|
904
|
+
choices: [
|
|
905
|
+
{ name: "Yes (recommended)", value: true },
|
|
906
|
+
{ name: "Not now (run `bezel install-skills` later)", value: false }
|
|
907
|
+
]
|
|
908
|
+
});
|
|
909
|
+
} catch (error) {
|
|
910
|
+
if (error instanceof Error && error.name === "ExitPromptError") return false;
|
|
911
|
+
throw error;
|
|
912
|
+
}
|
|
751
913
|
}
|
|
752
914
|
|
|
753
915
|
// src/cli.ts
|
|
754
916
|
var USAGE = `bezel \u2014 convert a W3C Design Tokens (DTCG) file into a scoped variables.css
|
|
755
917
|
|
|
756
918
|
Usage:
|
|
757
|
-
bezel install-mcp [--client <name>]
|
|
919
|
+
bezel install-mcp [--client <name>] [--skills]
|
|
920
|
+
Connect claude-code, codex, cursor, vscode, or other
|
|
921
|
+
bezel install-skills [--client <name>] [--no-agents-md]
|
|
922
|
+
Teach claude-code, codex, cursor, or all about Bezel
|
|
758
923
|
bezel init [options] Create a bezel.json for this project
|
|
759
924
|
bezel build [options] Generate the outputs (default command)
|
|
760
925
|
|
|
761
926
|
Reads design-tokens.json from the project root (the working directory).
|
|
762
927
|
|
|
928
|
+
install-mcp options:
|
|
929
|
+
--skills Also run install-skills for the same client (skips the question)
|
|
930
|
+
|
|
931
|
+
install-skills options:
|
|
932
|
+
--client <name> claude-code | codex | cursor | all (asked when omitted)
|
|
933
|
+
--no-agents-md Only write the skill folder; leave AGENTS.md alone
|
|
934
|
+
|
|
763
935
|
init options:
|
|
764
936
|
--dir <path> Directory for all outputs
|
|
765
937
|
(default: src/bezel, or bezel with no src/)
|
|
@@ -796,9 +968,9 @@ TypeScript project unless you pass the paths explicitly.
|
|
|
796
968
|
--project and --tokens-version update only their own key in an existing bezel.json; no --force needed.
|
|
797
969
|
`;
|
|
798
970
|
async function loadConfig(path) {
|
|
799
|
-
const abs = (0,
|
|
971
|
+
const abs = (0, import_node_path6.resolve)(process.cwd(), path);
|
|
800
972
|
if (/\.json$/.test(abs)) {
|
|
801
|
-
return JSON.parse(await (0,
|
|
973
|
+
return JSON.parse(await (0, import_promises5.readFile)(abs, "utf8"));
|
|
802
974
|
}
|
|
803
975
|
const mod = await import((0, import_node_url.pathToFileURL)(abs).href);
|
|
804
976
|
return mod.default ?? mod;
|
|
@@ -817,6 +989,8 @@ var OPTIONS = {
|
|
|
817
989
|
"tokens-version": { type: "string" },
|
|
818
990
|
stdout: { type: "boolean" },
|
|
819
991
|
"no-gitignore": { type: "boolean" },
|
|
992
|
+
"no-agents-md": { type: "boolean" },
|
|
993
|
+
skills: { type: "boolean" },
|
|
820
994
|
"no-contexts": { type: "boolean" },
|
|
821
995
|
"no-fonts": { type: "boolean" },
|
|
822
996
|
force: { type: "boolean", short: "f" },
|
|
@@ -840,7 +1014,7 @@ async function runInit(values) {
|
|
|
840
1014
|
projectId: values.project,
|
|
841
1015
|
version: values["tokens-version"]
|
|
842
1016
|
});
|
|
843
|
-
const configName = (0,
|
|
1017
|
+
const configName = (0, import_node_path6.relative)(cwd, result.configPath) || DEFAULT_CONFIG_FILE;
|
|
844
1018
|
if (result.updated) {
|
|
845
1019
|
if (values.project) {
|
|
846
1020
|
const changed = result.previousProjectId && result.previousProjectId !== values.project;
|
|
@@ -888,13 +1062,16 @@ ${TOKENS_FILE_HINT}
|
|
|
888
1062
|
if (!scripts["tokens"]) {
|
|
889
1063
|
steps.push('Add "tokens": "bezel build" to your package.json scripts.');
|
|
890
1064
|
}
|
|
1065
|
+
if (!skillDirectories("all").some((dir) => (0, import_node_fs2.existsSync)((0, import_node_path6.resolve)(cwd, dir, "bezel")))) {
|
|
1066
|
+
steps.push("Teach your AI coding agent about Bezel: npx bezel install-skills");
|
|
1067
|
+
}
|
|
891
1068
|
steps.push(`Run the build, then import ${result.config.variablesOutput} in your entry file.`);
|
|
892
1069
|
process.stderr.write("\nNext:\n");
|
|
893
1070
|
steps.forEach((step, i) => process.stderr.write(` ${i + 1}. ${step}
|
|
894
1071
|
`));
|
|
895
1072
|
}
|
|
896
1073
|
async function runBuild(values) {
|
|
897
|
-
const configPath = values.config ?? ((0, import_node_fs2.existsSync)((0,
|
|
1074
|
+
const configPath = values.config ?? ((0, import_node_fs2.existsSync)((0, import_node_path6.resolve)(process.cwd(), DEFAULT_CONFIG_FILE)) ? DEFAULT_CONFIG_FILE : void 0);
|
|
898
1075
|
const fileConfig = configPath ? await loadConfig(configPath) : {};
|
|
899
1076
|
const options = { ...fileConfig };
|
|
900
1077
|
if (values["variables-output"]) options.variablesOutput = values["variables-output"];
|
|
@@ -925,7 +1102,11 @@ async function main(argv) {
|
|
|
925
1102
|
}
|
|
926
1103
|
const command = positionals[0] ?? "build";
|
|
927
1104
|
if (command === "install-mcp") {
|
|
928
|
-
await installMcp(values.client);
|
|
1105
|
+
await installMcp(values.client, { skills: values.skills });
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
if (command === "install-skills") {
|
|
1109
|
+
await installSkillsCommand(values.client, { agentsMd: !values["no-agents-md"] });
|
|
929
1110
|
return;
|
|
930
1111
|
}
|
|
931
1112
|
if (command === "init") {
|