Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
52cd22f
Initial plan
Copilot Apr 24, 2026
2c18475
feat: make ai-config agent-agnostic with --agent and --skills-dir opt…
Copilot Apr 24, 2026
c67709e
refactor: improve analytics event and clean up skillsDir resolution
Copilot Apr 24, 2026
d4478a9
feat: update angular schematics ai-config to accept --agent and --ski…
Copilot Apr 24, 2026
5784c4a
feat: enhance ai-config to prompt for agent selection and improve ski…
Marina-L-Stoyanova Apr 27, 2026
39eff3d
feat: enhance ai-config command to handle agent selection and improve…
Marina-L-Stoyanova Apr 27, 2026
6c5d717
feat: make ai-config agent-agnostic by supporting multiple agents and…
Marina-L-Stoyanova Apr 28, 2026
14e062d
feat: refactor ai-config to support agent-based skills configuration …
Marina-L-Stoyanova Apr 28, 2026
026fb9e
Merge branch 'master' of https://github.com/IgniteUI/igniteui-cli int…
Marina-L-Stoyanova Apr 28, 2026
3669211
feat: add AI agent configuration options to project creation
Marina-L-Stoyanova Apr 28, 2026
f2f39a9
change():Moving skills to ai-config skills directory and updating the…
Marina-L-Stoyanova Apr 29, 2026
1379243
chore: enhance AI configuration options with labels and improved sele…
Marina-L-Stoyanova Apr 29, 2026
4fb65da
feat: update AI configuration options to include 'none' and improve p…
Marina-L-Stoyanova Apr 29, 2026
baee30a
feat: enhance AI configuration by adding instruction file handling an…
Marina-L-Stoyanova Apr 29, 2026
56ccdf8
Removing skills and instructions from _base templates
Marina-L-Stoyanova Apr 29, 2026
96f010b
Optimizing react ai-config template
Marina-L-Stoyanova Apr 29, 2026
9ca5b16
fix: remove unnecessary whitespace in PromptSession unit tests
Marina-L-Stoyanova Apr 30, 2026
100f1e4
chore: remove outdated AGENTS.md file from React template
Marina-L-Stoyanova Apr 30, 2026
fd28dde
feat: refactor AI agent selection to use promptForAgents function
Marina-L-Stoyanova May 4, 2026
f2656af
feat: add InquirerWrapper checkbox spy to new command tests
Marina-L-Stoyanova May 4, 2026
62a33a4
feat: update AI agent configuration and help command documentation
Marina-L-Stoyanova May 4, 2026
9d5d801
feat: enhance AI tooling configuration with agent selection and instr…
Marina-L-Stoyanova May 4, 2026
d30a1cd
feat: refactor AI configuration to use promptForAgents and update ski…
Marina-L-Stoyanova May 5, 2026
a892c47
feat: add required option to promptForAgents and update tests for ana…
Marina-L-Stoyanova May 5, 2026
ae4b10b
feat: apply review comments
Marina-L-Stoyanova May 5, 2026
ee143b8
Apply suggestion from @damyanpetev
Marina-L-Stoyanova May 5, 2026
dd7dff4
Apply suggestion from @damyanpetev
Marina-L-Stoyanova May 5, 2026
e9f9424
refactor: update skills directory resolution and clean up tests
Marina-L-Stoyanova May 5, 2026
2b5318e
chore():updating ai-config templates
Marina-L-Stoyanova May 5, 2026
bfc819f
feat: add template file resolution for AGENTS.md and update related t…
Marina-L-Stoyanova May 5, 2026
713f181
feat: enhance ai-config template validation and add file existence ch…
Marina-L-Stoyanova May 5, 2026
af0430a
chore: cleanup ai-config partials
damyanpetev May 7, 2026
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
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,21 @@ ig start

## Configure AI Tooling

To automatically configure Ignite UI AI tooling - MCP servers and AI coding skills, run:
To configure Ignite UI AI tooling MCP servers and AI coding skills run:

```bash
ig ai-config
```

This creates or updates `.vscode/mcp.json` in the current project with entries for both the [Ignite UI MCP](#mcp-server) and `igniteui-theming` MCP servers. Existing servers in the file are preserved. It also copies any AI coding skill files from installed Ignite UI packages into the project. New projects are created with AI tooling configuration out of the box.
You will be prompted to select which AI tools to configure (Claude and Generic are selected by default). You can also pass agents directly:

```bash
ig ai-config --agent claude copilot generic
```

This creates or updates `.vscode/mcp.json` with entries for the [Ignite UI MCP](#mcp-server) and `igniteui-theming` MCP servers (existing servers are preserved), copies AI coding skill files from installed Ignite UI packages, and generates agent-specific instruction files (e.g. `CLAUDE.md`, `AGENTS.md`).

The `ig new` command also prompts for AI tool configuration as part of project creation.

## MCP Server

Expand All @@ -170,14 +178,18 @@ ig mcp --debug # Enable debug logging to mcp-server.log

### Using with AI Assistants

For VS Code, the `ig ai-config` command handles configuration automatically (see above). For other MCP clients (e.g., Claude Desktop, Cursor), configure them manually to use the CLI as the MCP server:
For VS Code, the `ig ai-config` command handles configuration automatically (see above). For other MCP clients (e.g., Claude Desktop, Cursor), configure them manually:

```json
{
"mcpServers": {
"igniteui-cli": {
"command": "ig",
"args": ["mcp"]
"command": "npx",
"args": ["-y", "igniteui-cli", "mcp"]
},
"igniteui-theming": {
"command": "npx",
"args": ["-y", "igniteui-theming", "igniteui-theming-mcp"]
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/lib/PromptSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from "@igniteui/cli-core";
import * as path from "path";
import { default as add } from "./commands/add";
import { configure as aiConfigure } from "./commands/ai-config";
import { configure, configureMCP, promptForAgents } from "./commands/ai-config";
import { default as start } from "./commands/start";
import { default as upgrade } from "./commands/upgrade";
import { TemplateManager } from "./TemplateManager";
Expand Down Expand Up @@ -76,6 +76,8 @@ export class PromptSession extends BasePromptSession {
// project options:
theme = await this.getTheme(projLibrary);

const agents = await promptForAgents();

Util.log(" Generating project structure.");
const config = projTemplate.generateConfig(projectName, theme);
for (const templatePath of projTemplate.templatePaths) {
Expand All @@ -89,6 +91,10 @@ export class PromptSession extends BasePromptSession {
}
// move cwd to project folder
process.chdir(projectName);

if (agents?.length) {
await configure(agents);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the point of the reusable configureAI() method - to call that one as an entry for the AI config. Would you like to move that logic there instead of replacing it with configureMCP() which this calls too.

Yes, you might need to change its parameters if you want the prompt external, but it can be in the method too. And yes, that last part might mean the method is not placed in the correct place in the super. Also the start override should be removed lol, it's overridden just for the Angular message lol.

}
await this.chooseActionLoop(projLibrary);
//TODO: restore cwd?
Expand All @@ -106,7 +112,7 @@ export class PromptSession extends BasePromptSession {

protected async configureAI(): Promise<void> {
// skip adding skills since those are baked into the project template atm:
aiConfigure(false);
configureMCP();
}

/**
Expand Down
67 changes: 58 additions & 9 deletions packages/cli/lib/commands/ai-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addMcpServers, copyAISkillsToProject, GoogleAnalytics, Util, VS_CODE_MCP_PATH } from "@igniteui/cli-core";
import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_SKILLS_DIRS, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, getSkillsDir, GoogleAnalytics, InquirerWrapper, Util, VS_CODE_MCP_PATH } from "@igniteui/cli-core";
import { ArgumentsCamelCase, CommandModule } from "yargs";

export function configureMCP(): void {
Expand All @@ -11,8 +11,14 @@ export function configureMCP(): void {
Util.log(Util.greenCheck() + ` MCP servers configured in ${VS_CODE_MCP_PATH}`);
}

export function configureSkills(): void {
const result = copyAISkillsToProject();
export function configureSkills(agents: AIAgentTarget[]): void {
for (const agent of agents) {
configureSkillsForAgent(getSkillsDir(agent));
}
}

function configureSkillsForAgent(skillsDir: string): void {
const result = copyAISkillsToProject(skillsDir);
if (result.found === 0) {
Util.warn("No AI skill files found. Make sure packages are installed (npm install) " +
"and your Ignite UI packages are up-to-date.", "yellow");
Expand All @@ -26,18 +32,61 @@ export function configureSkills(): void {
}
}

export function configure(skills = true): void {
export async function configure(agents?: AIAgentTarget[], skills = true): Promise<void> {
if (!agents?.length) {
agents = await promptForAgents();
}
if (!agents.length) return;
configureMCP();
if (skills) {
configureSkills();
configureSkills(agents);
}
copyAgentInstructionFiles(agents);
}

const AI_AGENT_CHOICES = Object.keys(AI_AGENT_SKILLS_DIRS) as AIAgentTarget[];

const AI_AGENT_CHECKBOX_CHOICES = [
{ value: "none", name: "None (skip AI configuration)" },
...AI_AGENT_CHOICES.map(agent => ({
value: agent,
name: AI_AGENT_LABELS[agent],
checked: agent === "generic" || agent === "claude"
}))
];

export async function promptForAgents(): Promise<AIAgentTarget[]> {
const selected = await InquirerWrapper.checkbox({
message: "Which AI tools do you want to generate configuration files for?",
required: true,
choices: AI_AGENT_CHECKBOX_CHOICES
});
return selected.includes("none") ? [] : selected as AIAgentTarget[];
}
Comment on lines +47 to 65
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a bit iffy on prompt logic here, in the past it was concentrated behind the prompt class explicitly to contain the prompting solution behind a single facade if you will. The wrapper might be enough good enough for now, but need to see the rest.


const command: CommandModule = {
command: "ai-config",
describe: "Configures Ignite UI AI tooling (MCP servers and AI coding skills)",
builder: (yargs) => yargs,
async handler(_argv: ArgumentsCamelCase) {
builder: (yargs) => yargs
.usage("")
.option("agent", {
alias: "a",
describe: "AI agent(s) to configure skills for (determines the target skills directory)",
choices: AI_AGENT_CHOICES,
type: "array"
}),
Comment on lines +72 to +77
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we roll that option into one thing? Kinda odd to be able to pass both - like --agent claude --dir .github/skills

async handler(argv: ArgumentsCamelCase) {
let agents = argv.agent as AIAgentTarget[] | undefined;

if (!agents?.length) {
agents = await promptForAgents();
}

if (!agents.length) {
Util.log("No AI configuration selected. Skipping.");
return;
}
Comment on lines +85 to +88
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Erm, is there a case where there is no selection after the prompt? If there's a None option in there, should it not have a required flag to force an option and this check should compare if there's a "none" value selected? Also not sure if we want to skip analytics for this case, so this might need to move after


GoogleAnalytics.post({
t: "screenview",
cd: "MCP"
Expand All @@ -46,10 +95,10 @@ const command: CommandModule = {
GoogleAnalytics.post({
t: "event",
ec: "$ig ai-config",
ea: "client: vscode"
ea: `agent: ${agents.join(", ")}`
});

configure();
await configure(agents);
}
};

Expand Down
23 changes: 18 additions & 5 deletions packages/cli/lib/commands/new.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { GoogleAnalytics, PackageManager, ProjectConfig, ProjectLibrary, Util } from "@igniteui/cli-core";
import { AI_AGENT_SKILLS_DIRS, AIAgentTarget, GoogleAnalytics, PackageManager, ProjectConfig, ProjectLibrary, Util } from "@igniteui/cli-core";
import * as path from "path";
import { PromptSession } from "./../PromptSession";
import { NewCommandType, PositionalArgs } from "./types";
import { TemplateManager } from "../TemplateManager";
import { ArgumentsCamelCase, Choices } from "yargs";
import { configure } from "./ai-config";

const AI_AGENT_CHOICES = Object.keys(AI_AGENT_SKILLS_DIRS) as AIAgentTarget[];

// explicit typing because `type: "string"` will be inferred as `type: string` which yargs will not like
const _framework: {
Expand Down Expand Up @@ -59,6 +62,12 @@ const command: NewCommandType = {
describe: "Project template",
type: "string"
})
.option("agent", {
alias: "a",
describe: "AI agent(s) to configure skills for (determines the target skills directory)",
choices: AI_AGENT_CHOICES,
type: "array"
})
Comment on lines +65 to +70
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the behavior we want, might set defaults here too

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't we agree that if no agent is provided as an argument we prefer to prompt the user?

.example("$0 new my-app", "Scaffold a new project interactively")
.example("$0 new my-app -f angular -t igx-ts", "Scaffold an Ignite UI for Angular project");
},
Expand Down Expand Up @@ -152,16 +161,20 @@ const command: NewCommandType = {

Util.log(Util.greenCheck() + " Project Created");

if (!argv["skip-git"] && !ProjectConfig.getConfig().skipGit) {
Util.gitInit(process.cwd(), argv.name);
}

if (!argv.skipInstall) {
process.chdir(argv.name);
await PackageManager.installPackages();
process.chdir("..");
}

process.chdir(argv.name);
await configure(argv.agent as AIAgentTarget[] | undefined);
process.chdir("..");

if (!argv["skip-git"] && !ProjectConfig.getConfig().skipGit) {
Util.gitInit(process.cwd(), argv.name);
}

Util.log("");
Util.log("Next Steps:");
Util.log(` cd ${argv.name}`);
Expand Down

This file was deleted.

This file was deleted.

38 changes: 38 additions & 0 deletions packages/cli/templates/react/igr-ts/projects/ai-config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ControlExtraConfiguration, defaultDelimiters, ProjectTemplate } from "@igniteui/cli-core";
import * as path from "path";

// currently reusing hidden project impl as components/views pipeline go through registerInProject
// ideally would define a separate type/category for those partial files
export class IgrTsAiConfigPartial implements ProjectTemplate {
public id: string = "ai-config";
public name = "ai-config";
public description = "Ignite UI CLI AI config for React partial project files";
public framework: string = "react";
public projectType: string = "tsx";
public dependencies: string[] = [];
public hasExtraConfiguration: boolean = false;
public isHidden: boolean = true;
public delimiters = defaultDelimiters;

public get templatePaths(): string[] {
return [path.join(__dirname, "files")];
}

public generateConfig(_name: string, _theme: string, ..._options: any[]): {[key: string]: any} {
return { /* partials not using Util.processTemplates atm */ };
}

public installModules(): void {
throw new Error("Method not implemented.");
}
public async upgradeIgniteUIPackages(_projectPath: string, _packagePath: string): Promise<boolean> {
throw new Error("Method not implemented.");
}
public getExtraConfiguration(): ControlExtraConfiguration[] {
throw new Error("Method not implemented.");
}
public setExtraConfiguration(_extraConfigKeys: {}) {
throw new Error("Method not implemented.");
}
}
export default new IgrTsAiConfigPartial();

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ControlExtraConfiguration, defaultDelimiters, ProjectTemplate } from "@igniteui/cli-core";
import * as path from "path";

// currently reusing hidden project impl as components/views pipeline go through registerInProject
// ideally would define a separate type/category for those partial files
export class IgcTsAiConfigPartial implements ProjectTemplate {

public id: string = "ai-config";
public name = "ai-config";
public description = "Ignite UI CLI AI config for Web Components partial project files";
public framework: string = "webcomponents";
public projectType: string = "igc-ts";
public dependencies: string[];
public hasExtraConfiguration: boolean = false;
public isHidden: boolean = true;
public delimiters = defaultDelimiters;

public get templatePaths(): string[] {
return [path.join(__dirname, "files")];
}

installModules(): void {
throw new Error("Method not implemented.");
}
upgradeIgniteUIPackages(_projectPath: string, _packagePath: string): Promise<boolean> {
throw new Error("Method not implemented.");
}
generateConfig(_name: string, _theme: string, ..._options: any[]): { [key: string]: any; } {
throw new Error("Method not implemented.");
}
getExtraConfiguration(): ControlExtraConfiguration[] {
throw new Error("Method not implemented.");
}
setExtraConfiguration(_extraConfigKeys: {}) {
throw new Error("Method not implemented.");
Comment on lines +22 to +35
}
}
export default new IgcTsAiConfigPartial();


12 changes: 8 additions & 4 deletions packages/core/prompt/InquirerWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ type InputConfig = {
required?: boolean;
type?: string;
name?: string;
choices?: (string | Separator)[];
choices?: (string | Separator)[] | ({ value: string; name?: string; checked?: boolean } | Separator)[];
transformer?: (value: string, { isFinal }: {
isFinal: boolean;
}) => string;

// TODO: consider typing these by extracting the types from the inquirer package
validate?: any;
theme?: unknown;
theme?: any;
};

type InputChoicesConfig = InputConfig & {
choices: (string | Separator)[] | ({ value: string; name?: string; checked?: boolean } | Separator)[];
};

export class InquirerWrapper {
Expand All @@ -25,11 +29,11 @@ export class InquirerWrapper {
return input(message, context);
}

public static async select(message: InputConfig & { choices: (string | Separator)[] }, context?: Context): Promise<string> {
public static async select(message: InputChoicesConfig, context?: Context): Promise<string> {
return select(message, context);
}

public static async checkbox(message: InputConfig & { choices: (string | Separator)[] }, context?: Context): Promise<string[]> {
public static async checkbox(message: InputChoicesConfig, context?: Context): Promise<string[]> {
return checkbox(message, context);
}

Expand Down
Loading
Loading