@codecademy/gamut 68.6.1-alpha.edab62.0 → 68.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Modals/elements.d.ts +186 -1
- package/dist/Modals/elements.js +3 -3
- package/dist/{InternalFloatingCard/InternalFloatingCard.d.ts → PatternBackdrop/PatternBackdrop.d.ts} +8 -24
- package/dist/PatternBackdrop/PatternBackdrop.js +42 -0
- package/dist/Toast/Toast.js +4 -4
- package/package.json +8 -11
- package/agent-tools/.claude-plugin/marketplace.json +0 -16
- package/agent-tools/.claude-plugin/plugin.json +0 -7
- package/agent-tools/.cursor-plugin/plugin.json +0 -7
- package/agent-tools/DESIGN.Codecademy.md +0 -643
- package/agent-tools/DESIGN.LXStudio.md +0 -444
- package/agent-tools/DESIGN.Percipio.md +0 -435
- package/agent-tools/DESIGN.md +0 -1
- package/agent-tools/agents/.gitkeep +0 -0
- package/agent-tools/commands/gamut-review.md +0 -170
- package/agent-tools/guidelines/components/buttons.md +0 -44
- package/agent-tools/guidelines/components/overview.md +0 -44
- package/agent-tools/guidelines/foundations/color.md +0 -86
- package/agent-tools/guidelines/foundations/modes.md +0 -45
- package/agent-tools/guidelines/foundations/spacing.md +0 -66
- package/agent-tools/guidelines/foundations/typography.md +0 -50
- package/agent-tools/guidelines/overview.md +0 -35
- package/agent-tools/guidelines/setup.md +0 -42
- package/agent-tools/rules/accessibility.mdc +0 -69
- package/agent-tools/skills/gamut-accessibility/SKILL.md +0 -239
- package/agent-tools/skills/gamut-color-mode/SKILL.md +0 -99
- package/agent-tools/skills/gamut-system-props/SKILL.md +0 -173
- package/agent-tools/skills/gamut-testing/SKILL.md +0 -181
- package/agent-tools/skills/gamut-theming/SKILL.md +0 -113
- package/agent-tools/skills/gamut-typography/SKILL.md +0 -123
- package/bin/commands/plugin/install.mjs +0 -173
- package/bin/commands/plugin/list.mjs +0 -105
- package/bin/commands/plugin/remove.mjs +0 -116
- package/bin/commands/plugin/update.mjs +0 -49
- package/bin/gamut.mjs +0 -92
- package/bin/lib/claude.mjs +0 -52
- package/bin/lib/cursor.mjs +0 -40
- package/bin/lib/figma.mjs +0 -49
- package/bin/lib/resolve-plugin-dir.mjs +0 -38
- package/bin/lib/run-command.mjs +0 -22
- package/dist/InternalFloatingCard/InternalFloatingCard.js +0 -98
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: gamut-theming
|
|
3
|
-
description: Use this skill when working with GamutProvider themes, Emotion theme tokens, or behavior that differs across Core, Admin, Platform, LX Studio, and Percipio — including new themes, token access in styled components, or debugging theme-specific styles.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Gamut Theming
|
|
7
|
-
|
|
8
|
-
Source: `@codecademy/gamut-styles`
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
Gamut uses Emotion's theme system. All styled components have access to a typed theme object containing every design token. Themes are org-specific collections of tokens; components work across all themes without modification as long as they use token aliases rather than hardcoded values.
|
|
13
|
-
|
|
14
|
-
## Available themes
|
|
15
|
-
|
|
16
|
-
| Theme | Used for |
|
|
17
|
-
|---|---|
|
|
18
|
-
| Core | Codecademy default (public-facing products) |
|
|
19
|
-
| Admin | Codecademy admin tools |
|
|
20
|
-
| Platform | Codecademy learning environment / shared platform surfaces |
|
|
21
|
-
| LX Studio | Learning Experience Studio |
|
|
22
|
-
| Percipio | Skillsoft Percipio platform |
|
|
23
|
-
|
|
24
|
-
The active theme is set at the app level via `<GamutProvider>`. Components inside receive the current theme via Emotion's context.
|
|
25
|
-
|
|
26
|
-
## Accessing tokens in styled components
|
|
27
|
-
|
|
28
|
-
### Via `css()` utility (recommended for static styles)
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
import { css } from '@codecademy/gamut-styles';
|
|
32
|
-
import styled from '@emotion/styled';
|
|
33
|
-
|
|
34
|
-
// Static color token
|
|
35
|
-
const Box = styled.div(css({ bg: 'navy-400', p: 4 }));
|
|
36
|
-
|
|
37
|
-
// Semantic color alias (adapts to color mode and theme)
|
|
38
|
-
const Text = styled.div(css({ color: 'primary', p: 4 }));
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Via `theme` prop (for dynamic styles)
|
|
42
|
-
|
|
43
|
-
Every Emotion styled component receives `theme` as a prop:
|
|
44
|
-
|
|
45
|
-
```tsx
|
|
46
|
-
import styled from '@emotion/styled';
|
|
47
|
-
|
|
48
|
-
const DynamicBox = styled.div`
|
|
49
|
-
color: ${({ theme }) => theme.colors.blue};
|
|
50
|
-
font-size: ${({ theme }) => theme.fontSize[16]};
|
|
51
|
-
`;
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Via imported `theme` object (outside styled components)
|
|
55
|
-
|
|
56
|
-
```tsx
|
|
57
|
-
import { css as emotionCss } from '@emotion/react';
|
|
58
|
-
import { theme } from '@codecademy/gamut-styles';
|
|
59
|
-
|
|
60
|
-
// For use in plain CSS-in-JS outside of styled components
|
|
61
|
-
const myStyles = emotionCss`
|
|
62
|
-
font-size: ${theme.fontSize[14]};
|
|
63
|
-
color: ${theme.colors['navy-400']};
|
|
64
|
-
`;
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Via `useTheme()` hook
|
|
68
|
-
|
|
69
|
-
```tsx
|
|
70
|
-
import { useTheme } from '@emotion/react';
|
|
71
|
-
|
|
72
|
-
const MyComponent = () => {
|
|
73
|
-
const theme = useTheme();
|
|
74
|
-
return <div style={{ color: theme.colors.primary }} />;
|
|
75
|
-
};
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## System props as the primary token API
|
|
79
|
-
|
|
80
|
-
For most styling needs, use **system props** (see the `gamut-system-props` skill) rather than accessing the theme directly. System props are the idiomatic way to use design tokens in Gamut components:
|
|
81
|
-
|
|
82
|
-
```tsx
|
|
83
|
-
import { variance } from '@codecademy/variance';
|
|
84
|
-
import { system } from '@codecademy/gamut-styles';
|
|
85
|
-
|
|
86
|
-
const Card = styled.div(
|
|
87
|
-
variance.compose(system.layout, system.space, system.color)
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// token scale values are validated at the type level
|
|
91
|
-
<Card bg="navy-400" p={16} width="100%" />;
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Theme-aware vs. color-mode-aware
|
|
95
|
-
|
|
96
|
-
| Concern | Tool |
|
|
97
|
-
|---|---|
|
|
98
|
-
| Tokens change per theme (colors, sizes, fonts) | Access via `theme.*` or system props |
|
|
99
|
-
| Colors change per light/dark mode | Use semantic color aliases + `<ColorMode>` |
|
|
100
|
-
| Background contrast | Use `<Background>` from ColorMode |
|
|
101
|
-
|
|
102
|
-
Semantic aliases like `primary`, `secondary`, `text`, `background` serve double duty: they resolve to theme-specific values **and** switch between light/dark variants automatically.
|
|
103
|
-
|
|
104
|
-
## Creating a new theme
|
|
105
|
-
|
|
106
|
-
See `CreatingThemes.mdx` in the styleguide (`packages/styleguide/src/lib/Foundations/Theme/CreatingThemes.mdx`) for the full guide. Themes are defined in `@codecademy/gamut-styles` and must extend the base theme shape with all required token keys.
|
|
107
|
-
|
|
108
|
-
## Key principles
|
|
109
|
-
|
|
110
|
-
- Prefer semantic token aliases over raw token values when the style needs to respond to color mode changes.
|
|
111
|
-
- Use raw tokens (e.g. `navy-400`) only for styles that should be fixed regardless of mode.
|
|
112
|
-
- Never hardcode hex values in styled components — always go through the theme/system props.
|
|
113
|
-
- The `GamutProvider` at the app root wires up the theme, color mode, and logical properties settings; components should not need to know which theme is active.
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: gamut-typography
|
|
3
|
-
description: Use this skill when creating or reviewing UI text in Gamut apps — headlines, body, captions, labels, code snippets, or text-heavy layouts — even if the user does not name fonts or tokens. Covers Apercu Pro, Suisse Intl Mono, scale, line heights, line length, and alignment.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Gamut Typography
|
|
7
|
-
|
|
8
|
-
> **Scope**: This skill covers typography for **Codecademy products** using the Core, Admin, or Platform themes (Apercu + Suisse). Percipio uses Roboto for all type — see `DESIGN.md` for Percipio-specific guidance. LX Studio uses Hanken Grotesk in place of both Apercu and Suisse.
|
|
9
|
-
|
|
10
|
-
## Typefaces
|
|
11
|
-
|
|
12
|
-
Codecademy products use two typefaces:
|
|
13
|
-
|
|
14
|
-
### Apercu Pro (`fontFamily: "base"`)
|
|
15
|
-
|
|
16
|
-
The primary typeface. Geometric-ish, humanist sans-serif. Use for:
|
|
17
|
-
- Headlines (Bold weight)
|
|
18
|
-
- Body / paragraph text (Regular weight)
|
|
19
|
-
- UI labels, menu items (Regular weight)
|
|
20
|
-
- Emphasis within body copy (Italic — **not** Bold)
|
|
21
|
-
|
|
22
|
-
**Rules:**
|
|
23
|
-
- Do not use Bold to emphasize text within a Regular-weight paragraph. Use Italic instead.
|
|
24
|
-
- Set with generous line-height for body text: **150–175%** of the type size (e.g. 16px type → 24–28px line-height).
|
|
25
|
-
- Headlines should use **100–110%** line-height to appear intentional and grouped.
|
|
26
|
-
- Text should be **left-aligned** by default.
|
|
27
|
-
|
|
28
|
-
### Suisse Intl Mono (`fontFamily: "accent"`)
|
|
29
|
-
|
|
30
|
-
Monospace accent typeface. Use sparingly for:
|
|
31
|
-
- Code snippets and inline code
|
|
32
|
-
- Numbers, figures, and statistics
|
|
33
|
-
- Captions and labels that reference technical/engineering context
|
|
34
|
-
- Enumerated lists
|
|
35
|
-
- Quotations in a technical voice
|
|
36
|
-
|
|
37
|
-
**Rules:**
|
|
38
|
-
- Every character is the same width — avoid long paragraph-length prose in Suisse.
|
|
39
|
-
- It reads large for its point size: **reduce the size by ~10–15%** relative to Apercu text at the same visual scale (e.g. 14px Suisse ≈ 16px Apercu visually).
|
|
40
|
-
- Requires extra line-height to remain readable.
|
|
41
|
-
|
|
42
|
-
## Font size scale (`fontSize`)
|
|
43
|
-
|
|
44
|
-
Sizes are accessed via the theme's `fontSize` scale. Common keys:
|
|
45
|
-
|
|
46
|
-
```tsx
|
|
47
|
-
import { css } from '@codecademy/gamut-styles';
|
|
48
|
-
import styled from '@emotion/styled';
|
|
49
|
-
|
|
50
|
-
// Via system props
|
|
51
|
-
import { system } from '@codecademy/gamut-styles';
|
|
52
|
-
const Text = styled.p(system.typography);
|
|
53
|
-
<Text fontSize={16} />;
|
|
54
|
-
|
|
55
|
-
// Via css() utility
|
|
56
|
-
const Box = styled.div(css({ fontSize: 14 }));
|
|
57
|
-
|
|
58
|
-
// Via theme directly (outside styled components)
|
|
59
|
-
import { theme } from '@codecademy/gamut-styles';
|
|
60
|
-
const size = theme.fontSize[16];
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Line heights (`lineHeight`)
|
|
64
|
-
|
|
65
|
-
Line heights are limited to **multiples of 4px**. Type boxes are placed on an **8px placement grid**.
|
|
66
|
-
|
|
67
|
-
Guidelines:
|
|
68
|
-
- Body text: 150–175% of font size
|
|
69
|
-
- Headlines: 100–110% of font size
|
|
70
|
-
|
|
71
|
-
## Line length
|
|
72
|
-
|
|
73
|
-
Controlling line length is essential for readability:
|
|
74
|
-
|
|
75
|
-
| Context | Target |
|
|
76
|
-
|---|---|
|
|
77
|
-
| Single-column body text | ~66 characters (max 85) |
|
|
78
|
-
| Multi-column layouts | ≤50 characters per line |
|
|
79
|
-
| Minimum | 45 characters |
|
|
80
|
-
|
|
81
|
-
**How to control line length**: Start with the right text style for the design, then adjust the width or column count of the text container.
|
|
82
|
-
|
|
83
|
-
## Alignment
|
|
84
|
-
|
|
85
|
-
- **Left-align** paragraphs by default — this is easiest to read and supports grid alignment.
|
|
86
|
-
- **Center-align** only for short marketing headlines or specific interface components with brief text.
|
|
87
|
-
- **Never right-align** text in normal circumstances (exceptions: numbers, equations).
|
|
88
|
-
- Do not adjust letter-spacing.
|
|
89
|
-
|
|
90
|
-
## Accessing typography tokens
|
|
91
|
-
|
|
92
|
-
```tsx
|
|
93
|
-
// System props (recommended for styled components)
|
|
94
|
-
import { system } from '@codecademy/gamut-styles';
|
|
95
|
-
import { variance } from '@codecademy/variance';
|
|
96
|
-
|
|
97
|
-
const Heading = styled.h2(
|
|
98
|
-
variance.compose(system.typography, system.space)
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
<Heading fontSize={24} fontFamily="base" fontWeight="bold" lineHeight={1.1} mb={8} />;
|
|
102
|
-
|
|
103
|
-
// css() utility (recommended for static styles)
|
|
104
|
-
import { css } from '@codecademy/gamut-styles';
|
|
105
|
-
|
|
106
|
-
const Caption = styled.span(
|
|
107
|
-
css({ fontFamily: 'accent', fontSize: 12, color: 'secondary' })
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
// Theme object (outside styled components)
|
|
111
|
-
import { theme } from '@codecademy/gamut-styles';
|
|
112
|
-
import { css as emotionCss } from '@emotion/react';
|
|
113
|
-
|
|
114
|
-
const myStyles = emotionCss`
|
|
115
|
-
font-size: ${theme.fontSize[14]};
|
|
116
|
-
`;
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
## Semantic vs. visual sizing
|
|
120
|
-
|
|
121
|
-
- The term **"Title"** distinguishes visual size from semantic HTML hierarchy (H1–H6).
|
|
122
|
-
- A visually large title may use `<h2>` or `<p>` semantically — visual scale and semantic meaning are independent in Gamut.
|
|
123
|
-
- Choose HTML heading levels for document structure, choose font size for visual hierarchy.
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { cp, mkdir, readdir, rm, symlink } from 'node:fs/promises';
|
|
2
|
-
import { join, resolve } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { claudePluginSpec, marketplaceName } from '../../lib/claude.mjs';
|
|
5
|
-
import { cursorDestPath } from '../../lib/cursor.mjs';
|
|
6
|
-
import { resolveFigmaOutput } from '../../lib/figma.mjs';
|
|
7
|
-
import { getFlag, resolvePluginDir } from '../../lib/resolve-plugin-dir.mjs';
|
|
8
|
-
import { runCommand } from '../../lib/run-command.mjs';
|
|
9
|
-
|
|
10
|
-
export const TARGETS = ['cursor', 'claude', 'figma'];
|
|
11
|
-
export const SCOPES = ['all', 'skills', 'rules', 'commands', 'agents'];
|
|
12
|
-
|
|
13
|
-
export function help() {
|
|
14
|
-
console.log(`
|
|
15
|
-
Usage:
|
|
16
|
-
gamut plugin install [target] [options]
|
|
17
|
-
|
|
18
|
-
Install the Gamut plugin into an AI or design tool.
|
|
19
|
-
|
|
20
|
-
Arguments:
|
|
21
|
-
target Tool to install into (default: cursor)
|
|
22
|
-
cursor | claude | figma
|
|
23
|
-
|
|
24
|
-
Options:
|
|
25
|
-
--scope <scope> Content to install (default: all)
|
|
26
|
-
all | skills | rules | commands | agents
|
|
27
|
-
--output <path> [figma] Explicit destination directory for guidelines/.
|
|
28
|
-
If omitted, walks up from cwd to find figma.config.json.
|
|
29
|
-
--plugin-dir <path> Override the bundled agent-tools directory
|
|
30
|
-
-h, --help Show this help message
|
|
31
|
-
|
|
32
|
-
Examples:
|
|
33
|
-
gamut plugin install
|
|
34
|
-
gamut plugin install claude
|
|
35
|
-
gamut plugin install figma
|
|
36
|
-
gamut plugin install figma --output /path/to/project/guidelines
|
|
37
|
-
gamut plugin install cursor --scope skills
|
|
38
|
-
gamut plugin install cursor --plugin-dir ./my-agent-tools
|
|
39
|
-
`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
/** Directories in the plugin source that should not be installed to Cursor. */
|
|
45
|
-
const CURSOR_IGNORE = new Set([
|
|
46
|
-
'.claude-plugin', // Claude Code manifest — not a Cursor concept
|
|
47
|
-
'guidelines', // Figma Make only
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
/** @param {string} sourceRoot @param {string} scope */
|
|
51
|
-
async function installCursor(sourceRoot, scope) {
|
|
52
|
-
const dest = await cursorDestPath(sourceRoot);
|
|
53
|
-
|
|
54
|
-
if ((process.env.CURSOR_INSTALL_METHOD ?? 'copy') !== 'copy') {
|
|
55
|
-
// Symlink the whole plugin dir (dev convenience)
|
|
56
|
-
await rm(dest, { recursive: true, force: true });
|
|
57
|
-
await symlink(resolve(sourceRoot), dest, 'dir');
|
|
58
|
-
console.log(`Cursor: symlinked to ${dest}`);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Selective copy: always include the cursor manifest, then scoped content dirs
|
|
63
|
-
await rm(dest, { recursive: true, force: true });
|
|
64
|
-
await mkdir(dest, { recursive: true });
|
|
65
|
-
|
|
66
|
-
await cp(`${sourceRoot}/.cursor-plugin`, `${dest}/.cursor-plugin`, { recursive: true });
|
|
67
|
-
|
|
68
|
-
let dirs;
|
|
69
|
-
if (scope === 'all') {
|
|
70
|
-
const entries = await readdir(sourceRoot, { withFileTypes: true });
|
|
71
|
-
dirs = entries
|
|
72
|
-
.filter((e) => e.isDirectory() && !e.name.startsWith('.') && !CURSOR_IGNORE.has(e.name))
|
|
73
|
-
.map((e) => e.name);
|
|
74
|
-
} else {
|
|
75
|
-
dirs = [scope];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
for (const dir of dirs) {
|
|
79
|
-
await cp(`${sourceRoot}/${dir}`, `${dest}/${dir}`, { recursive: true }).catch(() => {
|
|
80
|
-
// directory may be empty/missing — not an error
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const scopeLabel = scope === 'all' ? 'all content' : scope;
|
|
85
|
-
console.log(`Cursor: installed (${scopeLabel}) → ${dest}`);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Claude Code only loads from recognized plugin directories: skills/, commands/, agents/.
|
|
89
|
-
// rules/ is Cursor-specific (.mdc format); .cursor-plugin/ and guidelines/ are also
|
|
90
|
-
// present in sourceRoot but ignored by Claude Code.
|
|
91
|
-
|
|
92
|
-
/** @param {string} sourceRoot */
|
|
93
|
-
async function installClaude(sourceRoot) {
|
|
94
|
-
const spec = await claudePluginSpec(sourceRoot);
|
|
95
|
-
const mpName = marketplaceName(spec);
|
|
96
|
-
const root = resolve(sourceRoot);
|
|
97
|
-
|
|
98
|
-
let code = await runCommand('claude', ['plugin', 'marketplace', 'add', root, '--scope', 'user']);
|
|
99
|
-
if (code !== 0) {
|
|
100
|
-
console.warn(
|
|
101
|
-
`warning: "claude plugin marketplace add" exited ${code} — ` +
|
|
102
|
-
`if it's already registered this is safe to ignore.`,
|
|
103
|
-
);
|
|
104
|
-
code = await runCommand('claude', ['plugin', 'marketplace', 'update', mpName]);
|
|
105
|
-
if (code !== 0) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`claude plugin marketplace add/update failed (exit ${code}).\n` +
|
|
108
|
-
`Try manually: claude plugin marketplace add ${root}`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
code = await runCommand('claude', ['plugin', 'install', spec, '--scope', 'user']);
|
|
114
|
-
if (code !== 0) {
|
|
115
|
-
throw new Error(
|
|
116
|
-
`claude plugin install failed (exit ${code}).\n` +
|
|
117
|
-
`Try manually: claude plugin install ${spec} --scope user`,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
console.log(`Claude Code: installed ${spec} (user scope)`);
|
|
122
|
-
console.log(` Tip: run /reload-plugins in Claude Code if skills don't appear immediately.`);
|
|
123
|
-
console.log(` One-off without install: claude --plugin-dir ${root}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @param {string} sourceRoot
|
|
128
|
-
* @param {string | undefined} outputArg
|
|
129
|
-
*/
|
|
130
|
-
async function installFigma(sourceRoot, outputArg) {
|
|
131
|
-
const src = join(sourceRoot, 'guidelines');
|
|
132
|
-
const { path: dest, discovered } = await resolveFigmaOutput(outputArg);
|
|
133
|
-
|
|
134
|
-
if (discovered) {
|
|
135
|
-
console.log(`Figma: found figma.config.json — installing to ${dest}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
await rm(dest, { recursive: true, force: true });
|
|
139
|
-
await cp(src, dest, { recursive: true });
|
|
140
|
-
console.log(`Figma: installed guidelines/ → ${dest}`);
|
|
141
|
-
console.log(` In Figma Make, point your kit at this guidelines/ directory for design system context.`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ---------------------------------------------------------------------------
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* gamut plugin install [cursor|claude|figma] [--scope all|skills|rules|commands|agents]
|
|
148
|
-
* [--plugin-dir <path>]
|
|
149
|
-
*
|
|
150
|
-
* @param {string[]} args
|
|
151
|
-
*/
|
|
152
|
-
export default async function install(args) {
|
|
153
|
-
const target = args.find((a) => !a.startsWith('-')) ?? 'cursor';
|
|
154
|
-
const scope = getFlag(args, '--scope', 'all') ?? 'all';
|
|
155
|
-
|
|
156
|
-
if (!TARGETS.includes(target)) {
|
|
157
|
-
throw new Error(`Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}`);
|
|
158
|
-
}
|
|
159
|
-
if (!SCOPES.includes(scope)) {
|
|
160
|
-
throw new Error(`Unknown scope: "${scope}". Choose from: ${SCOPES.join(', ')}`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const pluginDir = await resolvePluginDir(args);
|
|
164
|
-
|
|
165
|
-
if (target === 'cursor') {
|
|
166
|
-
await installCursor(pluginDir, scope);
|
|
167
|
-
} else if (target === 'claude') {
|
|
168
|
-
await installClaude(pluginDir);
|
|
169
|
-
} else if (target === 'figma') {
|
|
170
|
-
const output = getFlag(args, '--output', undefined);
|
|
171
|
-
await installFigma(pluginDir, output);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { stat } from 'node:fs/promises';
|
|
2
|
-
|
|
3
|
-
import { cursorDestPath } from '../../lib/cursor.mjs';
|
|
4
|
-
import { findFigmaConfigDir } from '../../lib/figma.mjs';
|
|
5
|
-
import { getFlag, resolvePluginDir } from '../../lib/resolve-plugin-dir.mjs';
|
|
6
|
-
|
|
7
|
-
export function help() {
|
|
8
|
-
console.log(`
|
|
9
|
-
Usage:
|
|
10
|
-
gamut plugin list [options]
|
|
11
|
-
|
|
12
|
-
Show installation status for all supported targets.
|
|
13
|
-
|
|
14
|
-
Options:
|
|
15
|
-
--output <path> [figma] Explicit path to DESIGN.md.
|
|
16
|
-
If omitted, walks up from cwd to find figma.config.json.
|
|
17
|
-
--plugin-dir <path> Override the bundled agent-tools directory
|
|
18
|
-
-h, --help Show this help message
|
|
19
|
-
|
|
20
|
-
Examples:
|
|
21
|
-
gamut plugin list
|
|
22
|
-
gamut plugin list --output ./docs/DESIGN.md
|
|
23
|
-
`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
/** @param {string} sourceRoot */
|
|
29
|
-
async function cursorStatus(sourceRoot) {
|
|
30
|
-
const dest = await cursorDestPath(sourceRoot);
|
|
31
|
-
const installed = !!(await stat(dest).catch(() => null));
|
|
32
|
-
return {
|
|
33
|
-
target: 'cursor',
|
|
34
|
-
status: installed ? '✓ installed' : '✗ not installed',
|
|
35
|
-
notes: installed ? dest : 'run: gamut plugin install cursor',
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function claudeStatus() {
|
|
40
|
-
// Claude Code doesn't expose a stable filesystem path we can check.
|
|
41
|
-
return {
|
|
42
|
-
target: 'claude',
|
|
43
|
-
status: '? unknown',
|
|
44
|
-
notes: 'run: claude plugin list',
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** @param {string | undefined} outputArg */
|
|
49
|
-
async function figmaStatus(outputArg) {
|
|
50
|
-
let dest;
|
|
51
|
-
if (outputArg) {
|
|
52
|
-
dest = outputArg;
|
|
53
|
-
} else {
|
|
54
|
-
const dir = await findFigmaConfigDir(process.cwd());
|
|
55
|
-
dest = dir ? `${dir}/DESIGN.md` : null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!dest) {
|
|
59
|
-
return {
|
|
60
|
-
target: 'figma',
|
|
61
|
-
status: '? unknown',
|
|
62
|
-
notes: 'figma.config.json not found — run from your project root or use --output',
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const installed = !!(await stat(dest).catch(() => null));
|
|
67
|
-
return {
|
|
68
|
-
target: 'figma',
|
|
69
|
-
status: installed ? '✓ installed' : '✗ not installed',
|
|
70
|
-
notes: installed ? dest : `run: gamut plugin install figma`,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* gamut plugin list
|
|
78
|
-
*
|
|
79
|
-
* Shows installation status for each supported target.
|
|
80
|
-
*
|
|
81
|
-
* @param {string[]} args
|
|
82
|
-
*/
|
|
83
|
-
export default async function list(args) {
|
|
84
|
-
const pluginDir = await resolvePluginDir(args);
|
|
85
|
-
const output = getFlag(args, '--output', undefined);
|
|
86
|
-
|
|
87
|
-
const rows = await Promise.all([
|
|
88
|
-
cursorStatus(pluginDir),
|
|
89
|
-
claudeStatus(),
|
|
90
|
-
figmaStatus(output),
|
|
91
|
-
]);
|
|
92
|
-
|
|
93
|
-
const col0 = Math.max(...rows.map((r) => r.target.length));
|
|
94
|
-
const col1 = Math.max(...rows.map((r) => r.status.length));
|
|
95
|
-
|
|
96
|
-
const header = `${'Target'.padEnd(col0)} ${'Status'.padEnd(col1)} Path / Notes`;
|
|
97
|
-
const rule = '─'.repeat(header.length);
|
|
98
|
-
|
|
99
|
-
console.log(`\n${header}`);
|
|
100
|
-
console.log(rule);
|
|
101
|
-
for (const row of rows) {
|
|
102
|
-
console.log(`${row.target.padEnd(col0)} ${row.status.padEnd(col1)} ${row.notes}`);
|
|
103
|
-
}
|
|
104
|
-
console.log();
|
|
105
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
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
|
-
}
|