From 5af454662f38ec812c1eaca54ba5953802c659e2 Mon Sep 17 00:00:00 2001 From: dreamwasp Date: Fri, 10 Apr 2026 14:59:11 -0400 Subject: [PATCH 1/3] feat(tools): set up skeleton for AI tooling --- tools/ai-tools/README.md | 42 +++++++++++++++++++ .../.claude-plugin/plugin.json | 8 ++++ .../skills/example-skeleton/SKILL.md | 8 ++++ .../.cursor-plugin/plugin.json | 14 +++++++ .../cursor-plugin-skeleton/rules/example.mdc | 8 ++++ .../skills/example-skeleton/SKILL.md | 8 ++++ tools/ai-tools/project.json | 16 +++++++ tools/ai-tools/scripts/validate-manifests.mjs | 17 ++++++++ 8 files changed, 121 insertions(+) create mode 100644 tools/ai-tools/README.md create mode 100644 tools/ai-tools/examples/claude-plugin-skeleton/.claude-plugin/plugin.json create mode 100644 tools/ai-tools/examples/claude-plugin-skeleton/skills/example-skeleton/SKILL.md create mode 100644 tools/ai-tools/examples/cursor-plugin-skeleton/.cursor-plugin/plugin.json create mode 100644 tools/ai-tools/examples/cursor-plugin-skeleton/rules/example.mdc create mode 100644 tools/ai-tools/examples/cursor-plugin-skeleton/skills/example-skeleton/SKILL.md create mode 100644 tools/ai-tools/project.json create mode 100644 tools/ai-tools/scripts/validate-manifests.mjs diff --git a/tools/ai-tools/README.md b/tools/ai-tools/README.md new file mode 100644 index 0000000000..b8599a4aa7 --- /dev/null +++ b/tools/ai-tools/README.md @@ -0,0 +1,42 @@ +# AI tooling (Gamut) + +Internal home for **Cursor plugins**, **Claude Code plugins**, and related agent tooling. Contents under `examples/` are **reference skeletons**—copy and adapt when starting something new; promote separate copies if they become production plugins. + +## Conventions + +- **Naming:** Use kebab-case for plugin directories and manifest `name` fields. +- **Review:** Treat changes like other shared engineering standards (PR review, clear purpose in the manifest `description`). +- **Scope:** Keep examples minimal; grow plugins in dedicated folders or repos when they need CI, tests, or release versioning. + +## Validate manifests + +From the repository root: + +```bash +npx nx run ai-tools:validate +``` + +This parses each example `plugin.json` so broken JSON is caught early. + +## Further reading + +### Official documentation + +- [Cursor — Plugins reference](https://cursor.com/docs/reference/plugins) +- [Claude Code — Create plugins](https://code.claude.com/docs/en/plugins) +- [Claude Code — Plugins reference (Anthropic docs)](https://docs.anthropic.com/en/docs/claude-code/plugins-reference) +- [Nx — Introduction](https://nx.dev/docs/getting-started/intro) +- [Nx — Crafting your workspace](https://nx.dev/docs/getting-started/tutorials/crafting-your-workspace) + +### Reference repositories + +- [cursor/plugins](https://github.com/cursor/plugins) — Spec, marketplace layout, multiple plugins in one repository +- [cursor/plugin-template](https://github.com/cursor/plugin-template) — Scaffold for single- and multi-plugin repos +- [planetscale/cursor-plugin](https://github.com/planetscale/cursor-plugin) — Third-party plugin example (skills, MCP-oriented patterns) +- [anthropics/claude-plugins-official](https://github.com/anthropics/claude-plugins-official) — Official Claude Code plugin catalog (`plugins/`, `external_plugins/`) +- [anthropics/claude-plugins-official — example-plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/example-plugin) — Reference plugin layout +- [modelcontextprotocol/servers](https://github.com/modelcontextprotocol/servers) — MCP server reference implementations + +### Monorepo structure + +- [Nx — Virtuous cycle of workspace structure](https://nx.dev/blog/virtuous-cycle-of-workspace-structure) diff --git a/tools/ai-tools/examples/claude-plugin-skeleton/.claude-plugin/plugin.json b/tools/ai-tools/examples/claude-plugin-skeleton/.claude-plugin/plugin.json new file mode 100644 index 0000000000..c14842912e --- /dev/null +++ b/tools/ai-tools/examples/claude-plugin-skeleton/.claude-plugin/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "claude-plugin-skeleton", + "description": "Minimal example Claude Code plugin for the Gamut repo. Replace name and metadata when copying.", + "author": { + "name": "Codecademy Engineers", + "email": "dev@codecademy.com" + } +} diff --git a/tools/ai-tools/examples/claude-plugin-skeleton/skills/example-skeleton/SKILL.md b/tools/ai-tools/examples/claude-plugin-skeleton/skills/example-skeleton/SKILL.md new file mode 100644 index 0000000000..e592c433d8 --- /dev/null +++ b/tools/ai-tools/examples/claude-plugin-skeleton/skills/example-skeleton/SKILL.md @@ -0,0 +1,8 @@ +--- +name: example-skeleton +description: Placeholder skill for the Gamut Claude Code plugin skeleton; replace when authoring a real skill. +--- + +# Example skill + +Use `skills//SKILL.md` with frontmatter `name` and `description`. Add optional scripts or references alongside this file as needed. diff --git a/tools/ai-tools/examples/cursor-plugin-skeleton/.cursor-plugin/plugin.json b/tools/ai-tools/examples/cursor-plugin-skeleton/.cursor-plugin/plugin.json new file mode 100644 index 0000000000..494cbd25f2 --- /dev/null +++ b/tools/ai-tools/examples/cursor-plugin-skeleton/.cursor-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "name": "cursor-plugin-skeleton", + "displayName": "Cursor plugin skeleton (Gamut)", + "version": "0.0.0", + "description": "Minimal example Cursor plugin for the Gamut repo. Replace name and paths when copying.", + "author": { + "name": "Codecademy Engineers", + "email": "dev@codecademy.com" + }, + "license": "MIT", + "keywords": ["gamut", "example", "skeleton"], + "rules": "./rules/", + "skills": "./skills/" +} diff --git a/tools/ai-tools/examples/cursor-plugin-skeleton/rules/example.mdc b/tools/ai-tools/examples/cursor-plugin-skeleton/rules/example.mdc new file mode 100644 index 0000000000..1bff90b85b --- /dev/null +++ b/tools/ai-tools/examples/cursor-plugin-skeleton/rules/example.mdc @@ -0,0 +1,8 @@ +--- +description: Example project rule for the Cursor plugin skeleton (replace with real guidance). +alwaysApply: false +--- + +# Example rule + +This file demonstrates where **rules** live in a Cursor plugin. Replace this content with conventions relevant to your project. diff --git a/tools/ai-tools/examples/cursor-plugin-skeleton/skills/example-skeleton/SKILL.md b/tools/ai-tools/examples/cursor-plugin-skeleton/skills/example-skeleton/SKILL.md new file mode 100644 index 0000000000..6a123d5a0c --- /dev/null +++ b/tools/ai-tools/examples/cursor-plugin-skeleton/skills/example-skeleton/SKILL.md @@ -0,0 +1,8 @@ +--- +name: example-skeleton +description: Placeholder skill for the Gamut Cursor plugin skeleton; replace when authoring a real skill. +--- + +# Example skill + +Use this folder layout for skills: `skills//SKILL.md` with frontmatter `name` and `description`. diff --git a/tools/ai-tools/project.json b/tools/ai-tools/project.json new file mode 100644 index 0000000000..6d80067c0b --- /dev/null +++ b/tools/ai-tools/project.json @@ -0,0 +1,16 @@ +{ + "name": "ai-tools", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "tools/ai-tools", + "projectType": "library", + "tags": ["scope:ai-tooling"], + "targets": { + "validate": { + "executor": "nx:run-commands", + "options": { + "commands": ["node tools/ai-tools/scripts/validate-manifests.mjs"], + "parallel": false + } + } + } +} diff --git a/tools/ai-tools/scripts/validate-manifests.mjs b/tools/ai-tools/scripts/validate-manifests.mjs new file mode 100644 index 0000000000..3d4f539716 --- /dev/null +++ b/tools/ai-tools/scripts/validate-manifests.mjs @@ -0,0 +1,17 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, '..'); + +const manifests = [ + path.join(root, 'examples/cursor-plugin-skeleton/.cursor-plugin/plugin.json'), + path.join(root, 'examples/claude-plugin-skeleton/.claude-plugin/plugin.json'), +]; + +for (const file of manifests) { + const text = fs.readFileSync(file, 'utf8'); + JSON.parse(text); + console.log('OK', path.relative(process.cwd(), file)); +} From d1435aca8eaff9d228edae40a6ffb7d12414d8bb Mon Sep 17 00:00:00 2001 From: dreamwasp Date: Mon, 13 Apr 2026 12:21:42 -0400 Subject: [PATCH 2/3] rename skeleton to template --- tools/ai-tools/README.md | 10 +- tools/ai-tools/agent-manager/README.md | 3 + tools/ai-tools/agent-manager/package.json | 16 ++ .../agent-manager/src/commands/install.ts | 193 ++++++++++++++++++ tools/ai-tools/agent-manager/src/index.ts | 23 +++ tools/ai-tools/agent-manager/tsconfig.json | 13 ++ .../.claude-plugin/marketplace.json | 9 + .../claude-plugin}/.claude-plugin/plugin.json | 3 +- .../skills/example-template}/SKILL.md | 4 +- .../cursor-plugin}/.cursor-plugin/plugin.json | 6 +- .../cursor-plugin}/rules/example.mdc | 2 +- .../skills/example-template}/SKILL.md | 4 +- tools/ai-tools/scripts/validate-manifests.mjs | 10 +- 13 files changed, 284 insertions(+), 12 deletions(-) create mode 100644 tools/ai-tools/agent-manager/README.md create mode 100644 tools/ai-tools/agent-manager/package.json create mode 100644 tools/ai-tools/agent-manager/src/commands/install.ts create mode 100644 tools/ai-tools/agent-manager/src/index.ts create mode 100644 tools/ai-tools/agent-manager/tsconfig.json create mode 100644 tools/ai-tools/examples/templates/claude-plugin/.claude-plugin/marketplace.json rename tools/ai-tools/examples/{claude-plugin-skeleton => templates/claude-plugin}/.claude-plugin/plugin.json (78%) rename tools/ai-tools/examples/{claude-plugin-skeleton/skills/example-skeleton => templates/claude-plugin/skills/example-template}/SKILL.md (76%) rename tools/ai-tools/examples/{cursor-plugin-skeleton => templates/cursor-plugin}/.cursor-plugin/plugin.json (68%) rename tools/ai-tools/examples/{cursor-plugin-skeleton => templates/cursor-plugin}/rules/example.mdc (76%) rename tools/ai-tools/examples/{cursor-plugin-skeleton/skills/example-skeleton => templates/cursor-plugin/skills/example-template}/SKILL.md (51%) diff --git a/tools/ai-tools/README.md b/tools/ai-tools/README.md index b8599a4aa7..39b1339a4c 100644 --- a/tools/ai-tools/README.md +++ b/tools/ai-tools/README.md @@ -1,6 +1,6 @@ # AI tooling (Gamut) -Internal home for **Cursor plugins**, **Claude Code plugins**, and related agent tooling. Contents under `examples/` are **reference skeletons**—copy and adapt when starting something new; promote separate copies if they become production plugins. +Internal home for **Cursor plugins**, **Claude Code plugins**, and related agent tooling. Contents under `examples/templates/` are **reference templates**—copy and adapt when starting something new; promote separate copies if they become production plugins. These are **not** published or distributed through Cursor or Claude Code marketplaces; they exist only as in-repo starting points. ## Conventions @@ -8,6 +8,14 @@ Internal home for **Cursor plugins**, **Claude Code plugins**, and related agent - **Review:** Treat changes like other shared engineering standards (PR review, clear purpose in the manifest `description`). - **Scope:** Keep examples minimal; grow plugins in dedicated folders or repos when they need CI, tests, or release versioning. +## Claude Code and these templates + +Use a copied template with **`--plugin-dir`** pointing at the plugin folder (for example `examples/templates/claude-plugin` while experimenting in this repo). See [Claude Code — Create plugins](https://code.claude.com/docs/en/plugins). + +We are **not** planning to distribute these templates through a plugin marketplace. If you later publish your own plugin, see Anthropic’s docs for [plugin marketplaces](https://code.claude.com/docs/en/plugin-marketplaces); that is separate from these Gamut reference templates. + +The Claude template includes a minimal `.claude-plugin/marketplace.json` **only** so the optional Gamut `install-plugin` helper can resolve a local `plugin@marketplace` spec when registering this directory with the Claude CLI on your machine. It does **not** imply publishing or listing these templates anywhere. + ## Validate manifests From the repository root: diff --git a/tools/ai-tools/agent-manager/README.md b/tools/ai-tools/agent-manager/README.md new file mode 100644 index 0000000000..9f17564fda --- /dev/null +++ b/tools/ai-tools/agent-manager/README.md @@ -0,0 +1,3 @@ +# agent-manager (vendored) + +Local copy of the web-platform CLI used by `yarn install-plugin` at the Gamut repo root. Canonical source and history: `experiments/agent-manager` in the web-platform repository. diff --git a/tools/ai-tools/agent-manager/package.json b/tools/ai-tools/agent-manager/package.json new file mode 100644 index 0000000000..659577286b --- /dev/null +++ b/tools/ai-tools/agent-manager/package.json @@ -0,0 +1,16 @@ +{ + "name": "agent-manager", + "version": "0.0.1", + "description": "A command-line helper to manage agent tooling.", + "type": "module", + "main": "src/index.ts", + "scripts": { + "typecheck": "tsc -p tsconfig.json" + }, + "dependencies": { + "@cliffy/command": "npm:@jsr/cliffy__command@^1.0.0" + }, + "devDependencies": { + "@types/node": "^25.5.0" + } +} diff --git a/tools/ai-tools/agent-manager/src/commands/install.ts b/tools/ai-tools/agent-manager/src/commands/install.ts new file mode 100644 index 0000000000..65c671661f --- /dev/null +++ b/tools/ai-tools/agent-manager/src/commands/install.ts @@ -0,0 +1,193 @@ +import { Command, EnumType } from '@cliffy/command'; +import { spawn } from 'node:child_process'; +import { cp, mkdir, readFile, rm, stat, symlink } from 'node:fs/promises'; +import { homedir } from 'node:os'; +import { basename, join, resolve as resolvePath } from 'node:path'; +import process from 'node:process'; + +const CURSOR_INSTALL_METHOD = process.env.CURSOR_INSTALL_METHOD ?? 'copy'; + +export type Agent = 'cursor' | 'claude'; + +type MarketplaceJson = { + name?: string; + plugins?: { name?: string; source?: string }[]; +}; + +function expandUserPath(raw: string): string { + if (raw === '~') { + return homedir(); + } + if (raw.startsWith('~/')) { + return join(homedir(), raw.slice(2)); + } + return raw; +} + +async function resolvePluginSource(raw: string): Promise { + const expanded = expandUserPath(raw); + const root = resolvePath(expanded); + const st = await stat(root).catch(() => undefined); + if (!st?.isDirectory()) { + throw new Error(`Source is not a directory: ${raw} → ${root}`); + } + return root; +} + +async function cursorDestFolderName(sourceRoot: string): Promise { + const cursorManifest = join(sourceRoot, '.cursor-plugin', 'plugin.json'); + try { + const text = await readFile(cursorManifest, 'utf8'); + const j = JSON.parse(text) as { name?: string }; + if (j.name && typeof j.name === 'string') { + return j.name.replace(/^@/, '').replace(/\//g, '-'); + } + } catch { + /* no manifest */ + } + return basename(sourceRoot); +} + +async function claudePluginSpecFromMarketplace( + sourceRoot: string +): Promise { + const mp = join(sourceRoot, '.claude-plugin', 'marketplace.json'); + let text: string; + try { + text = await readFile(mp, 'utf8'); + } catch { + throw new Error( + `Missing ${mp}. For Claude Code, add a local marketplace file (see https://code.claude.com/docs/en/plugin-marketplaces ) or use: claude --plugin-dir ${sourceRoot}` + ); + } + const j = JSON.parse(text) as MarketplaceJson; + const marketplaceName = j.name; + const plugins = j.plugins; + if (!marketplaceName || !Array.isArray(plugins) || plugins.length === 0) { + throw new Error( + `Invalid marketplace.json (need name and plugins[]): ${mp}` + ); + } + const entry = + plugins.find( + (p) => p.source === './' || p.source === '.' || p.source === undefined + ) ?? plugins[0]; + const pluginName = entry?.name; + if (!pluginName) { + throw new Error(`No plugin name in marketplace.json plugins[]: ${mp}`); + } + return `${pluginName}@${marketplaceName}`; +} + +function runCommand(command: string, args: string[]): Promise { + return new Promise((resolveCode, reject) => { + const child = spawn(command, args, { stdio: 'inherit', shell: false }); + child.on('error', (err: NodeJS.ErrnoException) => { + if (err.code === 'ENOENT') { + reject(new Error(`${command} not found on PATH.`)); + } else { + reject(err); + } + }); + child.on('close', (code) => resolveCode(code ?? 1)); + }); +} + +/** + * Registers the plugin folder as a local marketplace and installs into Claude’s plugin cache + * (see https://code.claude.com/docs/en/plugin-marketplaces ). + */ +async function installClaudeCode( + sourceRoot: string, + pluginSpec: string +): Promise { + const root = resolvePath(sourceRoot); + const marketplaceName = pluginSpec.split('@')[1]; + if (!marketplaceName) { + throw new Error(`Invalid plugin spec: ${pluginSpec}`); + } + + let code = await runCommand('claude', [ + 'plugin', + 'marketplace', + 'add', + root, + '--scope', + 'user', + ]); + if (code !== 0) { + console.warn( + `warning: claude plugin marketplace add exited ${code} (if the marketplace is already registered, you can ignore this).` + ); + code = await runCommand('claude', [ + 'plugin', + 'marketplace', + 'update', + marketplaceName, + ]); + if (code !== 0) { + throw new Error( + `claude plugin marketplace add/update failed (${code}). Try: claude plugin marketplace add ${root}` + ); + } + } + + code = await runCommand('claude', [ + 'plugin', + 'install', + pluginSpec, + '--scope', + 'user', + ]); + if (code !== 0) { + throw new Error( + `claude plugin install failed (${code}). Try: claude plugin install ${pluginSpec} --scope user` + ); + } + + console.log( + `Claude Code: installed ${pluginSpec} (user scope). Run /reload-plugins in Claude if needed.` + ); + console.log( + `One-off without install: claude --plugin-dir ${root} (https://code.claude.com/docs/en/plugins )` + ); +} + +async function installCursor( + sourceRoot: string, + destFolder: string +): Promise { + const home = homedir(); + const destRoot = + process.env.CURSOR_PLUGINS_LOCAL ?? + join(home, '.cursor', 'plugins', 'local'); + const dest = join(destRoot, destFolder); + await mkdir(destRoot, { recursive: true }); + await rm(dest, { recursive: true, force: true }); + if (CURSOR_INSTALL_METHOD === 'copy') { + await cp(sourceRoot, dest, { recursive: true }); + } else { + await symlink(sourceRoot, dest, 'dir'); + } + console.log(`Cursor plugin installed to ${dest}`); +} + +const installCmd = new Command() + .description('Install local agent tools.') + .arguments('') + .type('agent', new EnumType(['cursor', 'claude'])) + .option('-a, --agent ', 'Target agent.', { default: 'cursor' }) + .action(async ({ agent }, source: string) => { + const src = await resolvePluginSource(source); + if (agent === 'claude') { + const spec = await claudePluginSpecFromMarketplace(src); + return installClaudeCode(src, spec); + } + if (agent === 'cursor') { + const destFolder = await cursorDestFolderName(src); + return installCursor(src, destFolder); + } + throw new Error(`Unknown agent: ${agent}`); + }); + +export default installCmd; diff --git a/tools/ai-tools/agent-manager/src/index.ts b/tools/ai-tools/agent-manager/src/index.ts new file mode 100644 index 0000000000..d725dc65b9 --- /dev/null +++ b/tools/ai-tools/agent-manager/src/index.ts @@ -0,0 +1,23 @@ +#! /usr/bin/env tsx + +import { Command } from '@cliffy/command'; +import { readFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +import installCmd from './commands/install.js'; + +const pkg = JSON.parse( + await readFile( + join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), + 'utf8' + ) +) as { version: string; description: string }; + +await new Command() + .name('agent-manager') + .version(pkg.version) + .description(pkg.description) + .command('install', installCmd) + .parse(process.argv.slice(2)); diff --git a/tools/ai-tools/agent-manager/tsconfig.json b/tools/ai-tools/agent-manager/tsconfig.json new file mode 100644 index 0000000000..6fc6e582d4 --- /dev/null +++ b/tools/ai-tools/agent-manager/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/tools/ai-tools/examples/templates/claude-plugin/.claude-plugin/marketplace.json b/tools/ai-tools/examples/templates/claude-plugin/.claude-plugin/marketplace.json new file mode 100644 index 0000000000..544751d64d --- /dev/null +++ b/tools/ai-tools/examples/templates/claude-plugin/.claude-plugin/marketplace.json @@ -0,0 +1,9 @@ +{ + "name": "gamut-ai-tools-templates", + "plugins": [ + { + "name": "claude-plugin-template", + "source": "./" + } + ] +} diff --git a/tools/ai-tools/examples/claude-plugin-skeleton/.claude-plugin/plugin.json b/tools/ai-tools/examples/templates/claude-plugin/.claude-plugin/plugin.json similarity index 78% rename from tools/ai-tools/examples/claude-plugin-skeleton/.claude-plugin/plugin.json rename to tools/ai-tools/examples/templates/claude-plugin/.claude-plugin/plugin.json index c14842912e..9ebf14124c 100644 --- a/tools/ai-tools/examples/claude-plugin-skeleton/.claude-plugin/plugin.json +++ b/tools/ai-tools/examples/templates/claude-plugin/.claude-plugin/plugin.json @@ -1,6 +1,7 @@ { - "name": "claude-plugin-skeleton", + "name": "claude-plugin-template", "description": "Minimal example Claude Code plugin for the Gamut repo. Replace name and metadata when copying.", + "version": "0.0.0", "author": { "name": "Codecademy Engineers", "email": "dev@codecademy.com" diff --git a/tools/ai-tools/examples/claude-plugin-skeleton/skills/example-skeleton/SKILL.md b/tools/ai-tools/examples/templates/claude-plugin/skills/example-template/SKILL.md similarity index 76% rename from tools/ai-tools/examples/claude-plugin-skeleton/skills/example-skeleton/SKILL.md rename to tools/ai-tools/examples/templates/claude-plugin/skills/example-template/SKILL.md index e592c433d8..c7ec7ca5b7 100644 --- a/tools/ai-tools/examples/claude-plugin-skeleton/skills/example-skeleton/SKILL.md +++ b/tools/ai-tools/examples/templates/claude-plugin/skills/example-template/SKILL.md @@ -1,6 +1,6 @@ --- -name: example-skeleton -description: Placeholder skill for the Gamut Claude Code plugin skeleton; replace when authoring a real skill. +name: example-template +description: Placeholder skill for the Gamut Claude Code plugin template; replace when authoring a real skill. --- # Example skill diff --git a/tools/ai-tools/examples/cursor-plugin-skeleton/.cursor-plugin/plugin.json b/tools/ai-tools/examples/templates/cursor-plugin/.cursor-plugin/plugin.json similarity index 68% rename from tools/ai-tools/examples/cursor-plugin-skeleton/.cursor-plugin/plugin.json rename to tools/ai-tools/examples/templates/cursor-plugin/.cursor-plugin/plugin.json index 494cbd25f2..e0ceac6d85 100644 --- a/tools/ai-tools/examples/cursor-plugin-skeleton/.cursor-plugin/plugin.json +++ b/tools/ai-tools/examples/templates/cursor-plugin/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { - "name": "cursor-plugin-skeleton", - "displayName": "Cursor plugin skeleton (Gamut)", + "name": "cursor-plugin-template", + "displayName": "Cursor plugin template (Gamut)", "version": "0.0.0", "description": "Minimal example Cursor plugin for the Gamut repo. Replace name and paths when copying.", "author": { @@ -8,7 +8,7 @@ "email": "dev@codecademy.com" }, "license": "MIT", - "keywords": ["gamut", "example", "skeleton"], + "keywords": ["gamut", "example", "template"], "rules": "./rules/", "skills": "./skills/" } diff --git a/tools/ai-tools/examples/cursor-plugin-skeleton/rules/example.mdc b/tools/ai-tools/examples/templates/cursor-plugin/rules/example.mdc similarity index 76% rename from tools/ai-tools/examples/cursor-plugin-skeleton/rules/example.mdc rename to tools/ai-tools/examples/templates/cursor-plugin/rules/example.mdc index 1bff90b85b..4f1ade302b 100644 --- a/tools/ai-tools/examples/cursor-plugin-skeleton/rules/example.mdc +++ b/tools/ai-tools/examples/templates/cursor-plugin/rules/example.mdc @@ -1,5 +1,5 @@ --- -description: Example project rule for the Cursor plugin skeleton (replace with real guidance). +description: Example project rule for the Cursor plugin template (replace with real guidance). alwaysApply: false --- diff --git a/tools/ai-tools/examples/cursor-plugin-skeleton/skills/example-skeleton/SKILL.md b/tools/ai-tools/examples/templates/cursor-plugin/skills/example-template/SKILL.md similarity index 51% rename from tools/ai-tools/examples/cursor-plugin-skeleton/skills/example-skeleton/SKILL.md rename to tools/ai-tools/examples/templates/cursor-plugin/skills/example-template/SKILL.md index 6a123d5a0c..61b7219d3b 100644 --- a/tools/ai-tools/examples/cursor-plugin-skeleton/skills/example-skeleton/SKILL.md +++ b/tools/ai-tools/examples/templates/cursor-plugin/skills/example-template/SKILL.md @@ -1,6 +1,6 @@ --- -name: example-skeleton -description: Placeholder skill for the Gamut Cursor plugin skeleton; replace when authoring a real skill. +name: example-template +description: Placeholder skill for the Gamut Cursor plugin template; replace when authoring a real skill. --- # Example skill diff --git a/tools/ai-tools/scripts/validate-manifests.mjs b/tools/ai-tools/scripts/validate-manifests.mjs index 3d4f539716..10a379d128 100644 --- a/tools/ai-tools/scripts/validate-manifests.mjs +++ b/tools/ai-tools/scripts/validate-manifests.mjs @@ -6,8 +6,14 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(__dirname, '..'); const manifests = [ - path.join(root, 'examples/cursor-plugin-skeleton/.cursor-plugin/plugin.json'), - path.join(root, 'examples/claude-plugin-skeleton/.claude-plugin/plugin.json'), + path.join( + root, + 'examples/templates/cursor-plugin/.cursor-plugin/plugin.json' + ), + path.join( + root, + 'examples/templates/claude-plugin/.claude-plugin/plugin.json' + ), ]; for (const file of manifests) { From dcfbd3a2de480ba3d3aeee1da8e57bd61b51a2d9 Mon Sep 17 00:00:00 2001 From: dreamwasp Date: Mon, 13 Apr 2026 13:49:56 -0400 Subject: [PATCH 3/3] fix --- .../agent-manager/src/commands/install.ts | 20 +++++++++---------- tools/ai-tools/scripts/validate-manifests.mjs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tools/ai-tools/agent-manager/src/commands/install.ts b/tools/ai-tools/agent-manager/src/commands/install.ts index 65c671661f..9359541677 100644 --- a/tools/ai-tools/agent-manager/src/commands/install.ts +++ b/tools/ai-tools/agent-manager/src/commands/install.ts @@ -60,9 +60,9 @@ async function claudePluginSpecFromMarketplace( `Missing ${mp}. For Claude Code, add a local marketplace file (see https://code.claude.com/docs/en/plugin-marketplaces ) or use: claude --plugin-dir ${sourceRoot}` ); } - const j = JSON.parse(text) as MarketplaceJson; - const marketplaceName = j.name; - const plugins = j.plugins; + const { name: marketplaceName, plugins } = JSON.parse( + text + ) as MarketplaceJson; if (!marketplaceName || !Array.isArray(plugins) || plugins.length === 0) { throw new Error( `Invalid marketplace.json (need name and plugins[]): ${mp}` @@ -116,8 +116,8 @@ async function installClaudeCode( 'user', ]); if (code !== 0) { - console.warn( - `warning: claude plugin marketplace add exited ${code} (if the marketplace is already registered, you can ignore this).` + process.stderr.write( + `warning: claude plugin marketplace add exited ${code} (if the marketplace is already registered, you can ignore this).\n` ); code = await runCommand('claude', [ 'plugin', @@ -145,11 +145,11 @@ async function installClaudeCode( ); } - console.log( - `Claude Code: installed ${pluginSpec} (user scope). Run /reload-plugins in Claude if needed.` + process.stdout.write( + `Claude Code: installed ${pluginSpec} (user scope). Run /reload-plugins in Claude if needed.\n` ); - console.log( - `One-off without install: claude --plugin-dir ${root} (https://code.claude.com/docs/en/plugins )` + process.stdout.write( + `One-off without install: claude --plugin-dir ${root} (https://code.claude.com/docs/en/plugins )\n` ); } @@ -169,7 +169,7 @@ async function installCursor( } else { await symlink(sourceRoot, dest, 'dir'); } - console.log(`Cursor plugin installed to ${dest}`); + process.stdout.write(`Cursor plugin installed to ${dest}\n`); } const installCmd = new Command() diff --git a/tools/ai-tools/scripts/validate-manifests.mjs b/tools/ai-tools/scripts/validate-manifests.mjs index 10a379d128..c4d62a0cdc 100644 --- a/tools/ai-tools/scripts/validate-manifests.mjs +++ b/tools/ai-tools/scripts/validate-manifests.mjs @@ -19,5 +19,5 @@ const manifests = [ for (const file of manifests) { const text = fs.readFileSync(file, 'utf8'); JSON.parse(text); - console.log('OK', path.relative(process.cwd(), file)); + process.stdout.write(`OK ${path.relative(process.cwd(), file)}\n`); }