@matchkit.io/cli 0.1.3 → 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.
@@ -138,6 +138,37 @@ function writeAiRulesFiles(cwd, skillDir, theme) {
138
138
  }
139
139
  return written;
140
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
+ }
141
172
  export async function pullCommand(options) {
142
173
  p.intro(pc.bold("matchkit pull"));
143
174
  // Check auth
@@ -243,12 +274,22 @@ export async function pullCommand(options) {
243
274
  if (!existsSync(fullSkillDir)) {
244
275
  mkdirSync(fullSkillDir, { recursive: true });
245
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);
246
283
  let fileCount = 0;
247
284
  for (const [path, entry] of Object.entries(zip.files)) {
248
285
  if (entry.dir)
249
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;
250
291
  const content = await entry.async("string");
251
- const fullPath = join(fullSkillDir, path);
292
+ const fullPath = join(fullSkillDir, relativePath);
252
293
  const dir = dirname(fullPath);
253
294
  if (!existsSync(dir)) {
254
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.3",
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",