From b6b334a10931c5e2a970861e429b7870efa3080c Mon Sep 17 00:00:00 2001 From: Railly Date: Tue, 31 Mar 2026 17:13:35 -0500 Subject: [PATCH 1/3] feat(init): add framework-to-skills mapping and install harness Adds skills.ts with framework detection -> skill resolution mapping. Uses `npx skills add clerk/skills` under the hood to install the right Clerk skills for the detected framework (e.g. Next.js -> clerk-nextjs-patterns). Base skills (clerk, clerk-setup) are always included. Part of AIE-728 --- packages/cli-core/src/commands/init/skills.ts | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 packages/cli-core/src/commands/init/skills.ts diff --git a/packages/cli-core/src/commands/init/skills.ts b/packages/cli-core/src/commands/init/skills.ts new file mode 100644 index 00000000..7f818ddd --- /dev/null +++ b/packages/cli-core/src/commands/init/skills.ts @@ -0,0 +1,78 @@ +/** + * Install Clerk agent skills after scaffolding. + * + * Maps the detected framework to the appropriate skill set from + * github.com/clerk/skills, then installs via `npx skills add`. + */ + +import { dim, cyan, yellow } from "../../lib/color.js"; +import { isHuman } from "../../mode.js"; +import { confirm } from "../../lib/prompts.js"; + +/** Skills installed regardless of framework. */ +const BASE_SKILLS = ["clerk", "clerk-setup"]; + +/** Maps framework dep (from package.json) to the skill name in clerk/skills. */ +const FRAMEWORK_SKILL_MAP: Record = { + next: "clerk-nextjs-patterns", + react: "clerk-react-patterns", + "react-router": "clerk-react-router-patterns", + vue: "clerk-vue-patterns", + nuxt: "clerk-nuxt-patterns", + astro: "clerk-astro-patterns", + "@tanstack/react-start": "clerk-tanstack-patterns", + expo: "clerk-expo-patterns", + express: "clerk-backend-api", + fastify: "clerk-backend-api", +}; + +const SKILLS_SOURCE = "clerk/skills"; + +function resolveSkills(frameworkDep: string | undefined): string[] { + const skills = [...BASE_SKILLS]; + if (frameworkDep && FRAMEWORK_SKILL_MAP[frameworkDep]) { + skills.push(FRAMEWORK_SKILL_MAP[frameworkDep]); + } + return skills; +} + +export async function installSkills( + cwd: string, + frameworkDep: string | undefined, + skipPrompt: boolean, +): Promise { + const skills = resolveSkills(frameworkDep); + const skillList = skills.join(", "); + + if (isHuman() && !skipPrompt) { + const install = await confirm({ + message: `Install agent skills? (${skillList})`, + default: true, + }); + if (!install) return; + } + + console.log(`\nInstalling skills: ${cyan(skillList)}`); + + const skillFlags = skills.flatMap((s) => ["--skill", s]); + const proc = Bun.spawn( + ["npx", "skills", "add", SKILLS_SOURCE, ...skillFlags, "--agent", "*", "-y"], + { + cwd, + stdout: "inherit", + stderr: "inherit", + }, + ); + const exitCode = await proc.exited; + + if (exitCode !== 0) { + console.log( + yellow( + `\nSkills installation failed. You can install manually: npx skills add ${SKILLS_SOURCE}`, + ), + ); + return; + } + + console.log(dim("\nAgent skills installed. AI agents now have Clerk context in this project.")); +} From 4973238533a35b12c137aac46ad1dcf3140fffe9 Mon Sep 17 00:00:00 2001 From: Railly Date: Tue, 31 Mar 2026 17:13:41 -0500 Subject: [PATCH 2/3] feat(init): hook skills installation into init flow After scaffolding completes, clerk init now offers to install agent skills via npx skills add. Auto-detects framework and installs the matching skill set. Opt-out via --no-skills flag. Part of AIE-728 --- packages/cli-core/src/cli-program.ts | 4 +++- packages/cli-core/src/commands/init/index.ts | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/cli-core/src/cli-program.ts b/packages/cli-core/src/cli-program.ts index d5450ca8..3a95253b 100644 --- a/packages/cli-core/src/cli-program.ts +++ b/packages/cli-core/src/cli-program.ts @@ -67,6 +67,7 @@ export function createProgram() { ) .option("--prompt", "Output a prompt for an AI agent to integrate Clerk") .option("-y, --yes", "Skip confirmation prompts") + .option("--no-skills", "Skip agent skill installation") .addHelpText( "after", ` @@ -74,7 +75,8 @@ Examples: $ clerk init Auto-detect framework and set up Clerk $ clerk init --framework next Set up for Next.js (skips detection) $ clerk init --prompt Output a setup prompt for an AI agent - $ clerk init -y Skip all confirmation prompts`, + $ clerk init -y Skip all confirmation prompts + $ clerk init --no-skills Skip installing agent skills`, ) .action(init); diff --git a/packages/cli-core/src/commands/init/index.ts b/packages/cli-core/src/commands/init/index.ts index e1d0b934..f074febd 100644 --- a/packages/cli-core/src/commands/init/index.ts +++ b/packages/cli-core/src/commands/init/index.ts @@ -18,12 +18,14 @@ import { printOutro, getAuthenticatedEmail, } from "./heuristics.js"; +import { installSkills } from "./skills.js"; import type { ProjectContext } from "./frameworks/types.js"; interface InitOptions { framework?: string; yes?: boolean; prompt?: boolean; + noSkills?: boolean; } export async function init(options: InitOptions = {}) { @@ -48,6 +50,10 @@ export async function init(options: InitOptions = {}) { await authenticateAndLink(cwd); await detectAndInstall(cwd, ctx, options); + + if (!options.noSkills) { + await installSkills(cwd, ctx?.framework.dep, options.yes ?? false); + } } async function authenticateAndLink(cwd: string): Promise { From 9f7d949b5b175fc6cdf8581b079b32da4a6bb934 Mon Sep 17 00:00:00 2001 From: Railly Date: Mon, 6 Apr 2026 19:59:36 -0500 Subject: [PATCH 3/3] test(e2e): pass --no-skills to clerk init in fixture setup The skills install puts template files into skills/ that break tsc because framework templates reference auto-generated types (e.g. ./+types/root for React Router) that don't exist outside the real app/ directory. Skipping skills in test setup keeps the typecheck step focused on the actual scaffolded files. --- test/e2e/lib/fixture-setup.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/e2e/lib/fixture-setup.ts b/test/e2e/lib/fixture-setup.ts index 10ee7a40..a0ee596a 100644 --- a/test/e2e/lib/fixture-setup.ts +++ b/test/e2e/lib/fixture-setup.ts @@ -86,10 +86,12 @@ async function gitInit(projectDir: string): Promise { } /** - * Run `clerk init --yes` against the project directory using the pre-linked config. + * Run `clerk init --yes --no-skills` against the project directory using the + * pre-linked config. Skills install is skipped to avoid polluting the project + * with skill template files that break framework typecheck. */ async function runClerkInit(projectDir: string, configDir: string): Promise { - const result = await Bun.$`bun ${CLI_PATH} --mode human init --yes` + const result = await Bun.$`bun ${CLI_PATH} --mode human init --yes --no-skills` .cwd(projectDir) .env(clerkEnv(configDir)) .quiet()