@matchkit.io/cli 0.1.3 → 0.1.5

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.
@@ -23,10 +23,25 @@ function buildImportMap(fullSkillDir) {
23
23
  // Skip non-component entries (layouts, patterns)
24
24
  if (comp.type === "registry:layout" || comp.type === "registry:pattern")
25
25
  continue;
26
- const file = comp.file.replace(/\.tsx$/, "");
26
+ // Strip directory prefix from file path (e.g. "components/button.tsx" "button")
27
+ const basename = comp.file.replace(/^(components|lib)\//, "").replace(/\.tsx?$/, "");
27
28
  const exports = Array.isArray(comp.exportName) ? comp.exportName : [comp.exportName];
28
29
  const importStr = `{ ${exports.join(", ")} }`;
29
- lines.push(`| ${comp.name} | \`import ${importStr} from "@/components/ui/${file}"\` |`);
30
+ // Use the registry category to determine the correct import prefix:
31
+ // lib → @/lib/{name} (utils.ts)
32
+ // layout → @/components/layout/{name} (sidebar-nav, top-bar, page-header)
33
+ // ui/chart/other → @/components/ui/{name} (everything else)
34
+ let importPath;
35
+ if (comp.category === "lib") {
36
+ importPath = `@/lib/${basename}`;
37
+ }
38
+ else if (comp.category === "layout") {
39
+ importPath = `@/components/layout/${basename}`;
40
+ }
41
+ else {
42
+ importPath = `@/components/ui/${basename}`;
43
+ }
44
+ lines.push(`| ${comp.name} | \`import ${importStr} from "${importPath}"\` |`);
30
45
  }
31
46
  if (lines.length === 0)
32
47
  return "";
@@ -62,6 +77,9 @@ A full design system is installed at \`${skillDir}/\`. You MUST use it for ALL U
62
77
  export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
63
78
  \`\`\`
64
79
  5. **Copy component files** from \`${skillDir}/components/\` to \`src/components/ui/\` as needed
80
+ 6. **Copy layout files** from \`${skillDir}/layouts/\` to \`src/components/layout/\` as needed (sidebar-nav, top-bar, page-header)
81
+ 7. **Read \`${skillDir}/layouts/app-shell.tsx\`** — reference implementation showing how to compose the app shell
82
+ 8. **Read the .tsx source** of any component before using it — the file contains the exact props interface
65
83
 
66
84
  ### Rules — follow these at ALL times:
67
85
 
@@ -70,8 +88,10 @@ A full design system is installed at \`${skillDir}/\`. You MUST use it for ALL U
70
88
  - **NEVER** hardcode hex colors, pixel spacing, or font sizes — use CSS variables from \`globals.css\`
71
89
  - **NEVER** improvise a component if one already exists in the registry
72
90
  - **NEVER** modify files inside \`${skillDir}/\` — it is the upstream source of truth
73
- - **ALWAYS** use \`@/components/ui/{name}\` import paths for design system components
91
+ - **ALWAYS** use \`@/components/ui/{name}\` import paths for UI components
92
+ - **ALWAYS** use \`@/components/layout/{name}\` import paths for layout components (sidebar-nav, top-bar, page-header)
74
93
  - **ALWAYS** use \`@/lib/utils\` for the \`cn()\` helper
94
+ - **ALWAYS** read the component .tsx source to see its props interface before using it — do NOT guess props
75
95
 
76
96
  ### Available resources in \`${skillDir}/\`:
77
97
 
@@ -138,6 +158,37 @@ function writeAiRulesFiles(cwd, skillDir, theme) {
138
158
  }
139
159
  return written;
140
160
  }
161
+ /**
162
+ * Detect if ZIP paths share a common prefix that matches the skillDir.
163
+ * e.g., if skillDir is ".claude/skills/glass-ui" and ZIP paths start with
164
+ * ".claude/skills/glass-ui/components/...", return ".claude/skills/glass-ui/"
165
+ * so we can strip it and avoid double nesting.
166
+ *
167
+ * Returns the prefix string (with trailing slash) or null if paths are flat.
168
+ */
169
+ function detectPrefix(paths, skillDir) {
170
+ if (paths.length === 0)
171
+ return null;
172
+ // Normalize skillDir: remove leading/trailing slashes, then add trailing slash
173
+ const normalized = skillDir.replace(/^\/+|\/+$/g, "") + "/";
174
+ // Check if most paths start with the skillDir prefix
175
+ const matchCount = paths.filter((p) => p.startsWith(normalized)).length;
176
+ // If >50% of paths match, it's a prefixed ZIP
177
+ if (matchCount > paths.length / 2) {
178
+ return normalized;
179
+ }
180
+ // Also check for .claude/skills/<theme>-ui/ pattern in case skillDir differs
181
+ const firstPath = paths[0];
182
+ const match = firstPath.match(/^(\.claude\/skills\/[^/]+\/)/);
183
+ if (match) {
184
+ const candidatePrefix = match[1];
185
+ const prefixCount = paths.filter((p) => p.startsWith(candidatePrefix)).length;
186
+ if (prefixCount > paths.length / 2) {
187
+ return candidatePrefix;
188
+ }
189
+ }
190
+ return null;
191
+ }
141
192
  export async function pullCommand(options) {
142
193
  p.intro(pc.bold("matchkit pull"));
143
194
  // Check auth
@@ -243,12 +294,22 @@ export async function pullCommand(options) {
243
294
  if (!existsSync(fullSkillDir)) {
244
295
  mkdirSync(fullSkillDir, { recursive: true });
245
296
  }
297
+ // Detect if ZIP paths are prefixed with a skill directory
298
+ // (e.g., ".claude/skills/glass-ui/components/..." vs "components/...")
299
+ // The server may include the full skillDir prefix — we need to strip it
300
+ // so files land directly in fullSkillDir, not double-nested.
301
+ const allPaths = Object.keys(zip.files).filter((p) => !zip.files[p].dir);
302
+ const skillDirPrefix = detectPrefix(allPaths, config.skillDir);
246
303
  let fileCount = 0;
247
304
  for (const [path, entry] of Object.entries(zip.files)) {
248
305
  if (entry.dir)
249
306
  continue;
307
+ // Strip the detected prefix to avoid double nesting
308
+ const relativePath = skillDirPrefix && path.startsWith(skillDirPrefix)
309
+ ? path.slice(skillDirPrefix.length)
310
+ : path;
250
311
  const content = await entry.async("string");
251
- const fullPath = join(fullSkillDir, path);
312
+ const fullPath = join(fullSkillDir, relativePath);
252
313
  const dir = dirname(fullPath);
253
314
  if (!existsSync(dir)) {
254
315
  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.3",
3
+ "version": "0.1.5",
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",