@motion-proto/live-tokens 0.75.0 → 0.76.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 CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.76.0 — The skills directory matches the release
4
+
5
+ ### Changed (breaking)
6
+
7
+ - **`setup-claude --force` now makes `.claude/skills/` match the release.** It
8
+ replaced each bundled skill by merging over it, which left two kinds of stale
9
+ text behind: a skill a release renamed kept its old directory, where it
10
+ shadowed the replacement, and a reference file a release dropped survived
11
+ inside a skill that was otherwise current. Both are invisible until a model
12
+ reads them. A forced run now replaces each bundled directory outright and
13
+ removes any `live-tokens-` skill the release no longer ships, naming each one
14
+ it removed. A skill under any other name is the project's own and is never
15
+ touched, and an unforced run still deletes nothing. 0.75.0's rename of
16
+ `live-tokens-build-page` to `live-tokens-create-page` is the case that
17
+ prompted this; the changelog then told a reader to delete the directory by
18
+ hand.
19
+
20
+ Before a forced run, copy any bundled skill you edited in place to a name of
21
+ your own. A `live-tokens-` directory this release does not ship is deleted
22
+ without a prompt, and one it does ship is replaced rather than merged.
23
+
24
+ `setup-claude` moves out of `bin/cli.mjs` into `bin/setup-claude.mjs` as
25
+ `runSetupClaude` and `formatSetupResult`, matching `create.mjs`, and gains
26
+ `bin/setup-claude.test.ts`.
27
+
28
+ ### Changed
29
+
30
+ - **The `create` template installs the skills from `postinstall` and gitignores
31
+ them.** A scaffolded project no longer commits nine skill directories the
32
+ package owns, so a version bump stops showing twenty changed files that
33
+ nobody wrote. The skills instead refresh on every `npm install`. The
34
+ postinstall ends in `|| exit 0`: `setup-claude` refuses to run on Windows,
35
+ and a skills copy must never fail an install. A project created before this
36
+ release adds the two lines itself; the README carries them.
37
+
3
38
  ## 0.75.0 — A component passes when the tests pass
4
39
 
5
40
  ### Added
package/README.md CHANGED
@@ -322,7 +322,7 @@ npx @motion-proto/live-tokens <command>
322
322
  | Command | What it does |
323
323
  |---|---|
324
324
  | `create <dir> [--force]` | Scaffold a new Svelte + Vite app wired up with live-tokens. |
325
- | `setup-claude [--force]` | Install the bundled Claude Code skills into `./.claude/skills/`. |
325
+ | `setup-claude [--force]` | Install the bundled Claude Code skills into `./.claude/skills/`; `--force` makes the directory match this release. |
326
326
  | `components [id] [--json]` | List every component the project has, shipped and its own, with the props each takes; with an id, its props, variants, tokens, and defaults. |
327
327
  | `tokens [--scale <name>] [--json]` | List every design token the project's `tokens.css` declares, by scale, with its value. |
328
328
  | `report [--json]` | The project as facts: pending migrations, tokens each component reads, which page renders which component, and both checkers' findings by rule. Always exits 0. |
@@ -346,10 +346,20 @@ The package bundles nine Claude Code skills. They encode the conventions this RE
346
346
  npx @motion-proto/live-tokens setup-claude
347
347
  ```
348
348
 
349
- This copies every bundled skill into `./.claude/skills/` in the current directory. Re-run it after upgrading the package to pick up new and changed skills, adding `--force` to overwrite. macOS and Linux only. The equivalent by hand:
349
+ This copies every bundled skill into `./.claude/skills/` in the current directory, leaving any that already exist. macOS and Linux only.
350
350
 
351
- ```bash
352
- mkdir -p .claude/skills && cp -R node_modules/@motion-proto/live-tokens/.claude/skills/. .claude/skills/
351
+ Re-run it with `--force` after upgrading. That makes the directory match the release: each bundled skill is replaced outright, so a reference file the release dropped goes with it, and a `live-tokens-` skill the release no longer ships is removed. A skill under any other name is the project's own and is never touched. Without `--force` nothing is deleted and an existing skill is left alone, so a renamed skill keeps shadowing its replacement until a forced run clears it.
352
+
353
+ A project created with `create` runs the forced form from `postinstall` and gitignores the copy, so the skills track the installed version with nothing to commit. To do the same in an existing project:
354
+
355
+ ```jsonc
356
+ // package.json
357
+ "postinstall": "live-tokens setup-claude --force || exit 0"
358
+ ```
359
+
360
+ ```
361
+ # .gitignore
362
+ .claude/skills/live-tokens-*/
353
363
  ```
354
364
 
355
365
  ### `live-tokens-pick-component`
package/bin/cli.mjs CHANGED
@@ -14,7 +14,7 @@
14
14
  // save-theme <name> Compose the live state into themes/<slug>.json and open it.
15
15
  // migrate [...] Reconcile tokens.css, the data tree, and route references.
16
16
 
17
- import { cpSync, existsSync, mkdirSync, readdirSync, statSync, writeSync } from 'node:fs';
17
+ import { writeSync } from 'node:fs';
18
18
  import { dirname, join, resolve } from 'node:path';
19
19
  import { fileURLToPath } from 'node:url';
20
20
  import process from 'node:process';
@@ -40,6 +40,7 @@ import {
40
40
  } from './migrate.mjs';
41
41
  import { runMigrateRoutes, formatRouteResult } from './migrate-routes.mjs';
42
42
  import { runCreate, formatCreateResult } from './create.mjs';
43
+ import { runSetupClaude, formatSetupResult } from './setup-claude.mjs';
43
44
  import { runSetColors, formatSetColorsResult } from './set-colors.mjs';
44
45
  import { runSetGeometry, formatSetGeometryResult } from './set-geometry.mjs';
45
46
  import { runSetType, formatSetTypeResult } from './set-type.mjs';
@@ -407,61 +408,14 @@ if (process.platform === 'win32') {
407
408
  fail('setup-claude is macOS/Linux only.');
408
409
  }
409
410
 
410
- const force = rest.includes('--force');
411
-
412
- const srcSkills = join(pkgRoot, '.claude', 'skills');
413
-
414
- if (!existsSync(srcSkills)) {
415
- fail(`No bundled skills found at ${srcSkills}. Is the package installed correctly?`);
416
- }
417
-
418
- const skills = readdirSync(srcSkills).filter((name) =>
419
- statSync(join(srcSkills, name)).isDirectory(),
420
- );
421
-
422
- if (skills.length === 0) {
423
- fail('No bundled skills to install.');
424
- }
425
-
426
- const destSkills = join(process.cwd(), '.claude', 'skills');
427
- mkdirSync(destSkills, { recursive: true });
428
-
429
- let installed = 0;
430
- let skipped = 0;
431
- for (const skill of skills) {
432
- const src = join(srcSkills, skill);
433
- const dest = join(destSkills, skill);
434
- if (existsSync(dest) && !force) {
435
- console.log(` skip ${skill} (already exists; pass --force to overwrite)`);
436
- skipped++;
437
- continue;
438
- }
439
- cpSync(src, dest, { recursive: true });
440
- console.log(` ok ${skill}`);
441
- installed++;
442
- }
443
-
444
- console.log(`\n${installed} installed, ${skipped} skipped, in ${destSkills}`);
445
-
446
- const SAMPLE_PROMPTS = {
447
- 'live-tokens-create-page': 'build a pricing page using live-tokens components',
448
- 'live-tokens-pick-component': "what's the difference between TabBar and SegmentedControl?",
449
- 'live-tokens-create-component': 'author a new Toggle component for my live-tokens project',
450
- 'live-tokens-create-theme': 'make me a bright and cheerful theme',
451
- 'live-tokens-set-colors': 'give me a cooler palette, same fonts',
452
- 'live-tokens-set-type': 'pair some fonts for this theme',
453
- 'live-tokens-set-geometry': 'make the buttons pill shaped',
454
- 'live-tokens-fix-findings': 'make check:design pass',
455
- 'live-tokens-check-compliance': 'check this project against the design system',
456
- };
457
-
458
- const installedSamples = skills
459
- .map((s) => SAMPLE_PROMPTS[s] && [s, SAMPLE_PROMPTS[s]])
460
- .filter(Boolean);
461
-
462
- if (installedSamples.length > 0) {
463
- console.log(`\nIn Claude Code, prompts like these auto-trigger the matching skill:`);
464
- for (const [skill, prompt] of installedSamples) {
465
- console.log(` "${prompt}"\n ${skill}`);
466
- }
411
+ try {
412
+ const result = runSetupClaude({
413
+ pkgRoot,
414
+ cwd: process.cwd(),
415
+ force: rest.includes('--force'),
416
+ });
417
+ console.log(formatSetupResult(result));
418
+ process.exit(0);
419
+ } catch (err) {
420
+ fail(err instanceof Error ? err.message : String(err));
467
421
  }
@@ -0,0 +1,110 @@
1
+ // Installs the bundled Claude Code skills into a project's ./.claude/skills/.
2
+ //
3
+ // Claude Code discovers skills only under .claude/skills/, never inside
4
+ // node_modules, so the package's own copy has to be duplicated into each
5
+ // project that wants them. That makes this command the only thing keeping the
6
+ // two trees in step, and `--force` has to mean "make the destination match this
7
+ // release" rather than "write over the files I happen to ship today":
8
+ //
9
+ // - a skill this release renamed leaves its old directory behind, where it
10
+ // shadows the new one with stale instructions;
11
+ // - a reference file this release dropped survives inside a skill that is
12
+ // otherwise current, because cpSync merges into an existing directory.
13
+ //
14
+ // Both are invisible until a model reads the stale text, so --force replaces
15
+ // each directory outright and prunes the ones this release no longer ships.
16
+
17
+ import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+
20
+ // The package owns this prefix in a project's skills directory. A skill without
21
+ // it is the project's own and is never touched, whatever the flags say.
22
+ const SKILL_PREFIX = 'live-tokens-';
23
+
24
+ const SAMPLE_PROMPTS = {
25
+ 'live-tokens-create-page': 'build a pricing page using live-tokens components',
26
+ 'live-tokens-pick-component': "what's the difference between TabBar and SegmentedControl?",
27
+ 'live-tokens-create-component': 'author a new Toggle component for my live-tokens project',
28
+ 'live-tokens-create-theme': 'make me a bright and cheerful theme',
29
+ 'live-tokens-set-colors': 'give me a cooler palette, same fonts',
30
+ 'live-tokens-set-type': 'pair some fonts for this theme',
31
+ 'live-tokens-set-geometry': 'make the buttons pill shaped',
32
+ 'live-tokens-fix-findings': 'make check:design pass',
33
+ 'live-tokens-check-compliance': 'check this project against the design system',
34
+ };
35
+
36
+ function directoriesIn(dir) {
37
+ if (!existsSync(dir)) return [];
38
+ return readdirSync(dir).filter((name) => statSync(join(dir, name)).isDirectory());
39
+ }
40
+
41
+ export function runSetupClaude({ pkgRoot, cwd, force = false }) {
42
+ const srcSkills = join(pkgRoot, '.claude', 'skills');
43
+
44
+ if (!existsSync(srcSkills)) {
45
+ throw new Error(`No bundled skills found at ${srcSkills}. Is the package installed correctly?`);
46
+ }
47
+
48
+ const shipped = directoriesIn(srcSkills);
49
+ if (shipped.length === 0) {
50
+ throw new Error('No bundled skills to install.');
51
+ }
52
+
53
+ const destSkills = join(cwd, '.claude', 'skills');
54
+ mkdirSync(destSkills, { recursive: true });
55
+
56
+ const installed = [];
57
+ const skipped = [];
58
+ const removed = [];
59
+
60
+ for (const skill of shipped) {
61
+ const dest = join(destSkills, skill);
62
+ if (existsSync(dest) && !force) {
63
+ skipped.push(skill);
64
+ continue;
65
+ }
66
+ // Replace rather than merge: a file this release dropped must not survive
67
+ // inside a directory that otherwise looks current.
68
+ rmSync(dest, { recursive: true, force: true });
69
+ cpSync(join(srcSkills, skill), dest, { recursive: true });
70
+ installed.push(skill);
71
+ }
72
+
73
+ if (force) {
74
+ for (const name of directoriesIn(destSkills)) {
75
+ if (!name.startsWith(SKILL_PREFIX) || shipped.includes(name)) continue;
76
+ rmSync(join(destSkills, name), { recursive: true, force: true });
77
+ removed.push(name);
78
+ }
79
+ }
80
+
81
+ return { installed, skipped, removed, destSkills, shipped };
82
+ }
83
+
84
+ export function formatSetupResult({ installed, skipped, removed, destSkills, shipped }) {
85
+ const lines = [];
86
+
87
+ for (const skill of shipped) {
88
+ if (installed.includes(skill)) lines.push(` ok ${skill}`);
89
+ else lines.push(` skip ${skill} (already exists; pass --force to overwrite)`);
90
+ }
91
+ for (const skill of removed) {
92
+ lines.push(` gone ${skill} (this release no longer ships it; removed)`);
93
+ }
94
+
95
+ const counts = [`${installed.length} installed`, `${skipped.length} skipped`];
96
+ if (removed.length > 0) counts.push(`${removed.length} removed`);
97
+ lines.push(`\n${counts.join(', ')}, in ${destSkills}`);
98
+
99
+ if (skipped.length > 0 && removed.length === 0) {
100
+ lines.push(`\nRe-run with --force to bring every skill up to this release.`);
101
+ }
102
+
103
+ const samples = shipped.map((s) => SAMPLE_PROMPTS[s] && [s, SAMPLE_PROMPTS[s]]).filter(Boolean);
104
+ if (samples.length > 0) {
105
+ lines.push(`\nIn Claude Code, prompts like these auto-trigger the matching skill:`);
106
+ for (const [skill, prompt] of samples) lines.push(` "${prompt}"\n ${skill}`);
107
+ }
108
+
109
+ return lines.join('\n');
110
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.75.0",
3
+ "version": "0.76.0",
4
4
  "type": "module",
5
5
  "description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
6
6
  "keywords": [
@@ -7,6 +7,10 @@ dist-ssr
7
7
  playwright-report/
8
8
  test-results/
9
9
 
10
+ # The live-tokens skills are a copy of what the package ships; postinstall
11
+ # refreshes them, so they are the package's to version, not this project's.
12
+ .claude/skills/live-tokens-*/
13
+
10
14
  .DS_Store
11
15
  .vscode/*
12
16
  !.vscode/extensions.json
@@ -9,7 +9,8 @@
9
9
  "preview": "vite preview",
10
10
  "check": "svelte-check --tsconfig ./tsconfig.json",
11
11
  "check:design": "live-tokens check-page && live-tokens check-component",
12
- "test:design": "live-tokens check-component --tests"
12
+ "test:design": "live-tokens check-component --tests",
13
+ "postinstall": "live-tokens setup-claude --force || exit 0"
13
14
  },
14
15
  "dependencies": {
15
16
  "@motion-proto/live-tokens": "__LT_VERSION__"