rafters 0.0.26 → 0.0.28
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/index.js +685 -119
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12825,49 +12825,6 @@ async function detectShadcn(cwd) {
|
|
|
12825
12825
|
return null;
|
|
12826
12826
|
}
|
|
12827
12827
|
}
|
|
12828
|
-
function parseCssVariables(css3) {
|
|
12829
|
-
const light = {};
|
|
12830
|
-
const dark = {};
|
|
12831
|
-
const rootMatch = css3.match(/:root\s*\{([^}]+)\}/);
|
|
12832
|
-
if (rootMatch?.[1]) {
|
|
12833
|
-
parseVariablesIntoColors(rootMatch[1], light);
|
|
12834
|
-
}
|
|
12835
|
-
const darkMatch = css3.match(/\.dark\s*\{([^}]+)\}/);
|
|
12836
|
-
if (darkMatch?.[1]) {
|
|
12837
|
-
parseVariablesIntoColors(darkMatch[1], dark);
|
|
12838
|
-
}
|
|
12839
|
-
return { light, dark };
|
|
12840
|
-
}
|
|
12841
|
-
function parseVariablesIntoColors(block, colors) {
|
|
12842
|
-
const varMap = {
|
|
12843
|
-
"--background": "background",
|
|
12844
|
-
"--foreground": "foreground",
|
|
12845
|
-
"--card": "card",
|
|
12846
|
-
"--card-foreground": "cardForeground",
|
|
12847
|
-
"--popover": "popover",
|
|
12848
|
-
"--popover-foreground": "popoverForeground",
|
|
12849
|
-
"--primary": "primary",
|
|
12850
|
-
"--primary-foreground": "primaryForeground",
|
|
12851
|
-
"--secondary": "secondary",
|
|
12852
|
-
"--secondary-foreground": "secondaryForeground",
|
|
12853
|
-
"--muted": "muted",
|
|
12854
|
-
"--muted-foreground": "mutedForeground",
|
|
12855
|
-
"--accent": "accent",
|
|
12856
|
-
"--accent-foreground": "accentForeground",
|
|
12857
|
-
"--destructive": "destructive",
|
|
12858
|
-
"--destructive-foreground": "destructiveForeground",
|
|
12859
|
-
"--border": "border",
|
|
12860
|
-
"--input": "input",
|
|
12861
|
-
"--ring": "ring"
|
|
12862
|
-
};
|
|
12863
|
-
for (const [cssVar, colorKey] of Object.entries(varMap)) {
|
|
12864
|
-
const regex = new RegExp(`${cssVar}:\\s*([^;]+);`);
|
|
12865
|
-
const match2 = block.match(regex);
|
|
12866
|
-
if (match2?.[1]) {
|
|
12867
|
-
colors[colorKey] = match2[1].trim();
|
|
12868
|
-
}
|
|
12869
|
-
}
|
|
12870
|
-
}
|
|
12871
12828
|
function frameworkToTarget(framework) {
|
|
12872
12829
|
if (framework === "astro") return "astro";
|
|
12873
12830
|
return "react";
|
|
@@ -12994,14 +12951,6 @@ function log(event) {
|
|
|
12994
12951
|
}
|
|
12995
12952
|
context.spinner = ora("Generating tokens...").start();
|
|
12996
12953
|
break;
|
|
12997
|
-
case "init:shadcn_detected": {
|
|
12998
|
-
context.spinner?.info("Found existing shadcn colors");
|
|
12999
|
-
console.log(` Backed up: ${event.backupPath}`);
|
|
13000
|
-
const colors = event.colorsFound;
|
|
13001
|
-
console.log(` Colors: ${colors.light} light, ${colors.dark} dark`);
|
|
13002
|
-
context.spinner = ora("Generating tokens...").start();
|
|
13003
|
-
break;
|
|
13004
|
-
}
|
|
13005
12954
|
case "init:generated":
|
|
13006
12955
|
context.spinner?.succeed(`Generated ${event.tokenCount} tokens`);
|
|
13007
12956
|
context.spinner = ora("Saving registry...").start();
|
|
@@ -13026,8 +12975,16 @@ function log(event) {
|
|
|
13026
12975
|
context.spinner?.succeed(`Loaded ${event.tokenCount} tokens`);
|
|
13027
12976
|
context.spinner = ora("Generating outputs...").start();
|
|
13028
12977
|
break;
|
|
13029
|
-
case "init:
|
|
13030
|
-
|
|
12978
|
+
case "init:existing_design_detected":
|
|
12979
|
+
context.spinner?.info("Existing design system detected");
|
|
12980
|
+
console.log(` Found design decisions in ${event.cssPath}`);
|
|
12981
|
+
console.log("");
|
|
12982
|
+
console.log(" Your existing CSS has design decisions that should be");
|
|
12983
|
+
console.log(" migrated intentionally, not automatically.");
|
|
12984
|
+
console.log("");
|
|
12985
|
+
console.log(' Ask your AI agent: "Onboard my existing design system to Rafters"');
|
|
12986
|
+
console.log("");
|
|
12987
|
+
context.spinner = ora("Finishing up...").start();
|
|
13031
12988
|
break;
|
|
13032
12989
|
case "init:prompting_exports":
|
|
13033
12990
|
context.spinner?.succeed("Configuration ready");
|
|
@@ -58684,26 +58641,6 @@ async function init(options) {
|
|
|
58684
58641
|
await regenerateFromExisting(cwd, paths, shadcn, isAgentMode2, framework);
|
|
58685
58642
|
return;
|
|
58686
58643
|
}
|
|
58687
|
-
let existingColors = null;
|
|
58688
|
-
if (shadcn?.tailwind?.css) {
|
|
58689
|
-
const cssPath = join9(cwd, shadcn.tailwind.css);
|
|
58690
|
-
try {
|
|
58691
|
-
const cssContent = await readFile5(cssPath, "utf-8");
|
|
58692
|
-
existingColors = parseCssVariables(cssContent);
|
|
58693
|
-
const backupPath = await backupCss(cssPath);
|
|
58694
|
-
log({
|
|
58695
|
-
event: "init:shadcn_detected",
|
|
58696
|
-
cssPath: shadcn.tailwind.css,
|
|
58697
|
-
backupPath,
|
|
58698
|
-
colorsFound: {
|
|
58699
|
-
light: Object.keys(existingColors.light).length,
|
|
58700
|
-
dark: Object.keys(existingColors.dark).length
|
|
58701
|
-
}
|
|
58702
|
-
});
|
|
58703
|
-
} catch (err) {
|
|
58704
|
-
log({ event: "init:shadcn_css_error", error: String(err) });
|
|
58705
|
-
}
|
|
58706
|
-
}
|
|
58707
58644
|
let exports;
|
|
58708
58645
|
if (isAgentMode2) {
|
|
58709
58646
|
exports = DEFAULT_EXPORTS;
|
|
@@ -58723,39 +58660,6 @@ async function init(options) {
|
|
|
58723
58660
|
}
|
|
58724
58661
|
});
|
|
58725
58662
|
const { registry: registry2 } = result;
|
|
58726
|
-
if (existingColors) {
|
|
58727
|
-
const tokenMap = {
|
|
58728
|
-
background: "background",
|
|
58729
|
-
foreground: "foreground",
|
|
58730
|
-
card: "card",
|
|
58731
|
-
"card-foreground": "cardForeground",
|
|
58732
|
-
popover: "popover",
|
|
58733
|
-
"popover-foreground": "popoverForeground",
|
|
58734
|
-
primary: "primary",
|
|
58735
|
-
"primary-foreground": "primaryForeground",
|
|
58736
|
-
secondary: "secondary",
|
|
58737
|
-
"secondary-foreground": "secondaryForeground",
|
|
58738
|
-
muted: "muted",
|
|
58739
|
-
"muted-foreground": "mutedForeground",
|
|
58740
|
-
accent: "accent",
|
|
58741
|
-
"accent-foreground": "accentForeground",
|
|
58742
|
-
destructive: "destructive",
|
|
58743
|
-
"destructive-foreground": "destructiveForeground",
|
|
58744
|
-
border: "border",
|
|
58745
|
-
input: "input",
|
|
58746
|
-
ring: "ring"
|
|
58747
|
-
};
|
|
58748
|
-
for (const [tokenName, colorKey] of Object.entries(tokenMap)) {
|
|
58749
|
-
const colorValue = existingColors.light[colorKey];
|
|
58750
|
-
if (colorValue && registry2.has(tokenName)) {
|
|
58751
|
-
registry2.updateToken(tokenName, colorValue);
|
|
58752
|
-
}
|
|
58753
|
-
}
|
|
58754
|
-
log({
|
|
58755
|
-
event: "init:colors_imported",
|
|
58756
|
-
count: Object.keys(existingColors.light).length
|
|
58757
|
-
});
|
|
58758
|
-
}
|
|
58759
58663
|
log({
|
|
58760
58664
|
event: "init:generated",
|
|
58761
58665
|
tokenCount: registry2.size()
|
|
@@ -58823,12 +58727,34 @@ async function init(options) {
|
|
|
58823
58727
|
}
|
|
58824
58728
|
};
|
|
58825
58729
|
await writeFile3(paths.config, JSON.stringify(config3, null, 2));
|
|
58730
|
+
const existingCssPath = detectedCssPath ?? (shadcn?.tailwind?.css || null);
|
|
58731
|
+
if (existingCssPath) {
|
|
58732
|
+
try {
|
|
58733
|
+
const cssContent = await readFile5(join9(cwd, existingCssPath), "utf-8");
|
|
58734
|
+
if (hasExistingDesignDecisions(cssContent)) {
|
|
58735
|
+
log({
|
|
58736
|
+
event: "init:existing_design_detected",
|
|
58737
|
+
cssPath: existingCssPath,
|
|
58738
|
+
action: "run rafters_onboard analyze to begin migration"
|
|
58739
|
+
});
|
|
58740
|
+
}
|
|
58741
|
+
} catch {
|
|
58742
|
+
}
|
|
58743
|
+
}
|
|
58826
58744
|
log({
|
|
58827
58745
|
event: "init:complete",
|
|
58828
58746
|
outputs: [...outputs, "config.rafters.json"],
|
|
58829
58747
|
path: paths.output
|
|
58830
58748
|
});
|
|
58831
58749
|
}
|
|
58750
|
+
function hasExistingDesignDecisions(css3) {
|
|
58751
|
+
const stripped = css3.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*/g, "");
|
|
58752
|
+
const withoutBoilerplate = stripped.replace(/@import\s+['"][^'"]+['"];?/g, "").replace(/@tailwind\s+\w+;?/g, "").trim();
|
|
58753
|
+
if (withoutBoilerplate.length === 0) return false;
|
|
58754
|
+
const hasCustomProperties = /--[\w-]+\s*:/.test(stripped);
|
|
58755
|
+
const hasThemeBlock = /@theme\s*\{/.test(stripped);
|
|
58756
|
+
return hasCustomProperties || hasThemeBlock;
|
|
58757
|
+
}
|
|
58832
58758
|
|
|
58833
58759
|
// src/commands/mcp.ts
|
|
58834
58760
|
import { existsSync as existsSync6 } from "fs";
|
|
@@ -58841,7 +58767,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprot
|
|
|
58841
58767
|
|
|
58842
58768
|
// src/mcp/tools.ts
|
|
58843
58769
|
import { existsSync as existsSync4 } from "fs";
|
|
58844
|
-
import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
|
|
58770
|
+
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
58845
58771
|
import { basename, join as join10, relative as relative2 } from "path";
|
|
58846
58772
|
|
|
58847
58773
|
// src/mcp/cognitive-load.ts
|
|
@@ -59752,6 +59678,16 @@ If no component fits your need, check @/lib/utils for official behavioral utilit
|
|
|
59752
59678
|
COLORS ARE TAILWIND CLASSES.
|
|
59753
59679
|
Write border-l-primary, bg-success, text-info-foreground. Do not create color constants, mapping objects, or reference palette names. Palette families are internal. See quickstart.colorTokens for the full list of semantic tokens and usage examples.
|
|
59754
59680
|
|
|
59681
|
+
ONBOARDING IS INTENTIONAL.
|
|
59682
|
+
When onboarding an existing project, do NOT skip the learning step.
|
|
59683
|
+
1. Call rafters_vocabulary and explore token shapes first. Understand namespaces, dependency graph, the two primitives.
|
|
59684
|
+
2. Call rafters_onboard analyze to see what existing design decisions are in the CSS.
|
|
59685
|
+
3. Do NOT automatically map colors. Ask the designer about each ambiguous decision.
|
|
59686
|
+
4. Every token mutation needs a reason that captures design INTENT, not just origin.
|
|
59687
|
+
Bad: "imported from globals.css"
|
|
59688
|
+
Good: "Brand blue from --brand-primary, primary identity color used on nav and CTAs per designer"
|
|
59689
|
+
5. Call rafters_onboard map to execute the migration once the designer confirms.
|
|
59690
|
+
|
|
59755
59691
|
When in doubt: less code, not more. Rafters has already made the design decision.`;
|
|
59756
59692
|
var CONSUMER_QUICKSTART = {
|
|
59757
59693
|
rule1: "Components are pre-styled. Import and render. Do not add className for visual styling -- only for layout context (e.g. border-l-4 for accent). Input knows its focus ring. Label knows its weight. You arrange, you do not style.",
|
|
@@ -60116,13 +60052,34 @@ var TOOL_DEFINITIONS = [
|
|
|
60116
60052
|
},
|
|
60117
60053
|
{
|
|
60118
60054
|
name: "rafters_token",
|
|
60119
|
-
description: "
|
|
60055
|
+
description: "Read or write a design token. Read: returns full intelligence (value, derivation, dependencies, cascade impact, override context). Write: set/create/reset tokens with mandatory reason (why-gate). Every write cascades through the dependency graph, persists to disk, and regenerates CSS/TS/DTCG outputs.",
|
|
60120
60056
|
inputSchema: {
|
|
60121
60057
|
type: "object",
|
|
60122
60058
|
properties: {
|
|
60123
60059
|
name: {
|
|
60124
60060
|
type: "string",
|
|
60125
60061
|
description: 'Token name (e.g., "spacing-6", "primary-500", "spacing-base"). Use rafters_vocabulary to see available tokens.'
|
|
60062
|
+
},
|
|
60063
|
+
action: {
|
|
60064
|
+
type: "string",
|
|
60065
|
+
enum: ["get", "set", "create", "reset"],
|
|
60066
|
+
description: "Action to perform. get (default): read token intelligence. set: update existing token value. create: add a new token. reset: clear override, restore computed value."
|
|
60067
|
+
},
|
|
60068
|
+
value: {
|
|
60069
|
+
type: "string",
|
|
60070
|
+
description: "New token value (required for set and create actions)."
|
|
60071
|
+
},
|
|
60072
|
+
reason: {
|
|
60073
|
+
type: "string",
|
|
60074
|
+
description: "Why this change is being made (required for set, create, and reset). Every mutation needs a reason that captures design INTENT."
|
|
60075
|
+
},
|
|
60076
|
+
namespace: {
|
|
60077
|
+
type: "string",
|
|
60078
|
+
description: 'Token namespace (required for create). E.g., "color", "spacing", "typography".'
|
|
60079
|
+
},
|
|
60080
|
+
category: {
|
|
60081
|
+
type: "string",
|
|
60082
|
+
description: 'Token category (required for create). E.g., "color", "spacing", "font".'
|
|
60126
60083
|
}
|
|
60127
60084
|
},
|
|
60128
60085
|
required: ["name"]
|
|
@@ -60147,11 +60104,60 @@ var TOOL_DEFINITIONS = [
|
|
|
60147
60104
|
},
|
|
60148
60105
|
required: ["components"]
|
|
60149
60106
|
}
|
|
60107
|
+
},
|
|
60108
|
+
{
|
|
60109
|
+
name: "rafters_onboard",
|
|
60110
|
+
description: 'Analyze an existing project for design decisions (CSS custom properties, @theme blocks, shadcn colors) and map them into Rafters tokens. Use "analyze" to surface raw findings. Use "map" to execute token writes from a mapping plan. The agent interprets the findings, asks the designer about ambiguous decisions, then maps with intent.',
|
|
60111
|
+
inputSchema: {
|
|
60112
|
+
type: "object",
|
|
60113
|
+
properties: {
|
|
60114
|
+
action: {
|
|
60115
|
+
type: "string",
|
|
60116
|
+
enum: ["analyze", "map"],
|
|
60117
|
+
description: "analyze: scan project CSS and return structured findings. map: execute a mapping plan (array of source->target->reason)."
|
|
60118
|
+
},
|
|
60119
|
+
mappings: {
|
|
60120
|
+
type: "array",
|
|
60121
|
+
items: {
|
|
60122
|
+
type: "object",
|
|
60123
|
+
properties: {
|
|
60124
|
+
source: {
|
|
60125
|
+
type: "string",
|
|
60126
|
+
description: 'Original CSS variable name (e.g., "--brand-blue")'
|
|
60127
|
+
},
|
|
60128
|
+
target: {
|
|
60129
|
+
type: "string",
|
|
60130
|
+
description: 'Rafters token name to set or create (e.g., "primary")'
|
|
60131
|
+
},
|
|
60132
|
+
value: {
|
|
60133
|
+
type: "string",
|
|
60134
|
+
description: "The value to set (from the original CSS)"
|
|
60135
|
+
},
|
|
60136
|
+
reason: {
|
|
60137
|
+
type: "string",
|
|
60138
|
+
description: "Why this mapping makes sense (design intent)"
|
|
60139
|
+
},
|
|
60140
|
+
namespace: {
|
|
60141
|
+
type: "string",
|
|
60142
|
+
description: "Namespace for new tokens (required if token does not exist)"
|
|
60143
|
+
},
|
|
60144
|
+
category: {
|
|
60145
|
+
type: "string",
|
|
60146
|
+
description: "Category for new tokens (required if token does not exist)"
|
|
60147
|
+
}
|
|
60148
|
+
},
|
|
60149
|
+
required: ["source", "target", "value", "reason"]
|
|
60150
|
+
},
|
|
60151
|
+
description: "Array of mappings to execute (required for map action)"
|
|
60152
|
+
}
|
|
60153
|
+
},
|
|
60154
|
+
required: ["action"]
|
|
60155
|
+
}
|
|
60150
60156
|
}
|
|
60151
60157
|
];
|
|
60152
60158
|
var NO_PROJECT_ERROR = "No .rafters/ config found. Run `pnpm dlx rafters init` in your project first. If the MCP server was launched from a different directory, pass --project-root <path>.";
|
|
60153
60159
|
var PROJECT_INDEPENDENT_TOOLS = /* @__PURE__ */ new Set(["rafters_pattern"]);
|
|
60154
|
-
var RaftersToolHandler = class {
|
|
60160
|
+
var RaftersToolHandler = class _RaftersToolHandler {
|
|
60155
60161
|
adapter;
|
|
60156
60162
|
projectRoot;
|
|
60157
60163
|
cachedConfig;
|
|
@@ -60181,13 +60187,18 @@ var RaftersToolHandler = class {
|
|
|
60181
60187
|
return this.getPattern(args.pattern);
|
|
60182
60188
|
case "rafters_component":
|
|
60183
60189
|
return this.getComponent(args.name);
|
|
60184
|
-
case "rafters_token":
|
|
60185
|
-
|
|
60190
|
+
case "rafters_token": {
|
|
60191
|
+
const action = args.action ?? "get";
|
|
60192
|
+
if (action === "get") return this.getToken(args.name);
|
|
60193
|
+
return this.writeToken(action, args);
|
|
60194
|
+
}
|
|
60186
60195
|
case "rafters_cognitive_budget":
|
|
60187
60196
|
return this.getCognitiveBudget(
|
|
60188
60197
|
args.components,
|
|
60189
60198
|
args.tier ?? "page"
|
|
60190
60199
|
);
|
|
60200
|
+
case "rafters_onboard":
|
|
60201
|
+
return this.onboard(args);
|
|
60191
60202
|
default:
|
|
60192
60203
|
return {
|
|
60193
60204
|
content: [{ type: "text", text: `Unknown tool: ${name2}` }],
|
|
@@ -60717,6 +60728,487 @@ var RaftersToolHandler = class {
|
|
|
60717
60728
|
return this.handleError("getToken", error48);
|
|
60718
60729
|
}
|
|
60719
60730
|
}
|
|
60731
|
+
// ==================== Token Write Operations ====================
|
|
60732
|
+
/**
|
|
60733
|
+
* Handle set, create, and reset actions for rafters_token.
|
|
60734
|
+
* Loads the full registry, mutates, cascades, persists, and regenerates outputs.
|
|
60735
|
+
*/
|
|
60736
|
+
async writeToken(action, args) {
|
|
60737
|
+
const name2 = args.name;
|
|
60738
|
+
const reason = args.reason;
|
|
60739
|
+
const value2 = args.value;
|
|
60740
|
+
if (!reason) {
|
|
60741
|
+
return {
|
|
60742
|
+
content: [
|
|
60743
|
+
{
|
|
60744
|
+
type: "text",
|
|
60745
|
+
text: JSON.stringify({
|
|
60746
|
+
error: "Reason is required for every token mutation.",
|
|
60747
|
+
hint: "Every change needs a WHY that captures design intent, not just origin.",
|
|
60748
|
+
bad: "imported from globals.css",
|
|
60749
|
+
good: "Brand blue from --brand-primary, primary identity color per designer"
|
|
60750
|
+
})
|
|
60751
|
+
}
|
|
60752
|
+
],
|
|
60753
|
+
isError: true
|
|
60754
|
+
};
|
|
60755
|
+
}
|
|
60756
|
+
if (!this.adapter || !this.projectRoot) {
|
|
60757
|
+
return { content: [{ type: "text", text: NO_PROJECT_ERROR }], isError: true };
|
|
60758
|
+
}
|
|
60759
|
+
try {
|
|
60760
|
+
const allTokens = await this.adapter.load();
|
|
60761
|
+
const registry2 = new TokenRegistry(allTokens);
|
|
60762
|
+
registry2.setAdapter(this.adapter);
|
|
60763
|
+
switch (action) {
|
|
60764
|
+
case "set": {
|
|
60765
|
+
if (!value2) {
|
|
60766
|
+
return {
|
|
60767
|
+
content: [{ type: "text", text: '{"error": "value is required for set action"}' }],
|
|
60768
|
+
isError: true
|
|
60769
|
+
};
|
|
60770
|
+
}
|
|
60771
|
+
const existing = registry2.get(name2);
|
|
60772
|
+
if (!existing) {
|
|
60773
|
+
return {
|
|
60774
|
+
content: [
|
|
60775
|
+
{
|
|
60776
|
+
type: "text",
|
|
60777
|
+
text: JSON.stringify({
|
|
60778
|
+
error: `Token "${name2}" not found. Use action: "create" for new tokens.`
|
|
60779
|
+
})
|
|
60780
|
+
}
|
|
60781
|
+
],
|
|
60782
|
+
isError: true
|
|
60783
|
+
};
|
|
60784
|
+
}
|
|
60785
|
+
const previousValue = existing.value;
|
|
60786
|
+
existing.userOverride = {
|
|
60787
|
+
previousValue: typeof previousValue === "string" ? previousValue : JSON.stringify(previousValue),
|
|
60788
|
+
reason
|
|
60789
|
+
};
|
|
60790
|
+
await registry2.set(name2, value2);
|
|
60791
|
+
const affected = this.getAffectedTokens(registry2, name2);
|
|
60792
|
+
await this.regenerateOutputs(registry2);
|
|
60793
|
+
return {
|
|
60794
|
+
content: [
|
|
60795
|
+
{
|
|
60796
|
+
type: "text",
|
|
60797
|
+
text: JSON.stringify({
|
|
60798
|
+
ok: true,
|
|
60799
|
+
action: "set",
|
|
60800
|
+
name: name2,
|
|
60801
|
+
reason,
|
|
60802
|
+
cascaded: affected
|
|
60803
|
+
})
|
|
60804
|
+
}
|
|
60805
|
+
]
|
|
60806
|
+
};
|
|
60807
|
+
}
|
|
60808
|
+
case "create": {
|
|
60809
|
+
const namespace = args.namespace;
|
|
60810
|
+
const category = args.category;
|
|
60811
|
+
if (!value2) {
|
|
60812
|
+
return {
|
|
60813
|
+
content: [{ type: "text", text: '{"error": "value is required for create action"}' }],
|
|
60814
|
+
isError: true
|
|
60815
|
+
};
|
|
60816
|
+
}
|
|
60817
|
+
if (!namespace) {
|
|
60818
|
+
return {
|
|
60819
|
+
content: [
|
|
60820
|
+
{ type: "text", text: '{"error": "namespace is required for create action"}' }
|
|
60821
|
+
],
|
|
60822
|
+
isError: true
|
|
60823
|
+
};
|
|
60824
|
+
}
|
|
60825
|
+
if (!category) {
|
|
60826
|
+
return {
|
|
60827
|
+
content: [
|
|
60828
|
+
{ type: "text", text: '{"error": "category is required for create action"}' }
|
|
60829
|
+
],
|
|
60830
|
+
isError: true
|
|
60831
|
+
};
|
|
60832
|
+
}
|
|
60833
|
+
if (registry2.has(name2)) {
|
|
60834
|
+
return {
|
|
60835
|
+
content: [
|
|
60836
|
+
{
|
|
60837
|
+
type: "text",
|
|
60838
|
+
text: JSON.stringify({
|
|
60839
|
+
error: `Token "${name2}" already exists. Use action: "set" to update.`
|
|
60840
|
+
})
|
|
60841
|
+
}
|
|
60842
|
+
],
|
|
60843
|
+
isError: true
|
|
60844
|
+
};
|
|
60845
|
+
}
|
|
60846
|
+
const newToken = {
|
|
60847
|
+
name: name2,
|
|
60848
|
+
namespace,
|
|
60849
|
+
category,
|
|
60850
|
+
value: value2,
|
|
60851
|
+
containerQueryAware: true,
|
|
60852
|
+
userOverride: {
|
|
60853
|
+
previousValue: "",
|
|
60854
|
+
reason
|
|
60855
|
+
}
|
|
60856
|
+
};
|
|
60857
|
+
registry2.add(newToken);
|
|
60858
|
+
await this.adapter.save(registry2.list());
|
|
60859
|
+
await this.regenerateOutputs(registry2);
|
|
60860
|
+
return {
|
|
60861
|
+
content: [
|
|
60862
|
+
{
|
|
60863
|
+
type: "text",
|
|
60864
|
+
text: JSON.stringify({
|
|
60865
|
+
ok: true,
|
|
60866
|
+
action: "create",
|
|
60867
|
+
name: name2,
|
|
60868
|
+
namespace,
|
|
60869
|
+
reason
|
|
60870
|
+
})
|
|
60871
|
+
}
|
|
60872
|
+
]
|
|
60873
|
+
};
|
|
60874
|
+
}
|
|
60875
|
+
case "reset": {
|
|
60876
|
+
const existing = registry2.get(name2);
|
|
60877
|
+
if (!existing) {
|
|
60878
|
+
return {
|
|
60879
|
+
content: [
|
|
60880
|
+
{ type: "text", text: JSON.stringify({ error: `Token "${name2}" not found.` }) }
|
|
60881
|
+
],
|
|
60882
|
+
isError: true
|
|
60883
|
+
};
|
|
60884
|
+
}
|
|
60885
|
+
await registry2.set(name2, COMPUTED);
|
|
60886
|
+
const affected = this.getAffectedTokens(registry2, name2);
|
|
60887
|
+
await this.regenerateOutputs(registry2);
|
|
60888
|
+
return {
|
|
60889
|
+
content: [
|
|
60890
|
+
{
|
|
60891
|
+
type: "text",
|
|
60892
|
+
text: JSON.stringify({
|
|
60893
|
+
ok: true,
|
|
60894
|
+
action: "reset",
|
|
60895
|
+
name: name2,
|
|
60896
|
+
reason,
|
|
60897
|
+
cascaded: affected
|
|
60898
|
+
})
|
|
60899
|
+
}
|
|
60900
|
+
]
|
|
60901
|
+
};
|
|
60902
|
+
}
|
|
60903
|
+
default:
|
|
60904
|
+
return {
|
|
60905
|
+
content: [
|
|
60906
|
+
{
|
|
60907
|
+
type: "text",
|
|
60908
|
+
text: JSON.stringify({
|
|
60909
|
+
error: `Unknown action: ${action}. Use get, set, create, or reset.`
|
|
60910
|
+
})
|
|
60911
|
+
}
|
|
60912
|
+
],
|
|
60913
|
+
isError: true
|
|
60914
|
+
};
|
|
60915
|
+
}
|
|
60916
|
+
} catch (error48) {
|
|
60917
|
+
return this.handleError("writeToken", error48);
|
|
60918
|
+
}
|
|
60919
|
+
}
|
|
60920
|
+
/**
|
|
60921
|
+
* Get list of tokens that would be affected by changing the given token
|
|
60922
|
+
*/
|
|
60923
|
+
getAffectedTokens(registry2, tokenName) {
|
|
60924
|
+
const affected = [];
|
|
60925
|
+
for (const token of registry2.list()) {
|
|
60926
|
+
if (token.dependsOn?.includes(tokenName)) {
|
|
60927
|
+
affected.push(token.name);
|
|
60928
|
+
}
|
|
60929
|
+
}
|
|
60930
|
+
return affected;
|
|
60931
|
+
}
|
|
60932
|
+
/**
|
|
60933
|
+
* Regenerate output files (CSS, TS, DTCG) from registry state
|
|
60934
|
+
*/
|
|
60935
|
+
async regenerateOutputs(registry2) {
|
|
60936
|
+
if (!this.projectRoot) return;
|
|
60937
|
+
const paths = getRaftersPaths(this.projectRoot);
|
|
60938
|
+
const config3 = await this.loadConfig();
|
|
60939
|
+
const exports = config3?.exports ?? {
|
|
60940
|
+
tailwind: true,
|
|
60941
|
+
typescript: true,
|
|
60942
|
+
dtcg: false,
|
|
60943
|
+
compiled: false
|
|
60944
|
+
};
|
|
60945
|
+
const shadcn = config3?.shadcn ?? false;
|
|
60946
|
+
await mkdir4(paths.output, { recursive: true });
|
|
60947
|
+
if (exports.tailwind) {
|
|
60948
|
+
const css3 = registryToTailwind(registry2, { includeImport: !shadcn });
|
|
60949
|
+
await writeFile4(join10(paths.output, "rafters.css"), css3);
|
|
60950
|
+
}
|
|
60951
|
+
if (exports.typescript) {
|
|
60952
|
+
const ts = registryToTypeScript(registry2, { includeJSDoc: true });
|
|
60953
|
+
await writeFile4(join10(paths.output, "rafters.ts"), ts);
|
|
60954
|
+
}
|
|
60955
|
+
if (exports.dtcg) {
|
|
60956
|
+
const json3 = toDTCG(registry2.list());
|
|
60957
|
+
await writeFile4(join10(paths.output, "rafters.json"), JSON.stringify(json3, null, 2));
|
|
60958
|
+
}
|
|
60959
|
+
}
|
|
60960
|
+
// ==================== Tool 6: Onboard ====================
|
|
60961
|
+
/**
|
|
60962
|
+
* CSS file locations by framework (mirrors init.ts CSS_LOCATIONS)
|
|
60963
|
+
*/
|
|
60964
|
+
static CSS_LOCATIONS = {
|
|
60965
|
+
astro: ["src/styles/global.css", "src/styles/globals.css", "src/global.css"],
|
|
60966
|
+
next: ["src/app/globals.css", "app/globals.css", "styles/globals.css"],
|
|
60967
|
+
vite: ["src/index.css", "src/main.css", "src/styles.css", "src/app.css"],
|
|
60968
|
+
remix: ["app/styles/global.css", "app/globals.css", "app/root.css"],
|
|
60969
|
+
"react-router": ["app/app.css", "app/root.css", "app/styles.css", "app/globals.css"],
|
|
60970
|
+
unknown: [
|
|
60971
|
+
"src/styles/global.css",
|
|
60972
|
+
"src/styles/globals.css",
|
|
60973
|
+
"src/index.css",
|
|
60974
|
+
"src/main.css",
|
|
60975
|
+
"src/app.css",
|
|
60976
|
+
"styles/globals.css",
|
|
60977
|
+
"app/globals.css"
|
|
60978
|
+
]
|
|
60979
|
+
};
|
|
60980
|
+
/**
|
|
60981
|
+
* Handle rafters_onboard tool calls
|
|
60982
|
+
*/
|
|
60983
|
+
async onboard(args) {
|
|
60984
|
+
const action = args.action;
|
|
60985
|
+
switch (action) {
|
|
60986
|
+
case "analyze":
|
|
60987
|
+
return this.analyzeProject();
|
|
60988
|
+
case "map":
|
|
60989
|
+
return this.mapTokens(args.mappings);
|
|
60990
|
+
default:
|
|
60991
|
+
return {
|
|
60992
|
+
content: [
|
|
60993
|
+
{
|
|
60994
|
+
type: "text",
|
|
60995
|
+
text: JSON.stringify({ error: `Unknown action: ${action}. Use analyze or map.` })
|
|
60996
|
+
}
|
|
60997
|
+
],
|
|
60998
|
+
isError: true
|
|
60999
|
+
};
|
|
61000
|
+
}
|
|
61001
|
+
}
|
|
61002
|
+
/**
|
|
61003
|
+
* Scan the project for existing design decisions.
|
|
61004
|
+
* Returns raw findings -- the agent interprets, not the tool.
|
|
61005
|
+
*/
|
|
61006
|
+
async analyzeProject() {
|
|
61007
|
+
if (!this.projectRoot) {
|
|
61008
|
+
return { content: [{ type: "text", text: NO_PROJECT_ERROR }], isError: true };
|
|
61009
|
+
}
|
|
61010
|
+
try {
|
|
61011
|
+
const config3 = await this.loadConfig();
|
|
61012
|
+
const framework = config3?.framework ?? "unknown";
|
|
61013
|
+
const cssFindings = [];
|
|
61014
|
+
const locations = /* @__PURE__ */ new Set();
|
|
61015
|
+
const frameworkLocations = _RaftersToolHandler.CSS_LOCATIONS[framework] ?? _RaftersToolHandler.CSS_LOCATIONS.unknown ?? [];
|
|
61016
|
+
for (const loc of frameworkLocations) locations.add(loc);
|
|
61017
|
+
if (config3?.cssPath) locations.add(config3.cssPath);
|
|
61018
|
+
for (const cssPath of locations) {
|
|
61019
|
+
const fullPath = join10(this.projectRoot, cssPath);
|
|
61020
|
+
if (!existsSync4(fullPath)) continue;
|
|
61021
|
+
try {
|
|
61022
|
+
const content = await readFile6(fullPath, "utf-8");
|
|
61023
|
+
const finding = this.parseCssFindings(cssPath, content);
|
|
61024
|
+
if (finding.customProperties.length > 0 || finding.themeBlocks.length > 0 || finding.imports.length > 0) {
|
|
61025
|
+
cssFindings.push(finding);
|
|
61026
|
+
}
|
|
61027
|
+
} catch {
|
|
61028
|
+
}
|
|
61029
|
+
}
|
|
61030
|
+
let shadcn = { detected: false };
|
|
61031
|
+
try {
|
|
61032
|
+
const componentsJson = await readFile6(join10(this.projectRoot, "components.json"), "utf-8");
|
|
61033
|
+
const parsed = JSON.parse(componentsJson);
|
|
61034
|
+
const cssPath = parsed.tailwind?.css;
|
|
61035
|
+
shadcn = cssPath ? { detected: true, cssPath } : { detected: true };
|
|
61036
|
+
} catch {
|
|
61037
|
+
}
|
|
61038
|
+
let designDeps = [];
|
|
61039
|
+
try {
|
|
61040
|
+
const pkgContent = await readFile6(join10(this.projectRoot, "package.json"), "utf-8");
|
|
61041
|
+
const pkg = JSON.parse(pkgContent);
|
|
61042
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
61043
|
+
const designPatterns = [
|
|
61044
|
+
"@radix-ui",
|
|
61045
|
+
"class-variance-authority",
|
|
61046
|
+
"tailwind-merge",
|
|
61047
|
+
"clsx",
|
|
61048
|
+
"@headlessui",
|
|
61049
|
+
"lucide-react",
|
|
61050
|
+
"@heroicons"
|
|
61051
|
+
];
|
|
61052
|
+
designDeps = Object.keys(allDeps).filter(
|
|
61053
|
+
(dep) => designPatterns.some((p2) => dep.startsWith(p2))
|
|
61054
|
+
);
|
|
61055
|
+
} catch {
|
|
61056
|
+
}
|
|
61057
|
+
let existingTokenCount = 0;
|
|
61058
|
+
if (this.adapter) {
|
|
61059
|
+
const tokens = await this.adapter.load();
|
|
61060
|
+
existingTokenCount = tokens.length;
|
|
61061
|
+
}
|
|
61062
|
+
const result = {
|
|
61063
|
+
framework,
|
|
61064
|
+
cssFiles: cssFindings,
|
|
61065
|
+
shadcn,
|
|
61066
|
+
designDependencies: designDeps,
|
|
61067
|
+
existingTokenCount,
|
|
61068
|
+
guidance: 'Review the custom properties above. Map each to a rafters token using rafters_onboard with action: "map". For ambiguous decisions, ask the designer.'
|
|
61069
|
+
};
|
|
61070
|
+
return {
|
|
61071
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
61072
|
+
};
|
|
61073
|
+
} catch (error48) {
|
|
61074
|
+
return this.handleError("analyzeProject", error48);
|
|
61075
|
+
}
|
|
61076
|
+
}
|
|
61077
|
+
/**
|
|
61078
|
+
* Parse CSS content into structured findings
|
|
61079
|
+
*/
|
|
61080
|
+
parseCssFindings(path2, content) {
|
|
61081
|
+
const customProperties = [];
|
|
61082
|
+
const themeBlocks = [];
|
|
61083
|
+
const imports = [];
|
|
61084
|
+
const importMatches = content.matchAll(/@import\s+['"]([^'"]+)['"]/g);
|
|
61085
|
+
for (const match2 of importMatches) {
|
|
61086
|
+
if (match2[1]) imports.push(match2[1]);
|
|
61087
|
+
}
|
|
61088
|
+
const themeMatches = content.matchAll(/@theme\s*\{([^}]+)\}/g);
|
|
61089
|
+
for (const match2 of themeMatches) {
|
|
61090
|
+
if (match2[0]) themeBlocks.push(match2[0].trim());
|
|
61091
|
+
}
|
|
61092
|
+
const rootMatch = content.match(/:root\s*\{([^}]+)\}/);
|
|
61093
|
+
if (rootMatch?.[1]) {
|
|
61094
|
+
this.extractCustomProperties(rootMatch[1], ":root", customProperties);
|
|
61095
|
+
}
|
|
61096
|
+
const darkMatch = content.match(/\.dark\s*\{([^}]+)\}/);
|
|
61097
|
+
if (darkMatch?.[1]) {
|
|
61098
|
+
this.extractCustomProperties(darkMatch[1], ".dark", customProperties);
|
|
61099
|
+
}
|
|
61100
|
+
const prefersMatch = content.match(
|
|
61101
|
+
/@media\s*\(prefers-color-scheme:\s*dark\)\s*\{[^{]*\{([^}]+)\}/
|
|
61102
|
+
);
|
|
61103
|
+
if (prefersMatch?.[1]) {
|
|
61104
|
+
this.extractCustomProperties(prefersMatch[1], "prefers-color-scheme: dark", customProperties);
|
|
61105
|
+
}
|
|
61106
|
+
return { path: path2, customProperties, themeBlocks, imports };
|
|
61107
|
+
}
|
|
61108
|
+
/**
|
|
61109
|
+
* Extract CSS custom properties from a block
|
|
61110
|
+
*/
|
|
61111
|
+
extractCustomProperties(block, context2, out) {
|
|
61112
|
+
const propMatches = block.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g);
|
|
61113
|
+
for (const match2 of propMatches) {
|
|
61114
|
+
if (match2[1] && match2[2]) {
|
|
61115
|
+
out.push({ name: match2[1], value: match2[2].trim(), context: context2 });
|
|
61116
|
+
}
|
|
61117
|
+
}
|
|
61118
|
+
}
|
|
61119
|
+
/**
|
|
61120
|
+
* Execute a mapping plan -- calls writeToken for each mapping
|
|
61121
|
+
*/
|
|
61122
|
+
async mapTokens(mappings) {
|
|
61123
|
+
if (!mappings || mappings.length === 0) {
|
|
61124
|
+
return {
|
|
61125
|
+
content: [
|
|
61126
|
+
{
|
|
61127
|
+
type: "text",
|
|
61128
|
+
text: JSON.stringify({
|
|
61129
|
+
error: "mappings array is required for map action",
|
|
61130
|
+
hint: "Run analyze first, then provide mappings with source, target, value, and reason"
|
|
61131
|
+
})
|
|
61132
|
+
}
|
|
61133
|
+
],
|
|
61134
|
+
isError: true
|
|
61135
|
+
};
|
|
61136
|
+
}
|
|
61137
|
+
if (!this.adapter || !this.projectRoot) {
|
|
61138
|
+
return { content: [{ type: "text", text: NO_PROJECT_ERROR }], isError: true };
|
|
61139
|
+
}
|
|
61140
|
+
try {
|
|
61141
|
+
const allTokens = await this.adapter.load();
|
|
61142
|
+
const registry2 = new TokenRegistry(allTokens);
|
|
61143
|
+
registry2.setAdapter(this.adapter);
|
|
61144
|
+
const results = [];
|
|
61145
|
+
for (const mapping of mappings) {
|
|
61146
|
+
const { source, target, value: value2, reason, namespace, category } = mapping;
|
|
61147
|
+
if (!source || !target || !value2 || !reason) {
|
|
61148
|
+
results.push({
|
|
61149
|
+
source: source ?? "?",
|
|
61150
|
+
target: target ?? "?",
|
|
61151
|
+
action: "skipped",
|
|
61152
|
+
ok: false,
|
|
61153
|
+
error: "Missing required fields: source, target, value, reason"
|
|
61154
|
+
});
|
|
61155
|
+
continue;
|
|
61156
|
+
}
|
|
61157
|
+
const exists = registry2.has(target);
|
|
61158
|
+
if (exists) {
|
|
61159
|
+
const existing = registry2.get(target);
|
|
61160
|
+
if (existing) {
|
|
61161
|
+
const previousValue = existing.value;
|
|
61162
|
+
existing.userOverride = {
|
|
61163
|
+
previousValue: typeof previousValue === "string" ? previousValue : JSON.stringify(previousValue),
|
|
61164
|
+
reason: `Onboarded from ${source}: ${reason}`
|
|
61165
|
+
};
|
|
61166
|
+
await registry2.set(target, value2);
|
|
61167
|
+
results.push({ source, target, action: "set", ok: true });
|
|
61168
|
+
}
|
|
61169
|
+
} else {
|
|
61170
|
+
const ns = namespace ?? "color";
|
|
61171
|
+
const cat = category ?? ns;
|
|
61172
|
+
const newToken = {
|
|
61173
|
+
name: target,
|
|
61174
|
+
namespace: ns,
|
|
61175
|
+
category: cat,
|
|
61176
|
+
value: value2,
|
|
61177
|
+
containerQueryAware: true,
|
|
61178
|
+
userOverride: {
|
|
61179
|
+
previousValue: "",
|
|
61180
|
+
reason: `Onboarded from ${source}: ${reason}`
|
|
61181
|
+
}
|
|
61182
|
+
};
|
|
61183
|
+
registry2.add(newToken);
|
|
61184
|
+
results.push({ source, target, action: "create", ok: true });
|
|
61185
|
+
}
|
|
61186
|
+
}
|
|
61187
|
+
await this.adapter.save(registry2.list());
|
|
61188
|
+
await this.regenerateOutputs(registry2);
|
|
61189
|
+
const setCount = results.filter((r) => r.action === "set" && r.ok).length;
|
|
61190
|
+
const createCount = results.filter((r) => r.action === "create" && r.ok).length;
|
|
61191
|
+
const failCount = results.filter((r) => !r.ok).length;
|
|
61192
|
+
return {
|
|
61193
|
+
content: [
|
|
61194
|
+
{
|
|
61195
|
+
type: "text",
|
|
61196
|
+
text: JSON.stringify(
|
|
61197
|
+
{
|
|
61198
|
+
ok: true,
|
|
61199
|
+
summary: { set: setCount, created: createCount, failed: failCount },
|
|
61200
|
+
results
|
|
61201
|
+
},
|
|
61202
|
+
null,
|
|
61203
|
+
2
|
|
61204
|
+
)
|
|
61205
|
+
}
|
|
61206
|
+
]
|
|
61207
|
+
};
|
|
61208
|
+
} catch (error48) {
|
|
61209
|
+
return this.handleError("mapTokens", error48);
|
|
61210
|
+
}
|
|
61211
|
+
}
|
|
60720
61212
|
// ==================== Tool 5: Cognitive Budget ====================
|
|
60721
61213
|
/**
|
|
60722
61214
|
* Evaluate composition-level cognitive load
|
|
@@ -60942,7 +61434,7 @@ import { existsSync as existsSync7 } from "fs";
|
|
|
60942
61434
|
import { resolve as resolve6 } from "path";
|
|
60943
61435
|
|
|
60944
61436
|
// ../studio/src/api/vite-plugin.ts
|
|
60945
|
-
import { writeFile as
|
|
61437
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
60946
61438
|
import { join as join13 } from "path";
|
|
60947
61439
|
var TokenResponseSchema = external_exports.object({
|
|
60948
61440
|
ok: external_exports.literal(true),
|
|
@@ -61156,12 +61648,6 @@ async function handleBuildColor(req, res) {
|
|
|
61156
61648
|
}
|
|
61157
61649
|
}
|
|
61158
61650
|
async function handlePostToken(req, res, name2, registry2) {
|
|
61159
|
-
const existingToken = registry2.get(name2);
|
|
61160
|
-
if (!existingToken) {
|
|
61161
|
-
res.statusCode = 404;
|
|
61162
|
-
res.end(JSON.stringify({ ok: false, error: `Token "${name2}" not found` }));
|
|
61163
|
-
return;
|
|
61164
|
-
}
|
|
61165
61651
|
let body;
|
|
61166
61652
|
try {
|
|
61167
61653
|
body = await readJsonBody(req);
|
|
@@ -61171,6 +61657,53 @@ async function handlePostToken(req, res, name2, registry2) {
|
|
|
61171
61657
|
res.end(JSON.stringify({ ok: false, error: message }));
|
|
61172
61658
|
return;
|
|
61173
61659
|
}
|
|
61660
|
+
const existingToken = registry2.get(name2);
|
|
61661
|
+
if (!existingToken) {
|
|
61662
|
+
const createSchema = external_exports.object({
|
|
61663
|
+
namespace: external_exports.string().min(1),
|
|
61664
|
+
category: external_exports.string().min(1),
|
|
61665
|
+
value: external_exports.union([external_exports.string(), ColorValueSchema, ColorReferenceSchema]),
|
|
61666
|
+
userOverride: external_exports.object({
|
|
61667
|
+
previousValue: external_exports.union([external_exports.string(), ColorValueSchema, ColorReferenceSchema]),
|
|
61668
|
+
reason: external_exports.string().min(1, "Reason is required. Every token needs a why."),
|
|
61669
|
+
context: external_exports.string().optional()
|
|
61670
|
+
}),
|
|
61671
|
+
description: external_exports.string().optional()
|
|
61672
|
+
});
|
|
61673
|
+
const createResult = createSchema.safeParse(body);
|
|
61674
|
+
if (!createResult.success) {
|
|
61675
|
+
res.statusCode = 400;
|
|
61676
|
+
const issues = createResult.error.issues;
|
|
61677
|
+
const message = issues[0] ? `${issues[0].path.join(".") || "body"}: ${issues[0].message}` : createResult.error.message;
|
|
61678
|
+
res.end(JSON.stringify({ ok: false, error: message }));
|
|
61679
|
+
return;
|
|
61680
|
+
}
|
|
61681
|
+
const newToken = {
|
|
61682
|
+
name: name2,
|
|
61683
|
+
namespace: createResult.data.namespace,
|
|
61684
|
+
category: createResult.data.category,
|
|
61685
|
+
value: createResult.data.value,
|
|
61686
|
+
userOverride: createResult.data.userOverride,
|
|
61687
|
+
containerQueryAware: true,
|
|
61688
|
+
description: createResult.data.description
|
|
61689
|
+
};
|
|
61690
|
+
const tokenResult2 = TokenSchema.safeParse(newToken);
|
|
61691
|
+
if (!tokenResult2.success) {
|
|
61692
|
+
res.statusCode = 400;
|
|
61693
|
+
res.end(JSON.stringify({ ok: false, error: tokenResult2.error.message }));
|
|
61694
|
+
return;
|
|
61695
|
+
}
|
|
61696
|
+
try {
|
|
61697
|
+
registry2.add(tokenResult2.data);
|
|
61698
|
+
const response = TokenResponseSchema.parse({ ok: true, token: registry2.get(name2) });
|
|
61699
|
+
res.statusCode = 201;
|
|
61700
|
+
res.end(JSON.stringify(response));
|
|
61701
|
+
} catch (error48) {
|
|
61702
|
+
res.statusCode = 500;
|
|
61703
|
+
res.end(JSON.stringify({ ok: false, error: String(error48) }));
|
|
61704
|
+
}
|
|
61705
|
+
return;
|
|
61706
|
+
}
|
|
61174
61707
|
const namespaceSchema = getNamespacePatchSchema(existingToken.namespace);
|
|
61175
61708
|
const patchResult = namespaceSchema.safeParse(body);
|
|
61176
61709
|
if (!patchResult.success) {
|
|
@@ -61300,7 +61833,7 @@ function studioApiPlugin() {
|
|
|
61300
61833
|
}
|
|
61301
61834
|
registry2.setChangeCallback(async () => {
|
|
61302
61835
|
try {
|
|
61303
|
-
await
|
|
61836
|
+
await writeFile5(outputPath, registryToVars(registry2));
|
|
61304
61837
|
server.ws.send({ type: "custom", event: "rafters:css-updated" });
|
|
61305
61838
|
} catch (error48) {
|
|
61306
61839
|
console.log(`[rafters] CSS regeneration failed: ${error48}`);
|
|
@@ -61317,6 +61850,19 @@ function studioApiPlugin() {
|
|
|
61317
61850
|
}
|
|
61318
61851
|
const data = parsed.data;
|
|
61319
61852
|
const shouldPersist = data.persist !== false;
|
|
61853
|
+
const existingToken = registry2.get(data.name);
|
|
61854
|
+
const isColorFamily = existingToken?.namespace === "color" && typeof data.value === "object" && data.value !== null && "scale" in data.value;
|
|
61855
|
+
let enrichmentPromise = null;
|
|
61856
|
+
if (isColorFamily) {
|
|
61857
|
+
const colorValue = data.value;
|
|
61858
|
+
const base = colorValue.scale?.[5];
|
|
61859
|
+
if (base) {
|
|
61860
|
+
const l = base.l.toFixed(3);
|
|
61861
|
+
const c4 = base.c.toFixed(3);
|
|
61862
|
+
const h = Math.round(base.h);
|
|
61863
|
+
enrichmentPromise = fetch(`https://api.rafters.studio/color/${l}-${c4}-${h}?sync=true`).then((r) => r.ok ? r.json() : null).catch(() => null);
|
|
61864
|
+
}
|
|
61865
|
+
}
|
|
61320
61866
|
try {
|
|
61321
61867
|
if (shouldPersist) {
|
|
61322
61868
|
await registry2.set(data.name, data.value);
|
|
@@ -61328,6 +61874,26 @@ function studioApiPlugin() {
|
|
|
61328
61874
|
name: data.name,
|
|
61329
61875
|
persisted: shouldPersist
|
|
61330
61876
|
});
|
|
61877
|
+
if (enrichmentPromise) {
|
|
61878
|
+
const enrichment = await enrichmentPromise;
|
|
61879
|
+
if (enrichment && typeof enrichment === "object" && "color" in enrichment) {
|
|
61880
|
+
const apiColor = enrichment.color;
|
|
61881
|
+
if (apiColor?.intelligence) {
|
|
61882
|
+
const current = registry2.get(data.name);
|
|
61883
|
+
if (current && typeof current.value === "object" && current.value !== null) {
|
|
61884
|
+
const enrichedValue = {
|
|
61885
|
+
...current.value,
|
|
61886
|
+
intelligence: apiColor.intelligence
|
|
61887
|
+
};
|
|
61888
|
+
await registry2.set(data.name, enrichedValue);
|
|
61889
|
+
client.send("rafters:color-enriched", {
|
|
61890
|
+
name: data.name,
|
|
61891
|
+
intelligence: apiColor.intelligence
|
|
61892
|
+
});
|
|
61893
|
+
}
|
|
61894
|
+
}
|
|
61895
|
+
}
|
|
61896
|
+
}
|
|
61331
61897
|
} catch (error48) {
|
|
61332
61898
|
console.log(`[rafters] Token update failed for "${data.name}": ${error48}`);
|
|
61333
61899
|
client.send("rafters:token-updated", { ok: false, error: String(error48) });
|