@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.
- package/dist/commands/pull.js +80 -7
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/commands/pull.js
CHANGED
|
@@ -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
|
|
76
|
-
const
|
|
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,
|
|
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.
|
|
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