Skip to content
Merged
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: 3 additions & 3 deletions packages/cli/snap-tests-global/command-create-help/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Arguments:

Options:
--directory DIR Target directory for the generated project.
--agent NAME Create an agent instructions file for the specified agent.
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
--editor NAME Write editor config files for the specified editor.
--hooks Set up pre-commit hooks (default in non-interactive mode)
--no-hooks Skip pre-commit hooks setup
Expand Down Expand Up @@ -70,7 +70,7 @@ Arguments:

Options:
--directory DIR Target directory for the generated project.
--agent NAME Create an agent instructions file for the specified agent.
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
--editor NAME Write editor config files for the specified editor.
--hooks Set up pre-commit hooks (default in non-interactive mode)
--no-hooks Skip pre-commit hooks setup
Expand Down Expand Up @@ -126,7 +126,7 @@ Arguments:

Options:
--directory DIR Target directory for the generated project.
--agent NAME Create an agent instructions file for the specified agent.
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
--editor NAME Write editor config files for the specified editor.
--hooks Set up pre-commit hooks (default in non-interactive mode)
--no-hooks Skip pre-commit hooks setup
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/snap-tests-global/migration-check/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Arguments:
PATH Target directory to migrate (default: current directory)

Options:
--agent NAME Write agent instructions file into the project (e.g. chatgpt, claude, opencode).
--no-agent Skip writing agent instructions file
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
--no-agent Skip writing coding agent instructions
--editor NAME Write editor config files into the project.
--no-editor Skip writing editor config files
--hooks Set up pre-commit hooks (default in non-interactive mode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Arguments:
PATH Target directory to migrate (default: current directory)

Options:
--agent NAME Write agent instructions file into the project (e.g. chatgpt, claude, opencode).
--no-agent Skip writing agent instructions file
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
--no-agent Skip writing coding agent instructions
--editor NAME Write editor config files into the project.
--no-editor Skip writing editor config files
--hooks Set up pre-commit hooks (default in non-interactive mode)
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/snap-tests-global/new-check/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Arguments:

Options:
--directory DIR Target directory for the generated project.
--agent NAME Create an agent instructions file for the specified agent.
--agent NAME Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.
--editor NAME Write editor config files for the specified editor.
--hooks Set up pre-commit hooks (default in non-interactive mode)
--no-hooks Skip pre-commit hooks setup
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/create/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const helpMessage = renderCliDoc({
{ label: '--directory DIR', description: 'Target directory for the generated project.' },
{
label: '--agent NAME',
description: 'Create an agent instructions file for the specified agent.',
description: 'Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.',
},
{
label: '--editor NAME',
Expand Down
5 changes: 2 additions & 3 deletions packages/cli/src/migration/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,9 @@ const helpMessage = renderCliDoc({
rows: [
{
label: '--agent NAME',
description:
'Write agent instructions file into the project (e.g. chatgpt, claude, opencode).',
description: 'Write coding agent instructions to AGENTS.md, CLAUDE.md, etc.',
},
{ label: '--no-agent', description: 'Skip writing agent instructions file' },
{ label: '--no-agent', description: 'Skip writing coding agent instructions' },
{
label: '--editor NAME',
description: 'Write editor config files into the project.',
Expand Down
43 changes: 42 additions & 1 deletion packages/cli/src/utils/__tests__/agent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
hasExistingAgentInstructions,
replaceMarkedAgentInstructionsSection,
resolveAgentTargetPaths,
selectAgentTargetPaths,
writeAgentInstructions,
} from '../agent.js';
import { pkgRoot } from '../path.js';
Expand Down Expand Up @@ -252,13 +253,19 @@ async function createProjectDir() {
}

describe('resolveAgentTargetPaths', () => {
it('resolves comma-separated agent names and deduplicates target paths', () => {
it('resolves legacy agent names and deduplicates target paths', () => {
expect(resolveAgentTargetPaths('claude,amp,opencode,chatgpt')).toEqual([
'CLAUDE.md',
'AGENTS.md',
]);
});

it('resolves file names directly', () => {
expect(
resolveAgentTargetPaths(['AGENTS.md', 'CLAUDE.md', '.github/copilot-instructions.md']),
).toEqual(['AGENTS.md', 'CLAUDE.md', '.github/copilot-instructions.md']);
});

it('resolves repeated --agent values and trims whitespace', () => {
expect(resolveAgentTargetPaths([' claude ', ' amp, opencode ', 'codex'])).toEqual([
'CLAUDE.md',
Expand All @@ -272,6 +279,40 @@ describe('resolveAgentTargetPaths', () => {
});
});

describe('selectAgentTargetPaths', () => {
it('prompts with file-based targets and agent hints', async () => {
const multiselectSpy = vi.spyOn(prompts, 'multiselect').mockResolvedValue(['agents', 'claude']);

await expect(
selectAgentTargetPaths({
interactive: true,
onCancel: vi.fn(),
}),
).resolves.toEqual(['AGENTS.md', 'CLAUDE.md']);

expect(multiselectSpy).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining(
'Which coding agent instruction files should Vite+ create?',
),
initialValues: ['agents'],
options: expect.arrayContaining([
expect.objectContaining({
label: 'AGENTS.md',
value: 'agents',
hint: expect.stringContaining('Codex'),
}),
expect.objectContaining({
label: 'CLAUDE.md',
value: 'claude',
hint: 'Claude Code',
}),
]),
}),
);
});
});

describe('detectExistingAgentTargetPath', () => {
it('detects all existing regular agent files', async () => {
const dir = await createProjectDir();
Expand Down
88 changes: 62 additions & 26 deletions packages/cli/src/utils/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,37 +164,75 @@ export function detectAgents(root: string): AgentConfig[] {

// --- Backward-compatible exports ---

const AGENT_ALIASES: Record<string, string> = {
chatgpt: 'chatgpt-codex',
codex: 'chatgpt-codex',
};

export const AGENTS = [
{ id: 'chatgpt-codex', label: 'ChatGPT (Codex)', targetPath: 'AGENTS.md' },
{ id: 'claude', label: 'Claude Code', targetPath: 'CLAUDE.md' },
{ id: 'gemini', label: 'Gemini CLI', targetPath: 'GEMINI.md' },
{
id: 'agents',
label: 'AGENTS.md',
targetPath: 'AGENTS.md',
hint: 'Codex, Amp, OpenCode, and similar agents',
aliases: [
'agents.md',
'chatgpt',
'chatgpt-codex',
'codex',
'amp',
'kilo',
'kilo-code',
'kiro',
'kiro-cli',
'opencode',
'other',
],
},
{
id: 'claude',
label: 'CLAUDE.md',
targetPath: 'CLAUDE.md',
hint: 'Claude Code',
aliases: ['claude.md', 'claude-code'],
},
{
id: 'gemini',
label: 'GEMINI.md',
targetPath: 'GEMINI.md',
hint: 'Gemini CLI',
aliases: ['gemini.md', 'gemini-cli'],
},
{
id: 'copilot',
label: 'GitHub Copilot',
label: '.github/copilot-instructions.md',
targetPath: '.github/copilot-instructions.md',
hint: 'GitHub Copilot',
aliases: ['github-copilot', 'copilot-instructions.md'],
},
{
id: 'cursor',
label: '.cursor/rules/viteplus.mdc',
targetPath: '.cursor/rules/viteplus.mdc',
hint: 'Cursor',
aliases: ['viteplus.mdc'],
},
{ id: 'cursor', label: 'Cursor', targetPath: '.cursor/rules/viteplus.mdc' },
{
id: 'jetbrains',
label: 'JetBrains AI Assistant',
label: '.aiassistant/rules/viteplus.md',
targetPath: '.aiassistant/rules/viteplus.md',
hint: 'JetBrains AI Assistant',
aliases: ['jetbrains', 'jetbrains-ai-assistant', 'aiassistant', 'viteplus.md'],
},
{ id: 'amp', label: 'Amp', targetPath: 'AGENTS.md' },
{ id: 'kiro', label: 'Kiro', targetPath: 'AGENTS.md' },
{ id: 'opencode', label: 'OpenCode', targetPath: 'AGENTS.md' },
{ id: 'other', label: 'Other', targetPath: 'AGENTS.md' },
] as const;

type AgentSelection = string | string[] | false;
const AGENT_DEFAULT_ID = 'agents';
const AGENT_STANDARD_PATH = 'AGENTS.md';
const AGENT_INSTRUCTIONS_START_MARKER = '<!--VITE PLUS START-->';
const AGENT_INSTRUCTIONS_END_MARKER = '<!--VITE PLUS END-->';

const AGENT_ALIASES = Object.fromEntries(
AGENTS.flatMap((option) =>
(option.aliases ?? []).map((alias) => [normalizeAgentName(alias), option.id]),
),
) as Record<string, string>;

export async function selectAgentTargetPaths({
interactive,
agent,
Expand All @@ -211,18 +249,13 @@ export async function selectAgentTargetPaths({

if (interactive && !agent) {
const selectedAgents = await prompts.multiselect({
message:
'Which agents are you using?\n ' +
styleText(
'gray',
'Writes an instruction file for each selected agent to help it understand `vp` commands and the project workflow.',
),
message: 'Which coding agent instruction files should Vite+ create?',
options: AGENTS.map((option) => ({
label: option.label,
value: option.id,
hint: option.targetPath,
hint: option.hint,
})),
initialValues: ['chatgpt-codex'],
initialValues: [AGENT_DEFAULT_ID],
required: false,
});

Expand All @@ -237,7 +270,7 @@ export async function selectAgentTargetPaths({
return resolveAgentTargetPaths(selectedAgents);
}

return resolveAgentTargetPaths(agent ?? 'other');
return resolveAgentTargetPaths(agent ?? AGENT_DEFAULT_ID);
}

export async function selectAgentTargetPath({
Expand Down Expand Up @@ -359,9 +392,12 @@ function resolveSingleAgentTargetPath(agent: string) {
const resolved = alias ? normalizeAgentName(alias) : normalized;
const match = AGENTS.find(
(option) =>
normalizeAgentName(option.id) === resolved || normalizeAgentName(option.label) === resolved,
normalizeAgentName(option.id) === resolved ||
normalizeAgentName(option.label) === resolved ||
normalizeAgentName(option.targetPath) === resolved ||
option.aliases?.some((candidate) => normalizeAgentName(candidate) === resolved),
);
return match?.targetPath ?? AGENTS[AGENTS.length - 1].targetPath;
return match?.targetPath ?? AGENT_STANDARD_PATH;
}

export interface AgentConflictInfo {
Expand Down
Loading