@vegastack/design 0.1.0 → 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/bin/check-updates.mjs +706 -108
- package/bin/skills.mjs +262 -0
- package/bin/vegastack-design.mjs +32 -14
- package/bin/verify-registry-item.mjs +775 -214
- package/css/base.css +1 -1
- package/css/theme.css +1 -1
- package/css/utilities.css +1 -1
- package/dist/icons/index.cjs +199 -0
- package/dist/icons/index.d.cts +164 -0
- package/dist/icons/index.d.ts +39 -29
- package/dist/icons/index.js +81 -14
- package/dist/index.cjs +78 -0
- package/dist/index.d.cts +56 -0
- package/dist/index.d.ts +7 -0
- package/dist/preset.cjs +37 -0
- package/dist/preset.d.cts +19 -0
- package/dist/theme-scope.cjs +56 -0
- package/dist/theme-scope.d.cts +23 -0
- package/dist/theme-scope.d.ts +23 -0
- package/dist/theme-scope.js +21 -0
- package/package.json +40 -18
- package/skills/vegastack-brand/SKILL.md +29 -0
- package/skills/vegastack-consume/SKILL.md +182 -0
- package/skills/vegastack-consume/references/registry-integrity.md +132 -0
- package/skills/vegastack-design-audit/SKILL.md +123 -0
- package/skills/vegastack-design-system/SKILL.md +110 -0
- package/skills/vegastack-design-system/references/components.md +151 -0
package/bin/skills.mjs
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// vegastack-design skills — install the public VegaStack agent skills into a consuming project.
|
|
3
|
+
//
|
|
4
|
+
// The skills ship inside this package (see `files` in package.json), so this is a pure local
|
|
5
|
+
// copy: no network, no credentials, no registry access. Files are COPIED rather than symlinked
|
|
6
|
+
// because node_modules is ephemeral — a symlink into it breaks on the next clean install.
|
|
7
|
+
//
|
|
8
|
+
// Safety posture, matching the registry verifier:
|
|
9
|
+
// • never overwrite an existing file without --force (it reports what would change instead)
|
|
10
|
+
// • never write THROUGH a symlink at the destination — refuse it, do not follow it
|
|
11
|
+
// • --dry-run performs every check and writes nothing
|
|
12
|
+
import {
|
|
13
|
+
readFileSync,
|
|
14
|
+
readdirSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
existsSync,
|
|
18
|
+
lstatSync,
|
|
19
|
+
} from "node:fs";
|
|
20
|
+
import { join, dirname, relative, resolve, sep } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
|
|
23
|
+
const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
24
|
+
const SKILLS_DIR = join(PKG_ROOT, "skills");
|
|
25
|
+
|
|
26
|
+
const SURFACES = {
|
|
27
|
+
claude: ".claude/skills",
|
|
28
|
+
codex: ".agents/skills",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const USAGE = `vegastack-design skills — install the VegaStack agent skills
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
vegastack-design skills install [options] Copy the skills into this project
|
|
35
|
+
vegastack-design skills list Show the skills bundled with this package
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
--dir <path> Project root to install into (default: the current directory)
|
|
39
|
+
--claude Only install for Claude Code (.claude/skills)
|
|
40
|
+
--codex Only install for Codex (.agents/skills)
|
|
41
|
+
--force Overwrite files that already exist and differ
|
|
42
|
+
--dry-run Report what would change; write nothing
|
|
43
|
+
-h, --help Show this help
|
|
44
|
+
|
|
45
|
+
With neither --claude nor --codex, both surfaces are installed.`;
|
|
46
|
+
|
|
47
|
+
/** Skill directory names bundled in this package. */
|
|
48
|
+
function bundledSkills() {
|
|
49
|
+
if (!existsSync(SKILLS_DIR)) return [];
|
|
50
|
+
return readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
51
|
+
.filter(
|
|
52
|
+
(e) =>
|
|
53
|
+
e.isDirectory() && existsSync(join(SKILLS_DIR, e.name, "SKILL.md")),
|
|
54
|
+
)
|
|
55
|
+
.map((e) => e.name)
|
|
56
|
+
.sort();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Files of one skill, relative to that skill's directory. */
|
|
60
|
+
function skillFiles(skill) {
|
|
61
|
+
const base = join(SKILLS_DIR, skill);
|
|
62
|
+
const out = [];
|
|
63
|
+
for (const entry of readdirSync(base, {
|
|
64
|
+
withFileTypes: true,
|
|
65
|
+
recursive: true,
|
|
66
|
+
})) {
|
|
67
|
+
if (!entry.isFile()) continue;
|
|
68
|
+
const abs = join(entry.parentPath ?? entry.path, entry.name);
|
|
69
|
+
out.push(relative(base, abs).split(sep).join("/"));
|
|
70
|
+
}
|
|
71
|
+
return out.sort();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function describe(skill) {
|
|
75
|
+
const src = readFileSync(join(SKILLS_DIR, skill, "SKILL.md"), "utf8");
|
|
76
|
+
const fm = /^---\n([\s\S]*?)\n---/.exec(src);
|
|
77
|
+
const desc = fm && /^description:\s*(.+)$/m.exec(fm[1])?.[1]?.trim();
|
|
78
|
+
if (!desc) return "";
|
|
79
|
+
return desc.length > 100 ? `${desc.slice(0, 99)}…` : desc;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseArgs(argv) {
|
|
83
|
+
const opts = {
|
|
84
|
+
dir: process.cwd(),
|
|
85
|
+
surfaces: [],
|
|
86
|
+
force: false,
|
|
87
|
+
dryRun: false,
|
|
88
|
+
};
|
|
89
|
+
for (let i = 0; i < argv.length; i++) {
|
|
90
|
+
const arg = argv[i];
|
|
91
|
+
if (arg === "--dir") {
|
|
92
|
+
const value = argv[++i];
|
|
93
|
+
if (!value) throw new Error("--dir needs a path");
|
|
94
|
+
opts.dir = resolve(value);
|
|
95
|
+
} else if (arg === "--claude") opts.surfaces.push("claude");
|
|
96
|
+
else if (arg === "--codex") opts.surfaces.push("codex");
|
|
97
|
+
else if (arg === "--force") opts.force = true;
|
|
98
|
+
else if (arg === "--dry-run") opts.dryRun = true;
|
|
99
|
+
else throw new Error(`unknown option: ${arg}`);
|
|
100
|
+
}
|
|
101
|
+
if (opts.surfaces.length === 0) opts.surfaces = ["claude", "codex"];
|
|
102
|
+
return opts;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function list() {
|
|
106
|
+
const skills = bundledSkills();
|
|
107
|
+
if (skills.length === 0) {
|
|
108
|
+
console.error("no skills are bundled with this build of @vegastack/design");
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
console.log(`${skills.length} skill(s) bundled with @vegastack/design:\n`);
|
|
112
|
+
for (const skill of skills)
|
|
113
|
+
console.log(` ${skill}\n ${describe(skill)}\n`);
|
|
114
|
+
console.log("Install with: vegastack-design skills install");
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function install(argv) {
|
|
119
|
+
let opts;
|
|
120
|
+
try {
|
|
121
|
+
opts = parseArgs(argv);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error(`${error.message}\n`);
|
|
124
|
+
console.error(USAGE);
|
|
125
|
+
return 2;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const skills = bundledSkills();
|
|
129
|
+
if (skills.length === 0) {
|
|
130
|
+
console.error("no skills are bundled with this build of @vegastack/design");
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
if (!existsSync(opts.dir)) {
|
|
134
|
+
console.error(`target directory does not exist: ${opts.dir}`);
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const planned = []; // { to, from, action }
|
|
139
|
+
const blocked = [];
|
|
140
|
+
const conflicts = [];
|
|
141
|
+
|
|
142
|
+
for (const surface of opts.surfaces) {
|
|
143
|
+
for (const skill of skills) {
|
|
144
|
+
for (const file of skillFiles(skill)) {
|
|
145
|
+
const from = join(SKILLS_DIR, skill, file);
|
|
146
|
+
const to = join(opts.dir, SURFACES[surface], skill, file);
|
|
147
|
+
|
|
148
|
+
// Refuse to write through a symlink anywhere on the destination path we own.
|
|
149
|
+
let symlinked = false;
|
|
150
|
+
for (
|
|
151
|
+
let probe = to;
|
|
152
|
+
probe.startsWith(join(opts.dir, SURFACES[surface].split("/")[0]));
|
|
153
|
+
probe = dirname(probe)
|
|
154
|
+
) {
|
|
155
|
+
try {
|
|
156
|
+
if (lstatSync(probe).isSymbolicLink()) {
|
|
157
|
+
symlinked = true;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
/* does not exist yet — fine */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (symlinked) {
|
|
165
|
+
blocked.push(relative(opts.dir, to));
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (existsSync(to)) {
|
|
170
|
+
if (readFileSync(to).equals(readFileSync(from))) continue; // already correct
|
|
171
|
+
if (!opts.force) {
|
|
172
|
+
conflicts.push(relative(opts.dir, to));
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
planned.push({ from, to, action: "overwrite" });
|
|
176
|
+
} else {
|
|
177
|
+
planned.push({ from, to, action: "write" });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (blocked.length) {
|
|
184
|
+
console.error("refusing to write through a symlink:");
|
|
185
|
+
for (const path of blocked) console.error(` ${path}`);
|
|
186
|
+
console.error("\nRemove or relocate these entries, then run again.");
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (conflicts.length) {
|
|
191
|
+
console.error("these files already exist and differ — not overwriting:");
|
|
192
|
+
for (const path of conflicts) console.error(` ${path}`);
|
|
193
|
+
console.error(
|
|
194
|
+
"\nRe-run with --force to overwrite, or move your versions aside first.",
|
|
195
|
+
);
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (planned.length === 0) {
|
|
200
|
+
console.log(`✓ skills already up to date in ${opts.dir}`);
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (opts.dryRun) {
|
|
205
|
+
console.log(`Would write ${planned.length} file(s) into ${opts.dir}:\n`);
|
|
206
|
+
for (const { to, action } of planned)
|
|
207
|
+
console.log(` ${action} ${relative(opts.dir, to)}`);
|
|
208
|
+
console.log("\n(dry run — nothing was written)");
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// A read-only checkout, a permissions problem, or a full disk must fail with something a human
|
|
213
|
+
// can act on — not an unhandled stack trace out of a CLI a consumer just installed.
|
|
214
|
+
const written = [];
|
|
215
|
+
for (const { from, to } of planned) {
|
|
216
|
+
try {
|
|
217
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
218
|
+
writeFileSync(to, readFileSync(from));
|
|
219
|
+
written.push(to);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error(
|
|
222
|
+
`failed to write ${relative(opts.dir, to)}: ${error.code ?? ""} ${error.message}`,
|
|
223
|
+
);
|
|
224
|
+
if (written.length) {
|
|
225
|
+
console.error(
|
|
226
|
+
`\n${written.length} file(s) were already written before this failed:`,
|
|
227
|
+
);
|
|
228
|
+
for (const path of written)
|
|
229
|
+
console.error(` ${relative(opts.dir, path)}`);
|
|
230
|
+
console.error(
|
|
231
|
+
"Re-run once the cause is fixed; the command is idempotent.",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const surfaceLabel = opts.surfaces.map((s) => SURFACES[s]).join(" and ");
|
|
239
|
+
console.log(`✓ installed ${skills.length} skill(s) into ${surfaceLabel}`);
|
|
240
|
+
for (const skill of skills) console.log(` ${skill}`);
|
|
241
|
+
console.log(
|
|
242
|
+
"\nRestart your agent if it was already running, so it picks up the new directory.",
|
|
243
|
+
);
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function main(argv) {
|
|
248
|
+
const [sub, ...rest] = argv;
|
|
249
|
+
if (sub === "--help" || sub === "-h" || sub === "help" || sub == null) {
|
|
250
|
+
console.log(USAGE);
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
if (sub === "list") return list();
|
|
254
|
+
if (sub === "install") return install(rest);
|
|
255
|
+
console.error(`unknown skills subcommand: ${sub}\n`);
|
|
256
|
+
console.error(USAGE);
|
|
257
|
+
return 2;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
261
|
+
process.exit(main(process.argv.slice(2)));
|
|
262
|
+
}
|
package/bin/vegastack-design.mjs
CHANGED
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
// Subcommands:
|
|
5
5
|
// check-updates Show which copied-in components have newer registry versions (what to re-pull).
|
|
6
6
|
// verify Verify a registry item's integrity before/after `shadcn add` (Sigstore + hash).
|
|
7
|
+
// skills Install the bundled VegaStack agent skills into the consuming project.
|
|
7
8
|
//
|
|
8
9
|
// The bin is named `vegastack-design` (NOT `vegastack`) so it never collides with a platform CLI.
|
|
9
10
|
// `check-updates` is imported in-process; `verify` is spawned (it's the standalone, hash-parity-tested
|
|
10
11
|
// verifier — we run it untouched). Exit codes are forwarded from the subcommand.
|
|
11
|
-
import { spawnSync } from
|
|
12
|
-
import { readFileSync } from
|
|
13
|
-
import { fileURLToPath } from
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
14
15
|
|
|
15
|
-
const HERE = new URL(
|
|
16
|
+
const HERE = new URL(".", import.meta.url);
|
|
16
17
|
|
|
17
18
|
const USAGE = `vegastack-design — VegaStack design-system CLI
|
|
18
19
|
|
|
@@ -21,6 +22,7 @@ Usage: vegastack-design <command> [options]
|
|
|
21
22
|
Commands:
|
|
22
23
|
check-updates Show which copied-in components have newer registry versions
|
|
23
24
|
verify Verify a registry item's integrity (pre/post \`shadcn add\`)
|
|
25
|
+
skills Install the VegaStack agent skills (Claude Code + Codex)
|
|
24
26
|
|
|
25
27
|
Run \`vegastack-design <command> --help\` for command options.
|
|
26
28
|
-v, --version Print version
|
|
@@ -28,39 +30,55 @@ Run \`vegastack-design <command> --help\` for command options.
|
|
|
28
30
|
|
|
29
31
|
function version() {
|
|
30
32
|
try {
|
|
31
|
-
return JSON.parse(
|
|
33
|
+
return JSON.parse(
|
|
34
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
35
|
+
).version;
|
|
32
36
|
} catch {
|
|
33
|
-
return
|
|
37
|
+
return "0.0.0";
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
38
42
|
|
|
39
|
-
if (cmd ===
|
|
43
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
40
44
|
console.log(version());
|
|
41
45
|
process.exit(0);
|
|
42
46
|
}
|
|
43
|
-
if (cmd == null || cmd ===
|
|
47
|
+
if (cmd == null || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
44
48
|
console.log(USAGE);
|
|
45
49
|
process.exit(0);
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
if (cmd ===
|
|
52
|
+
if (cmd === "check-updates") {
|
|
49
53
|
// imported in-process (it's our own code with an exported main())
|
|
50
|
-
const { main } = await import(new URL(
|
|
54
|
+
const { main } = await import(new URL("./check-updates.mjs", HERE).href);
|
|
51
55
|
process.exit(await main(rest));
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
if (cmd ===
|
|
58
|
+
if (cmd === "skills") {
|
|
59
|
+
// imported in-process (our own code, no network, no credentials)
|
|
60
|
+
const { main } = await import(new URL("./skills.mjs", HERE).href);
|
|
61
|
+
process.exit(main(rest));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (cmd === "verify") {
|
|
55
65
|
// spawn the standalone verifier untouched; mark the dispatch so it skips its deprecation notice.
|
|
56
|
-
const verifier = fileURLToPath(new URL(
|
|
66
|
+
const verifier = fileURLToPath(new URL("./verify-registry-item.mjs", HERE));
|
|
57
67
|
const r = spawnSync(process.execPath, [verifier, ...rest], {
|
|
58
|
-
stdio:
|
|
59
|
-
env: { ...process.env, VEGASTACK_DESIGN_DISPATCH:
|
|
68
|
+
stdio: "inherit",
|
|
69
|
+
env: { ...process.env, VEGASTACK_DESIGN_DISPATCH: "1" },
|
|
60
70
|
});
|
|
61
71
|
process.exit(r.status ?? 1);
|
|
62
72
|
}
|
|
63
73
|
|
|
74
|
+
// Name the installed version: the most common cause of "unknown command" is a consumer following
|
|
75
|
+
// documentation for a newer release than the one they actually have installed.
|
|
64
76
|
console.error(`unknown command: ${cmd}\n`);
|
|
77
|
+
console.error(
|
|
78
|
+
`(this is @vegastack/design@${version()} — if you expected "${cmd}", check whether it`,
|
|
79
|
+
);
|
|
80
|
+
console.error(
|
|
81
|
+
`requires a newer version: npm view @vegastack/design version)\n`,
|
|
82
|
+
);
|
|
65
83
|
console.error(USAGE);
|
|
66
84
|
process.exit(2);
|