Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 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
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
23 changes: 21 additions & 2 deletions packages/cli/lib/PromptSession.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
AI_AGENT_LABELS, AI_AGENT_SKILLS_DIRS, AIAgentTarget,
BasePromptSession, GoogleAnalytics, InquirerWrapper, PackageManager, ProjectConfig,
ProjectLibrary, PromptTaskContext, Task, Util
} 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 } 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 +77,20 @@ export class PromptSession extends BasePromptSession {
// project options:
theme = await this.getTheme(projLibrary);

const AI_AGENT_CHOICES = Object.keys(AI_AGENT_SKILLS_DIRS) as AIAgentTarget[];
const selected = await InquirerWrapper.checkbox({
message: "Which AI tools do you want to generate configuration files for?",
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"
}))
]
});
const agents = selected.includes("none") ? [] : selected as AIAgentTarget[];

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

if (agents?.length) {
configure(agents);
}
}
await this.chooseActionLoop(projLibrary);
//TODO: restore cwd?
Expand All @@ -106,7 +125,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
54 changes: 44 additions & 10 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,8 @@ 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(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,30 +26,64 @@ export function configureSkills(): void {
}
}

export function configure(skills = true): void {
export function configure(agents: AIAgentTarget[]): void {
configureMCP();
if (skills) {
configureSkills();
for (const agent of agents) {
configureSkills(getSkillsDir(agent));
}
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"
}))
];

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 +53 to +58
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) {
GoogleAnalytics.post({
t: "screenview",
cd: "MCP"
});

let agents = argv.agent as AIAgentTarget[] | undefined;

if (!agents?.length) {
const selected = await InquirerWrapper.checkbox({
message: "Which AI tools do you want to generate configuration files for?",
choices: AI_AGENT_CHECKBOX_CHOICES
});
agents = selected.includes("none") ? [] : selected as AIAgentTarget[];
}

if (!agents.length) {
Util.log("No AI configuration selected. Skipping.");
return;
}

GoogleAnalytics.post({
t: "event",
ec: "$ig ai-config",
ea: "client: vscode"
ea: `agent: ${agents.join(", ")}`
});

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

Expand Down
18 changes: 17 additions & 1 deletion 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"
})
.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 @@ -162,6 +171,13 @@ const command: NewCommandType = {
process.chdir("..");
}

const agents = argv.agent as AIAgentTarget[] | undefined;
if (agents?.length) {
process.chdir(argv.name);
configure(agents);
process.chdir("..");
}

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

This file was deleted.

This file was deleted.

50 changes: 50 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,50 @@
import { ControlExtraConfiguration, defaultDelimiters, ProjectTemplate, updateWorkspace, Util } from "@igniteui/cli-core";
import * as path from "path";

export class BaseIgrTsAiConfigPartial implements ProjectTemplate {
public id: string = "ai-config";
public name = "ai-config";
public description = "Ignite UI CLI project for React";
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 this.getVariablesConfig(name, theme);
}

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.");
}

protected getVariablesConfig(name: string, theme: string) {
return {
name,
theme,
"cliVersion": Util.version(),
"dash-name": Util.lowerDashed(name),
"description": this.description,
"dot": ".",
"path": name,
"projectTemplate": this.id,
"yamlDefaultBranch": this.id === "base" ? "<%=yaml-default-branch%>" : "main"
};
}
}
export default new BaseIgrTsAiConfigPartial();

This file was deleted.

This file was deleted.

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

export class BaseIgcTsAiConfigPartial implements ProjectTemplate {

public id: string = "ai-config";
public name = "ai-config";
public description = "Project structure with routing";
public framework: string = "webcomponents";
public projectType: string = "igc-ts";
public dependencies: string[];
public hasExtraConfiguration: boolean = false;
public isHidden: boolean = true;

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.");
}
delimiters: TemplateDelimiters;
getExtraConfiguration(): ControlExtraConfiguration[] {
throw new Error("Method not implemented.");
}
setExtraConfiguration(extraConfigKeys: {}) {
throw new Error("Method not implemented.");
}
}
export default new BaseIgcTsAiConfigPartial();


6 changes: 3 additions & 3 deletions packages/core/prompt/InquirerWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type InputConfig = {
required?: boolean;
type?: string;
name?: string;
choices?: (string | Separator)[];
choices?: (string | { value: string; name?: string; checked?: boolean } | Separator)[];
transformer?: (value: string, { isFinal }: {
isFinal: boolean;
}) => string;
Expand All @@ -29,8 +29,8 @@ export class InquirerWrapper {
return select(message, context);
}

public static async checkbox(message: InputConfig & { choices: (string | Separator)[] }, context?: Context): Promise<string[]> {
return checkbox(message, context);
public static async checkbox(message: InputConfig & { choices: (string | { value: string; name?: string; checked?: boolean } | Separator)[] }, context?: Context): Promise<string[]> {
return checkbox(message as any, context);
}

public static async confirm(message: { message: string; default?: boolean }, context?: Context): Promise<boolean> {
Expand Down
Loading
Loading