Skip to content
Merged
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
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cli/ai/sessions/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async function resolveSessionByIdOrPrefix(
: sessions.filter( ( session ) => session.id.startsWith( sessionIdOrPrefix ) );

if ( candidates.length === 0 ) {
throw new Error( `AI session not found: ${ sessionIdOrPrefix }` );
throw new Error( `Code session not found: ${ sessionIdOrPrefix }` );
}

if ( candidates.length > 1 ) {
Expand Down
6 changes: 4 additions & 2 deletions apps/cli/ai/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ interface RemoteSiteContext {
id: number;
}

const AGENT_IDENTITY = `You are WordPress Studio Code, the AI agent built into WordPress Studio CLI. Your name is "WordPress Studio Code".`;

export function buildSystemPrompt( options?: { remoteSite?: RemoteSiteContext } ): string {
if ( options?.remoteSite ) {
return `${ buildRemoteIntro( options.remoteSite ) }
Expand All @@ -23,7 +25,7 @@ ${ LOCAL_DESIGN_GUIDELINES }
}

function buildRemoteIntro( site: RemoteSiteContext ): string {
return `You are WordPress Studio AI, the AI assistant built into WordPress Studio CLI. Your name is "WordPress Studio AI". You manage WordPress.com sites using the WordPress.com REST API.
return `${ AGENT_IDENTITY } You manage WordPress.com sites using the WordPress.com REST API.

IMPORTANT: The active site is a remote WordPress.com site: "${ site.name }" (ID: ${ site.id }) at ${ site.url }.
IMPORTANT: You MUST use the wpcom_request tool (prefixed with mcp__studio__) to manage this site. Do NOT use WP-CLI, file Write/Edit, Bash, or any local file operations — this site is hosted on WordPress.com and cannot be modified through the local filesystem.
Expand Down Expand Up @@ -89,7 +91,7 @@ Use \`per_page\` and \`page\` for pagination. Use \`status\` to filter by publis
}

function buildLocalIntro(): string {
return `You are WordPress Studio AI, the AI assistant built into WordPress Studio CLI. Your name is "WordPress Studio AI". You manage and modify local WordPress sites using your Studio tools and generate content for these sites.
return `${ AGENT_IDENTITY } You manage and modify local WordPress sites using your Studio tools and generate content for these sites.

IMPORTANT: You MUST use your mcp__studio__ tools to manage WordPress sites. Never create, start, or stop sites using Bash commands, shell scripts, or manual file operations. Never run \`wp\` commands via Bash — always use the wp_cli tool instead. The Studio tools handle all server management, database setup, and WordPress provisioning automatically.
IMPORTANT: For any generated content for the site, these three principles are mandatory:
Expand Down
15 changes: 12 additions & 3 deletions apps/cli/commands/ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ function getErrorMessage( error: unknown ): string {
}

export async function runCommand(
options: { resumeSession?: LoadedAiSession; noSessionPersistence?: boolean } = {}
options: {
resumeSession?: LoadedAiSession;
noSessionPersistence?: boolean;
showLegacyCommandNotice?: boolean;
} = {}
): Promise< void > {
const ui = new AiChatUI();
const resumeContext = resolveResumeSessionContext( options.resumeSession );
Expand All @@ -72,6 +76,10 @@ export async function runCommand(
ui.start();
ui.showWelcome();

if ( options.showLegacyCommandNotice ) {
ui.showInfo( __( 'ⓘ The "studio ai" command is now "studio code".' ) );
}

let sessionRecorder: AiSessionRecorder | undefined;
let didDisableSessionPersistence = options.noSessionPersistence === true;
let sessionId: string | undefined = resumeContext.sessionId;
Expand Down Expand Up @@ -708,7 +716,7 @@ export async function runCommand(
export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: '$0',
describe: __( 'AI-powered WordPress assistant' ),
describe: __( 'AI agent for building WordPress' ),
builder: ( yargs ) => {
return yargs
.option( 'path', {
Expand All @@ -717,7 +725,7 @@ export const registerCommand = ( yargs: StudioArgv ) => {
.option( 'session-persistence', {
type: 'boolean',
default: true,
description: __( 'Record this AI chat session to disk' ),
description: __( 'Record this code session to disk' ),
} );
},
handler: async ( argv ) => {
Expand All @@ -726,6 +734,7 @@ export const registerCommand = ( yargs: StudioArgv ) => {
( argv as { sessionPersistence?: boolean } ).sessionPersistence === false;
await runCommand( {
noSessionPersistence,
showLegacyCommandNotice: argv._[ 0 ] === 'ai',
} );
} catch ( error ) {
if ( error instanceof LoggerError ) {
Expand Down
10 changes: 5 additions & 5 deletions apps/cli/commands/ai/sessions/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function runCommand( sessionIdOrPrefix?: string ): Promise< void >
if ( ! resolvedSessionIdOrPrefix ) {
const selectedSession = await chooseSessionForAction(
__( 'Select a session to delete:' ),
__( 'No AI sessions found' )
__( 'No code sessions found' )
);
if ( ! selectedSession ) {
return;
Expand All @@ -24,20 +24,20 @@ export async function runCommand( sessionIdOrPrefix?: string ): Promise< void >
if ( resolvedSessionIdOrPrefix.toLowerCase() === 'latest' ) {
const sessions = await listAiSessions();
if ( sessions.length === 0 ) {
throw new Error( __( 'No AI sessions found' ) );
throw new Error( __( 'No code sessions found' ) );
}

resolvedSessionIdOrPrefix = sessions[ 0 ].id;
}

const deletedSession = await deleteAiSession( resolvedSessionIdOrPrefix );
console.log( `${ __( 'Deleted AI session' ) }: ${ deletedSession.id }` );
console.log( `${ __( 'Deleted code session' ) }: ${ deletedSession.id }` );
}

export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: 'delete [id]',
describe: __( 'Delete an AI session (id, prefix, "latest", or picker)' ),
describe: __( 'Delete a code session (id, prefix, "latest", or picker)' ),
builder: ( deleteYargs ) => {
return deleteYargs.positional( 'id', {
type: 'string',
Expand All @@ -51,7 +51,7 @@ export const registerCommand = ( yargs: StudioArgv ) => {
if ( error instanceof LoggerError ) {
logger.reportError( error );
} else {
logger.reportError( new LoggerError( __( 'Failed to delete AI session' ), error ) );
logger.reportError( new LoggerError( __( 'Failed to delete code session' ), error ) );
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/commands/ai/sessions/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function displaySessionsCompact( sessions: AiSessionSummary[] ): void {
};

console.log(
chalk.bold( __( 'AI Sessions' ) ) +
chalk.bold( __( 'Code Sessions' ) ) +
chalk.dim( ` (${ sessions.length })` ) +
chalk.dim( ` · ${ __( 'Most recent first' ) }` )
);
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/commands/ai/sessions/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function runCommand( format: SessionOutputFormat ): Promise< void >
}

if ( sessions.length === 0 ) {
console.log( __( 'No AI sessions found' ) );
console.log( __( 'No code sessions found' ) );
return;
}

Expand All @@ -27,7 +27,7 @@ export async function runCommand( format: SessionOutputFormat ): Promise< void >
export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: 'list',
describe: __( 'List AI sessions' ),
describe: __( 'List code sessions' ),
builder: ( listYargs ) => {
return listYargs.option( 'format', {
type: 'string',
Expand All @@ -43,7 +43,7 @@ export const registerCommand = ( yargs: StudioArgv ) => {
if ( error instanceof LoggerError ) {
logger.reportError( error );
} else {
logger.reportError( new LoggerError( __( 'Failed to list AI sessions' ), error ) );
logger.reportError( new LoggerError( __( 'Failed to list code sessions' ), error ) );
}
}
},
Expand Down
8 changes: 4 additions & 4 deletions apps/cli/commands/ai/sessions/resume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function runCommand(
if ( ! resolvedSessionIdOrPrefix ) {
const selectedSession = await chooseSessionForAction(
__( 'Select a session to resume:' ),
__( 'No AI sessions found' )
__( 'No code sessions found' )
);
if ( ! selectedSession ) {
return;
Expand All @@ -28,7 +28,7 @@ export async function runCommand(
if ( resolvedSessionIdOrPrefix.toLowerCase() === 'latest' ) {
const sessions = await listAiSessions();
if ( sessions.length === 0 ) {
throw new Error( __( 'No AI sessions found' ) );
throw new Error( __( 'No code sessions found' ) );
}

resolvedSessionIdOrPrefix = sessions[ 0 ].id;
Expand All @@ -44,7 +44,7 @@ export async function runCommand(
export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: 'resume [id]',
describe: __( 'Resume an AI session (id, prefix, "latest", or picker)' ),
describe: __( 'Resume a code session (id, prefix, "latest", or picker)' ),
builder: ( resumeYargs ) => {
return resumeYargs.positional( 'id', {
type: 'string',
Expand All @@ -62,7 +62,7 @@ export const registerCommand = ( yargs: StudioArgv ) => {
if ( error instanceof LoggerError ) {
logger.reportError( error );
} else {
const loggerError = new LoggerError( __( 'Failed to resume AI session' ), error );
const loggerError = new LoggerError( __( 'Failed to resume code session' ), error );
logger.reportError( loggerError );
}
}
Expand Down
12 changes: 6 additions & 6 deletions apps/cli/commands/ai/tests/ai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ vi.mock( 'cli/commands/auth/logout', () => ( {
runCommand: vi.fn().mockResolvedValue( undefined ),
} ) );

describe( 'CLI: studio ai sessions command', () => {
describe( 'CLI: studio code sessions command', () => {
beforeEach( () => {
vi.clearAllMocks();
vi.mocked( readCliConfig ).mockResolvedValue( {
Expand All @@ -186,31 +186,31 @@ describe( 'CLI: studio ai sessions command', () => {

function buildParser(): StudioArgv {
const parser = yargs( [] ).scriptName( 'studio' ).strict().exitProcess( false ) as StudioArgv;
parser.command( 'ai', __( 'AI-powered WordPress assistant' ), ( aiYargs ) => {
parser.command( [ 'code', 'ai' ], __( 'AI agent for building WordPress' ), ( aiYargs ) => {
registerAiCommand( aiYargs as StudioArgv );
aiYargs.command( 'sessions', __( 'Manage AI sessions' ), ( sessionsYargs ) => {
aiYargs.command( 'sessions', __( 'Manage code sessions' ), ( sessionsYargs ) => {
sessionsYargs
.option( 'path', {
hidden: true,
} )
.option( 'session-persistence', {
type: 'boolean',
default: true,
description: __( 'Record this AI chat session to disk' ),
description: __( 'Record this code session to disk' ),
} );
registerAiSessionsListCommand( sessionsYargs as StudioArgv );
registerAiSessionsResumeCommand( sessionsYargs as StudioArgv );
registerAiSessionsDeleteCommand( sessionsYargs as StudioArgv );
sessionsYargs
.version( false )
.demandCommand( 1, __( 'You must provide a valid ai sessions command' ) );
.demandCommand( 1, __( 'You must provide a valid code sessions command' ) );
} );
aiYargs.version( false );
} );
return parser;
}

it( 'does not record an empty session when running studio ai and exiting immediately', async () => {
it( 'does not record an empty session when running studio code and exiting immediately', async () => {
await buildParser().parseAsync( [ 'ai' ] );

expect( ( AiSessionRecorder as typeof AiSessionRecorder ).create ).not.toHaveBeenCalled();
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/commands/ai/tests/sessions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ describe( 'ai-sessions', () => {
process.env.E2E_APP_DATA_PATH = testRoot;

await expect( deleteAiSession( 'does-not-exist' ) ).rejects.toThrow(
'AI session not found: does-not-exist'
'Code session not found: does-not-exist'
);
} );

Expand Down
2 changes: 1 addition & 1 deletion apps/cli/commands/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function runCommand(): Promise< void > {
export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: 'mcp',
describe: __( 'MCP server for AI assistants' ),
describe: __( 'MCP server for AI agents' ),
builder: ( yargs ) => {
return yargs.help( false ).option( 'help', { type: 'boolean' } );
},
Expand Down
12 changes: 7 additions & 5 deletions apps/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,10 @@ async function main() {
.strict();

if ( __ENABLE_STUDIO_AI__ ) {
studioArgv.command( 'ai', __( 'AI-powered WordPress assistant' ), async ( aiYargs ) => {
const studioCodeCommandBuilder = async ( aiYargs: StudioArgv ) => {
const { registerCommand: registerAiCommand } = await import( 'cli/commands/ai' );
registerAiCommand( aiYargs );
aiYargs.command( 'sessions', __( 'Manage AI sessions' ), async ( sessionsYargs ) => {
aiYargs.command( 'sessions', __( 'Manage code sessions' ), async ( sessionsYargs ) => {
const [
{ registerCommand: registerAiSessionsListCommand },
{ registerCommand: registerAiSessionsResumeCommand },
Expand All @@ -190,17 +190,19 @@ async function main() {
.option( 'session-persistence', {
type: 'boolean',
default: true,
description: __( 'Record this AI chat session to disk' ),
description: __( 'Record this code session to disk' ),
} );
registerAiSessionsListCommand( sessionsYargs );
registerAiSessionsResumeCommand( sessionsYargs );
registerAiSessionsDeleteCommand( sessionsYargs );
sessionsYargs
.version( false )
.demandCommand( 1, __( 'You must provide a valid ai sessions command' ) );
.demandCommand( 1, __( 'You must provide a valid code sessions command' ) );
} );
aiYargs.version( false );
} );
};
studioArgv.command( 'code', __( 'AI agent for building WordPress' ), studioCodeCommandBuilder );
studioArgv.command( 'ai', false, studioCodeCommandBuilder );
}
const { registerCommand: registerMcpCommand } = await import( 'cli/commands/mcp' );
registerMcpCommand( studioArgv );
Expand Down
Loading