@matchkit.io/cli 0.1.2 → 0.1.4

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.
@@ -6,11 +6,41 @@ import { readConfig, configExists, writeConfig, createDefaultConfig } from "../u
6
6
  import { getApiKey, authFetch, API_BASE_URL } from "../utils/auth.js";
7
7
  // Marker used to detect MatchKit-managed AI rules files
8
8
  const MATCHKIT_MARKER = "<!-- managed:matchkit -->";
9
+ /**
10
+ * Read registry.json from the extracted skill directory and build
11
+ * a markdown import map table for AI rules files.
12
+ */
13
+ function buildImportMap(fullSkillDir) {
14
+ const registryPath = join(fullSkillDir, "registry.json");
15
+ if (!existsSync(registryPath))
16
+ return "";
17
+ try {
18
+ const registry = JSON.parse(readFileSync(registryPath, "utf-8"));
19
+ const lines = [];
20
+ for (const comp of registry.components) {
21
+ if (!comp.exportName || !comp.file)
22
+ continue;
23
+ // Skip non-component entries (layouts, patterns)
24
+ if (comp.type === "registry:layout" || comp.type === "registry:pattern")
25
+ continue;
26
+ const file = comp.file.replace(/\.tsx$/, "");
27
+ const exports = Array.isArray(comp.exportName) ? comp.exportName : [comp.exportName];
28
+ const importStr = `{ ${exports.join(", ")} }`;
29
+ lines.push(`| ${comp.name} | \`import ${importStr} from "@/components/ui/${file}"\` |`);
30
+ }
31
+ if (lines.length === 0)
32
+ return "";
33
+ return `\n### Component imports — use these EXACT names:\n\n| Component | Import |\n|-----------|--------|\n${lines.join("\n")}\n`;
34
+ }
35
+ catch {
36
+ return "";
37
+ }
38
+ }
9
39
  /**
10
40
  * Build the content for AI rules files.
11
41
  * This is the mandatory instruction set that tells AI tools to use the design system.
12
42
  */
13
- function buildAiRulesContent(skillDir, theme) {
43
+ function buildAiRulesContent(skillDir, theme, importMap) {
14
44
  return `${MATCHKIT_MARKER}
15
45
  # MatchKit Design System — ${theme}-ui
16
46
 
@@ -54,17 +84,17 @@ A full design system is installed at \`${skillDir}/\`. You MUST use it for ALL U
54
84
  | \`globals.css\` | Complete Tailwind v4 CSS with all tokens |
55
85
  | \`tokens/*.json\` | Design token values |
56
86
  | \`patterns/*.md\` | Responsive, dark mode, a11y, composition patterns |
57
- `;
87
+ ${importMap}`;
58
88
  }
59
89
  /**
60
90
  * Build Cursor .mdc format content (has YAML frontmatter with alwaysApply).
61
91
  */
62
- function buildCursorMdcContent(skillDir, theme) {
92
+ function buildCursorMdcContent(skillDir, theme, importMap) {
63
93
  return `---
64
94
  description: MatchKit ${theme}-ui design system — mandatory rules for all UI work
65
95
  alwaysApply: true
66
96
  ---
67
- ${buildAiRulesContent(skillDir, theme)}`;
97
+ ${buildAiRulesContent(skillDir, theme, importMap)}`;
68
98
  }
69
99
  /**
70
100
  * Write AI rules files to the project root.
@@ -72,8 +102,10 @@ ${buildAiRulesContent(skillDir, theme)}`;
72
102
  * Returns the list of files that were written.
73
103
  */
74
104
  function writeAiRulesFiles(cwd, skillDir, theme) {
75
- const content = buildAiRulesContent(skillDir, theme);
76
- const cursorMdcContent = buildCursorMdcContent(skillDir, theme);
105
+ const fullSkillDir = join(cwd, skillDir);
106
+ const importMap = buildImportMap(fullSkillDir);
107
+ const content = buildAiRulesContent(skillDir, theme, importMap);
108
+ const cursorMdcContent = buildCursorMdcContent(skillDir, theme, importMap);
77
109
  const written = [];
78
110
  const targets = [
79
111
  { path: "CLAUDE.md", label: "CLAUDE.md", content },
@@ -106,6 +138,37 @@ function writeAiRulesFiles(cwd, skillDir, theme) {
106
138
  }
107
139
  return written;
108
140
  }
141
+ /**
142
+ * Detect if ZIP paths share a common prefix that matches the skillDir.
143
+ * e.g., if skillDir is ".claude/skills/glass-ui" and ZIP paths start with
144
+ * ".claude/skills/glass-ui/components/...", return ".claude/skills/glass-ui/"
145
+ * so we can strip it and avoid double nesting.
146
+ *
147
+ * Returns the prefix string (with trailing slash) or null if paths are flat.
148
+ */
149
+ function detectPrefix(paths, skillDir) {
150
+ if (paths.length === 0)
151
+ return null;
152
+ // Normalize skillDir: remove leading/trailing slashes, then add trailing slash
153
+ const normalized = skillDir.replace(/^\/+|\/+$/g, "") + "/";
154
+ // Check if most paths start with the skillDir prefix
155
+ const matchCount = paths.filter((p) => p.startsWith(normalized)).length;
156
+ // If >50% of paths match, it's a prefixed ZIP
157
+ if (matchCount > paths.length / 2) {
158
+ return normalized;
159
+ }
160
+ // Also check for .claude/skills/<theme>-ui/ pattern in case skillDir differs
161
+ const firstPath = paths[0];
162
+ const match = firstPath.match(/^(\.claude\/skills\/[^/]+\/)/);
163
+ if (match) {
164
+ const candidatePrefix = match[1];
165
+ const prefixCount = paths.filter((p) => p.startsWith(candidatePrefix)).length;
166
+ if (prefixCount > paths.length / 2) {
167
+ return candidatePrefix;
168
+ }
169
+ }
170
+ return null;
171
+ }
109
172
  export async function pullCommand(options) {
110
173
  p.intro(pc.bold("matchkit pull"));
111
174
  // Check auth
@@ -211,12 +274,22 @@ export async function pullCommand(options) {
211
274
  if (!existsSync(fullSkillDir)) {
212
275
  mkdirSync(fullSkillDir, { recursive: true });
213
276
  }
277
+ // Detect if ZIP paths are prefixed with a skill directory
278
+ // (e.g., ".claude/skills/glass-ui/components/..." vs "components/...")
279
+ // The server may include the full skillDir prefix — we need to strip it
280
+ // so files land directly in fullSkillDir, not double-nested.
281
+ const allPaths = Object.keys(zip.files).filter((p) => !zip.files[p].dir);
282
+ const skillDirPrefix = detectPrefix(allPaths, config.skillDir);
214
283
  let fileCount = 0;
215
284
  for (const [path, entry] of Object.entries(zip.files)) {
216
285
  if (entry.dir)
217
286
  continue;
287
+ // Strip the detected prefix to avoid double nesting
288
+ const relativePath = skillDirPrefix && path.startsWith(skillDirPrefix)
289
+ ? path.slice(skillDirPrefix.length)
290
+ : path;
218
291
  const content = await entry.async("string");
219
- const fullPath = join(fullSkillDir, path);
292
+ const fullPath = join(fullSkillDir, relativePath);
220
293
  const dir = dirname(fullPath);
221
294
  if (!existsSync(dir)) {
222
295
  mkdirSync(dir, { recursive: true });
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ const program = new Command();
11
11
  program
12
12
  .name("matchkit")
13
13
  .description("MatchKit — style-agnostic design system CLI")
14
- .version("0.1.2");
14
+ .version("0.1.4");
15
15
  program
16
16
  .command("init")
17
17
  .description("Initialize a MatchKit design system in your project")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matchkit.io/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "CLI for MatchKit design system skills. Init projects, add components, manage your design system.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",