@codecademy/gamut 68.6.1-alpha.c211a2.0 → 68.6.1-alpha.d52035.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.
Files changed (35) hide show
  1. package/agent-tools/.claude-plugin/marketplace.json +16 -0
  2. package/agent-tools/.claude-plugin/plugin.json +7 -0
  3. package/agent-tools/.cursor-plugin/plugin.json +7 -0
  4. package/agent-tools/DESIGN.Codecademy.md +643 -0
  5. package/agent-tools/DESIGN.LXStudio.md +444 -0
  6. package/agent-tools/DESIGN.Percipio.md +435 -0
  7. package/agent-tools/DESIGN.md +1 -0
  8. package/agent-tools/agents/.gitkeep +0 -0
  9. package/agent-tools/commands/gamut-review.md +231 -0
  10. package/agent-tools/guidelines/components/buttons.md +91 -0
  11. package/agent-tools/guidelines/components/overview.md +44 -0
  12. package/agent-tools/guidelines/foundations/color.md +172 -0
  13. package/agent-tools/guidelines/foundations/modes.md +47 -0
  14. package/agent-tools/guidelines/foundations/spacing.md +66 -0
  15. package/agent-tools/guidelines/foundations/typography.md +50 -0
  16. package/agent-tools/guidelines/overview.md +38 -0
  17. package/agent-tools/guidelines/setup.md +42 -0
  18. package/agent-tools/rules/accessibility.mdc +68 -0
  19. package/agent-tools/skills/gamut-accessibility/SKILL.md +239 -0
  20. package/agent-tools/skills/gamut-color-mode/SKILL.md +138 -0
  21. package/agent-tools/skills/gamut-system-props/SKILL.md +173 -0
  22. package/agent-tools/skills/gamut-testing/SKILL.md +181 -0
  23. package/agent-tools/skills/gamut-theming/SKILL.md +113 -0
  24. package/agent-tools/skills/gamut-typography/SKILL.md +123 -0
  25. package/bin/commands/plugin/install.mjs +173 -0
  26. package/bin/commands/plugin/list.mjs +105 -0
  27. package/bin/commands/plugin/remove.mjs +116 -0
  28. package/bin/commands/plugin/update.mjs +49 -0
  29. package/bin/gamut.mjs +92 -0
  30. package/bin/lib/claude.mjs +52 -0
  31. package/bin/lib/cursor.mjs +40 -0
  32. package/bin/lib/figma.mjs +49 -0
  33. package/bin/lib/resolve-plugin-dir.mjs +38 -0
  34. package/bin/lib/run-command.mjs +22 -0
  35. package/package.json +11 -8
@@ -0,0 +1,116 @@
1
+ import { rm, stat } from 'node:fs/promises';
2
+
3
+ import { claudePluginSpec, marketplaceName } from '../../lib/claude.mjs';
4
+ import { cursorDestPath } from '../../lib/cursor.mjs';
5
+ import { resolveFigmaOutput } from '../../lib/figma.mjs';
6
+ import { getFlag, resolvePluginDir } from '../../lib/resolve-plugin-dir.mjs';
7
+ import { runCommand } from '../../lib/run-command.mjs';
8
+ import { TARGETS } from './install.mjs';
9
+
10
+ export function help() {
11
+ console.log(`
12
+ Usage:
13
+ gamut plugin remove [target] [options]
14
+
15
+ Remove the installed Gamut plugin from an AI or design tool.
16
+
17
+ Arguments:
18
+ target Tool to remove from (default: cursor)
19
+ cursor | claude | figma
20
+
21
+ Options:
22
+ --output <path> [figma] Path to the DESIGN.md that was installed.
23
+ If omitted, walks up from cwd to find figma.config.json.
24
+ --plugin-dir <path> Override the bundled agent-tools directory
25
+ -h, --help Show this help message
26
+
27
+ Examples:
28
+ gamut plugin remove
29
+ gamut plugin remove claude
30
+ gamut plugin remove figma
31
+ gamut plugin remove figma --output ./docs/DESIGN.md
32
+ `);
33
+ }
34
+
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /** @param {string} sourceRoot */
38
+ async function removeCursor(sourceRoot) {
39
+ const dest = await cursorDestPath(sourceRoot);
40
+ const st = await stat(dest).catch(() => null);
41
+
42
+ if (!st) {
43
+ console.log(`Cursor: nothing to remove — ${dest} does not exist.`);
44
+ return;
45
+ }
46
+
47
+ await rm(dest, { recursive: true, force: true });
48
+ console.log(`Cursor: removed ${dest}`);
49
+ }
50
+
51
+ /** @param {string} sourceRoot */
52
+ async function removeClaude(sourceRoot) {
53
+ const spec = await claudePluginSpec(sourceRoot);
54
+ const mpName = marketplaceName(spec);
55
+ const pluginName = spec.split('@')[0];
56
+
57
+ let code = await runCommand('claude', ['plugin', 'remove', pluginName, '--scope', 'user']);
58
+ if (code !== 0) {
59
+ console.warn(
60
+ `warning: "claude plugin remove" exited ${code} — the plugin may not have been installed.`,
61
+ );
62
+ } else {
63
+ console.log(`Claude Code: removed plugin "${pluginName}"`);
64
+ }
65
+
66
+ code = await runCommand('claude', ['plugin', 'marketplace', 'remove', mpName]);
67
+ if (code !== 0) {
68
+ console.warn(
69
+ `warning: "claude plugin marketplace remove" exited ${code} — ` +
70
+ `the marketplace entry may not exist or the command syntax may differ. ` +
71
+ `Run "claude plugin marketplace list" to check.`,
72
+ );
73
+ } else {
74
+ console.log(`Claude Code: removed marketplace "${mpName}"`);
75
+ }
76
+ }
77
+
78
+ /** @param {string | undefined} outputArg */
79
+ async function removeFigma(outputArg) {
80
+ const { path: dest } = await resolveFigmaOutput(outputArg);
81
+ const st = await stat(dest).catch(() => null);
82
+
83
+ if (!st) {
84
+ console.log(`Figma: nothing to remove — ${dest} does not exist.`);
85
+ return;
86
+ }
87
+
88
+ await rm(dest, { force: true });
89
+ console.log(`Figma: removed ${dest}`);
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+
94
+ /**
95
+ * gamut plugin remove [cursor|claude|figma] [--plugin-dir <path>]
96
+ *
97
+ * @param {string[]} args
98
+ */
99
+ export default async function remove(args) {
100
+ const target = args.find((a) => !a.startsWith('-')) ?? 'cursor';
101
+
102
+ if (!TARGETS.includes(target)) {
103
+ throw new Error(`Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}`);
104
+ }
105
+
106
+ const pluginDir = await resolvePluginDir(args);
107
+
108
+ if (target === 'cursor') {
109
+ await removeCursor(pluginDir);
110
+ } else if (target === 'claude') {
111
+ await removeClaude(pluginDir);
112
+ } else if (target === 'figma') {
113
+ const output = getFlag(args, '--output', undefined);
114
+ await removeFigma(output);
115
+ }
116
+ }
@@ -0,0 +1,49 @@
1
+ import { getFlag } from '../../lib/resolve-plugin-dir.mjs';
2
+ import install, { TARGETS } from './install.mjs';
3
+
4
+ export function help() {
5
+ console.log(`
6
+ Usage:
7
+ gamut plugin update [target] [options]
8
+
9
+ Update the Gamut plugin in an AI or design tool.
10
+ Equivalent to re-running install — replaces the existing installation in place.
11
+
12
+ Arguments:
13
+ target Tool to update (default: cursor)
14
+ cursor | claude | figma
15
+
16
+ Options:
17
+ --scope <scope> Content to update (default: all)
18
+ all | skills | rules | commands | agents
19
+ --plugin-dir <path> Override the bundled agent-tools directory
20
+ -h, --help Show this help message
21
+
22
+ Examples:
23
+ gamut plugin update
24
+ gamut plugin update claude
25
+ gamut plugin update cursor --scope skills
26
+ `);
27
+ }
28
+
29
+ /**
30
+ * gamut plugin update [cursor|claude|figma] [--scope all|skills|rules|commands|agents]
31
+ * [--plugin-dir <path>]
32
+ *
33
+ * Re-runs install with the same arguments. For Cursor this does an in-place
34
+ * copy replacing any existing installation. For Claude Code it updates the
35
+ * marketplace entry and re-installs.
36
+ *
37
+ * @param {string[]} args
38
+ */
39
+ export default async function update(args) {
40
+ const target = args.find((a) => !a.startsWith('-')) ?? 'cursor';
41
+ const scope = getFlag(args, '--scope', 'all') ?? 'all';
42
+
43
+ if (!TARGETS.includes(target)) {
44
+ throw new Error(`Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}`);
45
+ }
46
+
47
+ console.log(`Updating Gamut plugin for ${target}${scope !== 'all' ? ` (scope: ${scope})` : ''}…`);
48
+ await install(args);
49
+ }
package/bin/gamut.mjs ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Gamut CLI
5
+ *
6
+ * Usage:
7
+ * gamut plugin install [cursor|claude|figma] [--scope all|skills|rules|commands|agents]
8
+ * gamut plugin remove [cursor|claude|figma]
9
+ * gamut plugin update [cursor|claude|figma] [--scope all|skills|rules|commands|agents]
10
+ * gamut plugin list
11
+ */
12
+
13
+ const args = process.argv.slice(2);
14
+ const [noun, verb, ...rest] = args;
15
+
16
+ if (!noun || noun === '--help' || noun === '-h') {
17
+ printHelp();
18
+ process.exit(noun ? 0 : 1);
19
+ }
20
+
21
+ if (noun !== 'plugin') {
22
+ console.error(`Unknown command: "${noun}"`);
23
+ printHelp();
24
+ process.exit(1);
25
+ }
26
+
27
+ if (!verb || verb === '--help' || verb === '-h') {
28
+ printPluginHelp();
29
+ process.exit(verb ? 0 : 1);
30
+ }
31
+
32
+ let cmd;
33
+ try {
34
+ cmd = await import(`./commands/plugin/${verb}.mjs`);
35
+ } catch {
36
+ console.error(`Unknown plugin subcommand: "${verb}"`);
37
+ printPluginHelp();
38
+ process.exit(1);
39
+ }
40
+
41
+ if (rest.includes('--help') || rest.includes('-h')) {
42
+ cmd.help();
43
+ process.exit(0);
44
+ }
45
+
46
+ try {
47
+ await cmd.default(rest);
48
+ } catch (/** @type {any} */ err) {
49
+ console.error(`Error: ${err.message}`);
50
+ process.exit(1);
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Help
55
+ // ---------------------------------------------------------------------------
56
+
57
+ function printHelp() {
58
+ console.log(`
59
+ gamut — Gamut design system CLI
60
+
61
+ Usage:
62
+ gamut <command> [subcommand] [options]
63
+
64
+ Commands:
65
+ plugin Manage the Gamut plugin in your AI/design tools
66
+
67
+ Run "gamut plugin --help" for plugin subcommands.
68
+ `);
69
+ }
70
+
71
+ function printPluginHelp() {
72
+ console.log(`
73
+ gamut plugin — Manage the Gamut plugin
74
+
75
+ Subcommands:
76
+ install [target] [--scope <scope>] Install the plugin into a tool
77
+ remove [target] Remove an installed plugin
78
+ update [target] [--scope <scope>] Update an already-installed plugin
79
+ list Show installation status for all targets
80
+
81
+ Targets: cursor (default) | claude | figma
82
+ Scopes: all (default) | skills | rules | commands | agents
83
+
84
+ Examples:
85
+ gamut plugin install
86
+ gamut plugin install claude
87
+ gamut plugin install cursor --scope skills
88
+ gamut plugin remove claude
89
+ gamut plugin update
90
+ gamut plugin list
91
+ `);
92
+ }
@@ -0,0 +1,52 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+
4
+ /**
5
+ * Reads .claude-plugin/marketplace.json and returns a "name@marketplace" plugin spec.
6
+ *
7
+ * @param {string} sourceRoot
8
+ * @returns {Promise<string>}
9
+ */
10
+ export async function claudePluginSpec(sourceRoot) {
11
+ const mp = join(sourceRoot, '.claude-plugin', 'marketplace.json');
12
+ let text;
13
+ try {
14
+ text = await readFile(mp, 'utf8');
15
+ } catch {
16
+ throw new Error(
17
+ `Missing ${mp}.\n` +
18
+ `A .claude-plugin/marketplace.json is required for Claude Code installation.`,
19
+ );
20
+ }
21
+
22
+ const json =
23
+ /** @type {{ name?: string; plugins?: Array<{ name?: string; source?: string }> }} */ (
24
+ JSON.parse(text)
25
+ );
26
+ const { name: marketplaceName, plugins } = json;
27
+
28
+ if (!marketplaceName || !Array.isArray(plugins) || plugins.length === 0) {
29
+ throw new Error(`Invalid marketplace.json — needs "name" and "plugins[]": ${mp}`);
30
+ }
31
+
32
+ const entry =
33
+ plugins.find((p) => p.source === './' || p.source === '.' || p.source == null) ?? plugins[0];
34
+
35
+ if (!entry?.name) {
36
+ throw new Error(`No plugin name found in marketplace.json plugins[]: ${mp}`);
37
+ }
38
+
39
+ return `${entry.name}@${marketplaceName}`;
40
+ }
41
+
42
+ /**
43
+ * Returns just the marketplace name portion of a plugin spec ("name@marketplace").
44
+ *
45
+ * @param {string} spec
46
+ * @returns {string}
47
+ */
48
+ export function marketplaceName(spec) {
49
+ const name = spec.split('@')[1];
50
+ if (!name) throw new Error(`Could not parse marketplace name from plugin spec: ${spec}`);
51
+ return name;
52
+ }
@@ -0,0 +1,40 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ /** @returns {string} */
6
+ export function cursorPluginsRoot() {
7
+ return process.env.CURSOR_PLUGINS_LOCAL ?? join(homedir(), '.cursor', 'plugins', 'local');
8
+ }
9
+
10
+ /**
11
+ * Reads the .cursor-plugin/plugin.json manifest and derives a folder name.
12
+ * Falls back to "gamut-agent-tools" if no manifest is found.
13
+ *
14
+ * @param {string} sourceRoot
15
+ * @returns {Promise<string>}
16
+ */
17
+ export async function cursorFolderName(sourceRoot) {
18
+ const manifest = join(sourceRoot, '.cursor-plugin', 'plugin.json');
19
+ try {
20
+ const text = await readFile(manifest, 'utf8');
21
+ const json = /** @type {{ name?: string }} */ (JSON.parse(text));
22
+ if (json.name && typeof json.name === 'string') {
23
+ return json.name.replace(/^@/, '').replace(/\//g, '-');
24
+ }
25
+ } catch {
26
+ // no manifest — use default
27
+ }
28
+ return 'gamut-agent-tools';
29
+ }
30
+
31
+ /**
32
+ * Returns the absolute path where the plugin is/should be installed for Cursor.
33
+ *
34
+ * @param {string} sourceRoot
35
+ * @returns {Promise<string>}
36
+ */
37
+ export async function cursorDestPath(sourceRoot) {
38
+ const folderName = await cursorFolderName(sourceRoot);
39
+ return join(cursorPluginsRoot(), folderName);
40
+ }
@@ -0,0 +1,49 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { dirname, join, resolve } from 'node:path';
3
+
4
+ /**
5
+ * Walk up from `startDir` looking for a `figma.config.json` file.
6
+ * Returns the directory containing it, or null if not found before the filesystem root.
7
+ *
8
+ * @param {string} startDir
9
+ * @returns {Promise<string | null>}
10
+ */
11
+ export async function findFigmaConfigDir(startDir) {
12
+ let dir = startDir;
13
+ while (true) {
14
+ const st = await stat(join(dir, 'figma.config.json')).catch(() => null);
15
+ if (st?.isFile()) return dir;
16
+ const parent = dirname(dir);
17
+ if (parent === dir) return null;
18
+ dir = parent;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Resolves the destination directory for the guidelines/ folder.
24
+ *
25
+ * Priority:
26
+ * 1. --output <path> if provided (treated as the parent directory)
27
+ * 2. Directory containing the nearest figma.config.json (walking up from cwd)
28
+ *
29
+ * Throws with actionable guidance if neither resolves.
30
+ *
31
+ * @param {string | undefined} outputArg
32
+ * @returns {Promise<{ path: string; discovered: boolean }>}
33
+ */
34
+ export async function resolveFigmaOutput(outputArg) {
35
+ if (outputArg) {
36
+ return { path: resolve(outputArg), discovered: false };
37
+ }
38
+
39
+ const dir = await findFigmaConfigDir(process.cwd());
40
+ if (dir) {
41
+ return { path: join(dir, 'guidelines'), discovered: true };
42
+ }
43
+
44
+ throw new Error(
45
+ `Could not find figma.config.json in ${process.cwd()} or any parent directory.\n` +
46
+ `Provide the destination explicitly with --output:\n` +
47
+ ` gamut plugin install figma --output /path/to/your/project/guidelines`,
48
+ );
49
+ }
@@ -0,0 +1,38 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+
7
+ /**
8
+ * Returns the absolute path to the bundled agent-tools directory, or the
9
+ * value of --plugin-dir if provided.
10
+ *
11
+ * @param {string[]} args
12
+ * @returns {Promise<string>}
13
+ */
14
+ export async function resolvePluginDir(args) {
15
+ const override = getFlag(args, '--plugin-dir');
16
+ if (override) {
17
+ const abs = resolve(override);
18
+ const st = await stat(abs).catch(() => null);
19
+ if (!st?.isDirectory()) {
20
+ throw new Error(`--plugin-dir path not found: ${abs}`);
21
+ }
22
+ return abs;
23
+ }
24
+
25
+ // agent-tools/ is bundled at <package-root>/agent-tools/ relative to bin/lib/
26
+ return resolve(__dirname, '..', '..', 'agent-tools');
27
+ }
28
+
29
+ /**
30
+ * @param {string[]} argv
31
+ * @param {string} flag
32
+ * @param {string} [fallback]
33
+ * @returns {string | undefined}
34
+ */
35
+ export function getFlag(argv, flag, fallback) {
36
+ const idx = argv.indexOf(flag);
37
+ return idx !== -1 ? argv[idx + 1] : fallback;
38
+ }
@@ -0,0 +1,22 @@
1
+ import { spawn } from 'node:child_process';
2
+
3
+ /**
4
+ * Spawns an external command and returns its exit code.
5
+ *
6
+ * @param {string} command
7
+ * @param {string[]} args
8
+ * @returns {Promise<number>}
9
+ */
10
+ export function runCommand(command, args) {
11
+ return new Promise((resolve, reject) => {
12
+ const child = spawn(command, args, { stdio: 'inherit', shell: false });
13
+ child.on('error', (/** @type {NodeJS.ErrnoException} */ err) => {
14
+ if (err.code === 'ENOENT') {
15
+ reject(new Error(`"${command}" not found on PATH. Is it installed?`));
16
+ } else {
17
+ reject(err);
18
+ }
19
+ });
20
+ child.on('close', (code) => resolve(code ?? 1));
21
+ });
22
+ }
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@codecademy/gamut",
3
3
  "description": "Styleguide & Component library for Codecademy",
4
- "version": "68.6.1-alpha.c211a2.0",
4
+ "version": "68.6.1-alpha.d52035.0",
5
5
  "author": "Codecademy Engineering <dev@codecademy.com>",
6
+ "bin": "./bin/gamut.mjs",
6
7
  "dependencies": {
7
- "@codecademy/gamut-icons": "9.57.6-alpha.c211a2.0",
8
- "@codecademy/gamut-illustrations": "0.58.12-alpha.c211a2.0",
9
- "@codecademy/gamut-patterns": "0.10.31-alpha.c211a2.0",
10
- "@codecademy/gamut-styles": "18.0.1-alpha.c211a2.0",
11
- "@codecademy/variance": "0.26.2-alpha.c211a2.0",
8
+ "@codecademy/gamut-icons": "9.57.6-alpha.d52035.0",
9
+ "@codecademy/gamut-illustrations": "0.58.12-alpha.d52035.0",
10
+ "@codecademy/gamut-patterns": "0.10.31-alpha.d52035.0",
11
+ "@codecademy/gamut-styles": "18.0.1-alpha.d52035.0",
12
+ "@codecademy/variance": "0.26.2-alpha.d52035.0",
12
13
  "@formatjs/intl-locale": "5.3.1",
13
14
  "@react-aria/interactions": "3.25.0",
14
15
  "@types/marked": "^4.0.8",
@@ -30,7 +31,9 @@
30
31
  "sanitize-markdown": "^2.6.7"
31
32
  },
32
33
  "files": [
33
- "dist"
34
+ "dist",
35
+ "bin",
36
+ "agent-tools"
34
37
  ],
35
38
  "license": "MIT",
36
39
  "main": "./dist/index.js",
@@ -52,7 +55,7 @@
52
55
  "build": "nx build @codecademy/gamut",
53
56
  "build:watch": "yarn build && onchange ./src -- yarn build",
54
57
  "compile": "babel ./src --out-dir ./dist --extensions \".ts,.tsx\"",
55
- "verify": "tsc --noEmit"
58
+ "verify": "tsc --noEmit && tsc --project tsconfig.bin.json"
56
59
  },
57
60
  "sideEffects": [
58
61
  "**/*.css",