Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .nx/version-plans/version-plan-1778682208750.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
gamut-agent-tools: prerelease
gamut: minor
---

Create new gamut-agent-tools package
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@babel/preset-typescript": "^7.24.7",
"@codecademy/eslint-config": "8.0.0",
"@codecademy/gamut": "workspace:*",
"@codecademy/gamut-agent-tools": "workspace:*",
"@codecademy/prettier-config": "^0.2.0",
"@codecademy/tsconfig": "^0.3.0",
"@commander-js/extra-typings": "^14.0.0",
Expand Down
648 changes: 648 additions & 0 deletions packages/gamut-agent-tools/DESIGN.Codecademy.md

Large diffs are not rendered by default.

446 changes: 446 additions & 0 deletions packages/gamut-agent-tools/DESIGN.LXStudio.md

Large diffs are not rendered by default.

437 changes: 437 additions & 0 deletions packages/gamut-agent-tools/DESIGN.Percipio.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/gamut-agent-tools/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DEPRECATED. Use the appropriate DESIGN.\*.md from https://github.com/Codecademy/gamut/pull/3329 instead.
126 changes: 126 additions & 0 deletions packages/gamut-agent-tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# @codecademy/gamut-agent-tools

Static assets and a small **CLI** for AI-assisted development against the [Gamut](https://github.com/Codecademy/gamut) design system: Cursor/Claude plugin manifests, **skills**, **rules**, **guidelines**, **commands**, and product-specific **DESIGN** references.

## Install

Add it as a **development dependency** so skills, rules, and the CLI stay out of production bundles:

```bash
yarn add -D @codecademy/gamut-agent-tools
# or
npm install --save-dev @codecademy/gamut-agent-tools
```

If you truly need this package in a production `dependencies` tree (unusual), use `yarn add` / `npm install` without the dev flag.

The published binary is **`gamut-agent-tools`**. After install, run it via `yarn gamut-agent-tools`, `npx gamut-agent-tools`, or your package manager’s bin path.

**`--help`** on the CLI prints a short summary; full detail lives in this README.

## CLI overview

All subcommands use the shape:

```text
gamut-agent-tools plugin <subcommand> [target] [options]
```

The first argument after the binary must be **`plugin`**.

### Subcommands

| Subcommand | Purpose |
| ---------- | ------------------------------------------------------------------------------- |
| `install` | Install assets into Cursor, Claude Code, or a Figma-oriented `guidelines/` tree |
| `remove` | Remove a prior installation for the chosen target |
| `update` | Re-run `install` in place (same flags as install) |
| `list` | Show status for cursor / claude / figma |

### Targets

| Target | Default? | Behavior |
| -------- | ------------------------- | ------------------------------------------------------------------------------------------------------ |
| `cursor` | yes (when target omitted) | Copies scoped content into Cursor local plugins (see `.cursor-plugin/plugin.json`) |
| `claude` | | Registers and installs via `claude plugin …` using `.claude-plugin/marketplace.json` |
| `figma` | | Copies `guidelines/` from this package to a destination derived from `--output` or `figma.config.json` |

### Scopes (Cursor only: `install` / `update`)

| Scope | Meaning |
| ---------- | ------------------------------------------------------------------------------------------------------ |
| `all` | Default — install all top-level content dirs except ignored ones (e.g. `.claude-plugin`, `guidelines`) |
| `skills` | Only `skills/` |
| `rules` | Only `rules/` |
| `commands` | Only `commands/` |
| `agents` | Only `agents/` |

### Options

| Option | Subcommands | Meaning |
| --------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--scope <scope>` | `install`, `update` | Cursor scope (see table above). Ignored for claude/figma where not applicable. |
| `--output <path>` | `install figma`, `remove figma`, `list` | **Figma:** explicit path. For install: destination **directory** for `guidelines/` (parent of the folder you want). For remove/list: path to **`DESIGN.md`** or related when not using discovery. |
| `--plugin-dir <path>` | all | Override the **source** directory (defaults to the root of the installed `@codecademy/gamut-agent-tools` package — the assets shipped with the CLI). |

### Default plugin source

Unless you pass `--plugin-dir`, the CLI uses the **installed package root** of `@codecademy/gamut-agent-tools` (the copy on disk next to this `bin/` tree). You do not need to resolve the package from cwd.

### Environment

| Variable | Effect |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CURSOR_INSTALL_METHOD` | If set to something other than `copy`, Cursor install uses a **symlink** of the whole source tree instead of copying (dev convenience). Default behavior is `copy`. |
| `CURSOR_PLUGINS_LOCAL` | Overrides the base directory for Cursor local plugins (default: `~/.cursor/plugins/local`). |

## Examples

```bash
# Cursor — full install (default)
gamut-agent-tools plugin install

# Cursor — skills only
gamut-agent-tools plugin install cursor --scope skills

# Claude Code
gamut-agent-tools plugin install claude

# Figma — explicit guidelines destination
gamut-agent-tools plugin install figma --output /path/to/your/project/guidelines

# Figma — discover figma.config.json upward from cwd
gamut-agent-tools plugin install figma

# Status
gamut-agent-tools plugin list
gamut-agent-tools plugin list --output ./docs/DESIGN.md

# Remove / refresh
gamut-agent-tools plugin remove claude
gamut-agent-tools plugin update cursor --scope rules
```

### Custom or unreleased source tree

```bash
gamut-agent-tools plugin install cursor --plugin-dir /path/to/checkout/packages/gamut-agent-tools
```

## Package layout

| Path | Purpose |
| ----------------- | -------------------------------------------------------------------------------------- |
| `bin/` | `gamut-agent-tools` CLI entry (`bin/cli.mjs`) and implementation |
| `skills/` | Agent skills (e.g. theming, typography, accessibility) |
| `rules/` | Rule files (e.g. accessibility) |
| `guidelines/` | Written guidelines for foundations and components (Figma install target) |
| `commands/` | Command specs (e.g. `gamut-review`) |
| `agents/` | Placeholder for future agent definitions |
| `DESIGN*.md` | Product design references; consumers often copy one to `DESIGN.md` at the project root |
| `.cursor-plugin/` | Cursor local plugin manifest |
| `.claude-plugin/` | Claude plugin manifest and marketplace metadata |

## Repository

Published from the [Codecademy/gamut](https://github.com/Codecademy/gamut) monorepo. Versioning and release are handled in CI like other `@codecademy/*` packages in this repo.
68 changes: 68 additions & 0 deletions packages/gamut-agent-tools/bin/cli.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node

const DOCS =
'https://github.com/Codecademy/gamut/tree/main/packages/gamut-agent-tools#readme';

const args = process.argv.slice(2);
const [noun, verb, ...rest] = args;

if (!noun || noun === '--help' || noun === '-h') {
printHelp();
process.exit(noun ? 0 : 1);
}

if (noun !== 'plugin') {
console.error(`Unknown command: "${noun}"`);
printHelp();
process.exit(1);
}

if (!verb || verb === '--help' || verb === '-h') {
printPluginHelp();
process.exit(verb ? 0 : 1);
}

let cmd;
try {
cmd = await import(`./commands/plugin/${verb}.mjs`);
} catch {
console.error(`Unknown plugin subcommand: "${verb}"`);
printPluginHelp();
process.exit(1);
}

if (rest.includes('--help') || rest.includes('-h')) {
cmd.help();
process.exit(0);
}

try {
await cmd.default(rest);
} catch (/** @type {any} */ err) {
console.error(`Error: ${err.message}`);
process.exit(1);
}

function printHelp() {
console.log(`
gamut-agent-tools — install Gamut agent assets into Cursor, Claude Code, or Figma workflows

Usage:
gamut-agent-tools plugin <subcommand> [options]

Documentation (commands, targets, scopes, migration from \`gamut plugin\`):
${DOCS}
`);
}

function printPluginHelp() {
console.log(`
gamut-agent-tools plugin

Subcommands:
install [target] remove [target] update [target] list

Documentation:
${DOCS}
`);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,10 @@ export const SCOPES = ['all', 'skills', 'rules', 'commands', 'agents'];

export function help() {
console.log(`
Usage:
gamut plugin install [target] [options]

Install the Gamut plugin into an AI or design tool.

Arguments:
target Tool to install into (default: cursor)
cursor | claude | figma

Options:
--scope <scope> Content to install (default: all)
all | skills | rules | commands | agents
--output <path> [figma] Explicit destination directory for guidelines/.
If omitted, walks up from cwd to find figma.config.json.
--plugin-dir <path> Override the bundled agent-tools directory
-h, --help Show this help message

Examples:
gamut plugin install
gamut plugin install claude
gamut plugin install figma
gamut plugin install figma --output /path/to/project/guidelines
gamut plugin install cursor --scope skills
gamut plugin install cursor --plugin-dir ./my-agent-tools
Usage: gamut-agent-tools plugin install [cursor|claude|figma] [options]

Full reference (targets, scopes, figma --output, --plugin-dir, examples):
https://github.com/Codecademy/gamut/tree/main/packages/gamut-agent-tools#readme
`);
}

Expand All @@ -44,7 +24,7 @@ Examples:
/** Directories in the plugin source that should not be installed to Cursor. */
const CURSOR_IGNORE = new Set([
'.claude-plugin', // Claude Code manifest — not a Cursor concept
'guidelines', // Figma Make only
'guidelines', // Figma Make only
]);

/** @param {string} sourceRoot @param {string} scope */
Expand All @@ -63,20 +43,29 @@ async function installCursor(sourceRoot, scope) {
await rm(dest, { recursive: true, force: true });
await mkdir(dest, { recursive: true });

await cp(`${sourceRoot}/.cursor-plugin`, `${dest}/.cursor-plugin`, { recursive: true });
await cp(`${sourceRoot}/.cursor-plugin`, `${dest}/.cursor-plugin`, {
recursive: true,
});

let dirs;
if (scope === 'all') {
const entries = await readdir(sourceRoot, { withFileTypes: true });
dirs = entries
.filter((e) => e.isDirectory() && !e.name.startsWith('.') && !CURSOR_IGNORE.has(e.name))
.filter(
(e) =>
e.isDirectory() &&
!e.name.startsWith('.') &&
!CURSOR_IGNORE.has(e.name)
)
.map((e) => e.name);
} else {
dirs = [scope];
}

for (const dir of dirs) {
await cp(`${sourceRoot}/${dir}`, `${dest}/${dir}`, { recursive: true }).catch(() => {
await cp(`${sourceRoot}/${dir}`, `${dest}/${dir}`, {
recursive: true,
}).catch(() => {
// directory may be empty/missing — not an error
});
}
Expand All @@ -95,31 +84,51 @@ async function installClaude(sourceRoot) {
const mpName = marketplaceName(spec);
const root = resolve(sourceRoot);

let code = await runCommand('claude', ['plugin', 'marketplace', 'add', root, '--scope', 'user']);
let code = await runCommand('claude', [
'plugin',
'marketplace',
'add',
root,
'--scope',
'user',
]);
if (code !== 0) {
console.warn(
`warning: "claude plugin marketplace add" exited ${code} — ` +
`if it's already registered this is safe to ignore.`,
`if it's already registered this is safe to ignore.`
);
code = await runCommand('claude', ['plugin', 'marketplace', 'update', mpName]);
code = await runCommand('claude', [
'plugin',
'marketplace',
'update',
mpName,
]);
if (code !== 0) {
throw new Error(
`claude plugin marketplace add/update failed (exit ${code}).\n` +
`Try manually: claude plugin marketplace add ${root}`,
`Try manually: claude plugin marketplace add ${root}`
);
}
}

code = await runCommand('claude', ['plugin', 'install', spec, '--scope', 'user']);
code = await runCommand('claude', [
'plugin',
'install',
spec,
'--scope',
'user',
]);
if (code !== 0) {
throw new Error(
`claude plugin install failed (exit ${code}).\n` +
`Try manually: claude plugin install ${spec} --scope user`,
`Try manually: claude plugin install ${spec} --scope user`
);
}

console.log(`Claude Code: installed ${spec} (user scope)`);
console.log(` Tip: run /reload-plugins in Claude Code if skills don't appear immediately.`);
console.log(
` Tip: run /reload-plugins in Claude Code if skills don't appear immediately.`
);
console.log(` One-off without install: claude --plugin-dir ${root}`);
}

Expand All @@ -138,14 +147,15 @@ async function installFigma(sourceRoot, outputArg) {
await rm(dest, { recursive: true, force: true });
await cp(src, dest, { recursive: true });
console.log(`Figma: installed guidelines/ → ${dest}`);
console.log(` In Figma Make, point your kit at this guidelines/ directory for design system context.`);
console.log(
` In Figma Make, point your kit at this guidelines/ directory for design system context.`
);
}

// ---------------------------------------------------------------------------

/**
* gamut plugin install [cursor|claude|figma] [--scope all|skills|rules|commands|agents]
* [--plugin-dir <path>]
* gamut-agent-tools plugin install [cursor|claude|figma] [--scope …] [--plugin-dir …]
*
* @param {string[]} args
*/
Expand All @@ -154,10 +164,14 @@ export default async function install(args) {
const scope = getFlag(args, '--scope', 'all') ?? 'all';

if (!TARGETS.includes(target)) {
throw new Error(`Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}`);
throw new Error(
`Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}`
);
}
if (!SCOPES.includes(scope)) {
throw new Error(`Unknown scope: "${scope}". Choose from: ${SCOPES.join(', ')}`);
throw new Error(
`Unknown scope: "${scope}". Choose from: ${SCOPES.join(', ')}`
);
}

const pluginDir = await resolvePluginDir(args);
Expand Down
Loading
Loading