UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

266 lines (265 loc) • 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.configureAiAgentsHandler = configureAiAgentsHandler; exports.configureAiAgentsHandlerImpl = configureAiAgentsHandlerImpl; const enquirer_1 = require("enquirer"); const output_1 = require("../../utils/output"); const provenance_1 = require("../../utils/provenance"); const chalk = require("chalk"); const utils_1 = require("../../ai/utils"); const devkit_internals_1 = require("../../devkit-internals"); const workspace_root_1 = require("../../utils/workspace-root"); const ora = require("ora"); const path_1 = require("path"); async function configureAiAgentsHandler(args, inner = false) { // Use environment variable to force local execution if (process.env.NX_USE_LOCAL === 'true' || process.env.NX_AI_FILES_USE_LOCAL === 'true' || inner) { return await configureAiAgentsHandlerImpl(args); } let cleanup; try { await (0, provenance_1.ensurePackageHasProvenance)('nx', 'latest'); const packageInstallResults = (0, devkit_internals_1.installPackageToTmp)('nx', 'latest'); cleanup = packageInstallResults.cleanup; let modulePath = require.resolve('nx/src/command-line/configure-ai-agents/configure-ai-agents.js', { paths: [packageInstallResults.tempDir] }); const module = await Promise.resolve(`${modulePath}`).then(s => require(s)); const configureAiAgentsResult = await module.configureAiAgentsHandler(args, true); cleanup(); return configureAiAgentsResult; } catch (error) { if (cleanup) { cleanup(); } // Fall back to local implementation return configureAiAgentsHandlerImpl(args); } } async function configureAiAgentsHandlerImpl(options) { const normalizedOptions = normalizeOptions(options); const { nonConfiguredAgents, partiallyConfiguredAgents, fullyConfiguredAgents, disabledAgents, } = await (0, utils_1.getAgentConfigurations)(normalizedOptions.agents, workspace_root_1.workspaceRoot); if (disabledAgents.length > 0) { const commandNames = disabledAgents.map((a) => { if (a.name === 'cursor') return '"cursor"'; if (a.name === 'copilot') return '"code"/"code-insiders"'; return a; }); const title = commandNames.length === 1 ? `${commandNames[0]} command not available.` : `CLI commands ${commandNames .map((c) => `${c}`) .join('/')} not available.`; output_1.output.log({ title, bodyLines: [ chalk.dim('To manually configure the Nx MCP in your editor, install Nx Console (https://nx.dev/getting-started/editor-setup)'), ], }); } if (normalizedOptions.agents.filter((agentName) => !disabledAgents.find((a) => a.name === agentName)).length === 0) { output_1.output.error({ title: 'Please select at least one AI agent to configure.', }); process.exit(1); } // important for wording const usingAllAgents = normalizedOptions.agents.length === utils_1.supportedAgents.length; if (normalizedOptions.check) { const outOfDateAgents = fullyConfiguredAgents.filter((a) => a?.outdated); // only error if something is fully configured but outdated if (normalizedOptions.check === 'outdated') { if (fullyConfiguredAgents.length === 0) { output_1.output.log({ title: 'No AI agents are configured', bodyLines: [ 'You can configure AI agents by running `nx configure-ai-agents`.', ], }); process.exit(0); } if (outOfDateAgents.length === 0) { output_1.output.success({ title: 'All configured AI agents are up to date', bodyLines: fullyConfiguredAgents.map((a) => `- ${a.displayName}`), }); process.exit(0); } else { output_1.output.log({ title: 'The following AI agents are out of date:', bodyLines: [ ...outOfDateAgents.map((a) => { const rulesPath = a.rulesPath; const displayPath = rulesPath.startsWith(workspace_root_1.workspaceRoot) ? (0, path_1.relative)(workspace_root_1.workspaceRoot, rulesPath) : rulesPath; return `- ${a.displayName} (${displayPath})`; }), '', 'You can update them by running `nx configure-ai-agents`.', ], }); process.exit(1); } // error on any partial, outdated or non-configured agent } else if (normalizedOptions.check === 'all') { if (partiallyConfiguredAgents.length === 0 && outOfDateAgents.length === 0 && nonConfiguredAgents.length === 0) { output_1.output.success({ title: `All ${!usingAllAgents ? 'selected' : 'supported'} AI agents are fully configured and up to date`, bodyLines: fullyConfiguredAgents.map((a) => `- ${a.displayName}`), }); process.exit(0); } output_1.output.error({ title: 'The following agents are not fully configured or up to date:', bodyLines: [ ...partiallyConfiguredAgents, ...outOfDateAgents, ...nonConfiguredAgents, ].map((a) => getAgentChoiceForPrompt(a).message), }); process.exit(1); } } const allAgentChoices = []; const preselectedIndices = []; let currentIndex = 0; // Partially configured agents first (highest priority) partiallyConfiguredAgents.forEach((a) => { allAgentChoices.push(getAgentChoiceForPrompt(a)); preselectedIndices.push(currentIndex); currentIndex++; }); // Outdated agents second for (const a of fullyConfiguredAgents) { if (a.outdated) { allAgentChoices.push(getAgentChoiceForPrompt(a)); preselectedIndices.push(currentIndex); currentIndex++; } } // Non-configured agents last nonConfiguredAgents.forEach((a) => { allAgentChoices.push(getAgentChoiceForPrompt(a)); currentIndex++; }); if (allAgentChoices.length === 0) { output_1.output.success({ title: `No new agents to configure. All ${!usingAllAgents ? 'selected' : 'supported'} AI agents are already configured:`, bodyLines: fullyConfiguredAgents.map((agent) => `- ${agent.displayName}`), }); process.exit(0); } let selectedAgents; if (options.interactive !== false) { try { selectedAgents = (await (0, enquirer_1.prompt)({ type: 'multiselect', name: 'agents', message: 'Which AI agents would you like to configure? (space to select, enter to confirm)', choices: allAgentChoices, initial: preselectedIndices, required: true, footer: function () { const focused = this.focused; if (focused.partial) { return chalk.dim(focused.partialReason); } if (focused.agentConfiguration.outdated) { return chalk.dim(` The rules file at ${focused.rulesDisplayPath} can be updated with the latest Nx recommendations`); } if (!focused.agentConfiguration.mcp && !focused.agentConfiguration.rules) { return chalk.dim(` Configures agent rules at ${focused.rulesDisplayPath} and the Nx MCP server ${focused.mcpDisplayPath ? `at ${focused.mcpDisplayPath}` : 'via Nx Console'}`); } }, })).agents; } catch { process.exit(1); } } else { // in non-interactive mode, configure all selectedAgents = allAgentChoices.map((a) => a.name); } if (selectedAgents?.length === 0) { output_1.output.log({ title: 'No agents selected', }); process.exit(0); } const configSpinner = ora(`Configuring agent(s)...`).start(); try { await (0, utils_1.configureAgents)(selectedAgents, workspace_root_1.workspaceRoot, false); const configuredOrUpdatedAgents = [ ...new Set([ ...fullyConfiguredAgents.map((a) => a.name), ...selectedAgents, ]), ]; configSpinner.stop(); output_1.output.log({ title: 'AI agents set up successfully. Configured Agents:', bodyLines: configuredOrUpdatedAgents.map((agent) => `- ${utils_1.agentDisplayMap[agent]}`), }); return; } catch (e) { configSpinner.fail('Failed to set up AI agents'); output_1.output.error({ title: 'Error details:', bodyLines: [e.message], }); process.exit(1); } } function getAgentChoiceForPrompt(agent) { const partiallyConfigured = agent.mcp !== agent.rules; let message = agent.displayName; if (partiallyConfigured) { message += ` (${agent.rules ? 'MCP missing' : 'rules missing'})`; } else if (agent.outdated) { message += ' (out of date)'; } const rulesDisplayPath = agent.rulesPath.startsWith(workspace_root_1.workspaceRoot) ? (0, path_1.relative)(workspace_root_1.workspaceRoot, agent.rulesPath) : agent.rulesPath; const mcpDisplayPath = agent.mcpPath?.startsWith(workspace_root_1.workspaceRoot) ? (0, path_1.relative)(workspace_root_1.workspaceRoot, agent.mcpPath) : agent.mcpPath; const partialReason = partiallyConfigured ? agent.rules ? ` Partially configured: MCP missing ${agent.mcpPath ? `at ${mcpDisplayPath}` : 'via Nx Console'}` : ` Partially configured: rules file missing at ${rulesDisplayPath}` : undefined; return { name: agent.name, message, partial: partiallyConfigured, partialReason, agentConfiguration: agent, rulesDisplayPath, mcpDisplayPath, }; } function normalizeOptions(options) { const agents = (options.agents ?? utils_1.supportedAgents).filter((a) => utils_1.supportedAgents.includes(a)); // it used to be just --check which was implicitly 'outdated' const check = (options.check === true ? 'outdated' : options.check) ?? false; return { ...options, agents, check, }; }