@matchkit.io/cli 0.1.6 → 0.2.0
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/add.d.ts +5 -1
- package/dist/commands/add.js +98 -44
- package/dist/commands/diff.js +60 -17
- package/dist/commands/init.d.ts +4 -1
- package/dist/commands/init.js +74 -37
- package/dist/commands/list.js +2 -2
- package/dist/commands/pull.js +7 -5
- package/dist/commands/status.js +5 -2
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +100 -0
- package/dist/index.js +11 -2
- package/dist/utils/config.d.ts +18 -5
- package/dist/utils/config.js +46 -17
- package/dist/utils/registry.d.ts +14 -1
- package/dist/utils/registry.js +20 -6
- package/package.json +1 -1
package/dist/commands/add.d.ts
CHANGED
package/dist/commands/add.js
CHANGED
|
@@ -4,75 +4,129 @@ import * as p from "@clack/prompts";
|
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import { readConfig } from "../utils/config.js";
|
|
6
6
|
import { fetchComponent, fetchRegistry, isComponentInstalled, } from "../utils/registry.js";
|
|
7
|
-
export async function addCommand(
|
|
7
|
+
export async function addCommand(componentNames, options) {
|
|
8
8
|
const s = p.spinner();
|
|
9
9
|
try {
|
|
10
10
|
const config = readConfig();
|
|
11
|
-
//
|
|
12
|
-
s.start(
|
|
11
|
+
// Fetch the registry
|
|
12
|
+
s.start("Resolving components...");
|
|
13
13
|
const registry = await fetchRegistry(config);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
// Determine what to install
|
|
15
|
+
let targets;
|
|
16
|
+
if (options.all) {
|
|
17
|
+
targets = registry.components.map((c) => c.name);
|
|
18
|
+
s.stop(`Found ${pc.cyan(targets.length.toString())} components`);
|
|
19
|
+
}
|
|
20
|
+
else if (componentNames.length === 0) {
|
|
21
|
+
s.stop("No component specified");
|
|
22
|
+
p.log.error(`Specify component names: ${pc.cyan("matchkit add button card")}\nOr install everything: ${pc.cyan("matchkit add --all")}`);
|
|
19
23
|
process.exit(1);
|
|
20
24
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
process.exit(0);
|
|
25
|
+
else {
|
|
26
|
+
// Validate all names exist in registry
|
|
27
|
+
const available = new Set(registry.components.map((c) => c.name));
|
|
28
|
+
const invalid = componentNames.filter((n) => !available.has(n));
|
|
29
|
+
if (invalid.length > 0) {
|
|
30
|
+
s.stop(`Not found: ${pc.red(invalid.join(", "))}`);
|
|
31
|
+
p.log.error(`Unknown component(s): ${invalid.join(", ")}\n\nAvailable: ${pc.dim(Array.from(available).join(", "))}`);
|
|
32
|
+
process.exit(1);
|
|
30
33
|
}
|
|
31
|
-
|
|
34
|
+
targets = componentNames;
|
|
35
|
+
s.stop(`Resolved ${pc.cyan(targets.length.toString())} component(s)`);
|
|
32
36
|
}
|
|
33
|
-
//
|
|
34
|
-
const toInstall =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// Resolve all registry dependencies
|
|
38
|
+
const toInstall = new Set();
|
|
39
|
+
const registryMap = new Map(registry.components.map((c) => [c.name, c]));
|
|
40
|
+
function addWithDeps(name) {
|
|
41
|
+
if (toInstall.has(name))
|
|
42
|
+
return;
|
|
43
|
+
const comp = registryMap.get(name);
|
|
44
|
+
if (!comp)
|
|
45
|
+
return;
|
|
46
|
+
// Add dependencies first
|
|
47
|
+
for (const dep of comp.registryDependencies) {
|
|
48
|
+
addWithDeps(dep);
|
|
49
|
+
}
|
|
50
|
+
toInstall.add(name);
|
|
51
|
+
}
|
|
52
|
+
for (const name of targets) {
|
|
53
|
+
addWithDeps(name);
|
|
54
|
+
}
|
|
55
|
+
// Check what's already installed (skip unless --all)
|
|
56
|
+
const alreadyInstalled = [];
|
|
57
|
+
const needsInstall = [];
|
|
58
|
+
for (const name of toInstall) {
|
|
59
|
+
const comp = registryMap.get(name);
|
|
60
|
+
if (isComponentInstalled(config.skillDir, comp.file)) {
|
|
61
|
+
alreadyInstalled.push(name);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
needsInstall.push(name);
|
|
40
65
|
}
|
|
41
66
|
}
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
if (!options.all && alreadyInstalled.length > 0 && needsInstall.length === 0) {
|
|
68
|
+
p.log.info(`All ${alreadyInstalled.length} component(s) already installed. Use ${pc.cyan("matchkit add --all")} to overwrite.`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// For --all, overwrite everything. Otherwise only install missing.
|
|
72
|
+
const installList = options.all ? Array.from(toInstall) : needsInstall;
|
|
73
|
+
if (installList.length === 0) {
|
|
74
|
+
p.log.success("Everything is up to date.");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Fetch and write each component
|
|
78
|
+
s.start(`Installing ${installList.length} component(s)...`);
|
|
44
79
|
const installed = [];
|
|
45
|
-
const
|
|
46
|
-
for (const name of
|
|
80
|
+
const allNpmDeps = new Set();
|
|
81
|
+
for (const name of installList) {
|
|
47
82
|
const data = await fetchComponent(config, name);
|
|
83
|
+
// Write to skill dir
|
|
48
84
|
const targetPath = join(process.cwd(), config.skillDir, `components/${name}.tsx`);
|
|
49
|
-
// Ensure directory exists
|
|
50
85
|
const dir = dirname(targetPath);
|
|
51
86
|
if (!existsSync(dir)) {
|
|
52
87
|
mkdirSync(dir, { recursive: true });
|
|
53
88
|
}
|
|
54
89
|
writeFileSync(targetPath, data.source);
|
|
55
90
|
installed.push(name);
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
// Also write to output dir if different from skill dir
|
|
92
|
+
if (config.outputDir) {
|
|
93
|
+
const outputPath = join(process.cwd(), config.outputDir, `${name}.tsx`);
|
|
94
|
+
const outputDir = dirname(outputPath);
|
|
95
|
+
if (!existsSync(outputDir)) {
|
|
96
|
+
mkdirSync(outputDir, { recursive: true });
|
|
60
97
|
}
|
|
98
|
+
writeFileSync(outputPath, data.source);
|
|
99
|
+
}
|
|
100
|
+
for (const d of data.dependencies) {
|
|
101
|
+
allNpmDeps.add(d);
|
|
61
102
|
}
|
|
62
103
|
}
|
|
63
104
|
s.stop(`Installed ${pc.green(installed.length.toString())} component(s)`);
|
|
64
|
-
//
|
|
65
|
-
const lines = [
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
105
|
+
// Summary
|
|
106
|
+
const lines = [];
|
|
107
|
+
if (installed.length <= 5) {
|
|
108
|
+
for (const name of installed) {
|
|
109
|
+
const comp = registryMap.get(name);
|
|
110
|
+
lines.push(` ${pc.green("+")} ${name} ${pc.dim(`(Layer ${comp?.layer ?? "?"})`)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
lines.push(` ${pc.green("+")} ${installed.length} components installed`);
|
|
115
|
+
lines.push(` ${pc.dim(installed.slice(0, 5).join(", "))}${installed.length > 5 ? `, +${installed.length - 5} more` : ""}`);
|
|
116
|
+
}
|
|
117
|
+
if (alreadyInstalled.length > 0 && !options.all) {
|
|
118
|
+
lines.push("");
|
|
119
|
+
lines.push(` ${pc.dim(`${alreadyInstalled.length} already installed (skipped)`)}`);
|
|
120
|
+
}
|
|
121
|
+
lines.push("");
|
|
122
|
+
lines.push(`${pc.cyan("Skill dir:")} ${config.skillDir}/components/`);
|
|
123
|
+
if (config.outputDir) {
|
|
124
|
+
lines.push(`${pc.cyan("Output dir:")} ${config.outputDir}/`);
|
|
71
125
|
}
|
|
72
|
-
if (
|
|
126
|
+
if (allNpmDeps.size > 0) {
|
|
73
127
|
lines.push("");
|
|
74
|
-
lines.push(`${pc.yellow("npm dependencies
|
|
75
|
-
lines.push(` npm install ${
|
|
128
|
+
lines.push(`${pc.yellow("npm dependencies:")}`);
|
|
129
|
+
lines.push(` npm install ${Array.from(allNpmDeps).join(" ")}`);
|
|
76
130
|
}
|
|
77
131
|
p.note(lines.join("\n"), "Added");
|
|
78
132
|
}
|
package/dist/commands/diff.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import * as p from "@clack/prompts";
|
|
4
5
|
import pc from "picocolors";
|
|
5
6
|
import { readConfig, configExists } from "../utils/config.js";
|
|
6
|
-
import { fetchRegistry } from "../utils/registry.js";
|
|
7
|
+
import { fetchRegistry, loadLocalHashes } from "../utils/registry.js";
|
|
7
8
|
export async function diffCommand() {
|
|
8
9
|
if (!configExists()) {
|
|
9
|
-
p.log.error("No
|
|
10
|
+
p.log.error("No matchkit.json found. Run `matchkit init` first.");
|
|
10
11
|
process.exit(1);
|
|
11
12
|
}
|
|
12
13
|
const config = readConfig();
|
|
@@ -14,33 +15,75 @@ export async function diffCommand() {
|
|
|
14
15
|
s.start("Comparing local vs registry...");
|
|
15
16
|
try {
|
|
16
17
|
const registry = await fetchRegistry(config);
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
const remoteHashes = registry.componentHashes ?? {};
|
|
19
|
+
const localHashes = loadLocalHashes(config.skillDir);
|
|
20
|
+
const missing = [];
|
|
21
|
+
const modified = [];
|
|
22
|
+
const upToDate = [];
|
|
22
23
|
for (const component of registry.components) {
|
|
23
24
|
const localPath = join(process.cwd(), config.skillDir, component.file);
|
|
24
|
-
if (existsSync(localPath)) {
|
|
25
|
-
|
|
25
|
+
if (!existsSync(localPath)) {
|
|
26
|
+
missing.push(component);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Compare via content hashes if available (fast path)
|
|
30
|
+
const remoteHash = remoteHashes[component.name]?.hash;
|
|
31
|
+
const localHash = localHashes?.[component.name]?.hash;
|
|
32
|
+
if (remoteHash && localHash) {
|
|
33
|
+
// Both have hashes — fast comparison
|
|
34
|
+
if (remoteHash !== localHash) {
|
|
35
|
+
modified.push(component);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
upToDate.push(component);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else if (remoteHash) {
|
|
42
|
+
// Remote has hash, compute local hash on the fly
|
|
43
|
+
const localContent = readFileSync(localPath, "utf-8");
|
|
44
|
+
const computedHash = createHash("sha256").update(localContent).digest("hex").slice(0, 16);
|
|
45
|
+
if (computedHash !== remoteHash) {
|
|
46
|
+
modified.push(component);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
upToDate.push(component);
|
|
50
|
+
}
|
|
26
51
|
}
|
|
27
52
|
else {
|
|
28
|
-
|
|
53
|
+
// No hashes available — treat as up to date (can't compare)
|
|
54
|
+
upToDate.push(component);
|
|
29
55
|
}
|
|
30
56
|
}
|
|
57
|
+
s.stop("Comparison complete");
|
|
31
58
|
console.log("");
|
|
32
|
-
console.log(` ${pc.cyan(config.
|
|
59
|
+
console.log(` ${pc.cyan(config.preset + "-ui")} · ${upToDate.length} current, ${modified.length} modified, ${missing.length} missing`);
|
|
33
60
|
console.log("");
|
|
34
|
-
if (
|
|
35
|
-
console.log(` ${pc.yellow("
|
|
36
|
-
for (const c of
|
|
61
|
+
if (modified.length > 0) {
|
|
62
|
+
console.log(` ${pc.yellow("Modified (remote has updates):")}`);
|
|
63
|
+
for (const c of modified) {
|
|
64
|
+
console.log(` ${pc.yellow("~")} ${c.name}`);
|
|
65
|
+
}
|
|
66
|
+
console.log("");
|
|
67
|
+
}
|
|
68
|
+
if (missing.length > 0) {
|
|
69
|
+
console.log(` ${pc.red("Missing (not installed):")}`);
|
|
70
|
+
for (const c of missing) {
|
|
37
71
|
console.log(` ${pc.dim("○")} ${c.name}`);
|
|
38
72
|
}
|
|
39
73
|
console.log("");
|
|
40
|
-
|
|
74
|
+
}
|
|
75
|
+
if (modified.length === 0 && missing.length === 0) {
|
|
76
|
+
console.log(` ${pc.green("Everything is up to date.")}`);
|
|
41
77
|
}
|
|
42
78
|
else {
|
|
43
|
-
|
|
79
|
+
const suggestions = [];
|
|
80
|
+
if (modified.length > 0) {
|
|
81
|
+
suggestions.push(` ${pc.cyan("matchkit update")} Update modified components`);
|
|
82
|
+
}
|
|
83
|
+
if (missing.length > 0) {
|
|
84
|
+
suggestions.push(` ${pc.cyan("matchkit add --all")} Install everything`);
|
|
85
|
+
}
|
|
86
|
+
console.log(suggestions.join("\n"));
|
|
44
87
|
}
|
|
45
88
|
console.log("");
|
|
46
89
|
}
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import pc from "picocolors";
|
|
3
|
-
import { configExists, writeConfig, createDefaultConfig } from "../utils/config.js";
|
|
3
|
+
import { configExists, writeConfig, createDefaultConfig, getLegacyConfigPath } from "../utils/config.js";
|
|
4
4
|
import { isAuthenticated, authFetch, API_BASE_URL } from "../utils/auth.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{ value: "
|
|
9
|
-
{ value: "
|
|
5
|
+
import { existsSync, unlinkSync, rmSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
const PRESETS = [
|
|
8
|
+
{ value: "clarity", label: "Clarity", hint: "Free — Sharp, structured B2B SaaS" },
|
|
9
|
+
{ value: "soft", label: "Soft", hint: "Pro — Warm-minimal for AI tools" },
|
|
10
|
+
{ value: "brutal", label: "Brutal", hint: "Pro — Neobrutalism — bold, graphic" },
|
|
11
|
+
{ value: "glass", label: "Glass", hint: "Pro — Technical, translucent" },
|
|
10
12
|
];
|
|
11
13
|
const ACCENT_PRESETS = [
|
|
12
14
|
{ value: "#4F46E5", label: "Indigo" },
|
|
@@ -18,29 +20,54 @@ const ACCENT_PRESETS = [
|
|
|
18
20
|
{ value: "#EC4899", label: "Pink" },
|
|
19
21
|
{ value: "custom", label: "Custom hex" },
|
|
20
22
|
];
|
|
21
|
-
export async function initCommand() {
|
|
23
|
+
export async function initCommand(options) {
|
|
24
|
+
const nonInteractive = !!options?.preset;
|
|
22
25
|
p.intro(pc.bgCyan(pc.black(" matchkit init ")));
|
|
23
|
-
if (configExists()) {
|
|
26
|
+
if (configExists() && !nonInteractive) {
|
|
24
27
|
const shouldOverwrite = await p.confirm({
|
|
25
|
-
message: "A
|
|
28
|
+
message: "A matchkit.json already exists. Overwrite it?",
|
|
26
29
|
});
|
|
27
30
|
if (p.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
28
31
|
p.cancel("Init cancelled.");
|
|
29
32
|
process.exit(0);
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
// Non-interactive: skip prompts when --preset is provided
|
|
36
|
+
if (nonInteractive) {
|
|
37
|
+
const presetId = options.preset;
|
|
38
|
+
const validPresets = PRESETS.map((p) => p.value);
|
|
39
|
+
if (!validPresets.includes(presetId)) {
|
|
40
|
+
p.log.error(`Unknown preset: ${pc.red(presetId)}. Valid: ${validPresets.join(", ")}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
if (presetId !== "clarity" && !isAuthenticated()) {
|
|
44
|
+
p.log.warn(`${pc.bold(presetId)} is a premium preset. Run ${pc.bold("matchkit login")} first.`);
|
|
45
|
+
p.outro(`Or use ${pc.bold("--preset clarity")} (free).`);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
const accent = options.accent ?? (presetId === "clarity" ? "#4F46E5" : "#4F46E5");
|
|
49
|
+
const config = createDefaultConfig(presetId, accent);
|
|
50
|
+
writeConfig(config);
|
|
51
|
+
p.log.success(`Initialized ${pc.bold(presetId + "-ui")} with accent ${pc.cyan(accent)}`);
|
|
52
|
+
p.log.info(`Config: ${pc.dim("matchkit.json")}`);
|
|
53
|
+
p.log.info(`Skill dir: ${pc.dim(config.skillDir)}`);
|
|
54
|
+
p.log.info("");
|
|
55
|
+
p.log.info(`Next: ${pc.green("matchkit add button")} or ${pc.green("matchkit add --all")}`);
|
|
56
|
+
p.outro(pc.green("MatchKit initialized!"));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Step 1: Pick preset
|
|
60
|
+
const preset = await p.select({
|
|
61
|
+
message: "Pick a preset:",
|
|
62
|
+
options: PRESETS,
|
|
36
63
|
});
|
|
37
|
-
if (p.isCancel(
|
|
64
|
+
if (p.isCancel(preset)) {
|
|
38
65
|
p.cancel("Init cancelled.");
|
|
39
66
|
process.exit(0);
|
|
40
67
|
}
|
|
41
|
-
// Premium
|
|
42
|
-
if (
|
|
43
|
-
p.log.warn(`${pc.bold(String(
|
|
68
|
+
// Premium presets require authentication
|
|
69
|
+
if (preset !== "clarity" && !isAuthenticated()) {
|
|
70
|
+
p.log.warn(`${pc.bold(String(preset))} is a premium preset. Run ${pc.bold("matchkit login")} first.`);
|
|
44
71
|
p.log.info(`Get your API key at ${pc.cyan("matchkit.io/app/keys")}`);
|
|
45
72
|
p.outro(`Or use ${pc.bold("clarity")} (free) — all 27 components, no account needed.`);
|
|
46
73
|
process.exit(0);
|
|
@@ -72,7 +99,7 @@ export async function initCommand() {
|
|
|
72
99
|
}
|
|
73
100
|
// Step 3: Use default axes or customize
|
|
74
101
|
const useDefaults = await p.confirm({
|
|
75
|
-
message: "Use default axis settings for this
|
|
102
|
+
message: "Use default axis settings for this preset?",
|
|
76
103
|
initialValue: true,
|
|
77
104
|
});
|
|
78
105
|
if (p.isCancel(useDefaults)) {
|
|
@@ -125,22 +152,32 @@ export async function initCommand() {
|
|
|
125
152
|
"spacing-density": density,
|
|
126
153
|
};
|
|
127
154
|
}
|
|
128
|
-
// Write
|
|
129
|
-
const config = createDefaultConfig(
|
|
155
|
+
// Write config to project root (matchkit.json)
|
|
156
|
+
const config = createDefaultConfig(preset, accent, overrides);
|
|
130
157
|
writeConfig(config);
|
|
158
|
+
// Clean up legacy config if it exists
|
|
159
|
+
const legacyPath = getLegacyConfigPath();
|
|
160
|
+
if (existsSync(legacyPath)) {
|
|
161
|
+
unlinkSync(legacyPath);
|
|
162
|
+
// Remove .matchkit dir if empty
|
|
163
|
+
try {
|
|
164
|
+
rmSync(dirname(legacyPath), { recursive: false });
|
|
165
|
+
}
|
|
166
|
+
catch { /* not empty, that's fine */ }
|
|
167
|
+
p.log.info(pc.dim("Migrated .matchkit/config.json → matchkit.json"));
|
|
168
|
+
}
|
|
131
169
|
// Step 4: Link to a server-side project (premium + authenticated only)
|
|
132
170
|
let linkedConfigId;
|
|
133
|
-
if (
|
|
171
|
+
if (preset !== "clarity" && isAuthenticated()) {
|
|
134
172
|
const s = p.spinner();
|
|
135
173
|
s.start("Checking your projects...");
|
|
136
174
|
try {
|
|
137
175
|
const res = await authFetch(`${API_BASE_URL}/api/v1/configs`);
|
|
138
176
|
if (res.ok) {
|
|
139
177
|
const userConfigs = (await res.json());
|
|
140
|
-
|
|
141
|
-
const matching = userConfigs.filter((c) => c.theme === theme);
|
|
178
|
+
const matching = userConfigs.filter((c) => c.theme === preset);
|
|
142
179
|
s.stop(matching.length > 0
|
|
143
|
-
? `Found ${matching.length} ${
|
|
180
|
+
? `Found ${matching.length} ${preset} project${matching.length > 1 ? "s" : ""}`
|
|
144
181
|
: "No projects found");
|
|
145
182
|
if (matching.length > 0) {
|
|
146
183
|
const choice = await p.select({
|
|
@@ -163,7 +200,7 @@ export async function initCommand() {
|
|
|
163
200
|
}
|
|
164
201
|
}
|
|
165
202
|
else {
|
|
166
|
-
p.log.info(`No ${pc.bold(String(
|
|
203
|
+
p.log.info(`No ${pc.bold(String(preset))} projects found. Create one at ${pc.cyan("matchkit.io/app")}`);
|
|
167
204
|
}
|
|
168
205
|
}
|
|
169
206
|
else {
|
|
@@ -173,7 +210,6 @@ export async function initCommand() {
|
|
|
173
210
|
catch {
|
|
174
211
|
s.stop("Offline — skipping project linking");
|
|
175
212
|
}
|
|
176
|
-
// Update config with configId if linked
|
|
177
213
|
if (linkedConfigId) {
|
|
178
214
|
const updatedConfig = { ...config, configId: linkedConfigId };
|
|
179
215
|
writeConfig(updatedConfig);
|
|
@@ -182,29 +218,30 @@ export async function initCommand() {
|
|
|
182
218
|
}
|
|
183
219
|
// Summary
|
|
184
220
|
const summaryLines = [
|
|
185
|
-
`${pc.cyan("
|
|
186
|
-
`${pc.cyan("Accent:")}
|
|
187
|
-
`${pc.cyan("Overrides:")}
|
|
188
|
-
`${pc.cyan("Config:")}
|
|
189
|
-
`${pc.cyan("Skill dir:")}
|
|
221
|
+
`${pc.cyan("Preset:")} ${preset}`,
|
|
222
|
+
`${pc.cyan("Accent:")} ${accent}`,
|
|
223
|
+
`${pc.cyan("Overrides:")} ${Object.keys(overrides).length === 0 ? "defaults" : Object.entries(overrides).map(([k, v]) => `${k}=${v}`).join(", ")}`,
|
|
224
|
+
`${pc.cyan("Config:")} matchkit.json`,
|
|
225
|
+
`${pc.cyan("Skill dir:")} ${config.skillDir}`,
|
|
226
|
+
`${pc.cyan("Output dir:")} ${config.outputDir}`,
|
|
190
227
|
];
|
|
191
228
|
if (linkedConfigId) {
|
|
192
|
-
summaryLines.push(`${pc.cyan("Project ID:")}
|
|
229
|
+
summaryLines.push(`${pc.cyan("Project ID:")} ${linkedConfigId}`);
|
|
193
230
|
summaryLines.push("");
|
|
194
231
|
summaryLines.push(`Next steps:`);
|
|
195
232
|
summaryLines.push(` ${pc.green("matchkit pull")} Sync your design system`);
|
|
196
|
-
summaryLines.push(` ${pc.green("matchkit
|
|
233
|
+
summaryLines.push(` ${pc.green("matchkit add button")} Add your first component`);
|
|
197
234
|
}
|
|
198
235
|
else {
|
|
199
236
|
summaryLines.push("");
|
|
200
237
|
summaryLines.push(`Next steps:`);
|
|
201
238
|
summaryLines.push(` ${pc.green("matchkit add button")} Add your first component`);
|
|
202
|
-
summaryLines.push(` ${pc.green("matchkit
|
|
239
|
+
summaryLines.push(` ${pc.green("matchkit add --all")} Add all 27 components`);
|
|
240
|
+
summaryLines.push(` ${pc.green("matchkit list")} See available components`);
|
|
203
241
|
}
|
|
204
242
|
p.note(summaryLines.join("\n"), "Configuration saved");
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
p.log.info(pc.dim("Unlock 3 more themes + custom axes → ") +
|
|
243
|
+
if (preset === "clarity" && !isAuthenticated()) {
|
|
244
|
+
p.log.info(pc.dim("Unlock 3 more presets + custom axes → ") +
|
|
208
245
|
pc.cyan("matchkit.io/configure"));
|
|
209
246
|
}
|
|
210
247
|
p.outro(pc.green("MatchKit initialized!"));
|
package/dist/commands/list.js
CHANGED
|
@@ -4,7 +4,7 @@ import { readConfig, configExists } from "../utils/config.js";
|
|
|
4
4
|
import { loadLocalRegistry, isComponentInstalled, fetchRegistry, } from "../utils/registry.js";
|
|
5
5
|
export async function listCommand() {
|
|
6
6
|
if (!configExists()) {
|
|
7
|
-
p.log.error("No
|
|
7
|
+
p.log.error("No matchkit.json found. Run `matchkit init` first.");
|
|
8
8
|
process.exit(1);
|
|
9
9
|
}
|
|
10
10
|
const config = readConfig();
|
|
@@ -41,7 +41,7 @@ export async function listCommand() {
|
|
|
41
41
|
};
|
|
42
42
|
const installedCount = components.filter((c) => isComponentInstalled(config.skillDir, c.file)).length;
|
|
43
43
|
console.log("");
|
|
44
|
-
console.log(` ${pc.cyan(config.
|
|
44
|
+
console.log(` ${pc.cyan(config.preset + "-ui")} · ${pc.dim(`${installedCount}/${components.length} installed`)}`);
|
|
45
45
|
console.log("");
|
|
46
46
|
console.log(` ${pc.bold("Layer 1 — Primitives")} (${layer1.length})`);
|
|
47
47
|
for (const c of layer1) {
|
package/dist/commands/pull.js
CHANGED
|
@@ -213,7 +213,7 @@ export async function pullCommand(options) {
|
|
|
213
213
|
cs.stop("Project found");
|
|
214
214
|
const newConfig = createDefaultConfig(data.theme, data.accent ?? "#4F46E5", (data.overrides ?? {}));
|
|
215
215
|
writeConfig({ ...newConfig, configId: options.configId });
|
|
216
|
-
p.log.success(`Created
|
|
216
|
+
p.log.success(`Created matchkit.json for ${pc.bold(data.theme + "-ui")}`);
|
|
217
217
|
}
|
|
218
218
|
catch {
|
|
219
219
|
cs.stop("Failed");
|
|
@@ -229,7 +229,7 @@ export async function pullCommand(options) {
|
|
|
229
229
|
}
|
|
230
230
|
// Check config
|
|
231
231
|
if (!configExists()) {
|
|
232
|
-
p.log.error("No
|
|
232
|
+
p.log.error("No matchkit.json found. Use " +
|
|
233
233
|
pc.bold("--config-id <id>") +
|
|
234
234
|
" or run " +
|
|
235
235
|
pc.bold("matchkit init") +
|
|
@@ -239,7 +239,7 @@ export async function pullCommand(options) {
|
|
|
239
239
|
const config = readConfig();
|
|
240
240
|
const configId = config.configId;
|
|
241
241
|
if (!configId) {
|
|
242
|
-
p.log.error("No configId in
|
|
242
|
+
p.log.error("No configId in matchkit.json. Use " +
|
|
243
243
|
pc.bold("matchkit pull --config-id <id>") +
|
|
244
244
|
" to link a project.");
|
|
245
245
|
process.exit(1);
|
|
@@ -266,7 +266,7 @@ export async function pullCommand(options) {
|
|
|
266
266
|
zipBuffer = await pullRes.arrayBuffer();
|
|
267
267
|
buildId = pullRes.headers.get("x-build-id") ?? "unknown";
|
|
268
268
|
version = parseInt(pullRes.headers.get("x-version") ?? "0", 10);
|
|
269
|
-
theme = pullRes.headers.get("x-theme") ?? config.
|
|
269
|
+
theme = pullRes.headers.get("x-theme") ?? config.preset;
|
|
270
270
|
}
|
|
271
271
|
else {
|
|
272
272
|
// Server returned JSON with R2 download URL
|
|
@@ -317,10 +317,12 @@ export async function pullCommand(options) {
|
|
|
317
317
|
writeFileSync(fullPath, content);
|
|
318
318
|
fileCount++;
|
|
319
319
|
}
|
|
320
|
-
// Update local config
|
|
320
|
+
// Update local config with pull metadata
|
|
321
321
|
const updatedConfig = {
|
|
322
322
|
...config,
|
|
323
323
|
configId,
|
|
324
|
+
buildId,
|
|
325
|
+
lastPull: new Date().toISOString(),
|
|
324
326
|
};
|
|
325
327
|
writeConfig(updatedConfig);
|
|
326
328
|
s.stop(`Extracted ${fileCount} files`);
|
package/dist/commands/status.js
CHANGED
|
@@ -6,16 +6,19 @@ export async function statusCommand() {
|
|
|
6
6
|
p.intro(pc.bold("matchkit status"));
|
|
7
7
|
// Check config
|
|
8
8
|
if (!configExists()) {
|
|
9
|
-
p.log.error("No
|
|
9
|
+
p.log.error("No matchkit.json found. Run " +
|
|
10
10
|
pc.bold("matchkit init") +
|
|
11
11
|
" first.");
|
|
12
12
|
process.exit(1);
|
|
13
13
|
}
|
|
14
14
|
const config = readConfig();
|
|
15
15
|
const configId = config.configId;
|
|
16
|
-
p.log.info(`
|
|
16
|
+
p.log.info(`Preset: ${pc.bold(config.preset)}`);
|
|
17
17
|
p.log.info(`Accent: ${pc.bold(config.accent)}`);
|
|
18
18
|
p.log.info(`Dir: ${pc.dim(config.skillDir)}`);
|
|
19
|
+
if (config.buildId) {
|
|
20
|
+
p.log.info(`Build: ${pc.dim(config.buildId)}`);
|
|
21
|
+
}
|
|
19
22
|
if (!configId) {
|
|
20
23
|
p.log.warn("No configId — this is a local-only config (free tier).");
|
|
21
24
|
p.outro("Upgrade at " + pc.cyan("matchkit.io/configure") + " for premium themes + CLI sync.");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function updateCommand(componentNames: string[]): Promise<void>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import { readConfig } from "../utils/config.js";
|
|
7
|
+
import { fetchRegistry, fetchComponent, loadLocalHashes, } from "../utils/registry.js";
|
|
8
|
+
export async function updateCommand(componentNames) {
|
|
9
|
+
const s = p.spinner();
|
|
10
|
+
try {
|
|
11
|
+
const config = readConfig();
|
|
12
|
+
s.start("Checking for updates...");
|
|
13
|
+
const registry = await fetchRegistry(config);
|
|
14
|
+
const registryMap = new Map(registry.components.map((c) => [c.name, c]));
|
|
15
|
+
const remoteHashes = registry.componentHashes ?? {};
|
|
16
|
+
const localHashes = loadLocalHashes(config.skillDir);
|
|
17
|
+
// Determine what to update
|
|
18
|
+
let targets;
|
|
19
|
+
if (componentNames.length > 0) {
|
|
20
|
+
// Update specific components
|
|
21
|
+
const invalid = componentNames.filter((n) => !registryMap.has(n));
|
|
22
|
+
if (invalid.length > 0) {
|
|
23
|
+
s.stop(`Not found: ${pc.red(invalid.join(", "))}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
targets = componentNames;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Find all installed components that have changes (via hashes)
|
|
30
|
+
targets = [];
|
|
31
|
+
for (const component of registry.components) {
|
|
32
|
+
const localPath = join(process.cwd(), config.skillDir, component.file);
|
|
33
|
+
if (!existsSync(localPath))
|
|
34
|
+
continue;
|
|
35
|
+
const remoteHash = remoteHashes[component.name]?.hash;
|
|
36
|
+
if (!remoteHash)
|
|
37
|
+
continue; // Can't compare without remote hash
|
|
38
|
+
const localHash = localHashes?.[component.name]?.hash;
|
|
39
|
+
if (localHash && localHash === remoteHash)
|
|
40
|
+
continue; // Up to date
|
|
41
|
+
// No local hash — compute on the fly
|
|
42
|
+
if (!localHash) {
|
|
43
|
+
const localContent = readFileSync(localPath, "utf-8");
|
|
44
|
+
const computed = createHash("sha256").update(localContent).digest("hex").slice(0, 16);
|
|
45
|
+
if (computed === remoteHash)
|
|
46
|
+
continue; // Up to date
|
|
47
|
+
}
|
|
48
|
+
targets.push(component.name);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (targets.length === 0) {
|
|
52
|
+
s.stop("Everything is up to date");
|
|
53
|
+
p.log.success("No updates available.");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
s.stop(`Found ${targets.length} component(s) to update`);
|
|
57
|
+
// Confirm update
|
|
58
|
+
if (componentNames.length === 0) {
|
|
59
|
+
const confirm = await p.confirm({
|
|
60
|
+
message: `Update ${targets.length} component(s)? ${pc.dim(targets.join(", "))}`,
|
|
61
|
+
});
|
|
62
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
63
|
+
p.cancel("Update cancelled.");
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Fetch and overwrite
|
|
68
|
+
const updated = [];
|
|
69
|
+
const s2 = p.spinner();
|
|
70
|
+
s2.start(`Updating ${targets.length} component(s)...`);
|
|
71
|
+
for (const name of targets) {
|
|
72
|
+
const data = await fetchComponent(config, name);
|
|
73
|
+
const targetPath = join(process.cwd(), config.skillDir, `components/${name}.tsx`);
|
|
74
|
+
const dir = dirname(targetPath);
|
|
75
|
+
if (!existsSync(dir)) {
|
|
76
|
+
mkdirSync(dir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
writeFileSync(targetPath, data.source);
|
|
79
|
+
// Also update output dir if configured
|
|
80
|
+
if (config.outputDir) {
|
|
81
|
+
const outputPath = join(process.cwd(), config.outputDir, `${name}.tsx`);
|
|
82
|
+
const outputDir = dirname(outputPath);
|
|
83
|
+
if (!existsSync(outputDir)) {
|
|
84
|
+
mkdirSync(outputDir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
writeFileSync(outputPath, data.source);
|
|
87
|
+
}
|
|
88
|
+
updated.push(name);
|
|
89
|
+
}
|
|
90
|
+
s2.stop(`Updated ${pc.green(updated.length.toString())} component(s)`);
|
|
91
|
+
for (const name of updated) {
|
|
92
|
+
console.log(` ${pc.green("~")} ${name}`);
|
|
93
|
+
}
|
|
94
|
+
console.log("");
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
p.log.error(err instanceof Error ? err.message : "An unknown error occurred");
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { initCommand } from "./commands/init.js";
|
|
|
7
7
|
import { addCommand } from "./commands/add.js";
|
|
8
8
|
import { listCommand } from "./commands/list.js";
|
|
9
9
|
import { diffCommand } from "./commands/diff.js";
|
|
10
|
+
import { updateCommand } from "./commands/update.js";
|
|
10
11
|
import { loginCommand } from "./commands/login.js";
|
|
11
12
|
import { pullCommand } from "./commands/pull.js";
|
|
12
13
|
import { statusCommand } from "./commands/status.js";
|
|
@@ -20,11 +21,14 @@ program
|
|
|
20
21
|
program
|
|
21
22
|
.command("init")
|
|
22
23
|
.description("Initialize a MatchKit design system in your project")
|
|
24
|
+
.option("-p, --preset <preset>", "Preset name (skip interactive selection)")
|
|
25
|
+
.option("--accent <color>", "Accent color hex (e.g. #4F46E5)")
|
|
23
26
|
.action(initCommand);
|
|
24
27
|
program
|
|
25
28
|
.command("add")
|
|
26
|
-
.description("Add
|
|
27
|
-
.argument("
|
|
29
|
+
.description("Add component(s) to your project")
|
|
30
|
+
.argument("[components...]", "Component name(s) (e.g. button data-table)")
|
|
31
|
+
.option("-a, --all", "Install all components")
|
|
28
32
|
.action(addCommand);
|
|
29
33
|
program
|
|
30
34
|
.command("list")
|
|
@@ -34,6 +38,11 @@ program
|
|
|
34
38
|
.command("diff")
|
|
35
39
|
.description("Show changes between local components and the registry")
|
|
36
40
|
.action(diffCommand);
|
|
41
|
+
program
|
|
42
|
+
.command("update")
|
|
43
|
+
.description("Update installed component(s) to latest version")
|
|
44
|
+
.argument("[components...]", "Component name(s) to update (omit for all)")
|
|
45
|
+
.action(updateCommand);
|
|
37
46
|
program
|
|
38
47
|
.command("login")
|
|
39
48
|
.description("Authenticate with MatchKit (opens browser)")
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
export interface MatchKitConfig {
|
|
2
2
|
$schema?: string;
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/** Preset/theme id (e.g. "clarity", "soft", "brutal", "glass") */
|
|
4
|
+
preset: string;
|
|
5
|
+
/** Accent color hex */
|
|
5
6
|
accent: string;
|
|
7
|
+
/** Axis overrides from preset defaults */
|
|
6
8
|
overrides: Record<string, string>;
|
|
7
|
-
|
|
9
|
+
/** Where to write component TSX files */
|
|
10
|
+
outputDir: string;
|
|
11
|
+
/** Where the skill directory lives (SKILL.md, tokens, etc.) */
|
|
8
12
|
skillDir: string;
|
|
13
|
+
/** Registry API base URL */
|
|
9
14
|
registryUrl: string;
|
|
10
|
-
/** Server config ID for authenticated pull (premium
|
|
15
|
+
/** Server config ID for authenticated pull (premium presets) */
|
|
11
16
|
configId?: string;
|
|
17
|
+
/** Last pull timestamp (ISO 8601) */
|
|
18
|
+
lastPull?: string;
|
|
19
|
+
/** Build ID from last pull/generate */
|
|
20
|
+
buildId?: string;
|
|
12
21
|
}
|
|
13
22
|
export declare function getConfigPath(cwd?: string): string;
|
|
23
|
+
export declare function getLegacyConfigPath(cwd?: string): string;
|
|
14
24
|
export declare function configExists(cwd?: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Read config, auto-migrating from legacy format if needed.
|
|
27
|
+
*/
|
|
15
28
|
export declare function readConfig(cwd?: string): MatchKitConfig;
|
|
16
29
|
export declare function writeConfig(config: MatchKitConfig, cwd?: string): void;
|
|
17
|
-
export declare function createDefaultConfig(
|
|
30
|
+
export declare function createDefaultConfig(preset: string, accent: string, overrides?: Record<string, string>): MatchKitConfig;
|
package/dist/utils/config.js
CHANGED
|
@@ -1,36 +1,65 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync
|
|
2
|
-
import { join
|
|
3
|
-
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
// New config location (project root, ShadCN-style)
|
|
4
|
+
const CONFIG_FILE = "matchkit.json";
|
|
5
|
+
// Legacy config location
|
|
6
|
+
const LEGACY_CONFIG_FILE = ".matchkit/config.json";
|
|
4
7
|
export function getConfigPath(cwd = process.cwd()) {
|
|
5
8
|
return join(cwd, CONFIG_FILE);
|
|
6
9
|
}
|
|
10
|
+
export function getLegacyConfigPath(cwd = process.cwd()) {
|
|
11
|
+
return join(cwd, LEGACY_CONFIG_FILE);
|
|
12
|
+
}
|
|
7
13
|
export function configExists(cwd = process.cwd()) {
|
|
8
|
-
return existsSync(getConfigPath(cwd));
|
|
14
|
+
return existsSync(getConfigPath(cwd)) || existsSync(getLegacyConfigPath(cwd));
|
|
9
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Read config, auto-migrating from legacy format if needed.
|
|
18
|
+
*/
|
|
10
19
|
export function readConfig(cwd = process.cwd()) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
const newPath = getConfigPath(cwd);
|
|
21
|
+
const legacyPath = getLegacyConfigPath(cwd);
|
|
22
|
+
let raw;
|
|
23
|
+
if (existsSync(newPath)) {
|
|
24
|
+
raw = JSON.parse(readFileSync(newPath, "utf-8"));
|
|
25
|
+
}
|
|
26
|
+
else if (existsSync(legacyPath)) {
|
|
27
|
+
raw = JSON.parse(readFileSync(legacyPath, "utf-8"));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
throw new Error("No matchkit.json found. Run `matchkit init` first.");
|
|
14
31
|
}
|
|
15
|
-
|
|
32
|
+
// Auto-migrate legacy fields
|
|
33
|
+
return migrateLegacyConfig(raw);
|
|
34
|
+
}
|
|
35
|
+
/** Migrate legacy config fields to new format */
|
|
36
|
+
function migrateLegacyConfig(raw) {
|
|
37
|
+
const legacy = raw;
|
|
38
|
+
return {
|
|
39
|
+
$schema: raw.$schema ?? "https://matchkit.io/schemas/config.json",
|
|
40
|
+
preset: legacy.theme ?? raw.preset ?? "clarity",
|
|
41
|
+
accent: raw.accent ?? "#4F46E5",
|
|
42
|
+
overrides: raw.overrides ?? {},
|
|
43
|
+
outputDir: legacy.componentsDir ?? raw.outputDir ?? "src/components/ui",
|
|
44
|
+
skillDir: raw.skillDir ?? `.claude/skills/${(legacy.theme ?? raw.preset ?? "clarity")}-ui`,
|
|
45
|
+
registryUrl: raw.registryUrl ?? "https://www.matchkit.io/api/registry",
|
|
46
|
+
configId: raw.configId,
|
|
47
|
+
lastPull: raw.lastPull,
|
|
48
|
+
buildId: raw.buildId,
|
|
49
|
+
};
|
|
16
50
|
}
|
|
17
51
|
export function writeConfig(config, cwd = process.cwd()) {
|
|
18
52
|
const path = getConfigPath(cwd);
|
|
19
|
-
const dir = dirname(path);
|
|
20
|
-
if (!existsSync(dir)) {
|
|
21
|
-
mkdirSync(dir, { recursive: true });
|
|
22
|
-
}
|
|
23
53
|
writeFileSync(path, JSON.stringify(config, null, 2) + "\n");
|
|
24
54
|
}
|
|
25
|
-
export function createDefaultConfig(
|
|
55
|
+
export function createDefaultConfig(preset, accent, overrides = {}) {
|
|
26
56
|
return {
|
|
27
57
|
$schema: "https://matchkit.io/schemas/config.json",
|
|
28
|
-
|
|
29
|
-
theme,
|
|
58
|
+
preset,
|
|
30
59
|
accent,
|
|
31
60
|
overrides,
|
|
32
|
-
|
|
33
|
-
skillDir: `.claude/skills/${
|
|
61
|
+
outputDir: "src/components/ui",
|
|
62
|
+
skillDir: `.claude/skills/${preset}-ui`,
|
|
34
63
|
registryUrl: "https://www.matchkit.io/api/registry",
|
|
35
64
|
};
|
|
36
65
|
}
|
package/dist/utils/registry.d.ts
CHANGED
|
@@ -10,13 +10,22 @@ export interface RegistryComponent {
|
|
|
10
10
|
dependencies: string[];
|
|
11
11
|
registryDependencies: string[];
|
|
12
12
|
}
|
|
13
|
+
/** Per-component content hash for incremental updates */
|
|
14
|
+
export interface ComponentHashEntry {
|
|
15
|
+
hash: string;
|
|
16
|
+
file: string;
|
|
17
|
+
}
|
|
13
18
|
export interface RegistryData {
|
|
14
19
|
$schema: string;
|
|
15
20
|
basePath: string;
|
|
16
21
|
components: RegistryComponent[];
|
|
22
|
+
/** Content hashes per component (for incremental CLI updates) */
|
|
23
|
+
componentHashes?: Record<string, ComponentHashEntry>;
|
|
24
|
+
/** Build ID associated with these hashes */
|
|
25
|
+
buildId?: string;
|
|
17
26
|
}
|
|
18
27
|
/**
|
|
19
|
-
* Fetch the full registry for a
|
|
28
|
+
* Fetch the full registry for a preset from the registry API.
|
|
20
29
|
*/
|
|
21
30
|
export declare function fetchRegistry(config: MatchKitConfig): Promise<RegistryData>;
|
|
22
31
|
/**
|
|
@@ -31,6 +40,10 @@ export declare function fetchComponent(config: MatchKitConfig, name: string): Pr
|
|
|
31
40
|
* Load a local registry.json from the skill directory.
|
|
32
41
|
*/
|
|
33
42
|
export declare function loadLocalRegistry(skillDir: string): RegistryData | null;
|
|
43
|
+
/**
|
|
44
|
+
* Load local component hashes from component-hashes.json.
|
|
45
|
+
*/
|
|
46
|
+
export declare function loadLocalHashes(skillDir: string): Record<string, ComponentHashEntry> | null;
|
|
34
47
|
/**
|
|
35
48
|
* Check if a component is installed locally.
|
|
36
49
|
*/
|
package/dist/utils/registry.js
CHANGED
|
@@ -2,12 +2,11 @@ import { readFileSync, existsSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAuthHeaders } from "./auth.js";
|
|
4
4
|
/**
|
|
5
|
-
* Fetch the full registry for a
|
|
5
|
+
* Fetch the full registry for a preset from the registry API.
|
|
6
6
|
*/
|
|
7
7
|
export async function fetchRegistry(config) {
|
|
8
|
-
const url = `${config.registryUrl}?theme=${encodeURIComponent(config.
|
|
9
|
-
|
|
10
|
-
const headers = config.theme !== "clarity" ? getAuthHeaders() : {};
|
|
8
|
+
const url = `${config.registryUrl}?theme=${encodeURIComponent(config.preset)}`;
|
|
9
|
+
const headers = config.preset !== "clarity" ? getAuthHeaders() : {};
|
|
11
10
|
const res = await fetch(url, { headers });
|
|
12
11
|
if (!res.ok) {
|
|
13
12
|
throw new Error(`Failed to fetch registry: ${res.status} ${res.statusText}`);
|
|
@@ -18,8 +17,8 @@ export async function fetchRegistry(config) {
|
|
|
18
17
|
* Fetch a single component's source code from the registry API.
|
|
19
18
|
*/
|
|
20
19
|
export async function fetchComponent(config, name) {
|
|
21
|
-
const url = `${config.registryUrl}/component?theme=${encodeURIComponent(config.
|
|
22
|
-
const headers = config.
|
|
20
|
+
const url = `${config.registryUrl}/component?theme=${encodeURIComponent(config.preset)}&name=${encodeURIComponent(name)}`;
|
|
21
|
+
const headers = config.preset !== "clarity" ? getAuthHeaders() : {};
|
|
23
22
|
const res = await fetch(url, { headers });
|
|
24
23
|
if (!res.ok) {
|
|
25
24
|
throw new Error(`Failed to fetch component "${name}": ${res.status} ${res.statusText}`);
|
|
@@ -41,6 +40,21 @@ export function loadLocalRegistry(skillDir) {
|
|
|
41
40
|
return null;
|
|
42
41
|
}
|
|
43
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Load local component hashes from component-hashes.json.
|
|
45
|
+
*/
|
|
46
|
+
export function loadLocalHashes(skillDir) {
|
|
47
|
+
const hashesPath = join(process.cwd(), skillDir, "component-hashes.json");
|
|
48
|
+
if (!existsSync(hashesPath))
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
const data = JSON.parse(readFileSync(hashesPath, "utf-8"));
|
|
52
|
+
return data.components ?? null;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
44
58
|
/**
|
|
45
59
|
* Check if a component is installed locally.
|
|
46
60
|
*/
|
package/package.json
CHANGED