@matchkit.io/cli 0.1.0 → 0.1.2

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/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # @matchkit.io/cli
2
+
3
+ Command-line interface for MatchKit design system skills. Sync your design system, add components, and integrate with CI/CD pipelines.
4
+
5
+ ## Features
6
+
7
+ - 🔐 **Browser-based authentication** - Secure OAuth flow via matchkit.io
8
+ - 📦 **Component management** - Add individual components with automatic dependency resolution
9
+ - 🔄 **Sync & update** - Pull latest design system updates from your MatchKit configuration
10
+ - 🤖 **CI/CD ready** - Use API keys for automated deployments
11
+ - 📊 **Status tracking** - See what's installed, what's available, and what's changed
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install -g @matchkit.io/cli
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ # 1. Authenticate (opens browser)
23
+ matchkit login
24
+
25
+ # 2. Initialize your project
26
+ matchkit init
27
+
28
+ # 3. Pull your design system
29
+ matchkit pull
30
+ ```
31
+
32
+ Your design system files are installed to `.claude/skills/[theme]-ui/` and work automatically with Claude Code, Cursor, VS Code, and Windsurf.
33
+
34
+ ## Commands
35
+
36
+ ### `matchkit login`
37
+
38
+ Authenticate with MatchKit. Opens your browser to sign in, then saves your API key locally.
39
+
40
+ ```bash
41
+ matchkit login
42
+ ```
43
+
44
+ ### `matchkit init`
45
+
46
+ Initialize a MatchKit design system in your project. Links to your configuration on matchkit.io.
47
+
48
+ ```bash
49
+ matchkit init
50
+ ```
51
+
52
+ ### `matchkit pull`
53
+
54
+ Download your latest resolved design system from MatchKit.
55
+
56
+ ```bash
57
+ matchkit pull
58
+ ```
59
+
60
+ ### `matchkit add <component>`
61
+
62
+ Install a single component with its dependencies.
63
+
64
+ ```bash
65
+ matchkit add button
66
+ matchkit add data-table
67
+ ```
68
+
69
+ ### `matchkit list`
70
+
71
+ Show all 27 components with their installation status.
72
+
73
+ ```bash
74
+ matchkit list
75
+ ```
76
+
77
+ ### `matchkit status`
78
+
79
+ Show current configuration, theme, version, and sync status.
80
+
81
+ ```bash
82
+ matchkit status
83
+ ```
84
+
85
+ ### `matchkit diff`
86
+
87
+ Show what changed since your last pull.
88
+
89
+ ```bash
90
+ matchkit diff
91
+ ```
92
+
93
+ ## Configuration
94
+
95
+ The CLI stores:
96
+ - **Project config**: `.matchkit/config.json` (in your project)
97
+ - **API key**: `~/.matchkit/credentials` (global, never committed)
98
+
99
+ ### .matchkit/config.json
100
+
101
+ ```json
102
+ {
103
+ "$schema": "https://matchkit.io/schemas/config.json",
104
+ "configId": "cfg_a1b2c3d4e5f6",
105
+ "version": 3,
106
+ "theme": "soft",
107
+ "accent": "#14B8A6",
108
+ "overrides": { "radius": "xl", "button-style": "ghost" },
109
+ "componentsDir": "src/components/ui",
110
+ "skillDir": ".claude/skills/soft-ui",
111
+ "registryUrl": "https://matchkit.io/api/registry"
112
+ }
113
+ ```
114
+
115
+ ## CI/CD Usage
116
+
117
+ For automated deployments, set the `MATCHKIT_API_KEY` environment variable:
118
+
119
+ ```yaml
120
+ # GitHub Actions
121
+ - name: Pull MatchKit design system
122
+ env:
123
+ MATCHKIT_API_KEY: ${{ secrets.MATCHKIT_API_KEY }}
124
+ run: |
125
+ npm install -g @matchkit.io/cli
126
+ matchkit pull
127
+ ```
128
+
129
+ Get your API key from https://matchkit.io/app/keys
130
+
131
+ ## How It Works
132
+
133
+ 1. **Authentication**: Browser OAuth flow creates an API key
134
+ 2. **Configuration**: Links your project to your MatchKit design system
135
+ 3. **Resolution**: Server-side resolution generates your personalized skill
136
+ 4. **Download**: ZIP download with all components, tokens, and documentation
137
+ 5. **Extraction**: Files land in `.claude/skills/[theme]-ui/`
138
+
139
+ ## Requirements
140
+
141
+ - Node.js 18 or later
142
+ - A MatchKit account (free tier available at https://matchkit.io)
143
+
144
+ ## Links
145
+
146
+ - **Website**: https://matchkit.io
147
+ - **Documentation**: https://matchkit.io/docs
148
+ - **Dashboard**: https://matchkit.io/app
149
+ - **Repository**: https://github.com/Maes9/matchkit
150
+ - **Issues**: https://github.com/Maes9/matchkit/issues
151
+
152
+ ## License
153
+
154
+ MIT
@@ -33,17 +33,7 @@ async function verifyKey(apiKey) {
33
33
  }
34
34
  export async function loginCommand(options) {
35
35
  p.intro(pc.bold("matchkit login"));
36
- const existing = getApiKey();
37
- if (existing) {
38
- const overwrite = await p.confirm({
39
- message: "You already have an API key stored. Replace it?",
40
- });
41
- if (p.isCancel(overwrite) || !overwrite) {
42
- p.outro("Keeping existing key.");
43
- return;
44
- }
45
- }
46
- // --key flag or non-interactive: fall back to manual key entry
36
+ // --key flag or non-interactive: skip all interactive prompts
47
37
  if (options?.key || !process.stdin.isTTY) {
48
38
  const apiKey = options?.key ?? process.env.MATCHKIT_API_KEY;
49
39
  if (!apiKey) {
@@ -64,6 +54,17 @@ export async function loginCommand(options) {
64
54
  p.outro("Key stored in " + pc.dim("~/.matchkit/credentials"));
65
55
  return;
66
56
  }
57
+ // Interactive flow below: requires TTY
58
+ const existing = getApiKey();
59
+ if (existing) {
60
+ const overwrite = await p.confirm({
61
+ message: "You already have an API key stored. Replace it?",
62
+ });
63
+ if (p.isCancel(overwrite) || !overwrite) {
64
+ p.outro("Keeping existing key.");
65
+ return;
66
+ }
67
+ }
67
68
  // Browser-based auth flow
68
69
  const s = p.spinner();
69
70
  const callbackPromise = new Promise((resolve, reject) => {
@@ -1 +1,3 @@
1
- export declare function pullCommand(): Promise<void>;
1
+ export declare function pullCommand(options?: {
2
+ configId?: string;
3
+ }): Promise<void>;
@@ -1,33 +1,164 @@
1
1
  import * as p from "@clack/prompts";
2
2
  import pc from "picocolors";
3
- import { writeFileSync, mkdirSync, existsSync } from "node:fs";
3
+ import { writeFileSync, mkdirSync, existsSync, readFileSync } from "node:fs";
4
4
  import { join, dirname } from "node:path";
5
- import { readConfig, configExists, writeConfig } from "../utils/config.js";
5
+ import { readConfig, configExists, writeConfig, createDefaultConfig } from "../utils/config.js";
6
6
  import { getApiKey, authFetch, API_BASE_URL } from "../utils/auth.js";
7
- export async function pullCommand() {
7
+ // Marker used to detect MatchKit-managed AI rules files
8
+ const MATCHKIT_MARKER = "<!-- managed:matchkit -->";
9
+ /**
10
+ * Build the content for AI rules files.
11
+ * This is the mandatory instruction set that tells AI tools to use the design system.
12
+ */
13
+ function buildAiRulesContent(skillDir, theme) {
14
+ return `${MATCHKIT_MARKER}
15
+ # MatchKit Design System — ${theme}-ui
16
+
17
+ > Auto-generated by \`matchkit pull\`. Do not edit the marker line above.
18
+
19
+ ## MANDATORY — This project uses a complete design system
20
+
21
+ A full design system is installed at \`${skillDir}/\`. You MUST use it for ALL UI work.
22
+
23
+ ### Before writing ANY UI code:
24
+
25
+ 1. **Read \`${skillDir}/SKILL.md\`** — contains design rules, component specs, and setup instructions
26
+ 2. **Read \`${skillDir}/registry.json\`** — lists all 27 available components with their dependencies
27
+ 3. **Copy \`${skillDir}/globals.css\`** to \`src/styles/globals.css\` if it doesn't exist yet
28
+ 4. **Create \`src/lib/utils.ts\`** with the \`cn()\` helper if it doesn't exist yet:
29
+ \`\`\`ts
30
+ import { clsx, type ClassValue } from "clsx";
31
+ import { twMerge } from "tailwind-merge";
32
+ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
33
+ \`\`\`
34
+ 5. **Copy component files** from \`${skillDir}/components/\` to \`src/components/ui/\` as needed
35
+
36
+ ### Rules — follow these at ALL times:
37
+
38
+ - **ALWAYS** check \`${skillDir}/registry.json\` before creating any component — if it exists there, use it
39
+ - **NEVER** create your own \`globals.css\`, Tailwind theme, or color tokens — use the provided \`globals.css\`
40
+ - **NEVER** hardcode hex colors, pixel spacing, or font sizes — use CSS variables from \`globals.css\`
41
+ - **NEVER** improvise a component if one already exists in the registry
42
+ - **NEVER** modify files inside \`${skillDir}/\` — it is the upstream source of truth
43
+ - **ALWAYS** use \`@/components/ui/{name}\` import paths for design system components
44
+ - **ALWAYS** use \`@/lib/utils\` for the \`cn()\` helper
45
+
46
+ ### Available resources in \`${skillDir}/\`:
47
+
48
+ | Path | Contents |
49
+ |------|----------|
50
+ | \`SKILL.md\` | Design rules, philosophy, component specs |
51
+ | \`registry.json\` | Component registry with types, layers, dependencies |
52
+ | \`components/*.tsx\` | 27 production-ready components |
53
+ | \`layouts/*.tsx\` | 6 page layout references |
54
+ | \`globals.css\` | Complete Tailwind v4 CSS with all tokens |
55
+ | \`tokens/*.json\` | Design token values |
56
+ | \`patterns/*.md\` | Responsive, dark mode, a11y, composition patterns |
57
+ `;
58
+ }
59
+ /**
60
+ * Build Cursor .mdc format content (has YAML frontmatter with alwaysApply).
61
+ */
62
+ function buildCursorMdcContent(skillDir, theme) {
63
+ return `---
64
+ description: MatchKit ${theme}-ui design system — mandatory rules for all UI work
65
+ alwaysApply: true
66
+ ---
67
+ ${buildAiRulesContent(skillDir, theme)}`;
68
+ }
69
+ /**
70
+ * Write AI rules files to the project root.
71
+ * Only writes if the file doesn't exist or is MatchKit-managed (has the marker).
72
+ * Returns the list of files that were written.
73
+ */
74
+ function writeAiRulesFiles(cwd, skillDir, theme) {
75
+ const content = buildAiRulesContent(skillDir, theme);
76
+ const cursorMdcContent = buildCursorMdcContent(skillDir, theme);
77
+ const written = [];
78
+ const targets = [
79
+ { path: "CLAUDE.md", label: "CLAUDE.md", content },
80
+ { path: ".cursorrules", label: ".cursorrules", content },
81
+ { path: ".cursor/rules/matchkit.mdc", label: ".cursor/rules/matchkit.mdc", content: cursorMdcContent },
82
+ { path: ".github/copilot-instructions.md", label: ".github/copilot-instructions.md", content },
83
+ { path: ".windsurfrules", label: ".windsurfrules", content },
84
+ ];
85
+ for (const target of targets) {
86
+ const fullPath = join(cwd, target.path);
87
+ let shouldWrite = false;
88
+ if (!existsSync(fullPath)) {
89
+ shouldWrite = true;
90
+ }
91
+ else {
92
+ // File exists — only overwrite if it's MatchKit-managed
93
+ const existing = readFileSync(fullPath, "utf-8");
94
+ if (existing.includes(MATCHKIT_MARKER)) {
95
+ shouldWrite = true;
96
+ }
97
+ }
98
+ if (shouldWrite) {
99
+ const dir = dirname(fullPath);
100
+ if (!existsSync(dir)) {
101
+ mkdirSync(dir, { recursive: true });
102
+ }
103
+ writeFileSync(fullPath, target.content);
104
+ written.push(target.label);
105
+ }
106
+ }
107
+ return written;
108
+ }
109
+ export async function pullCommand(options) {
8
110
  p.intro(pc.bold("matchkit pull"));
9
111
  // Check auth
10
112
  const apiKey = getApiKey();
11
113
  if (!apiKey) {
12
- p.log.error("Not logged in. Run " + pc.bold("matchkit login") + " first.");
114
+ p.log.error("Not logged in. Run " + pc.bold("matchkit login --key <key>") + " first.");
13
115
  process.exit(1);
14
116
  }
117
+ // Auto-create config.json if --config-id provided and no config exists
118
+ if (options?.configId) {
119
+ if (!configExists()) {
120
+ const cs = p.spinner();
121
+ cs.start("Fetching project details...");
122
+ try {
123
+ const res = await authFetch(`${API_BASE_URL}/api/v1/configs/${options.configId}`);
124
+ if (!res.ok) {
125
+ cs.stop("Failed");
126
+ p.log.error(`Project ${pc.dim(options.configId)} not found (${res.status})`);
127
+ process.exit(1);
128
+ }
129
+ const data = (await res.json());
130
+ cs.stop("Project found");
131
+ const newConfig = createDefaultConfig(data.theme, data.accent ?? "#4F46E5", (data.overrides ?? {}));
132
+ writeConfig({ ...newConfig, configId: options.configId });
133
+ p.log.success(`Created .matchkit/config.json for ${pc.bold(data.theme + "-ui")}`);
134
+ }
135
+ catch {
136
+ cs.stop("Failed");
137
+ p.log.error("Could not fetch project details. Check your connection and API key.");
138
+ process.exit(1);
139
+ }
140
+ }
141
+ else {
142
+ // Config exists — update configId
143
+ const existing = readConfig();
144
+ writeConfig({ ...existing, configId: options.configId });
145
+ }
146
+ }
15
147
  // Check config
16
148
  if (!configExists()) {
17
- p.log.error("No .matchkit/config.json found. Run " +
149
+ p.log.error("No .matchkit/config.json found. Use " +
150
+ pc.bold("--config-id <id>") +
151
+ " or run " +
18
152
  pc.bold("matchkit init") +
19
- " first.");
153
+ ".");
20
154
  process.exit(1);
21
155
  }
22
156
  const config = readConfig();
23
157
  const configId = config.configId;
24
158
  if (!configId) {
25
- p.log.error("No configId in .matchkit/config.json. This project was set up before the hosted resolution model.");
26
- p.log.info("Create a config at " +
27
- pc.cyan("matchkit.io/app") +
28
- " and run " +
29
- pc.bold("matchkit init") +
30
- " again.");
159
+ p.log.error("No configId in .matchkit/config.json. Use " +
160
+ pc.bold("matchkit pull --config-id <id>") +
161
+ " to link a project.");
31
162
  process.exit(1);
32
163
  }
33
164
  const s = p.spinner();
@@ -102,6 +233,11 @@ export async function pullCommand() {
102
233
  s.stop(`Extracted ${fileCount} files`);
103
234
  p.log.success(`${pc.bold(theme + "-ui")} v${version} → ${pc.dim(skillDir)}`);
104
235
  p.log.info(`Build: ${pc.dim(buildId)}`);
236
+ // Generate AI rules files so every AI tool uses the design system
237
+ const writtenRules = writeAiRulesFiles(process.cwd(), skillDir, theme);
238
+ if (writtenRules.length > 0) {
239
+ p.log.success(`AI rules → ${writtenRules.map((f) => pc.dim(f)).join(", ")}`);
240
+ }
105
241
  p.outro("Design system is up to date.");
106
242
  }
107
243
  catch (err) {
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.0");
14
+ .version("0.1.2");
15
15
  program
16
16
  .command("init")
17
17
  .description("Initialize a MatchKit design system in your project")
@@ -37,6 +37,7 @@ program
37
37
  program
38
38
  .command("pull")
39
39
  .description("Pull your latest resolved design system from the server")
40
+ .option("--config-id <id>", "Server config ID (skips matchkit init)")
40
41
  .action(pullCommand);
41
42
  program
42
43
  .command("status")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matchkit.io/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",