rafters 0.0.4 → 0.0.6

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.
Files changed (3) hide show
  1. package/README.md +130 -0
  2. package/dist/index.js +299 -80
  3. package/package.json +2 -1
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # rafters
2
+
3
+ Design Intelligence CLI. Scaffold tokens, add components, and serve an MCP server so AI agents build UIs with designer-level judgment instead of guessing.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx rafters init
9
+ ```
10
+
11
+ Or install globally:
12
+
13
+ ```bash
14
+ pnpm add -g rafters
15
+ ```
16
+
17
+ ## Commands
18
+
19
+ ### `rafters init`
20
+
21
+ Initialize a project with design tokens. Detects your framework, scaffolds `.rafters/` with a complete token system, and generates output files.
22
+
23
+ ```bash
24
+ rafters init # Interactive setup
25
+ rafters init --force # Regenerate output from existing config
26
+ rafters init --agent # JSON output for CI/machine consumption
27
+ ```
28
+
29
+ **Supported frameworks:** Next.js, Vite, Remix, React Router, Astro
30
+
31
+ **Export formats** (configured during init):
32
+
33
+ | Format | File | Default | Description |
34
+ |--------|------|---------|-------------|
35
+ | Tailwind CSS | `rafters.css` | Yes | CSS custom properties with `@theme` |
36
+ | TypeScript | `rafters.ts` | Yes | Type-safe constants with JSDoc intelligence |
37
+ | DTCG JSON | `rafters.json` | No | W3C Design Tokens standard format |
38
+ | Compiled CSS | `rafters.compiled.css` | No | Pre-processed, no build step needed |
39
+
40
+ Automatically detects and migrates existing shadcn/ui color values. Requires Tailwind v4.
41
+
42
+ ### `rafters add [components...]`
43
+
44
+ Add components from the Rafters registry to your project.
45
+
46
+ ```bash
47
+ rafters add button dialog # Add specific components
48
+ rafters add --list # List all available components
49
+ rafters add --overwrite # Replace existing files
50
+ ```
51
+
52
+ Components include embedded design intelligence: cognitive load ratings (1-7), accessibility requirements, do/never guidance, and trust-building patterns. Dependencies are resolved automatically.
53
+
54
+ ### `rafters studio`
55
+
56
+ Launch Studio for visual token editing.
57
+
58
+ ```bash
59
+ rafters studio
60
+ ```
61
+
62
+ Opens a Vite-powered UI where you design by doing: pick a primary color, explain why, watch the system paint your scale. Every decision is recorded with reasoning so AI agents read intent instead of guessing.
63
+
64
+ ### `rafters mcp`
65
+
66
+ Start the MCP server for AI agent access via stdio transport.
67
+
68
+ ```bash
69
+ rafters mcp
70
+ ```
71
+
72
+ ## MCP Tools
73
+
74
+ Four tools give AI agents complete design system access:
75
+
76
+ ### `rafters_vocabulary`
77
+
78
+ System overview. Colors, spacing, typography, components, cognitive loads, and available patterns. Call this first to orient.
79
+
80
+ ### `rafters_pattern`
81
+
82
+ Deep guidance for specific UI scenarios. Returns which components to use, which tokens to apply, accessibility requirements, and do/never rules.
83
+
84
+ **Available patterns:** `destructive-action`, `form-validation`, `empty-state`, `loading-state`, `navigation-hierarchy`, `data-table`, `modal-dialog`, `tooltip-guidance`, `card-layout`, `dropdown-actions`
85
+
86
+ ### `rafters_component`
87
+
88
+ Full intelligence for a specific component. Cognitive load rating, attention economics, accessibility requirements, trust-building patterns, variants, sizes, and primitives.
89
+
90
+ ### `rafters_token`
91
+
92
+ Token dependency graph, derivation rules, and human override context. Returns how a token is computed, what it depends on, and whether a designer manually overrode it (with their reasoning).
93
+
94
+ **Derivation rules:** `calc()`, `state:hover`, `scale:600`, `contrast:auto`, `invert`
95
+
96
+ ## How It Works
97
+
98
+ Rafters is a Design Intelligence Protocol. AI agents don't have taste - they guess at colors, spacing, hierarchy. Rafters encodes a designer's judgment into queryable data so AI reads decisions instead of guessing.
99
+
100
+ Three layers:
101
+
102
+ - **What** (Components) - 55 React components with embedded intelligence metadata
103
+ - **Where** (Tokens) - 240+ tokens with dependency graph and human override tracking
104
+ - **Why** (Decisions) - Do/never patterns, cognitive load scores, trust patterns, accessibility requirements
105
+
106
+ The token system uses OKLCH color space, modular scales based on musical ratios, and a dependency engine that automatically derives related values. When a designer overrides a computed value, the system records the reason so AI agents respect the intent.
107
+
108
+ ## Project Structure
109
+
110
+ ```
111
+ .rafters/
112
+ config.rafters.json # Framework paths and export settings
113
+ tokens/
114
+ color.rafters.json # Color tokens with OKLCH values
115
+ spacing.rafters.json # Spacing scale
116
+ typography.rafters.json # Type scale
117
+ output/
118
+ rafters.css # Tailwind CSS export
119
+ rafters.ts # TypeScript constants
120
+ ```
121
+
122
+ ## Requirements
123
+
124
+ - Node.js >= 24.12.0
125
+ - Tailwind CSS v4
126
+ - React >= 19.0.0
127
+
128
+ ## License
129
+
130
+ MIT
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { Command } from "commander";
9
9
 
10
10
  // src/commands/add.ts
11
11
  import { existsSync } from "fs";
12
- import { access, mkdir, writeFile } from "fs/promises";
12
+ import { access, mkdir, readFile, writeFile } from "fs/promises";
13
13
  import { dirname, join as join2 } from "path";
14
14
 
15
15
  // ../../node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/external.js
@@ -12730,7 +12730,7 @@ function setAgentMode(agent) {
12730
12730
  }
12731
12731
  function log(event) {
12732
12732
  if (context.agent) {
12733
- console.log(event);
12733
+ console.log(JSON.stringify(event));
12734
12734
  return;
12735
12735
  }
12736
12736
  const eventType = event.event;
@@ -12748,13 +12748,14 @@ function log(event) {
12748
12748
  }
12749
12749
  context.spinner = ora("Generating tokens...").start();
12750
12750
  break;
12751
- case "init:shadcn_detected":
12751
+ case "init:shadcn_detected": {
12752
12752
  context.spinner?.info("Found existing shadcn colors");
12753
12753
  console.log(` Backed up: ${event.backupPath}`);
12754
12754
  const colors = event.colorsFound;
12755
12755
  console.log(` Colors: ${colors.light} light, ${colors.dark} dark`);
12756
12756
  context.spinner = ora("Generating tokens...").start();
12757
12757
  break;
12758
+ }
12758
12759
  case "init:generated":
12759
12760
  context.spinner?.succeed(`Generated ${event.tokenCount} tokens`);
12760
12761
  context.spinner = ora("Saving registry...").start();
@@ -12782,7 +12783,18 @@ function log(event) {
12782
12783
  case "init:colors_imported":
12783
12784
  console.log(` Imported ${event.count} existing colors`);
12784
12785
  break;
12785
- case "init:complete":
12786
+ case "init:exports_default":
12787
+ console.log(" Using default exports (agent mode)");
12788
+ break;
12789
+ case "init:exports_selected":
12790
+ context.spinner = ora("Generating outputs...").start();
12791
+ break;
12792
+ case "init:compiling_css":
12793
+ if (context.spinner) {
12794
+ context.spinner.text = "Compiling CSS with Tailwind...";
12795
+ }
12796
+ break;
12797
+ case "init:complete": {
12786
12798
  context.spinner?.succeed("Done!");
12787
12799
  console.log(`
12788
12800
  Output: ${event.path}`);
@@ -12792,12 +12804,14 @@ function log(event) {
12792
12804
  }
12793
12805
  console.log("");
12794
12806
  break;
12807
+ }
12795
12808
  // Add events
12796
- case "add:start":
12809
+ case "add:start": {
12797
12810
  const components = event.components;
12798
12811
  context.spinner = ora(`Adding ${components.join(", ")}...`).start();
12799
12812
  break;
12800
- case "add:installed":
12813
+ }
12814
+ case "add:installed": {
12801
12815
  context.spinner?.succeed(`Installed ${event.component}`);
12802
12816
  const files = event.files;
12803
12817
  for (const file2 of files) {
@@ -12805,6 +12819,7 @@ function log(event) {
12805
12819
  }
12806
12820
  context.spinner = ora("Installing...").start();
12807
12821
  break;
12822
+ }
12808
12823
  case "add:skip":
12809
12824
  context.spinner?.warn(`Skipped ${event.component} (already exists)`);
12810
12825
  context.spinner = ora("Installing...").start();
@@ -12843,7 +12858,7 @@ function log(event) {
12843
12858
  }
12844
12859
  function error46(message) {
12845
12860
  if (context.agent) {
12846
- console.error({ event: "error", message });
12861
+ console.error(JSON.stringify({ event: "error", message }));
12847
12862
  return;
12848
12863
  }
12849
12864
  context.spinner?.fail(message);
@@ -12927,39 +12942,77 @@ async function isInitialized(cwd) {
12927
12942
  return false;
12928
12943
  }
12929
12944
  }
12945
+ async function loadConfig(cwd) {
12946
+ const paths = getRaftersPaths(cwd);
12947
+ try {
12948
+ const content = await readFile(paths.config, "utf-8");
12949
+ return JSON.parse(content);
12950
+ } catch (err) {
12951
+ if (existsSync(paths.config)) {
12952
+ const message = err instanceof Error ? err.message : String(err);
12953
+ log({ event: "add:warning", message: `Failed to load config: ${message}` });
12954
+ }
12955
+ return null;
12956
+ }
12957
+ }
12958
+ function transformPath(registryPath, config3) {
12959
+ if (!config3) return registryPath;
12960
+ if (registryPath.startsWith("components/ui/")) {
12961
+ return registryPath.replace("components/ui/", `${config3.componentsPath}/`);
12962
+ }
12963
+ if (registryPath.startsWith("lib/primitives/")) {
12964
+ return registryPath.replace("lib/primitives/", `${config3.primitivesPath}/`);
12965
+ }
12966
+ return registryPath;
12967
+ }
12930
12968
  function fileExists(cwd, relativePath) {
12931
12969
  return existsSync(join2(cwd, relativePath));
12932
12970
  }
12933
- function transformFileContent(content) {
12971
+ function transformFileContent(content, config3) {
12934
12972
  let transformed = content;
12973
+ const componentsPath = config3?.componentsPath ?? "components/ui";
12974
+ const primitivesPath = config3?.primitivesPath ?? "lib/primitives";
12935
12975
  transformed = transformed.replace(
12936
12976
  /from\s+['"]\.\.\/\.\.\/primitives\/([^'"]+)['"]/g,
12937
- "from '@/lib/primitives/$1'"
12977
+ `from '@/${primitivesPath}/$1'`
12938
12978
  );
12939
12979
  transformed = transformed.replace(
12940
12980
  /from\s+['"]\.\.\/primitives\/([^'"]+)['"]/g,
12941
- "from '@/lib/primitives/$1'"
12981
+ `from '@/${primitivesPath}/$1'`
12982
+ );
12983
+ transformed = transformed.replace(
12984
+ /from\s+['"]\.\/([^'"]+)['"]/g,
12985
+ `from '@/${componentsPath}/$1'`
12986
+ );
12987
+ const libPath = dirname(primitivesPath);
12988
+ transformed = transformed.replace(
12989
+ /from\s+['"]\.\.\/lib\/([^'"]+)['"]/g,
12990
+ `from '@/${libPath}/$1'`
12991
+ );
12992
+ const componentsMatch = componentsPath.match(/^(.*)components\/ui$/);
12993
+ const hooksPath = componentsMatch ? `${componentsMatch[1]}hooks`.replace(/^\//, "") : "hooks";
12994
+ transformed = transformed.replace(
12995
+ /from\s+['"]\.\.\/hooks\/([^'"]+)['"]/g,
12996
+ `from '@/${hooksPath}/$1'`
12942
12997
  );
12943
- transformed = transformed.replace(/from\s+['"]\.\/([^'"]+)['"]/g, "from '@/components/ui/$1'");
12944
- transformed = transformed.replace(/from\s+['"]\.\.\/lib\/([^'"]+)['"]/g, "from '@/lib/$1'");
12945
- transformed = transformed.replace(/from\s+['"]\.\.\/hooks\/([^'"]+)['"]/g, "from '@/hooks/$1'");
12946
12998
  transformed = transformed.replace(
12947
12999
  /from\s+['"]\.\.\/(?!lib\/|hooks\/)([^'"]+)['"]/g,
12948
- "from '@/components/ui/$1'"
13000
+ `from '@/${componentsPath}/$1'`
12949
13001
  );
12950
13002
  return transformed;
12951
13003
  }
12952
- async function installItem(cwd, item, options) {
13004
+ async function installItem(cwd, item, options, config3) {
12953
13005
  const installedFiles = [];
12954
13006
  let skipped = false;
12955
13007
  for (const file2 of item.files) {
12956
- const targetPath = join2(cwd, file2.path);
12957
- if (fileExists(cwd, file2.path)) {
13008
+ const projectPath = transformPath(file2.path, config3);
13009
+ const targetPath = join2(cwd, projectPath);
13010
+ if (fileExists(cwd, projectPath)) {
12958
13011
  if (!options.overwrite) {
12959
13012
  log({
12960
13013
  event: "add:skip",
12961
13014
  component: item.name,
12962
- file: file2.path,
13015
+ file: projectPath,
12963
13016
  reason: "exists"
12964
13017
  });
12965
13018
  skipped = true;
@@ -12967,9 +13020,9 @@ async function installItem(cwd, item, options) {
12967
13020
  }
12968
13021
  }
12969
13022
  await mkdir(dirname(targetPath), { recursive: true });
12970
- const transformedContent = transformFileContent(file2.content);
13023
+ const transformedContent = transformFileContent(file2.content, config3);
12971
13024
  await writeFile(targetPath, transformedContent, "utf-8");
12972
- installedFiles.push(file2.path);
13025
+ installedFiles.push(projectPath);
12973
13026
  }
12974
13027
  return {
12975
13028
  installed: installedFiles.length > 0,
@@ -13001,6 +13054,7 @@ async function add(components, options) {
13001
13054
  process.exitCode = 1;
13002
13055
  return;
13003
13056
  }
13057
+ const config3 = await loadConfig(cwd);
13004
13058
  if (components.length === 0) {
13005
13059
  error46("No components specified. Usage: rafters add <component...>");
13006
13060
  process.exitCode = 1;
@@ -13033,7 +13087,7 @@ async function add(components, options) {
13033
13087
  const skipped = [];
13034
13088
  for (const item of allItems) {
13035
13089
  try {
13036
- const result = await installItem(cwd, item, options);
13090
+ const result = await installItem(cwd, item, options, config3);
13037
13091
  if (result.installed) {
13038
13092
  installed.push(item.name);
13039
13093
  log({
@@ -13089,9 +13143,10 @@ async function add(components, options) {
13089
13143
  }
13090
13144
 
13091
13145
  // src/commands/init.ts
13092
- import { existsSync as existsSync2 } from "fs";
13093
- import { copyFile, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
13094
- import { join as join8, relative } from "path";
13146
+ import { checkbox } from "@inquirer/prompts";
13147
+ import { existsSync as existsSync3 } from "fs";
13148
+ import { copyFile, mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
13149
+ import { join as join9, relative } from "path";
13095
13150
 
13096
13151
  // ../design-tokens/src/generation-rules.ts
13097
13152
  var GenerationRuleParser = class {
@@ -24121,7 +24176,7 @@ var require_volume = __commonJS({
24121
24176
  var Dir_1 = require_Dir();
24122
24177
  var resolveCrossPlatform = pathModule.resolve;
24123
24178
  var { O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC, O_APPEND, O_DIRECTORY, O_SYMLINK, F_OK, COPYFILE_EXCL, COPYFILE_FICLONE_FORCE } = constants_1.constants;
24124
- var { sep: sep2, relative: relative2, join: join11, dirname: dirname4 } = pathModule.posix ? pathModule.posix : pathModule;
24179
+ var { sep: sep2, relative: relative2, join: join12, dirname: dirname4 } = pathModule.posix ? pathModule.posix : pathModule;
24125
24180
  var kMinPoolSpace = 128;
24126
24181
  var EPERM = "EPERM";
24127
24182
  var ENOENT2 = "ENOENT";
@@ -24187,7 +24242,7 @@ var require_volume = __commonJS({
24187
24242
  function flatten(pathPrefix, node) {
24188
24243
  for (const path2 in node) {
24189
24244
  const contentOrNode = node[path2];
24190
- const joinedPath = join11(pathPrefix, path2);
24245
+ const joinedPath = join12(pathPrefix, path2);
24191
24246
  if (typeof contentOrNode === "string" || contentOrNode instanceof buffer_1.Buffer) {
24192
24247
  flatJSON[joinedPath] = contentOrNode;
24193
24248
  } else if (typeof contentOrNode === "object" && contentOrNode !== null && Object.keys(contentOrNode).length > 0) {
@@ -24344,7 +24399,7 @@ var require_volume = __commonJS({
24344
24399
  return null;
24345
24400
  node = curr === null || curr === void 0 ? void 0 : curr.getNode();
24346
24401
  if (resolveSymlinks && node.isSymlink()) {
24347
- const resolvedPath = pathModule.isAbsolute(node.symlink) ? node.symlink : join11(pathModule.dirname(curr.getPath()), node.symlink);
24402
+ const resolvedPath = pathModule.isAbsolute(node.symlink) ? node.symlink : join12(pathModule.dirname(curr.getPath()), node.symlink);
24348
24403
  steps = filenameToSteps(resolvedPath).concat(steps.slice(i + 1));
24349
24404
  curr = this.root;
24350
24405
  i = 0;
@@ -48960,7 +49015,7 @@ function buildColorSystem(options = {}) {
48960
49015
  }
48961
49016
 
48962
49017
  // ../design-tokens/src/persistence/node-adapter.ts
48963
- import { access as access2, mkdir as mkdir2, readdir as readdir2, readFile, writeFile as writeFile2 } from "fs/promises";
49018
+ import { access as access2, mkdir as mkdir2, readdir as readdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
48964
49019
  import { join as join5 } from "path";
48965
49020
 
48966
49021
  // ../../node_modules/.pnpm/comment-parser@1.4.1/node_modules/comment-parser/es6/primitives.js
@@ -49099,7 +49154,7 @@ function tagTokenizer() {
49099
49154
 
49100
49155
  // ../../node_modules/.pnpm/comment-parser@1.4.1/node_modules/comment-parser/es6/parser/tokenizers/type.js
49101
49156
  function typeTokenizer(spacing = "compact") {
49102
- const join11 = getJoiner(spacing);
49157
+ const join12 = getJoiner(spacing);
49103
49158
  return (spec) => {
49104
49159
  let curlies = 0;
49105
49160
  let lines = [];
@@ -49142,7 +49197,7 @@ function typeTokenizer(spacing = "compact") {
49142
49197
  }
49143
49198
  parts[0] = parts[0].slice(1);
49144
49199
  parts[parts.length - 1] = parts[parts.length - 1].slice(0, -1);
49145
- spec.type = join11(parts);
49200
+ spec.type = join12(parts);
49146
49201
  return spec;
49147
49202
  };
49148
49203
  }
@@ -49240,9 +49295,9 @@ function nameTokenizer() {
49240
49295
 
49241
49296
  // ../../node_modules/.pnpm/comment-parser@1.4.1/node_modules/comment-parser/es6/parser/tokenizers/description.js
49242
49297
  function descriptionTokenizer(spacing = "compact", markers = Markers) {
49243
- const join11 = getJoiner2(spacing);
49298
+ const join12 = getJoiner2(spacing);
49244
49299
  return (spec) => {
49245
- spec.description = join11(spec.source, markers);
49300
+ spec.description = join12(spec.source, markers);
49246
49301
  return spec;
49247
49302
  };
49248
49303
  }
@@ -49925,7 +49980,7 @@ var NodePersistenceAdapter = class {
49925
49980
  }
49926
49981
  async loadNamespace(namespace) {
49927
49982
  const filePath = this.getFilePath(namespace);
49928
- const content = await readFile(filePath, "utf-8");
49983
+ const content = await readFile2(filePath, "utf-8");
49929
49984
  const json3 = JSON.parse(content);
49930
49985
  const data = NamespaceFileSchema.parse(json3);
49931
49986
  return data.tokens;
@@ -49985,16 +50040,19 @@ var RuleContextSchema = external_exports.object({
49985
50040
  });
49986
50041
 
49987
50042
  // src/utils/detect.ts
49988
- import { readFile as readFile2 } from "fs/promises";
50043
+ import { readFile as readFile3 } from "fs/promises";
49989
50044
  import { join as join7 } from "path";
49990
50045
  async function detectFramework(cwd) {
49991
50046
  try {
49992
- const content = await readFile2(join7(cwd, "package.json"), "utf-8");
50047
+ const content = await readFile3(join7(cwd, "package.json"), "utf-8");
49993
50048
  const pkg = JSON.parse(content);
49994
50049
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
49995
50050
  if (deps.next) {
49996
50051
  return "next";
49997
50052
  }
50053
+ if (deps["react-router"]) {
50054
+ return "react-router";
50055
+ }
49998
50056
  const hasRemix = Object.keys(deps).some((dep) => dep.startsWith("@remix-run/"));
49999
50057
  if (hasRemix) {
50000
50058
  return "remix";
@@ -50012,7 +50070,7 @@ async function detectFramework(cwd) {
50012
50070
  }
50013
50071
  async function detectTailwindVersion(cwd) {
50014
50072
  try {
50015
- const content = await readFile2(join7(cwd, "package.json"), "utf-8");
50073
+ const content = await readFile3(join7(cwd, "package.json"), "utf-8");
50016
50074
  const pkg = JSON.parse(content);
50017
50075
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
50018
50076
  const tailwindVersion = deps.tailwindcss;
@@ -50033,7 +50091,7 @@ function isTailwindV3(version2) {
50033
50091
  }
50034
50092
  async function detectShadcn(cwd) {
50035
50093
  try {
50036
- const content = await readFile2(join7(cwd, "components.json"), "utf-8");
50094
+ const content = await readFile3(join7(cwd, "components.json"), "utf-8");
50037
50095
  return JSON.parse(content);
50038
50096
  } catch {
50039
50097
  return null;
@@ -50095,6 +50153,79 @@ async function detectProject(cwd) {
50095
50153
  };
50096
50154
  }
50097
50155
 
50156
+ // src/utils/exports.ts
50157
+ import { execFileSync } from "child_process";
50158
+ import { existsSync as existsSync2 } from "fs";
50159
+ import { join as join8 } from "path";
50160
+ var DEFAULT_EXPORTS = {
50161
+ tailwind: true,
50162
+ typescript: true,
50163
+ dtcg: false,
50164
+ compiled: false
50165
+ };
50166
+ var EXPORT_CHOICES = [
50167
+ {
50168
+ name: "Tailwind CSS (web projects)",
50169
+ value: "tailwind",
50170
+ checked: true
50171
+ },
50172
+ {
50173
+ name: "TypeScript (type-safe constants)",
50174
+ value: "typescript",
50175
+ checked: true
50176
+ },
50177
+ {
50178
+ name: "DTCG JSON (Figma Tokens, Style Dictionary)",
50179
+ value: "dtcg",
50180
+ checked: false
50181
+ },
50182
+ {
50183
+ name: "Compiled CSS (documentation, no build step)",
50184
+ value: "compiled",
50185
+ checked: false
50186
+ }
50187
+ ];
50188
+ var FUTURE_EXPORTS = [
50189
+ {
50190
+ name: "iOS (Swift/SwiftUI)",
50191
+ value: "tailwind",
50192
+ // placeholder
50193
+ checked: false,
50194
+ disabled: "coming soon"
50195
+ },
50196
+ {
50197
+ name: "Android (Compose)",
50198
+ value: "tailwind",
50199
+ // placeholder
50200
+ checked: false,
50201
+ disabled: "coming soon"
50202
+ }
50203
+ ];
50204
+ async function generateCompiledCss(cwd, inputPath, outputPath) {
50205
+ const nodeModulesBin = join8(cwd, "node_modules", ".bin", "tailwindcss");
50206
+ const hasTailwindCli = existsSync2(nodeModulesBin);
50207
+ const args = ["-i", inputPath, "-o", outputPath, "--minify"];
50208
+ if (hasTailwindCli) {
50209
+ execFileSync(nodeModulesBin, args, { cwd, stdio: "pipe" });
50210
+ } else {
50211
+ try {
50212
+ execFileSync("pnpm", ["exec", "tailwindcss", ...args], { cwd, stdio: "pipe" });
50213
+ } catch {
50214
+ throw new Error(
50215
+ "Tailwind CLI not found. Install it with: pnpm add -D @tailwindcss/cli"
50216
+ );
50217
+ }
50218
+ }
50219
+ }
50220
+ function selectionsToConfig(selections) {
50221
+ return {
50222
+ tailwind: selections.includes("tailwind"),
50223
+ typescript: selections.includes("typescript"),
50224
+ dtcg: selections.includes("dtcg"),
50225
+ compiled: selections.includes("compiled")
50226
+ };
50227
+ }
50228
+
50098
50229
  // src/commands/init.ts
50099
50230
  async function backupCss(cssPath) {
50100
50231
  const backupPath = cssPath.replace(/\.css$/, ".backup.css");
@@ -50106,25 +50237,34 @@ var CSS_LOCATIONS = {
50106
50237
  next: ["src/app/globals.css", "app/globals.css", "styles/globals.css"],
50107
50238
  vite: ["src/index.css", "src/main.css", "src/styles.css", "src/app.css"],
50108
50239
  remix: ["app/styles/global.css", "app/globals.css", "app/root.css"],
50240
+ "react-router": ["app/app.css", "app/root.css", "app/styles.css", "app/globals.css"],
50109
50241
  unknown: ["src/styles/global.css", "src/index.css", "styles/globals.css"]
50110
50242
  };
50243
+ var COMPONENT_PATHS = {
50244
+ astro: { components: "src/components/ui", primitives: "src/lib/primitives" },
50245
+ next: { components: "components/ui", primitives: "lib/primitives" },
50246
+ vite: { components: "src/components/ui", primitives: "src/lib/primitives" },
50247
+ remix: { components: "app/components/ui", primitives: "app/lib/primitives" },
50248
+ "react-router": { components: "app/components/ui", primitives: "app/lib/primitives" },
50249
+ unknown: { components: "components/ui", primitives: "lib/primitives" }
50250
+ };
50111
50251
  async function findMainCssFile(cwd, framework) {
50112
50252
  const locations = CSS_LOCATIONS[framework] || CSS_LOCATIONS.unknown;
50113
50253
  for (const location of locations) {
50114
- const fullPath = join8(cwd, location);
50115
- if (existsSync2(fullPath)) {
50254
+ const fullPath = join9(cwd, location);
50255
+ if (existsSync3(fullPath)) {
50116
50256
  return location;
50117
50257
  }
50118
50258
  }
50119
50259
  return null;
50120
50260
  }
50121
50261
  async function updateMainCss(cwd, cssPath, themePath) {
50122
- const fullCssPath = join8(cwd, cssPath);
50123
- const cssContent = await readFile3(fullCssPath, "utf-8");
50124
- const cssDir = join8(cwd, cssPath, "..");
50125
- const themeFullPath = join8(cwd, themePath);
50262
+ const fullCssPath = join9(cwd, cssPath);
50263
+ const cssContent = await readFile4(fullCssPath, "utf-8");
50264
+ const cssDir = join9(cwd, cssPath, "..");
50265
+ const themeFullPath = join9(cwd, themePath);
50126
50266
  const relativeThemePath = relative(cssDir, themeFullPath);
50127
- if (cssContent.includes(".rafters/output/theme.css")) {
50267
+ if (cssContent.includes(".rafters/output/rafters.css")) {
50128
50268
  log({ event: "init:css_already_imported", cssPath });
50129
50269
  return;
50130
50270
  }
@@ -50146,8 +50286,66 @@ ${cssContent}`;
50146
50286
  themePath: relativeThemePath
50147
50287
  });
50148
50288
  }
50149
- async function regenerateFromExisting(cwd, paths, shadcn) {
50289
+ async function promptExportFormats(existingConfig) {
50290
+ const choices = EXPORT_CHOICES.map((choice) => ({
50291
+ name: choice.name,
50292
+ value: choice.value,
50293
+ checked: existingConfig ? existingConfig[choice.value] : choice.checked
50294
+ }));
50295
+ const allChoices = [
50296
+ ...choices,
50297
+ ...FUTURE_EXPORTS.map((choice) => ({
50298
+ name: `${choice.name} (${choice.disabled})`,
50299
+ value: choice.value,
50300
+ checked: false,
50301
+ disabled: true
50302
+ }))
50303
+ ];
50304
+ const selections = await checkbox({
50305
+ message: "What would you like to export?",
50306
+ choices: allChoices,
50307
+ required: true
50308
+ });
50309
+ return selectionsToConfig(selections);
50310
+ }
50311
+ async function generateOutputs(cwd, paths, registry2, exports, shadcn) {
50312
+ const outputs = [];
50313
+ if (exports.tailwind) {
50314
+ const tailwindCss = registryToTailwind(registry2, { includeImport: !shadcn });
50315
+ await writeFile3(join9(paths.output, "rafters.css"), tailwindCss);
50316
+ outputs.push("rafters.css");
50317
+ }
50318
+ if (exports.typescript) {
50319
+ const typescriptSrc = registryToTypeScript(registry2, { includeJSDoc: true });
50320
+ await writeFile3(join9(paths.output, "rafters.ts"), typescriptSrc);
50321
+ outputs.push("rafters.ts");
50322
+ }
50323
+ if (exports.dtcg) {
50324
+ const dtcgJson = toDTCG(registry2.list());
50325
+ await writeFile3(join9(paths.output, "rafters.json"), JSON.stringify(dtcgJson, null, 2));
50326
+ outputs.push("rafters.json");
50327
+ }
50328
+ if (exports.compiled) {
50329
+ if (!exports.tailwind) {
50330
+ const tailwindCss = registryToTailwind(registry2, { includeImport: !shadcn });
50331
+ await writeFile3(join9(paths.output, "rafters.css"), tailwindCss);
50332
+ }
50333
+ const inputPath = join9(paths.output, "rafters.css");
50334
+ const outputPath = join9(paths.output, "rafters.compiled.css");
50335
+ log({ event: "init:compiling_css" });
50336
+ await generateCompiledCss(cwd, inputPath, outputPath);
50337
+ outputs.push("rafters.compiled.css");
50338
+ }
50339
+ return outputs;
50340
+ }
50341
+ async function regenerateFromExisting(cwd, paths, shadcn, isAgentMode) {
50150
50342
  log({ event: "init:regenerate", cwd });
50343
+ let existingConfig = null;
50344
+ try {
50345
+ const configContent = await readFile4(paths.config, "utf-8");
50346
+ existingConfig = JSON.parse(configContent);
50347
+ } catch {
50348
+ }
50151
50349
  const adapter = new NodePersistenceAdapter(cwd);
50152
50350
  const namespaces = await adapter.listNamespaces();
50153
50351
  if (namespaces.length === 0) {
@@ -50164,21 +50362,27 @@ async function regenerateFromExisting(cwd, paths, shadcn) {
50164
50362
  namespaces
50165
50363
  });
50166
50364
  const registry2 = new TokenRegistry(allTokens);
50167
- const tailwindCss = registryToTailwind(registry2, { includeImport: !shadcn });
50168
- const typescriptSrc = registryToTypeScript(registry2, { includeJSDoc: true });
50169
- const dtcgJson = toDTCG(allTokens);
50365
+ let exports;
50366
+ if (isAgentMode) {
50367
+ exports = existingConfig?.exports ?? DEFAULT_EXPORTS;
50368
+ } else {
50369
+ exports = await promptExportFormats(existingConfig?.exports);
50370
+ }
50170
50371
  await mkdir3(paths.output, { recursive: true });
50171
- await writeFile3(join8(paths.output, "theme.css"), tailwindCss);
50172
- await writeFile3(join8(paths.output, "tokens.ts"), typescriptSrc);
50173
- await writeFile3(join8(paths.output, "tokens.json"), JSON.stringify(dtcgJson, null, 2));
50372
+ const outputs = await generateOutputs(cwd, paths, registry2, exports, shadcn);
50373
+ if (existingConfig) {
50374
+ existingConfig.exports = exports;
50375
+ await writeFile3(paths.config, JSON.stringify(existingConfig, null, 2));
50376
+ }
50174
50377
  log({
50175
50378
  event: "init:complete",
50176
- outputs: ["theme.css", "tokens.ts", "tokens.json"],
50379
+ outputs,
50177
50380
  path: paths.output
50178
50381
  });
50179
50382
  }
50180
50383
  async function init(options) {
50181
50384
  setAgentMode(options.agent ?? false);
50385
+ const isAgentMode = options.agent ?? false;
50182
50386
  const cwd = process.cwd();
50183
50387
  const paths = getRaftersPaths(cwd);
50184
50388
  log({ event: "init:start", cwd });
@@ -50192,21 +50396,21 @@ async function init(options) {
50192
50396
  if (isTailwindV3(tailwindVersion)) {
50193
50397
  throw new Error("Tailwind v3 detected. Rafters requires Tailwind v4.");
50194
50398
  }
50195
- const raftersExists = existsSync2(paths.root);
50399
+ const raftersExists = existsSync3(paths.root);
50196
50400
  if (raftersExists && !options.force) {
50197
50401
  throw new Error(
50198
50402
  ".rafters/ directory already exists. Use --force to regenerate output files from existing config."
50199
50403
  );
50200
50404
  }
50201
50405
  if (raftersExists && options.force) {
50202
- await regenerateFromExisting(cwd, paths, shadcn);
50406
+ await regenerateFromExisting(cwd, paths, shadcn, isAgentMode);
50203
50407
  return;
50204
50408
  }
50205
50409
  let existingColors = null;
50206
50410
  if (shadcn?.tailwind?.css) {
50207
- const cssPath = join8(cwd, shadcn.tailwind.css);
50411
+ const cssPath = join9(cwd, shadcn.tailwind.css);
50208
50412
  try {
50209
- const cssContent = await readFile3(cssPath, "utf-8");
50413
+ const cssContent = await readFile4(cssPath, "utf-8");
50210
50414
  existingColors = parseCssVariables(cssContent);
50211
50415
  const backupPath = await backupCss(cssPath);
50212
50416
  log({
@@ -50222,6 +50426,14 @@ async function init(options) {
50222
50426
  log({ event: "init:shadcn_css_error", error: String(err) });
50223
50427
  }
50224
50428
  }
50429
+ let exports;
50430
+ if (isAgentMode) {
50431
+ exports = DEFAULT_EXPORTS;
50432
+ log({ event: "init:exports_default", exports });
50433
+ } else {
50434
+ exports = await promptExportFormats();
50435
+ log({ event: "init:exports_selected", exports });
50436
+ }
50225
50437
  const result = buildColorSystem({
50226
50438
  exports: {
50227
50439
  tailwind: { includeImport: !shadcn },
@@ -50285,27 +50497,34 @@ async function init(options) {
50285
50497
  path: paths.tokens,
50286
50498
  namespaceCount: tokensByNamespace.size
50287
50499
  });
50288
- const tailwindCss = registryToTailwind(registry2, { includeImport: !shadcn });
50289
- const typescriptSrc = registryToTypeScript(registry2, { includeJSDoc: true });
50290
- const dtcgJson = toDTCG(registry2.list());
50291
- await writeFile3(join8(paths.output, "theme.css"), tailwindCss);
50292
- await writeFile3(join8(paths.output, "tokens.ts"), typescriptSrc);
50293
- await writeFile3(join8(paths.output, "tokens.json"), JSON.stringify(dtcgJson, null, 2));
50294
- if (!shadcn) {
50295
- const mainCssPath = await findMainCssFile(cwd, framework);
50296
- if (mainCssPath) {
50297
- await updateMainCss(cwd, mainCssPath, ".rafters/output/theme.css");
50500
+ const outputs = await generateOutputs(cwd, paths, registry2, exports, shadcn);
50501
+ let detectedCssPath = null;
50502
+ if (!shadcn && exports.tailwind) {
50503
+ detectedCssPath = await findMainCssFile(cwd, framework);
50504
+ if (detectedCssPath) {
50505
+ await updateMainCss(cwd, detectedCssPath, ".rafters/output/rafters.css");
50298
50506
  } else {
50299
50507
  log({
50300
50508
  event: "init:css_not_found",
50301
- message: 'No main CSS file found. Add @import ".rafters/output/theme.css" manually.',
50509
+ message: 'No main CSS file found. Add @import ".rafters/output/rafters.css" manually.',
50302
50510
  searchedLocations: CSS_LOCATIONS[framework] || CSS_LOCATIONS.unknown
50303
50511
  });
50304
50512
  }
50513
+ } else if (shadcn?.tailwind?.css) {
50514
+ detectedCssPath = shadcn.tailwind.css;
50305
50515
  }
50516
+ const frameworkPaths = COMPONENT_PATHS[framework] || COMPONENT_PATHS.unknown;
50517
+ const config3 = {
50518
+ framework,
50519
+ componentsPath: frameworkPaths.components,
50520
+ primitivesPath: frameworkPaths.primitives,
50521
+ cssPath: detectedCssPath,
50522
+ exports
50523
+ };
50524
+ await writeFile3(paths.config, JSON.stringify(config3, null, 2));
50306
50525
  log({
50307
50526
  event: "init:complete",
50308
- outputs: ["theme.css", "tokens.ts", "tokens.json"],
50527
+ outputs: [...outputs, "config.rafters.json"],
50309
50528
  path: paths.output
50310
50529
  });
50311
50530
  }
@@ -50316,8 +50535,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
50316
50535
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
50317
50536
 
50318
50537
  // src/mcp/tools.ts
50319
- import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
50320
- import { basename, join as join9 } from "path";
50538
+ import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
50539
+ import { basename, join as join10 } from "path";
50321
50540
  var DESIGN_PATTERNS = {
50322
50541
  "destructive-action": {
50323
50542
  name: "Destructive Action",
@@ -50867,7 +51086,7 @@ var RaftersToolHandler = class {
50867
51086
  * Get path to UI components directory
50868
51087
  */
50869
51088
  getComponentsPath() {
50870
- const monorepoPath = join9(this.projectRoot, "packages/ui/src/components/ui");
51089
+ const monorepoPath = join10(this.projectRoot, "packages/ui/src/components/ui");
50871
51090
  return monorepoPath;
50872
51091
  }
50873
51092
  /**
@@ -50875,9 +51094,9 @@ var RaftersToolHandler = class {
50875
51094
  */
50876
51095
  async loadComponentMetadata(name2) {
50877
51096
  const componentsPath = this.getComponentsPath();
50878
- const filePath = join9(componentsPath, `${name2}.tsx`);
51097
+ const filePath = join10(componentsPath, `${name2}.tsx`);
50879
51098
  try {
50880
- const source = await readFile4(filePath, "utf-8");
51099
+ const source = await readFile5(filePath, "utf-8");
50881
51100
  const intelligence = parseJSDocIntelligence(source);
50882
51101
  const description = parseDescription(source);
50883
51102
  const metadata = {
@@ -51227,22 +51446,22 @@ async function mcp() {
51227
51446
  }
51228
51447
 
51229
51448
  // src/commands/studio.ts
51230
- import { existsSync as existsSync3 } from "fs";
51231
- import { dirname as dirname3, join as join10 } from "path";
51449
+ import { existsSync as existsSync4 } from "fs";
51450
+ import { dirname as dirname3, join as join11 } from "path";
51232
51451
  import { fileURLToPath as fileURLToPath3 } from "url";
51233
51452
  import { execa as execa2 } from "execa";
51234
51453
  var __dirname2 = dirname3(fileURLToPath3(import.meta.url));
51235
51454
  async function studio() {
51236
51455
  const cwd = process.cwd();
51237
51456
  const paths = getRaftersPaths(cwd);
51238
- if (!existsSync3(paths.root)) {
51457
+ if (!existsSync4(paths.root)) {
51239
51458
  console.error('No .rafters/ directory found. Run "rafters init" first.');
51240
51459
  process.exit(1);
51241
51460
  }
51242
- const devStudioPath = join10(__dirname2, "..", "..", "..", "studio");
51243
- const prodStudioPath = join10(__dirname2, "..", "node_modules", "@rafters", "studio");
51244
- const studioPath = existsSync3(devStudioPath) ? devStudioPath : prodStudioPath;
51245
- if (!existsSync3(studioPath)) {
51461
+ const devStudioPath = join11(__dirname2, "..", "..", "..", "studio");
51462
+ const prodStudioPath = join11(__dirname2, "..", "node_modules", "@rafters", "studio");
51463
+ const studioPath = existsSync4(devStudioPath) ? devStudioPath : prodStudioPath;
51464
+ if (!existsSync4(studioPath)) {
51246
51465
  console.error("Studio package not found. Please reinstall @rafters/cli.");
51247
51466
  process.exit(1);
51248
51467
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafters",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "CLI for Rafters design system - scaffold tokens and add components",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  ],
15
15
  "dependencies": {
16
16
  "@antfu/ni": "^28.1.0",
17
+ "@inquirer/prompts": "^8.2.0",
17
18
  "@modelcontextprotocol/sdk": "^1.25.1",
18
19
  "commander": "^13.0.0",
19
20
  "execa": "^9.6.1",