@getvitops/cli 0.5.0 → 0.7.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/CHANGELOG.md ADDED
@@ -0,0 +1,169 @@
1
+ # @getvitops/cli
2
+
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 44de07f: Split the button pattern into two tiers named by intent: `.cta` (persuasion) and `.btn`
8
+ (affordance).
9
+
10
+ **Breaking: a bare `<button>` is no longer a filled brand-primary button.** It now renders as a
11
+ quiet interactive control — geometry, cursor, subtle hover, focus ring — with no fill, no
12
+ `font-weight: 600`, and no shadow.
13
+
14
+ **Migration:** add `class="cta"` to any button that should stay prominent. Submit buttons, hero
15
+ actions, and anything driving a conversion are the usual candidates; dialog closes, toolbar
16
+ buttons, icon buttons and toggles should keep the new default. If you want the old behaviour
17
+ globally, set `patterns.items.btn` in your `design-system.json` back to the previous filled base.
18
+
19
+ New in this release:
20
+ - **`.cta`** — filled, bolder, roomier, lifts on hover. It is a class, not an element rule, so it
21
+ finally works on `<a>` — which is what a call to action usually is, since it navigates. Role
22
+ variants: `.cta-{success,danger,warning,info}`.
23
+ - **`.btn`** — the affordance tier. Emitted as one zero-specificity `:where(button, .btn)` rule, so
24
+ a bare `<button>` gets it with no class, `.btn` carries it to any other tag, and any explicit
25
+ class (including `.cta`, or a component's own class) overrides it without `!important`.
26
+ - **`:where(a, .link)`** — the link pattern now pairs its element with a class the same way.
27
+ - A pattern may now set **both `element` and `class`**, emitting one combined
28
+ `:where(<element>, .<class>)` rule. This is the general mechanism behind the above.
29
+ - A pattern may declare **`fill: true|false`** to state whether states and role variants drive
30
+ `background-color` (plus `on-solid` text) or `color`, instead of relying on the previous
31
+ inference from the pattern's name and base declarations. Existing configs are unaffected — the
32
+ old inference is still the fallback.
33
+
34
+ **Breaking: `chip` is retired as vocabulary.** The two small-label patterns are now split by
35
+ behaviour, not size: **`badge`** is a _static_ label (status, count, category) and **`tag`** is an
36
+ _editable and/or dismissable_ one (e.g. entries in a filter list).
37
+ - `.chip-list` → **`.tag-list`**, and its `--*-chip-list` / `--chip-list-focus-color` tokens →
38
+ `--*-tag-list` / `--tag-list-focus-color`.
39
+ - `.chip-list__chip` and `.chip-list__chip-remove` are **removed**. A tag list is a list of tags, so
40
+ its items are now the existing `.tag` / `.tag__remove` — which also means they pick up tag role
41
+ variants. **Migration:** replace `<span class="chip-list__chip">x <button
42
+ class="chip-list__chip-remove">` with `<span class="tag">x <button class="tag__remove">`. Note the
43
+ items change appearance: `.tag` is outlined (border + neutral text) where the old chip was filled
44
+ with `--color-surface-muted`.
45
+ - The `radii.chip` primitive is **removed** from the example config; it was only ever an alias of
46
+ `--br-tag`. Consumers who set `--br-chip` should use `--br-tag`.
47
+
48
+ **Pattern geometry now resolves through the group alias layer.** Every grouped pattern already
49
+ emitted `--<prop>-<name>-group` aliases (e.g. `--br-btn-group: var(--br-control)`), but most
50
+ patterns bypassed them and hard-coded `var(--br-control, …)` into the rule. `btn`, `cta`, `badge`,
51
+ `tag`, `card` and `status` now reference the alias and restate only their deviations via
52
+ `overrides`, so the whole chain — `--p-btn` → `--p-btn-group` → `--p-control` → `--p-default` — is
53
+ live CSS custom properties, inspectable and editable in the browser. Computed values are unchanged.
54
+
55
+ Also fixed:
56
+ - Role variants on element patterns were emitted at specificity 0-1-1 (`button.danger`), which
57
+ outranked any plain class. They now emit as `:where(button, .btn).danger, .btn-danger` — both at
58
+ class specificity, and reachable from a non-`<button>` host.
59
+ - The `link` pattern declared `default_role: "brand-primary"` while hard-coding a `ui-primary` base
60
+ colour, so hovering shifted hue instead of intensifying. Its `default_role` is now `ui-primary`.
61
+ - `@getvitops/astro`'s `FormRenderer` defaulted its submit button to `class="btn btn-primary"`, a
62
+ class that never existed and a role that is not emitted; it now defaults to `.cta`.
63
+ - The Tailwind bundle is no longer assembled during `css` / `bricks` builds, where it was computed
64
+ and discarded (it also read every framework partial off disk).
65
+
66
+ - 44de07f: Shared toolchain version + changelogs that reach consumers.
67
+ - **`@getvitops/astro` now shares the toolchain version** (`core`/`generator`/`utils`/`cli`/`vite`),
68
+ so it moves from its own `0.4.x` line onto the group's. The number changes; the package does not —
69
+ install it at the same version as `@getvitops/cli`. It was already being bumped on every toolchain
70
+ release by its dependency updates, and it depends on core, generator, utils _and_ vite, so a
71
+ separate version line cost the same churn while leaving the compatible pairing implicit. The
72
+ lockstep is load-bearing: the generator ships a snapshot of core's CSS + web-component bundles
73
+ while the Astro integration copies the _installed_ core's bundles, so mismatched versions can leave
74
+ the CSS and the components disagreeing.
75
+
76
+ - **Every package now ships its `CHANGELOG.md` in the published tarball.** npm does not include
77
+ changelogs by default, so none of this history previously reached anyone who installed the
78
+ packages. Per-package history now reads from `node_modules/@getvitops/<pkg>/CHANGELOG.md`;
79
+ curated toolchain-level release notes live in the repo's root `CHANGELOG.md`.
80
+
81
+ ### Patch Changes
82
+
83
+ - @getvitops/generator@0.7.0
84
+ - @getvitops/utils@0.7.0
85
+
86
+ ## 0.6.0
87
+
88
+ ### Minor Changes
89
+
90
+ - 2cc847d: Package-resident agent skill + `vitops docs`:
91
+ - `@getvitops/cli` now ships the `vitops-design-system` agent skill inside the package
92
+ (`skill/SKILL.md`). `vitops agents` no longer emits a generated skill into the repo — it
93
+ symlinks `.agents/skills/` and `.claude/skills/` entries to the installed package (logical
94
+ `node_modules/@getvitops/cli/skill` target, surviving version bumps) and writes the
95
+ AGENTS.md pointer block. Old generated-skill directories are migrated automatically;
96
+ `--docs-dir` keeps the emit-files layout.
97
+ - New `vitops docs [topic]` command prints reference docs to stdout, rendered live from the
98
+ project's `design-system.json` (topics: classes, authoring, formats, color, scales,
99
+ patterns, elements; `--all` concatenates).
100
+ - `renderSkill()` removed from `@getvitops/generator` (superseded by the packaged skill).
101
+
102
+ ### Patch Changes
103
+
104
+ - Updated dependencies [2cc847d]
105
+ - @getvitops/generator@0.6.0
106
+ - @getvitops/utils@0.6.0
107
+
108
+ ## 0.5.0
109
+
110
+ ### Minor Changes
111
+
112
+ - Ship design-system context to downstream agents:
113
+ - JSON Schema descriptions: every `design-system.json` and site-config field now carries a
114
+ `description` (authored in the zod schemas via `desc()`), emitted into `schema.json` /
115
+ `site.schema.json` for editor hovers and agent consumption.
116
+ - New generated OKF docs: `authoring.md` (field reference walked from the JSON Schema),
117
+ `formats.md` (tailwind vs css vs bricks, including the TW_CLASH utilities Tailwind
118
+ provides natively), and `concepts/{color,scales,patterns}.md` (seeded OKLCH colour
119
+ system, fluid modular scales, pattern token cascade + override hooks).
120
+ - `vitops agents` now emits a generated `vitops-design-system` agent skill into
121
+ `.agents/skills/vitops-design-system/` (SKILL.md + the docs bundle as `references/`,
122
+ with an idempotent `.claude/skills/` symlink) and writes a compact pointer block into
123
+ `AGENTS.md`. Pass `--docs-dir` for the legacy docs-only layout.
124
+ - `TW_CLASH` and `BASE_HOOK` are exported from `@getvitops/generator`.
125
+
126
+ ### Patch Changes
127
+
128
+ - Updated dependencies
129
+ - @getvitops/generator@0.5.0
130
+ - @getvitops/utils@0.5.0
131
+
132
+ ## 0.4.0
133
+
134
+ ### Patch Changes
135
+
136
+ - Updated dependencies [d7e6491]
137
+ - @getvitops/utils@0.4.0
138
+ - @getvitops/generator@0.4.0
139
+
140
+ ## 0.3.0
141
+
142
+ ### Patch Changes
143
+
144
+ - Updated dependencies
145
+ - @getvitops/utils@0.3.0
146
+ - @getvitops/generator@0.3.0
147
+
148
+ ## 0.2.1
149
+
150
+ ### Patch Changes
151
+
152
+ - Updated dependencies [d28aae7]
153
+ - @getvitops/generator@0.2.1
154
+ - @getvitops/utils@0.2.1
155
+
156
+ ## 0.2.0
157
+
158
+ ### Minor Changes
159
+
160
+ - d35515e: Add `vitops agents` — writes a managed, marker-delimited block into a consumer's `AGENTS.md`
161
+ (or `--out CLAUDE.md`) so AI coding agents can discover the CLI and the design-system class/element
162
+ vocabulary. Idempotent (re-run to update between the `<!-- vitops:start -->`/`<!-- vitops:end -->`
163
+ markers) and also emits the OKF docs bundle it points at (`--docs-dir`, default `.vitops/docs`).
164
+
165
+ ### Patch Changes
166
+
167
+ - Updated dependencies
168
+ - @getvitops/generator@0.2.0
169
+ - @getvitops/utils@0.2.0
package/README.md CHANGED
@@ -23,7 +23,8 @@ npx vitops generate --format bricks,css --out dist
23
23
  | `vitops validate <file>` | schema-check (non-zero exit on error) |
24
24
  | `vitops generate -i <file> -f <formats> -o <dir>` | generate `bricks` / `css` / `tailwind` output |
25
25
  | `vitops favicon -i <svg\|png> -o <dir>` | generate a favicon set from a source image |
26
- | `vitops agents [-o AGENTS.md] [--docs-dir <dir>]` | teach AI agents about the design system |
26
+ | `vitops agents [-o AGENTS.md] [--docs-dir <dir>]` | link the agent skill + AGENTS.md pointer |
27
+ | `vitops docs [topic] [--all]` | print live reference docs to stdout |
27
28
 
28
29
  ## Using the output
29
30
 
@@ -79,22 +80,42 @@ The CLI only writes files — how they reach WordPress is up to you:
79
80
  - **CI:** run `vitops generate --format bricks` in your pipeline and deploy the artifact the
80
81
  same way you deploy the rest of the theme.
81
82
 
82
- ## Teaching AI agents (`vitops agents`)
83
+ ## Teaching AI agents (`vitops agents` + `vitops docs`)
83
84
 
84
- So an AI coding agent in a consumer project can discover the design system, `vitops agents`
85
- writes a small, marker-delimited **managed block** into your `AGENTS.md` (idempotent — re-run to
86
- update) and emits the reference docs bundle it points at:
85
+ This package **ships an agent skill** (`skill/SKILL.md`, the [Agent Skills](https://code.claude.com/docs/en/skills)
86
+ format) that teaches AI coding agents to fetch design-system context on demand. Nothing is
87
+ generated into your repo — reference docs print live via `vitops docs`:
87
88
 
88
89
  ```sh
89
- vitops agents # updates ./AGENTS.md, docs → ./.vitops/docs
90
- vitops agents --out CLAUDE.md --docs-dir docs/vitops --input design-system.json
90
+ vitops docs # list topics
91
+ vitops docs classes # the class vocabulary, rendered from YOUR design-system.json
92
+ vitops docs authoring # every config field, from the JSON Schema
93
+ vitops docs formats # tailwind vs css vs bricks (incl. which utilities Tailwind owns)
91
94
  ```
92
95
 
93
- The block lists the CLI commands and points at the generated class vocabulary
94
- (`<docs-dir>/css/classes.md`) and Bricks element reference (`<docs-dir>/bricks/elements.md`), both
95
- derived from your `design-system.json`. Re-running only replaces the content between the
96
- `<!-- vitops:start -->` / `<!-- vitops:end -->` markers, leaving the rest of the file untouched. If
97
- you'd rather not commit the generated docs, add your `--docs-dir` (e.g. `.vitops/`) to
98
- `.gitignore` and run `vitops agents` in a postinstall/CI step.
96
+ Because output is rendered at call time from your config + the installed package version, it is
97
+ never stale. `vitops agents` wires up discovery:
98
+
99
+ ```sh
100
+ vitops agents # links the skill + updates ./AGENTS.md
101
+ vitops agents --out CLAUDE.md # different pointer file
102
+ ```
103
+
104
+ It symlinks `.agents/skills/vitops-design-system` and `.claude/skills/vitops-design-system` to the
105
+ skill inside the installed package (the links target the logical `node_modules/@getvitops/cli/skill`
106
+ path, so they survive version bumps and reinstalls — re-run only if you delete them), and writes a
107
+ marker-delimited **managed block** into your `AGENTS.md` listing the CLI commands and `vitops docs`
108
+ topics. Re-running only replaces the content between the `<!-- vitops:start -->` /
109
+ `<!-- vitops:end -->` markers.
110
+
111
+ Prefer materialized files (offline/CI contexts, or agents that can't run commands)? The legacy
112
+ layout still works: `vitops agents --docs-dir .vitops/docs` writes the full docs bundle as files
113
+ and points the AGENTS.md block at them (no skill link).
99
114
 
100
115
  Powered by [`@getvitops/generator`](https://www.npmjs.com/package/@getvitops/generator).
116
+
117
+ ## Changelog
118
+
119
+ This package's history: [`CHANGELOG.md`](./CHANGELOG.md) (shipped in the npm tarball, so it
120
+ also reads from `node_modules/@getvitops/cli/CHANGELOG.md`). `@getvitops/core`, `generator`,
121
+ `utils`, `cli`, `vite` and `astro` share one version and are released together.
package/dist/cli.mjs CHANGED
@@ -2,10 +2,99 @@
2
2
  import { createRequire } from "node:module";
3
3
  import { parseArgs } from "node:util";
4
4
  import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
5
- import { dirname, join, relative, resolve } from "node:path";
6
- import { SCHEMA_URL, defaultConfig, generate, generateDocs, renderSkill, validate } from "@getvitops/generator";
5
+ import { dirname, join, parse, relative, resolve } from "node:path";
6
+ import { SCHEMA_URL, defaultConfig, generate, generateDocs, validate } from "@getvitops/generator";
7
7
  import { generateFavicons } from "@getvitops/utils";
8
8
 
9
+ //#region src/agents.ts
10
+ /**
11
+ * Side-effect-free helpers for the `vitops agents` / `vitops docs` commands.
12
+ * Kept out of cli.ts (which runs main() on import) so they are unit-testable.
13
+ */
14
+ const SKILL_NAME = "vitops-design-system";
15
+ /** Marker the 0.5.0-era generated skill carried; lets us migrate that layout. */
16
+ const GENERATED_MARKER = "<!-- GENERATED by @getvitops/cli";
17
+ /**
18
+ * `vitops docs` topics → docs-bundle paths (as returned by `generateDocs`).
19
+ * Order is the `--all` concatenation order.
20
+ */
21
+ const TOPICS = {
22
+ classes: {
23
+ path: "css/classes.md",
24
+ summary: "CSS framework class vocabulary (colour, type, space, layout, animation, patterns)"
25
+ },
26
+ authoring: {
27
+ path: "authoring.md",
28
+ summary: "every design-system.json field, rendered from the JSON Schema"
29
+ },
30
+ formats: {
31
+ path: "formats.md",
32
+ summary: "tailwind vs css vs bricks: what's emitted vs what the platform provides"
33
+ },
34
+ color: {
35
+ path: "concepts/color.md",
36
+ summary: "seeded OKLCH scales, functional tokens, automatic dark mode"
37
+ },
38
+ scales: {
39
+ path: "concepts/scales.md",
40
+ summary: "fluid modular type & space scales"
41
+ },
42
+ patterns: {
43
+ path: "concepts/patterns.md",
44
+ summary: "pattern CSS: token cascade, override hooks, states, role variants"
45
+ },
46
+ elements: {
47
+ path: "bricks/elements.md",
48
+ summary: "Bricks Builder custom elements, controls, defaults"
49
+ }
50
+ };
51
+ /**
52
+ * Find the installed package's skill directory by walking up from `cwd` looking
53
+ * for `node_modules/@getvitops/cli/skill/SKILL.md`. Returns the LOGICAL path
54
+ * (through the `node_modules/@getvitops/cli` link, unresolved) or null.
55
+ *
56
+ * Deliberately not derived from import.meta.url: under pnpm the running module's
57
+ * URL is the realpath in `node_modules/.pnpm/@getvitops+cli@<version>/…`, which
58
+ * changes on every version bump — a symlink to it would dangle after upgrades.
59
+ * The logical path is recreated by the package manager on every install, so a
60
+ * link through it always resolves to the currently installed version.
61
+ */
62
+ function findSkillTarget(cwd) {
63
+ let dir = resolve(cwd);
64
+ const { root } = parse(dir);
65
+ for (;;) {
66
+ const skillDir = join(dir, "node_modules", "@getvitops", "cli", "skill");
67
+ if (existsSync(join(skillDir, "SKILL.md"))) return skillDir;
68
+ if (dir === root) return null;
69
+ dir = dirname(dir);
70
+ }
71
+ }
72
+ /**
73
+ * Idempotently symlink `linkPath` → `targetDir` (relative target). Replaces a
74
+ * stale symlink; migrates a real directory left by the old emitting `vitops
75
+ * agents` (identified by GENERATED_MARKER in its SKILL.md); refuses to clobber
76
+ * anything else. Returns a warning string, or null on success / no-op.
77
+ */
78
+ function linkSkill(linkPath, targetDir) {
79
+ const target = relative(dirname(linkPath), resolve(targetDir));
80
+ try {
81
+ const st = lstatSync(linkPath, { throwIfNoEntry: false });
82
+ if (st && !st.isSymbolicLink()) {
83
+ const oldSkill = join(linkPath, "SKILL.md");
84
+ if (!(st.isDirectory() && existsSync(oldSkill) && readFileSync(oldSkill, "utf8").includes("<!-- GENERATED by @getvitops/cli"))) return `left ${linkPath} alone (exists and is not a symlink) — link it to ${targetDir} yourself if wanted`;
85
+ rmSync(linkPath, { recursive: true });
86
+ } else if (st) {
87
+ if (readlinkSync(linkPath) === target) return null;
88
+ rmSync(linkPath);
89
+ } else mkdirSync(dirname(linkPath), { recursive: true });
90
+ symlinkSync(target, linkPath);
91
+ return null;
92
+ } catch (err) {
93
+ return `could not symlink ${linkPath}: ${err.message}`;
94
+ }
95
+ }
96
+
97
+ //#endregion
9
98
  //#region src/cli.ts
10
99
  /**
11
100
  * `vitops` — CLI for the Vitops design-system generator.
@@ -14,7 +103,8 @@ import { generateFavicons } from "@getvitops/utils";
14
103
  * vitops init [--out design-system.json] [--force]
15
104
  * vitops validate <design-system.json>
16
105
  * vitops favicon --input <svg|png> --out <dir> [--low-res <svg|png>]
17
- * vitops agents [--input <json>] [--out AGENTS.md] [--skill-dir <dir>] [--docs-dir <dir>]
106
+ * vitops agents [--input <json>] [--out AGENTS.md] [--docs-dir <dir>]
107
+ * vitops docs [topic] [--input <json>] [--all]
18
108
  *
19
109
  * Thin wrapper over @getvitops/generator (generation) and @getvitops/utils (favicons).
20
110
  * Every client brings their own consumer-editable design-system.json.
@@ -31,7 +121,8 @@ Usage:
31
121
  vitops init [options] Scaffold a starter design-system.json
32
122
  vitops validate <file> Validate a config against the schema
33
123
  vitops favicon [options] Generate a favicon set from a source image
34
- vitops agents [options] Emit the design-system agent skill + AGENTS.md pointer
124
+ vitops agents [options] Link the design-system agent skill + AGENTS.md pointer
125
+ vitops docs [topic] Print live design-system reference docs to stdout
35
126
 
36
127
  Generate options:
37
128
  -i, --input <path> Config file (default: ./design-system.json)
@@ -48,12 +139,16 @@ Favicon options:
48
139
  --low-res <path> Optional simplified source for the 16px icon
49
140
 
50
141
  Agents options:
51
- -i, --input <path> Config file (default: ./design-system.json)
142
+ -i, --input <path> Config file (default: ./design-system.json; validated if present)
52
143
  -o, --out <path> Doc file to update, idempotently (default: ./AGENTS.md)
53
- --skill-dir <dir> Where the generated skill lands (default:
54
- ./.agents/skills/vitops-design-system; also symlinked from
55
- ./.claude/skills/vitops-design-system)
56
- --docs-dir <dir> Legacy layout: write only the docs bundle to this dir (no skill)
144
+ --docs-dir <dir> Legacy layout: write the docs bundle as files to this dir
145
+ instead of linking the packaged skill
146
+
147
+ Docs options:
148
+ <topic> classes | authoring | formats | color | scales | patterns | elements
149
+ (no topic: list topics with summaries)
150
+ -i, --input <path> Config file (default: ./design-system.json)
151
+ --all Print every topic, concatenated
57
152
 
58
153
  Common:
59
154
  -h, --help Show this help
@@ -207,29 +302,33 @@ function generatorAssetsDir() {
207
302
  }
208
303
  const AGENTS_START = "<!-- vitops:start -->";
209
304
  const AGENTS_END = "<!-- vitops:end -->";
210
- const SKILL_NAME = "vitops-design-system";
211
- /**
212
- * Idempotently symlink `.claude/skills/vitops-design-system` → the skill dir so
213
- * Claude Code discovers the agent-agnostic `.agents/` skill. Replaces a stale
214
- * symlink; refuses to clobber a real file/dir. Returns a warning, or null on
215
- * success / nothing-to-do.
216
- */
217
- function linkClaudeSkill(skillDir) {
218
- const linkPath = resolve(".claude", "skills", SKILL_NAME);
219
- const target = relative(dirname(linkPath), resolve(skillDir));
220
- if (target === "") return null;
221
- try {
222
- const st = lstatSync(linkPath, { throwIfNoEntry: false });
223
- if (st && !st.isSymbolicLink()) return `left ${linkPath} alone (exists and is not a symlink) — link it to ${skillDir} yourself if wanted`;
224
- if (st) {
225
- if (readlinkSync(linkPath) === target) return null;
226
- rmSync(linkPath);
227
- } else mkdirSync(dirname(linkPath), { recursive: true });
228
- symlinkSync(target, linkPath);
229
- return null;
230
- } catch (err) {
231
- return `could not symlink ${linkPath}: ${err.message}`;
305
+ /** Print a live reference doc (rendered from the project's config) to stdout. */
306
+ function cmdDocs(argv) {
307
+ const { values, positionals } = parseArgs({
308
+ args: argv,
309
+ options: {
310
+ input: {
311
+ type: "string",
312
+ short: "i",
313
+ default: "design-system.json"
314
+ },
315
+ all: {
316
+ type: "boolean",
317
+ default: false
318
+ }
319
+ },
320
+ allowPositionals: true
321
+ });
322
+ const topic = positionals[0];
323
+ const topicList = () => Object.entries(TOPICS).map(([name, t]) => ` ${name.padEnd(10)} ${t.summary}`).join("\n");
324
+ if (!topic && !values.all) {
325
+ console.log(`vitops docs <topic> — print a live design-system reference to stdout\n\nTopics:\n${topicList()}\n\nAlso: vitops docs --all (every topic, concatenated)`);
326
+ return;
232
327
  }
328
+ if (topic && !TOPICS[topic]) fail(`unknown topic "${topic}". Valid topics:\n${topicList()}`);
329
+ const docs = generateDocs(loadConfig(resolve(values.input)), generatorAssetsDir());
330
+ const paths = values.all ? Object.values(TOPICS).map((t) => t.path) : [TOPICS[topic].path];
331
+ process.stdout.write(paths.map((p) => docs[p]).join("\n"));
233
332
  }
234
333
  function cmdAgents(argv) {
235
334
  const { values } = parseArgs({
@@ -245,48 +344,58 @@ function cmdAgents(argv) {
245
344
  short: "o",
246
345
  default: "AGENTS.md"
247
346
  },
248
- "skill-dir": {
249
- type: "string",
250
- default: join(".agents", "skills", SKILL_NAME)
251
- },
252
347
  "docs-dir": { type: "string" }
253
348
  },
254
349
  allowPositionals: false
255
350
  });
256
351
  const inputRel = values.input;
257
- const ds = loadConfig(resolve(inputRel));
258
- const skillDir = values["skill-dir"];
352
+ let ds = null;
353
+ if (existsSync(resolve(inputRel))) ds = loadConfig(resolve(inputRel));
354
+ else console.warn(` ⚠ ${inputRel} not found — run \`vitops init\` to scaffold one`);
259
355
  const legacyDocsDir = values["docs-dir"];
260
- const docsDir = legacyDocsDir ?? join(skillDir, "references");
261
- let docs = {};
262
- try {
263
- docs = generateDocs(ds, generatorAssetsDir());
264
- } catch (err) {
265
- fail(`could not generate docs: ${err.message}`);
266
- }
267
- for (const [rel, content] of Object.entries(docs)) {
268
- const p = resolve(docsDir, rel);
269
- mkdirSync(dirname(p), { recursive: true });
270
- writeFileSync(p, content);
271
- }
272
- let skillNote = "";
273
- if (!legacyDocsDir) {
274
- writeFileSync(resolve(skillDir, "SKILL.md"), renderSkill(ds));
275
- const warn = linkClaudeSkill(skillDir);
276
- if (warn) console.warn(` ⚠ ${warn}`);
277
- skillNote = ` + skill at ${skillDir}/`;
356
+ let summary;
357
+ let pointer;
358
+ if (legacyDocsDir) {
359
+ if (!ds) fail(`--docs-dir needs a config to render docs from (${inputRel} not found)`);
360
+ let docs = {};
361
+ try {
362
+ docs = generateDocs(ds, generatorAssetsDir());
363
+ } catch (err) {
364
+ fail(`could not generate docs: ${err.message}`);
365
+ }
366
+ for (const [rel, content] of Object.entries(docs)) {
367
+ const p = resolve(legacyDocsDir, rel);
368
+ mkdirSync(dirname(p), { recursive: true });
369
+ writeFileSync(p, content);
370
+ }
371
+ summary = `wrote ${Object.keys(docs).length} docs to ${legacyDocsDir}/`;
372
+ pointer = [
373
+ "Prefer the framework’s utility + component classes over hand-written CSS.",
374
+ `Reference docs (regenerate with \`vitops agents --docs-dir ${legacyDocsDir}\`): \`${legacyDocsDir}/css/classes.md\`,`,
375
+ `\`${legacyDocsDir}/authoring.md\`, \`${legacyDocsDir}/formats.md\`, \`${legacyDocsDir}/bricks/elements.md\`.`
376
+ ];
377
+ } else {
378
+ const target = findSkillTarget(process.cwd());
379
+ if (target) {
380
+ for (const dir of [".agents", ".claude"]) {
381
+ const warn = linkSkill(resolve(dir, "skills", SKILL_NAME), target);
382
+ if (warn) console.warn(` ⚠ ${warn}`);
383
+ }
384
+ summary = `linked {.agents,.claude}/skills/${SKILL_NAME} → ${target}`;
385
+ } else {
386
+ console.warn(` ⚠ could not find node_modules/@getvitops/cli/skill — install @getvitops/cli as a devDependency, then re-run \`vitops agents\``);
387
+ summary = "no skill linked";
388
+ }
389
+ pointer = [
390
+ "Prefer the framework’s utility + component classes over hand-written CSS.",
391
+ `Full design-system context lives in the \`${SKILL_NAME}\` skill (linked from`,
392
+ "`.agents/skills/` and `.claude/skills/` into the installed `@getvitops/cli`;",
393
+ "re-link with `vitops agents` if the links are deleted — they survive version",
394
+ "bumps). Reference docs print live, from this project’s config, via:",
395
+ "",
396
+ ...Object.entries(TOPICS).map(([name, t]) => `- \`vitops docs ${name}\` — ${t.summary}`)
397
+ ];
278
398
  }
279
- const pointer = legacyDocsDir ? [
280
- "Prefer the framework’s utility + component classes over hand-written CSS.",
281
- `Reference docs (regenerate with \`vitops agents\`): \`${docsDir}/css/classes.md\`,`,
282
- `\`${docsDir}/authoring.md\`, \`${docsDir}/formats.md\`, \`${docsDir}/bricks/elements.md\`.`
283
- ] : [
284
- "Prefer the framework’s utility + component classes over hand-written CSS.",
285
- `Full design-system context lives in the \`${SKILL_NAME}\` skill at \`${skillDir}/\`:`,
286
- "class vocabulary, `design-system.json` field reference, per-format output",
287
- "differences (Tailwind vs Bricks vs CSS), colour/scale/pattern concepts, and the",
288
- "Bricks element reference (under its `references/`). Regenerate with `vitops agents`."
289
- ];
290
399
  const block = [
291
400
  AGENTS_START,
292
401
  "## Vitops design system",
@@ -324,7 +433,7 @@ function cmdAgents(argv) {
324
433
  action = "created";
325
434
  }
326
435
  writeFileSync(outPath, next);
327
- console.log(`✓ ${action} ${values.out} + wrote ${Object.keys(docs).length} docs to ${docsDir}/${skillNote}`);
436
+ console.log(`✓ ${action} ${values.out} + ${summary}`);
328
437
  }
329
438
  async function main() {
330
439
  const [command, ...rest] = process.argv.slice(2);
@@ -338,7 +447,8 @@ async function main() {
338
447
  case "validate": return cmdValidate(rest);
339
448
  case "favicon": return cmdFavicon(rest);
340
449
  case "agents": return cmdAgents(rest);
341
- default: fail(`unknown command "${command}" (expected: generate | init | validate | favicon | agents). Try: vitops --help`);
450
+ case "docs": return cmdDocs(rest);
451
+ default: fail(`unknown command "${command}" (expected: generate | init | validate | favicon | agents | docs). Try: vitops --help`);
342
452
  }
343
453
  }
344
454
  main().catch((err) => fail(err.message));
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `vitops` — CLI for the Vitops design-system generator.\n *\n * vitops generate --input <json> --format <bricks|css|tailwind[,…]> --out <dir>\n * vitops init [--out design-system.json] [--force]\n * vitops validate <design-system.json>\n * vitops favicon --input <svg|png> --out <dir> [--low-res <svg|png>]\n * vitops agents [--input <json>] [--out AGENTS.md] [--skill-dir <dir>] [--docs-dir <dir>]\n *\n * Thin wrapper over @getvitops/generator (generation) and @getvitops/utils (favicons).\n * Every client brings their own consumer-editable design-system.json.\n */\nimport { parseArgs } from 'node:util';\nimport {\n writeFileSync,\n existsSync,\n readFileSync,\n mkdirSync,\n lstatSync,\n readlinkSync,\n rmSync,\n symlinkSync,\n} from 'node:fs';\nimport { resolve, join, dirname, relative } from 'node:path';\nimport { createRequire } from 'node:module';\nimport {\n generate,\n validate,\n defaultConfig,\n generateDocs,\n renderSkill,\n SCHEMA_URL,\n type Format,\n type DesignSystem,\n} from '@getvitops/generator';\nimport { generateFavicons } from '@getvitops/utils';\n\nconst FORMATS = new Set<Format>(['bricks', 'css', 'tailwind']);\n\nconst HELP = `vitops — generate design-system outputs from a design-system.json\n\nUsage:\n vitops generate [options] Generate platform output from a config\n vitops init [options] Scaffold a starter design-system.json\n vitops validate <file> Validate a config against the schema\n vitops favicon [options] Generate a favicon set from a source image\n vitops agents [options] Emit the design-system agent skill + AGENTS.md pointer\n\nGenerate options:\n -i, --input <path> Config file (default: ./design-system.json)\n -f, --format <list> bricks | css | tailwind (comma-separated; default: bricks)\n -o, --out <dir> Output directory (default: ./dist)\n\nInit options:\n -o, --out <path> Where to write (default: ./design-system.json)\n --force Overwrite an existing file\n\nFavicon options:\n -i, --input <path> Source SVG or PNG (required)\n -o, --out <dir> Output directory (default: ./public)\n --low-res <path> Optional simplified source for the 16px icon\n\nAgents options:\n -i, --input <path> Config file (default: ./design-system.json)\n -o, --out <path> Doc file to update, idempotently (default: ./AGENTS.md)\n --skill-dir <dir> Where the generated skill lands (default:\n ./.agents/skills/vitops-design-system; also symlinked from\n ./.claude/skills/vitops-design-system)\n --docs-dir <dir> Legacy layout: write only the docs bundle to this dir (no skill)\n\nCommon:\n -h, --help Show this help\n`;\n\nfunction fail(msg: string): never {\n console.error(`✖ ${msg}`);\n process.exit(1);\n}\n\nasync function cmdGenerate(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n format: { type: 'string', short: 'f', default: 'bricks' },\n out: { type: 'string', short: 'o', default: 'dist' },\n },\n allowPositionals: false,\n });\n const input = resolve(values.input as string);\n if (!existsSync(input)) fail(`config not found: ${input}`);\n const formats = (values.format as string)\n .split(',')\n .map((f) => f.trim())\n .filter(Boolean);\n for (const f of formats)\n if (!FORMATS.has(f as Format))\n fail(`unknown format \"${f}\" (expected: bricks | css | tailwind)`);\n\n for (const format of formats as Format[]) {\n try {\n const res = await generate({ input, format, outDir: values.out as string });\n console.log(`✓ ${format} → ${res.outDir} (${res.written.length} paths)`);\n } catch (err) {\n fail((err as Error).message);\n }\n }\n}\n\nfunction cmdInit(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n out: { type: 'string', short: 'o', default: 'design-system.json' },\n force: { type: 'boolean', default: false },\n },\n allowPositionals: false,\n });\n const out = resolve(values.out as string);\n if (existsSync(out) && !values.force) fail(`${out} already exists (use --force to overwrite)`);\n writeFileSync(out, JSON.stringify(defaultConfig(), null, 2) + '\\n');\n console.log(`✓ wrote ${out}`);\n console.log(\n ` Edit it, then: vitops generate --input ${values.out} --format tailwind --out src/styles`,\n );\n console.log(` Editors read the \"$schema\" (${SCHEMA_URL}) for autocomplete + validation.`);\n}\n\nfunction cmdValidate(argv: string[]) {\n const { values, positionals } = parseArgs({\n args: argv,\n options: { input: { type: 'string', short: 'i' } },\n allowPositionals: true,\n });\n const path = resolve((positionals[0] ?? values.input ?? 'design-system.json') as string);\n if (!existsSync(path)) fail(`config not found: ${path}`);\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n const result = validate(raw);\n if (result.ok) {\n console.log(`✓ ${path} is valid`);\n return;\n }\n console.error(`✖ ${path} is invalid:`);\n for (const e of result.errors) console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n process.exit(1);\n}\n\nasync function cmdFavicon(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i' },\n out: { type: 'string', short: 'o', default: 'public' },\n 'low-res': { type: 'string' },\n },\n allowPositionals: false,\n });\n if (!values.input) fail('favicon: --input <svg|png> is required');\n const source = resolve(values.input as string);\n if (!existsSync(source)) fail(`source not found: ${source}`);\n try {\n const written = await generateFavicons({\n source,\n outputDir: values.out as string,\n ...(values['low-res'] ? { lowResSource: resolve(values['low-res'] as string) } : {}),\n });\n console.log(`✓ favicons → ${values.out} (${written.length} files)`);\n } catch (err) {\n fail((err as Error).message);\n }\n}\n\n// Read + parse + validate a design-system.json, or exit with a message.\nfunction loadConfig(path: string): DesignSystem {\n if (!existsSync(path)) fail(`config not found: ${path}`);\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n const result = validate(raw);\n if (!result.ok) {\n console.error(`✖ ${path} is invalid:`);\n for (const e of result.errors)\n console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n process.exit(1);\n }\n return result.data;\n}\n\n// This CLI's own version (dist/cli.mjs → package-root package.json).\nfunction cliVersion(): string {\n try {\n return (\n JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version ??\n '0.0.0'\n );\n } catch {\n return '0.0.0';\n }\n}\n\n// The framework asset root shipped inside @getvitops/generator (holds the Bricks\n// PHP that generateDocs reads for the element reference).\nfunction generatorAssetsDir(): string {\n const pkg = createRequire(import.meta.url).resolve('@getvitops/generator/package.json');\n return join(dirname(pkg), 'assets');\n}\n\nconst AGENTS_START = '<!-- vitops:start -->';\nconst AGENTS_END = '<!-- vitops:end -->';\nconst SKILL_NAME = 'vitops-design-system';\n\n/**\n * Idempotently symlink `.claude/skills/vitops-design-system` → the skill dir so\n * Claude Code discovers the agent-agnostic `.agents/` skill. Replaces a stale\n * symlink; refuses to clobber a real file/dir. Returns a warning, or null on\n * success / nothing-to-do.\n */\nfunction linkClaudeSkill(skillDir: string): string | null {\n const linkPath = resolve('.claude', 'skills', SKILL_NAME);\n const target = relative(dirname(linkPath), resolve(skillDir));\n if (target === '') return null; // skill dir IS the .claude path\n try {\n const st = lstatSync(linkPath, { throwIfNoEntry: false });\n if (st && !st.isSymbolicLink())\n return `left ${linkPath} alone (exists and is not a symlink) — link it to ${skillDir} yourself if wanted`;\n if (st) {\n if (readlinkSync(linkPath) === target) return null;\n rmSync(linkPath);\n } else {\n mkdirSync(dirname(linkPath), { recursive: true });\n }\n symlinkSync(target, linkPath);\n return null;\n } catch (err) {\n return `could not symlink ${linkPath}: ${(err as Error).message}`;\n }\n}\n\nfunction cmdAgents(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n out: { type: 'string', short: 'o', default: 'AGENTS.md' },\n 'skill-dir': { type: 'string', default: join('.agents', 'skills', SKILL_NAME) },\n 'docs-dir': { type: 'string' },\n },\n allowPositionals: false,\n });\n const inputRel = values.input as string;\n const ds = loadConfig(resolve(inputRel));\n const skillDir = values['skill-dir'] as string;\n // Explicit --docs-dir = legacy layout: just the docs bundle, no skill.\n const legacyDocsDir = values['docs-dir'] as string | undefined;\n const docsDir = legacyDocsDir ?? join(skillDir, 'references');\n\n // Emit the OKF docs bundle so the AGENTS.md pointer resolves for every consumer\n // (only the `bricks` generate format writes docs/ otherwise).\n let docs: Record<string, string> = {};\n try {\n docs = generateDocs(ds, generatorAssetsDir());\n } catch (err) {\n fail(`could not generate docs: ${(err as Error).message}`);\n }\n for (const [rel, content] of Object.entries(docs)) {\n const p = resolve(docsDir, rel);\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, content);\n }\n\n let skillNote = '';\n if (!legacyDocsDir) {\n writeFileSync(resolve(skillDir, 'SKILL.md'), renderSkill(ds));\n const warn = linkClaudeSkill(skillDir);\n if (warn) console.warn(` ⚠ ${warn}`);\n skillNote = ` + skill at ${skillDir}/`;\n }\n\n const pointer = legacyDocsDir\n ? [\n 'Prefer the framework’s utility + component classes over hand-written CSS.',\n `Reference docs (regenerate with \\`vitops agents\\`): \\`${docsDir}/css/classes.md\\`,`,\n `\\`${docsDir}/authoring.md\\`, \\`${docsDir}/formats.md\\`, \\`${docsDir}/bricks/elements.md\\`.`,\n ]\n : [\n 'Prefer the framework’s utility + component classes over hand-written CSS.',\n `Full design-system context lives in the \\`${SKILL_NAME}\\` skill at \\`${skillDir}/\\`:`,\n 'class vocabulary, `design-system.json` field reference, per-format output',\n 'differences (Tailwind vs Bricks vs CSS), colour/scale/pattern concepts, and the',\n 'Bricks element reference (under its `references/`). Regenerate with `vitops agents`.',\n ];\n\n const block = [\n AGENTS_START,\n '## Vitops design system',\n '',\n `Styled with the Vitops design system (\\`@getvitops/*\\`); tokens live in \\`${inputRel}\\`.`,\n 'Generate output with the CLI:',\n '',\n '- `vitops generate --format tailwind --out src/styles` — Tailwind v4 / Astro',\n '- `vitops generate --format css --out dist` — standalone CSS',\n '- `vitops generate --format bricks --out <theme>/dist` — WordPress / Bricks',\n '- `vitops init` · `vitops validate` · `vitops favicon`',\n '',\n ...pointer,\n '',\n `<!-- regenerate: vitops agents · @getvitops/cli@${cliVersion()} -->`,\n AGENTS_END,\n ].join('\\n');\n\n const outPath = resolve(values.out as string);\n let next: string;\n let action: string;\n if (existsSync(outPath)) {\n const cur = readFileSync(outPath, 'utf8');\n const s = cur.indexOf(AGENTS_START);\n const e = cur.indexOf(AGENTS_END);\n if (s !== -1 && e !== -1 && e > s) {\n next = cur.slice(0, s) + block + cur.slice(e + AGENTS_END.length);\n action = 'updated block in';\n } else {\n next = cur.replace(/\\s*$/, '') + '\\n\\n' + block + '\\n';\n action = 'appended block to';\n }\n } else {\n mkdirSync(dirname(outPath), { recursive: true });\n next = block + '\\n';\n action = 'created';\n }\n writeFileSync(outPath, next);\n console.log(\n `✓ ${action} ${values.out} + wrote ${Object.keys(docs).length} docs to ${docsDir}/${skillNote}`,\n );\n}\n\nasync function main() {\n const [command, ...rest] = process.argv.slice(2);\n if (!command || command === '-h' || command === '--help' || command === 'help') {\n console.log(HELP);\n return;\n }\n switch (command) {\n case 'generate':\n return cmdGenerate(rest);\n case 'init':\n return cmdInit(rest);\n case 'validate':\n return cmdValidate(rest);\n case 'favicon':\n return cmdFavicon(rest);\n case 'agents':\n return cmdAgents(rest);\n default:\n fail(\n `unknown command \"${command}\" (expected: generate | init | validate | favicon | agents). Try: vitops --help`,\n );\n }\n}\n\nmain().catch((err) => fail((err as Error).message));\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/agents.ts","../src/cli.ts"],"sourcesContent":["/**\n * Side-effect-free helpers for the `vitops agents` / `vitops docs` commands.\n * Kept out of cli.ts (which runs main() on import) so they are unit-testable.\n */\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n readlinkSync,\n rmSync,\n symlinkSync,\n} from 'node:fs';\nimport { dirname, join, parse, relative, resolve } from 'node:path';\n\nexport const SKILL_NAME = 'vitops-design-system';\n\n/** Marker the 0.5.0-era generated skill carried; lets us migrate that layout. */\nexport const GENERATED_MARKER = '<!-- GENERATED by @getvitops/cli';\n\n/**\n * `vitops docs` topics → docs-bundle paths (as returned by `generateDocs`).\n * Order is the `--all` concatenation order.\n */\nexport const TOPICS: Record<string, { path: string; summary: string }> = {\n classes: {\n path: 'css/classes.md',\n summary: 'CSS framework class vocabulary (colour, type, space, layout, animation, patterns)',\n },\n authoring: {\n path: 'authoring.md',\n summary: 'every design-system.json field, rendered from the JSON Schema',\n },\n formats: {\n path: 'formats.md',\n summary: \"tailwind vs css vs bricks: what's emitted vs what the platform provides\",\n },\n color: {\n path: 'concepts/color.md',\n summary: 'seeded OKLCH scales, functional tokens, automatic dark mode',\n },\n scales: {\n path: 'concepts/scales.md',\n summary: 'fluid modular type & space scales',\n },\n patterns: {\n path: 'concepts/patterns.md',\n summary: 'pattern CSS: token cascade, override hooks, states, role variants',\n },\n elements: {\n path: 'bricks/elements.md',\n summary: 'Bricks Builder custom elements, controls, defaults',\n },\n};\n\n/**\n * Find the installed package's skill directory by walking up from `cwd` looking\n * for `node_modules/@getvitops/cli/skill/SKILL.md`. Returns the LOGICAL path\n * (through the `node_modules/@getvitops/cli` link, unresolved) or null.\n *\n * Deliberately not derived from import.meta.url: under pnpm the running module's\n * URL is the realpath in `node_modules/.pnpm/@getvitops+cli@<version>/…`, which\n * changes on every version bump — a symlink to it would dangle after upgrades.\n * The logical path is recreated by the package manager on every install, so a\n * link through it always resolves to the currently installed version.\n */\nexport function findSkillTarget(cwd: string): string | null {\n let dir = resolve(cwd);\n const { root } = parse(dir);\n for (;;) {\n const skillDir = join(dir, 'node_modules', '@getvitops', 'cli', 'skill');\n if (existsSync(join(skillDir, 'SKILL.md'))) return skillDir;\n if (dir === root) return null;\n dir = dirname(dir);\n }\n}\n\n/**\n * Idempotently symlink `linkPath` → `targetDir` (relative target). Replaces a\n * stale symlink; migrates a real directory left by the old emitting `vitops\n * agents` (identified by GENERATED_MARKER in its SKILL.md); refuses to clobber\n * anything else. Returns a warning string, or null on success / no-op.\n */\nexport function linkSkill(linkPath: string, targetDir: string): string | null {\n const target = relative(dirname(linkPath), resolve(targetDir));\n try {\n const st = lstatSync(linkPath, { throwIfNoEntry: false });\n if (st && !st.isSymbolicLink()) {\n const oldSkill = join(linkPath, 'SKILL.md');\n const isGenerated =\n st.isDirectory() &&\n existsSync(oldSkill) &&\n readFileSync(oldSkill, 'utf8').includes(GENERATED_MARKER);\n if (!isGenerated)\n return `left ${linkPath} alone (exists and is not a symlink) — link it to ${targetDir} yourself if wanted`;\n rmSync(linkPath, { recursive: true }); // old generated skill layout → replace\n } else if (st) {\n if (readlinkSync(linkPath) === target) return null;\n rmSync(linkPath);\n } else {\n mkdirSync(dirname(linkPath), { recursive: true });\n }\n symlinkSync(target, linkPath);\n return null;\n } catch (err) {\n return `could not symlink ${linkPath}: ${(err as Error).message}`;\n }\n}\n","#!/usr/bin/env node\n/**\n * `vitops` — CLI for the Vitops design-system generator.\n *\n * vitops generate --input <json> --format <bricks|css|tailwind[,…]> --out <dir>\n * vitops init [--out design-system.json] [--force]\n * vitops validate <design-system.json>\n * vitops favicon --input <svg|png> --out <dir> [--low-res <svg|png>]\n * vitops agents [--input <json>] [--out AGENTS.md] [--docs-dir <dir>]\n * vitops docs [topic] [--input <json>] [--all]\n *\n * Thin wrapper over @getvitops/generator (generation) and @getvitops/utils (favicons).\n * Every client brings their own consumer-editable design-system.json.\n */\nimport { parseArgs } from 'node:util';\nimport { writeFileSync, existsSync, readFileSync, mkdirSync } from 'node:fs';\nimport { resolve, join, dirname } from 'node:path';\nimport { createRequire } from 'node:module';\nimport {\n generate,\n validate,\n defaultConfig,\n generateDocs,\n SCHEMA_URL,\n type Format,\n type DesignSystem,\n} from '@getvitops/generator';\nimport { generateFavicons } from '@getvitops/utils';\nimport { findSkillTarget, linkSkill, SKILL_NAME, TOPICS } from './agents.ts';\n\nconst FORMATS = new Set<Format>(['bricks', 'css', 'tailwind']);\n\nconst HELP = `vitops — generate design-system outputs from a design-system.json\n\nUsage:\n vitops generate [options] Generate platform output from a config\n vitops init [options] Scaffold a starter design-system.json\n vitops validate <file> Validate a config against the schema\n vitops favicon [options] Generate a favicon set from a source image\n vitops agents [options] Link the design-system agent skill + AGENTS.md pointer\n vitops docs [topic] Print live design-system reference docs to stdout\n\nGenerate options:\n -i, --input <path> Config file (default: ./design-system.json)\n -f, --format <list> bricks | css | tailwind (comma-separated; default: bricks)\n -o, --out <dir> Output directory (default: ./dist)\n\nInit options:\n -o, --out <path> Where to write (default: ./design-system.json)\n --force Overwrite an existing file\n\nFavicon options:\n -i, --input <path> Source SVG or PNG (required)\n -o, --out <dir> Output directory (default: ./public)\n --low-res <path> Optional simplified source for the 16px icon\n\nAgents options:\n -i, --input <path> Config file (default: ./design-system.json; validated if present)\n -o, --out <path> Doc file to update, idempotently (default: ./AGENTS.md)\n --docs-dir <dir> Legacy layout: write the docs bundle as files to this dir\n instead of linking the packaged skill\n\nDocs options:\n <topic> classes | authoring | formats | color | scales | patterns | elements\n (no topic: list topics with summaries)\n -i, --input <path> Config file (default: ./design-system.json)\n --all Print every topic, concatenated\n\nCommon:\n -h, --help Show this help\n`;\n\nfunction fail(msg: string): never {\n console.error(`✖ ${msg}`);\n process.exit(1);\n}\n\nasync function cmdGenerate(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n format: { type: 'string', short: 'f', default: 'bricks' },\n out: { type: 'string', short: 'o', default: 'dist' },\n },\n allowPositionals: false,\n });\n const input = resolve(values.input as string);\n if (!existsSync(input)) fail(`config not found: ${input}`);\n const formats = (values.format as string)\n .split(',')\n .map((f) => f.trim())\n .filter(Boolean);\n for (const f of formats)\n if (!FORMATS.has(f as Format))\n fail(`unknown format \"${f}\" (expected: bricks | css | tailwind)`);\n\n for (const format of formats as Format[]) {\n try {\n const res = await generate({ input, format, outDir: values.out as string });\n console.log(`✓ ${format} → ${res.outDir} (${res.written.length} paths)`);\n } catch (err) {\n fail((err as Error).message);\n }\n }\n}\n\nfunction cmdInit(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n out: { type: 'string', short: 'o', default: 'design-system.json' },\n force: { type: 'boolean', default: false },\n },\n allowPositionals: false,\n });\n const out = resolve(values.out as string);\n if (existsSync(out) && !values.force) fail(`${out} already exists (use --force to overwrite)`);\n writeFileSync(out, JSON.stringify(defaultConfig(), null, 2) + '\\n');\n console.log(`✓ wrote ${out}`);\n console.log(\n ` Edit it, then: vitops generate --input ${values.out} --format tailwind --out src/styles`,\n );\n console.log(` Editors read the \"$schema\" (${SCHEMA_URL}) for autocomplete + validation.`);\n}\n\nfunction cmdValidate(argv: string[]) {\n const { values, positionals } = parseArgs({\n args: argv,\n options: { input: { type: 'string', short: 'i' } },\n allowPositionals: true,\n });\n const path = resolve((positionals[0] ?? values.input ?? 'design-system.json') as string);\n if (!existsSync(path)) fail(`config not found: ${path}`);\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n const result = validate(raw);\n if (result.ok) {\n console.log(`✓ ${path} is valid`);\n return;\n }\n console.error(`✖ ${path} is invalid:`);\n for (const e of result.errors) console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n process.exit(1);\n}\n\nasync function cmdFavicon(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i' },\n out: { type: 'string', short: 'o', default: 'public' },\n 'low-res': { type: 'string' },\n },\n allowPositionals: false,\n });\n if (!values.input) fail('favicon: --input <svg|png> is required');\n const source = resolve(values.input as string);\n if (!existsSync(source)) fail(`source not found: ${source}`);\n try {\n const written = await generateFavicons({\n source,\n outputDir: values.out as string,\n ...(values['low-res'] ? { lowResSource: resolve(values['low-res'] as string) } : {}),\n });\n console.log(`✓ favicons → ${values.out} (${written.length} files)`);\n } catch (err) {\n fail((err as Error).message);\n }\n}\n\n// Read + parse + validate a design-system.json, or exit with a message.\nfunction loadConfig(path: string): DesignSystem {\n if (!existsSync(path)) fail(`config not found: ${path}`);\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n const result = validate(raw);\n if (!result.ok) {\n console.error(`✖ ${path} is invalid:`);\n for (const e of result.errors)\n console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n process.exit(1);\n }\n return result.data;\n}\n\n// This CLI's own version (dist/cli.mjs → package-root package.json).\nfunction cliVersion(): string {\n try {\n return (\n JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version ??\n '0.0.0'\n );\n } catch {\n return '0.0.0';\n }\n}\n\n// The framework asset root shipped inside @getvitops/generator (holds the Bricks\n// PHP that generateDocs reads for the element reference).\nfunction generatorAssetsDir(): string {\n const pkg = createRequire(import.meta.url).resolve('@getvitops/generator/package.json');\n return join(dirname(pkg), 'assets');\n}\n\nconst AGENTS_START = '<!-- vitops:start -->';\nconst AGENTS_END = '<!-- vitops:end -->';\n\n/** Print a live reference doc (rendered from the project's config) to stdout. */\nfunction cmdDocs(argv: string[]) {\n const { values, positionals } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n all: { type: 'boolean', default: false },\n },\n allowPositionals: true,\n });\n const topic = positionals[0];\n const topicList = () =>\n Object.entries(TOPICS)\n .map(([name, t]) => ` ${name.padEnd(10)} ${t.summary}`)\n .join('\\n');\n if (!topic && !values.all) {\n console.log(\n `vitops docs <topic> — print a live design-system reference to stdout\\n\\nTopics:\\n${topicList()}\\n\\nAlso: vitops docs --all (every topic, concatenated)`,\n );\n return;\n }\n if (topic && !TOPICS[topic]) fail(`unknown topic \"${topic}\". Valid topics:\\n${topicList()}`);\n const ds = loadConfig(resolve(values.input as string));\n const docs = generateDocs(ds, generatorAssetsDir());\n const paths = values.all\n ? Object.values(TOPICS).map((t) => t.path)\n : [TOPICS[topic as string]!.path];\n process.stdout.write(paths.map((p) => docs[p]).join('\\n'));\n}\n\nfunction cmdAgents(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n out: { type: 'string', short: 'o', default: 'AGENTS.md' },\n 'docs-dir': { type: 'string' },\n },\n allowPositionals: false,\n });\n const inputRel = values.input as string;\n // The command is pure wiring: validate the config when present (fail on an\n // invalid one), but don't require it — docs are rendered live by `vitops docs`.\n let ds: DesignSystem | null = null;\n if (existsSync(resolve(inputRel))) ds = loadConfig(resolve(inputRel));\n else console.warn(` ⚠ ${inputRel} not found — run \\`vitops init\\` to scaffold one`);\n\n // Explicit --docs-dir = legacy layout: write the docs bundle as files, no skill.\n const legacyDocsDir = values['docs-dir'] as string | undefined;\n let summary: string;\n let pointer: string[];\n\n if (legacyDocsDir) {\n if (!ds) fail(`--docs-dir needs a config to render docs from (${inputRel} not found)`);\n let docs: Record<string, string> = {};\n try {\n docs = generateDocs(ds, generatorAssetsDir());\n } catch (err) {\n fail(`could not generate docs: ${(err as Error).message}`);\n }\n for (const [rel, content] of Object.entries(docs)) {\n const p = resolve(legacyDocsDir, rel);\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, content);\n }\n summary = `wrote ${Object.keys(docs).length} docs to ${legacyDocsDir}/`;\n pointer = [\n 'Prefer the framework’s utility + component classes over hand-written CSS.',\n `Reference docs (regenerate with \\`vitops agents --docs-dir ${legacyDocsDir}\\`): \\`${legacyDocsDir}/css/classes.md\\`,`,\n `\\`${legacyDocsDir}/authoring.md\\`, \\`${legacyDocsDir}/formats.md\\`, \\`${legacyDocsDir}/bricks/elements.md\\`.`,\n ];\n } else {\n // Link the packaged skill (ships inside @getvitops/cli) into the agent\n // discovery locations; nothing is generated into the repo.\n const target = findSkillTarget(process.cwd());\n if (target) {\n for (const dir of ['.agents', '.claude']) {\n const warn = linkSkill(resolve(dir, 'skills', SKILL_NAME), target);\n if (warn) console.warn(` ⚠ ${warn}`);\n }\n summary = `linked {.agents,.claude}/skills/${SKILL_NAME} → ${target}`;\n } else {\n console.warn(\n ` ⚠ could not find node_modules/@getvitops/cli/skill — install @getvitops/cli as a devDependency, then re-run \\`vitops agents\\``,\n );\n summary = 'no skill linked';\n }\n pointer = [\n 'Prefer the framework’s utility + component classes over hand-written CSS.',\n `Full design-system context lives in the \\`${SKILL_NAME}\\` skill (linked from`,\n '`.agents/skills/` and `.claude/skills/` into the installed `@getvitops/cli`;',\n 're-link with `vitops agents` if the links are deleted — they survive version',\n 'bumps). Reference docs print live, from this project’s config, via:',\n '',\n ...Object.entries(TOPICS).map(([name, t]) => `- \\`vitops docs ${name}\\` — ${t.summary}`),\n ];\n }\n\n const block = [\n AGENTS_START,\n '## Vitops design system',\n '',\n `Styled with the Vitops design system (\\`@getvitops/*\\`); tokens live in \\`${inputRel}\\`.`,\n 'Generate output with the CLI:',\n '',\n '- `vitops generate --format tailwind --out src/styles` — Tailwind v4 / Astro',\n '- `vitops generate --format css --out dist` — standalone CSS',\n '- `vitops generate --format bricks --out <theme>/dist` — WordPress / Bricks',\n '- `vitops init` · `vitops validate` · `vitops favicon`',\n '',\n ...pointer,\n '',\n `<!-- regenerate: vitops agents · @getvitops/cli@${cliVersion()} -->`,\n AGENTS_END,\n ].join('\\n');\n\n const outPath = resolve(values.out as string);\n let next: string;\n let action: string;\n if (existsSync(outPath)) {\n const cur = readFileSync(outPath, 'utf8');\n const s = cur.indexOf(AGENTS_START);\n const e = cur.indexOf(AGENTS_END);\n if (s !== -1 && e !== -1 && e > s) {\n next = cur.slice(0, s) + block + cur.slice(e + AGENTS_END.length);\n action = 'updated block in';\n } else {\n next = cur.replace(/\\s*$/, '') + '\\n\\n' + block + '\\n';\n action = 'appended block to';\n }\n } else {\n mkdirSync(dirname(outPath), { recursive: true });\n next = block + '\\n';\n action = 'created';\n }\n writeFileSync(outPath, next);\n console.log(`✓ ${action} ${values.out} + ${summary}`);\n}\n\nasync function main() {\n const [command, ...rest] = process.argv.slice(2);\n if (!command || command === '-h' || command === '--help' || command === 'help') {\n console.log(HELP);\n return;\n }\n switch (command) {\n case 'generate':\n return cmdGenerate(rest);\n case 'init':\n return cmdInit(rest);\n case 'validate':\n return cmdValidate(rest);\n case 'favicon':\n return cmdFavicon(rest);\n case 'agents':\n return cmdAgents(rest);\n case 'docs':\n return cmdDocs(rest);\n default:\n fail(\n `unknown command \"${command}\" (expected: generate | init | validate | favicon | agents | docs). Try: vitops --help`,\n );\n }\n}\n\nmain().catch((err) => fail((err as Error).message));\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAa,aAAa;;AAG1B,MAAa,mBAAmB;;;;;AAMhC,MAAa,SAA4D;CACvE,SAAS;EACP,MAAM;EACN,SAAS;CACX;CACA,WAAW;EACT,MAAM;EACN,SAAS;CACX;CACA,SAAS;EACP,MAAM;EACN,SAAS;CACX;CACA,OAAO;EACL,MAAM;EACN,SAAS;CACX;CACA,QAAQ;EACN,MAAM;EACN,SAAS;CACX;CACA,UAAU;EACR,MAAM;EACN,SAAS;CACX;CACA,UAAU;EACR,MAAM;EACN,SAAS;CACX;AACF;;;;;;;;;;;;AAaA,SAAgB,gBAAgB,KAA4B;CAC1D,IAAI,MAAM,QAAQ,GAAG;CACrB,MAAM,EAAE,SAAS,MAAM,GAAG;CAC1B,SAAS;EACP,MAAM,WAAW,KAAK,KAAK,gBAAgB,cAAc,OAAO,OAAO;EACvE,IAAI,WAAW,KAAK,UAAU,UAAU,CAAC,GAAG,OAAO;EACnD,IAAI,QAAQ,MAAM,OAAO;EACzB,MAAM,QAAQ,GAAG;CACnB;AACF;;;;;;;AAQA,SAAgB,UAAU,UAAkB,WAAkC;CAC5E,MAAM,SAAS,SAAS,QAAQ,QAAQ,GAAG,QAAQ,SAAS,CAAC;CAC7D,IAAI;EACF,MAAM,KAAK,UAAU,UAAU,EAAE,gBAAgB,MAAM,CAAC;EACxD,IAAI,MAAM,CAAC,GAAG,eAAe,GAAG;GAC9B,MAAM,WAAW,KAAK,UAAU,UAAU;GAK1C,IAAI,EAHF,GAAG,YAAY,KACf,WAAW,QAAQ,KACnB,aAAa,UAAU,MAAM,CAAC,CAAC,2CAAyB,IAExD,OAAO,QAAQ,SAAS,oDAAoD,UAAU;GACxF,OAAO,UAAU,EAAE,WAAW,KAAK,CAAC;EACtC,OAAO,IAAI,IAAI;GACb,IAAI,aAAa,QAAQ,MAAM,QAAQ,OAAO;GAC9C,OAAO,QAAQ;EACjB,OACE,UAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EAElD,YAAY,QAAQ,QAAQ;EAC5B,OAAO;CACT,SAAS,KAAK;EACZ,OAAO,qBAAqB,SAAS,IAAK,IAAc;CAC1D;AACF;;;;;;;;;;;;;;;;;AC7EA,MAAM,UAAU,IAAI,IAAY;CAAC;CAAU;CAAO;AAAU,CAAC;AAE7D,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCb,SAAS,KAAK,KAAoB;CAChC,QAAQ,MAAM,KAAK,KAAK;CACxB,QAAQ,KAAK,CAAC;AAChB;AAEA,eAAe,YAAY,MAAgB;CACzC,MAAM,EAAE,WAAW,UAAU;EAC3B,MAAM;EACN,SAAS;GACP,OAAO;IAAE,MAAM;IAAU,OAAO;IAAK,SAAS;GAAqB;GACnE,QAAQ;IAAE,MAAM;IAAU,OAAO;IAAK,SAAS;GAAS;GACxD,KAAK;IAAE,MAAM;IAAU,OAAO;IAAK,SAAS;GAAO;EACrD;EACA,kBAAkB;CACpB,CAAC;CACD,MAAM,QAAQ,QAAQ,OAAO,KAAe;CAC5C,IAAI,CAAC,WAAW,KAAK,GAAG,KAAK,qBAAqB,OAAO;CACzD,MAAM,UAAW,OAAO,OACrB,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,OAAO,OAAO;CACjB,KAAK,MAAM,KAAK,SACd,IAAI,CAAC,QAAQ,IAAI,CAAW,GAC1B,KAAK,mBAAmB,EAAE,sCAAsC;CAEpE,KAAK,MAAM,UAAU,SACnB,IAAI;EACF,MAAM,MAAM,MAAM,SAAS;GAAE;GAAO;GAAQ,QAAQ,OAAO;EAAc,CAAC;EAC1E,QAAQ,IAAI,KAAK,OAAO,KAAK,IAAI,OAAO,IAAI,IAAI,QAAQ,OAAO,QAAQ;CACzE,SAAS,KAAK;EACZ,KAAM,IAAc,OAAO;CAC7B;AAEJ;AAEA,SAAS,QAAQ,MAAgB;CAC/B,MAAM,EAAE,WAAW,UAAU;EAC3B,MAAM;EACN,SAAS;GACP,KAAK;IAAE,MAAM;IAAU,OAAO;IAAK,SAAS;GAAqB;GACjE,OAAO;IAAE,MAAM;IAAW,SAAS;GAAM;EAC3C;EACA,kBAAkB;CACpB,CAAC;CACD,MAAM,MAAM,QAAQ,OAAO,GAAa;CACxC,IAAI,WAAW,GAAG,KAAK,CAAC,OAAO,OAAO,KAAK,GAAG,IAAI,2CAA2C;CAC7F,cAAc,KAAK,KAAK,UAAU,cAAc,GAAG,MAAM,CAAC,IAAI,IAAI;CAClE,QAAQ,IAAI,WAAW,KAAK;CAC5B,QAAQ,IACN,4CAA4C,OAAO,IAAI,oCACzD;CACA,QAAQ,IAAI,iCAAiC,WAAW,iCAAiC;AAC3F;AAEA,SAAS,YAAY,MAAgB;CACnC,MAAM,EAAE,QAAQ,gBAAgB,UAAU;EACxC,MAAM;EACN,SAAS,EAAE,OAAO;GAAE,MAAM;GAAU,OAAO;EAAI,EAAE;EACjD,kBAAkB;CACpB,CAAC;CACD,MAAM,OAAO,QAAS,YAAY,MAAM,OAAO,SAAS,oBAA+B;CACvF,IAAI,CAAC,WAAW,IAAI,GAAG,KAAK,qBAAqB,MAAM;CACvD,IAAI;CACJ,IAAI;EACF,MAAM,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;CAC7C,SAAS,KAAK;EACZ,KAAK,yBAA0B,IAAc,SAAS;CACxD;CACA,MAAM,SAAS,SAAS,GAAG;CAC3B,IAAI,OAAO,IAAI;EACb,QAAQ,IAAI,KAAK,KAAK,UAAU;EAChC;CACF;CACA,QAAQ,MAAM,KAAK,KAAK,aAAa;CACrC,KAAK,MAAM,KAAK,OAAO,QAAQ,QAAQ,MAAM,OAAO,EAAE,KAAK,KAAK,GAAG,KAAK,SAAS,IAAI,EAAE,SAAS;CAChG,QAAQ,KAAK,CAAC;AAChB;AAEA,eAAe,WAAW,MAAgB;CACxC,MAAM,EAAE,WAAW,UAAU;EAC3B,MAAM;EACN,SAAS;GACP,OAAO;IAAE,MAAM;IAAU,OAAO;GAAI;GACpC,KAAK;IAAE,MAAM;IAAU,OAAO;IAAK,SAAS;GAAS;GACrD,WAAW,EAAE,MAAM,SAAS;EAC9B;EACA,kBAAkB;CACpB,CAAC;CACD,IAAI,CAAC,OAAO,OAAO,KAAK,wCAAwC;CAChE,MAAM,SAAS,QAAQ,OAAO,KAAe;CAC7C,IAAI,CAAC,WAAW,MAAM,GAAG,KAAK,qBAAqB,QAAQ;CAC3D,IAAI;EACF,MAAM,UAAU,MAAM,iBAAiB;GACrC;GACA,WAAW,OAAO;GAClB,GAAI,OAAO,aAAa,EAAE,cAAc,QAAQ,OAAO,UAAoB,EAAE,IAAI,CAAC;EACpF,CAAC;EACD,QAAQ,IAAI,gBAAgB,OAAO,IAAI,IAAI,QAAQ,OAAO,QAAQ;CACpE,SAAS,KAAK;EACZ,KAAM,IAAc,OAAO;CAC7B;AACF;AAGA,SAAS,WAAW,MAA4B;CAC9C,IAAI,CAAC,WAAW,IAAI,GAAG,KAAK,qBAAqB,MAAM;CACvD,IAAI;CACJ,IAAI;EACF,MAAM,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;CAC7C,SAAS,KAAK;EACZ,KAAK,yBAA0B,IAAc,SAAS;CACxD;CACA,MAAM,SAAS,SAAS,GAAG;CAC3B,IAAI,CAAC,OAAO,IAAI;EACd,QAAQ,MAAM,KAAK,KAAK,aAAa;EACrC,KAAK,MAAM,KAAK,OAAO,QACrB,QAAQ,MAAM,OAAO,EAAE,KAAK,KAAK,GAAG,KAAK,SAAS,IAAI,EAAE,SAAS;EACnE,QAAQ,KAAK,CAAC;CAChB;CACA,OAAO,OAAO;AAChB;AAGA,SAAS,aAAqB;CAC5B,IAAI;EACF,OACE,KAAK,MAAM,aAAa,IAAI,IAAI,mBAAmB,OAAO,KAAK,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,WAC9E;CAEJ,QAAQ;EACN,OAAO;CACT;AACF;AAIA,SAAS,qBAA6B;CAEpC,OAAO,KAAK,QADA,cAAc,OAAO,KAAK,GAAG,CAAC,CAAC,QAAQ,mCAC7B,CAAC,GAAG,QAAQ;AACpC;AAEA,MAAM,eAAe;AACrB,MAAM,aAAa;;AAGnB,SAAS,QAAQ,MAAgB;CAC/B,MAAM,EAAE,QAAQ,gBAAgB,UAAU;EACxC,MAAM;EACN,SAAS;GACP,OAAO;IAAE,MAAM;IAAU,OAAO;IAAK,SAAS;GAAqB;GACnE,KAAK;IAAE,MAAM;IAAW,SAAS;GAAM;EACzC;EACA,kBAAkB;CACpB,CAAC;CACD,MAAM,QAAQ,YAAY;CAC1B,MAAM,kBACJ,OAAO,QAAQ,MAAM,CAAC,CACnB,KAAK,CAAC,MAAM,OAAO,KAAK,KAAK,OAAO,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,CACvD,KAAK,IAAI;CACd,IAAI,CAAC,SAAS,CAAC,OAAO,KAAK;EACzB,QAAQ,IACN,oFAAoF,UAAU,EAAE,wDAClG;EACA;CACF;CACA,IAAI,SAAS,CAAC,OAAO,QAAQ,KAAK,kBAAkB,MAAM,oBAAoB,UAAU,GAAG;CAE3F,MAAM,OAAO,aADF,WAAW,QAAQ,OAAO,KAAe,CACzB,GAAG,mBAAmB,CAAC;CAClD,MAAM,QAAQ,OAAO,MACjB,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI,IACvC,CAAC,OAAO,MAAgB,CAAE,IAAI;CAClC,QAAQ,OAAO,MAAM,MAAM,KAAK,MAAM,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;AAC3D;AAEA,SAAS,UAAU,MAAgB;CACjC,MAAM,EAAE,WAAW,UAAU;EAC3B,MAAM;EACN,SAAS;GACP,OAAO;IAAE,MAAM;IAAU,OAAO;IAAK,SAAS;GAAqB;GACnE,KAAK;IAAE,MAAM;IAAU,OAAO;IAAK,SAAS;GAAY;GACxD,YAAY,EAAE,MAAM,SAAS;EAC/B;EACA,kBAAkB;CACpB,CAAC;CACD,MAAM,WAAW,OAAO;CAGxB,IAAI,KAA0B;CAC9B,IAAI,WAAW,QAAQ,QAAQ,CAAC,GAAG,KAAK,WAAW,QAAQ,QAAQ,CAAC;MAC/D,QAAQ,KAAK,OAAO,SAAS,iDAAiD;CAGnF,MAAM,gBAAgB,OAAO;CAC7B,IAAI;CACJ,IAAI;CAEJ,IAAI,eAAe;EACjB,IAAI,CAAC,IAAI,KAAK,kDAAkD,SAAS,YAAY;EACrF,IAAI,OAA+B,CAAC;EACpC,IAAI;GACF,OAAO,aAAa,IAAI,mBAAmB,CAAC;EAC9C,SAAS,KAAK;GACZ,KAAK,4BAA6B,IAAc,SAAS;EAC3D;EACA,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,IAAI,GAAG;GACjD,MAAM,IAAI,QAAQ,eAAe,GAAG;GACpC,UAAU,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;GACzC,cAAc,GAAG,OAAO;EAC1B;EACA,UAAU,SAAS,OAAO,KAAK,IAAI,CAAC,CAAC,OAAO,WAAW,cAAc;EACrE,UAAU;GACR;GACA,8DAA8D,cAAc,SAAS,cAAc;GACnG,KAAK,cAAc,qBAAqB,cAAc,mBAAmB,cAAc;EACzF;CACF,OAAO;EAGL,MAAM,SAAS,gBAAgB,QAAQ,IAAI,CAAC;EAC5C,IAAI,QAAQ;GACV,KAAK,MAAM,OAAO,CAAC,WAAW,SAAS,GAAG;IACxC,MAAM,OAAO,UAAU,QAAQ,KAAK,UAAU,UAAU,GAAG,MAAM;IACjE,IAAI,MAAM,QAAQ,KAAK,OAAO,MAAM;GACtC;GACA,UAAU,mCAAmC,WAAW,KAAK;EAC/D,OAAO;GACL,QAAQ,KACN,iIACF;GACA,UAAU;EACZ;EACA,UAAU;GACR;GACA,6CAA6C,WAAW;GACxD;GACA;GACA;GACA;GACA,GAAG,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,OAAO,mBAAmB,KAAK,OAAO,EAAE,SAAS;EACzF;CACF;CAEA,MAAM,QAAQ;EACZ;EACA;EACA;EACA,6EAA6E,SAAS;EACtF;EACA;EACA;EACA;EACA;EACA;EACA;EACA,GAAG;EACH;EACA,mDAAmD,WAAW,EAAE;EAChE;CACF,CAAC,CAAC,KAAK,IAAI;CAEX,MAAM,UAAU,QAAQ,OAAO,GAAa;CAC5C,IAAI;CACJ,IAAI;CACJ,IAAI,WAAW,OAAO,GAAG;EACvB,MAAM,MAAM,aAAa,SAAS,MAAM;EACxC,MAAM,IAAI,IAAI,QAAQ,YAAY;EAClC,MAAM,IAAI,IAAI,QAAQ,UAAU;EAChC,IAAI,MAAM,MAAM,MAAM,MAAM,IAAI,GAAG;GACjC,OAAO,IAAI,MAAM,GAAG,CAAC,IAAI,QAAQ,IAAI,MAAM,IAAI,EAAiB;GAChE,SAAS;EACX,OAAO;GACL,OAAO,IAAI,QAAQ,QAAQ,EAAE,IAAI,SAAS,QAAQ;GAClD,SAAS;EACX;CACF,OAAO;EACL,UAAU,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;EAC/C,OAAO,QAAQ;EACf,SAAS;CACX;CACA,cAAc,SAAS,IAAI;CAC3B,QAAQ,IAAI,KAAK,OAAO,GAAG,OAAO,IAAI,KAAK,SAAS;AACtD;AAEA,eAAe,OAAO;CACpB,MAAM,CAAC,SAAS,GAAG,QAAQ,QAAQ,KAAK,MAAM,CAAC;CAC/C,IAAI,CAAC,WAAW,YAAY,QAAQ,YAAY,YAAY,YAAY,QAAQ;EAC9E,QAAQ,IAAI,IAAI;EAChB;CACF;CACA,QAAQ,SAAR;EACE,KAAK,YACH,OAAO,YAAY,IAAI;EACzB,KAAK,QACH,OAAO,QAAQ,IAAI;EACrB,KAAK,YACH,OAAO,YAAY,IAAI;EACzB,KAAK,WACH,OAAO,WAAW,IAAI;EACxB,KAAK,UACH,OAAO,UAAU,IAAI;EACvB,KAAK,QACH,OAAO,QAAQ,IAAI;EACrB,SACE,KACE,oBAAoB,QAAQ,uFAC9B;CACJ;AACF;AAEA,KAAK,CAAC,CAAC,OAAO,QAAQ,KAAM,IAAc,OAAO,CAAC"}
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@getvitops/cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "CLI for the Vitops design-system generator: generate, init, and validate a design-system.json.",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "vitops": "./dist/cli.mjs"
8
8
  },
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "skill",
12
+ "CHANGELOG.md"
11
13
  ],
12
14
  "type": "module",
13
15
  "exports": {
@@ -20,7 +22,10 @@
20
22
  "access": "public"
21
23
  },
22
24
  "dependencies": {
23
- "@getvitops/utils": "0.5.0",
24
- "@getvitops/generator": "0.5.0"
25
+ "@getvitops/generator": "0.7.0",
26
+ "@getvitops/utils": "0.7.0"
27
+ },
28
+ "devDependencies": {
29
+ "vitest": "^4.1.9"
25
30
  }
26
31
  }
package/skill/SKILL.md ADDED
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: vitops-design-system
3
+ description: >-
4
+ Work with this project's Vitops design system. Use when styling or authoring
5
+ pages or components (choosing utility/pattern classes, colours, spacing,
6
+ typography), when editing design-system.json (tokens, palette, patterns,
7
+ scales, animations), when generating output (vitops generate --format
8
+ tailwind|css|bricks), or when asking what the design system provides on a
9
+ given platform (Tailwind vs Bricks vs standalone CSS).
10
+ ---
11
+
12
+ # Vitops design system
13
+
14
+ This project is styled with the Vitops design system (`@getvitops/*`): a variable-driven
15
+ CSS framework generated from `design-system.json` (the single source of truth — semantic
16
+ colour roles, fluid type/space scales, component patterns, animation effects).
17
+
18
+ **Core rule:** prefer the framework's utility + component classes over hand-written CSS or
19
+ ad-hoc values — they encode the tokens, respond to dark mode, and update with the system.
20
+
21
+ ## Getting the current reference docs
22
+
23
+ Run **`vitops docs <topic>`** — it prints the reference as markdown to stdout, generated
24
+ live from this project's `design-system.json` and the installed package version, so it is
25
+ never stale and always names the project's actual tokens. (If `vitops` isn't on PATH, use
26
+ `pnpm exec vitops docs <topic>` or `npx vitops docs <topic>`.)
27
+
28
+ | Run | When you need |
29
+ | ----------------------- | ---------------------------------------------------------------------- |
30
+ | `vitops docs classes` | which class to apply — the full class vocabulary as naming rules |
31
+ | `vitops docs authoring` | what a `design-system.json` field means / what's valid |
32
+ | `vitops docs formats` | tailwind vs css vs bricks output — incl. which utilities Tailwind owns |
33
+ | `vitops docs color` | how the colour system works (seeded OKLCH scales, roles, dark mode) |
34
+ | `vitops docs scales` | how the fluid type/space scales work |
35
+ | `vitops docs patterns` | how pattern CSS is assembled (token cascade, override hooks, states) |
36
+ | `vitops docs elements` | the custom Bricks Builder elements and their controls |
37
+
38
+ `vitops docs` (no topic) lists the topics; `vitops docs --all` prints everything.
39
+
40
+ ## Other commands
41
+
42
+ - `vitops generate --format <tailwind|css|bricks>` — generate the platform output.
43
+ - `vitops init` / `vitops validate` — scaffold / check a `design-system.json`.
44
+ - `vitops agents` — (re-)link this skill into `.agents/skills/` + `.claude/skills/` and
45
+ refresh the AGENTS.md pointer block. The links point into the installed package and
46
+ survive version bumps; re-run only if they were deleted.