UNPKG

aicm

Version:

A TypeScript CLI tool for managing AI IDE rules across different projects and teams

422 lines (421 loc) 18.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.installWorkspaces = installWorkspaces; const chalk_1 = __importDefault(require("chalk")); const node_path_1 = __importDefault(require("node:path")); const hooks_1 = require("../utils/hooks"); const working_directory_1 = require("../utils/working-directory"); const workspace_discovery_1 = require("../utils/workspace-discovery"); const install_1 = require("./install"); function mergeWorkspaceCommands(packages) { var _a; const commands = []; const seenPresetCommands = new Set(); for (const pkg of packages) { const hasCursorTarget = pkg.config.config.targets.includes("cursor"); if (!hasCursorTarget) { continue; } for (const command of (_a = pkg.config.commands) !== null && _a !== void 0 ? _a : []) { if (command.presetName) { const presetKey = `${command.presetName}::${command.name}`; if (seenPresetCommands.has(presetKey)) { continue; } seenPresetCommands.add(presetKey); } commands.push(command); } } return commands; } function collectWorkspaceCommandTargets(packages) { const targets = new Set(); for (const pkg of packages) { if (pkg.config.config.targets.includes("cursor")) { targets.add("cursor"); } } return Array.from(targets); } /** * Merge skills from multiple workspace packages * Skills are merged flat (not namespaced by preset) * Dedupes preset skills that appear in multiple packages */ function mergeWorkspaceSkills(packages) { var _a; const skills = []; const seenPresetSkills = new Set(); for (const pkg of packages) { // Skills are supported by cursor, claude, and codex targets const hasSkillsTarget = pkg.config.config.targets.includes("cursor") || pkg.config.config.targets.includes("claude") || pkg.config.config.targets.includes("codex"); if (!hasSkillsTarget) { continue; } for (const skill of (_a = pkg.config.skills) !== null && _a !== void 0 ? _a : []) { if (skill.presetName) { // Dedupe preset skills by preset+name combination const presetKey = `${skill.presetName}::${skill.name}`; if (seenPresetSkills.has(presetKey)) { continue; } seenPresetSkills.add(presetKey); } skills.push(skill); } } return skills; } /** * Collect all targets that support skills from workspace packages */ function collectWorkspaceSkillTargets(packages) { const targets = new Set(); for (const pkg of packages) { for (const target of pkg.config.config.targets) { // Skills are supported by cursor, claude, and codex if (target === "cursor" || target === "claude" || target === "codex") { targets.add(target); } } } return Array.from(targets); } /** * Merge agents from multiple workspace packages * Agents are merged flat (not namespaced by preset) * Dedupes preset agents that appear in multiple packages */ function mergeWorkspaceAgents(packages) { var _a; const agents = []; const seenPresetAgents = new Set(); for (const pkg of packages) { // Agents are supported by cursor and claude targets const hasAgentsTarget = pkg.config.config.targets.includes("cursor") || pkg.config.config.targets.includes("claude"); if (!hasAgentsTarget) { continue; } for (const agent of (_a = pkg.config.agents) !== null && _a !== void 0 ? _a : []) { if (agent.presetName) { // Dedupe preset agents by preset+name combination const presetKey = `${agent.presetName}::${agent.name}`; if (seenPresetAgents.has(presetKey)) { continue; } seenPresetAgents.add(presetKey); } agents.push(agent); } } return agents; } /** * Collect all targets that support agents from workspace packages */ function collectWorkspaceAgentTargets(packages) { const targets = new Set(); for (const pkg of packages) { for (const target of pkg.config.config.targets) { // Agents are supported by cursor and claude if (target === "cursor" || target === "claude") { targets.add(target); } } } return Array.from(targets); } function mergeWorkspaceMcpServers(packages) { const merged = {}; const info = {}; for (const pkg of packages) { for (const [key, value] of Object.entries(pkg.config.mcpServers)) { if (value === false) continue; const json = JSON.stringify(value); if (!info[key]) { info[key] = { configs: new Set([json]), packages: [pkg.relativePath], chosen: pkg.relativePath, }; } else { info[key].packages.push(pkg.relativePath); info[key].configs.add(json); info[key].chosen = pkg.relativePath; } merged[key] = value; } } const conflicts = []; for (const [key, data] of Object.entries(info)) { if (data.configs.size > 1) { conflicts.push({ key, packages: data.packages, chosen: data.chosen }); } } return { merged, conflicts }; } /** * Merge hooks from multiple workspace packages */ function mergeWorkspaceHooks(packages) { const allHooksConfigs = []; const allHookFiles = []; for (const pkg of packages) { // Collect hooks configs if (pkg.config.hooks) { allHooksConfigs.push(pkg.config.hooks); } // Collect hook files allHookFiles.push(...pkg.config.hookFiles); } // Merge hooks configs const merged = (0, hooks_1.mergeHooksConfigs)(allHooksConfigs); // Dedupe hook files by basename with MD5 checking const dedupedHookFiles = (0, hooks_1.dedupeHookFiles)(allHookFiles); return { merged, hookFiles: dedupedHookFiles }; } /** * Install aicm configurations for all packages in a workspace */ async function installWorkspacesPackages(packages, options = {}) { const results = []; let totalRuleCount = 0; let totalCommandCount = 0; let totalAssetCount = 0; let totalHookCount = 0; let totalSkillCount = 0; let totalAgentCount = 0; // Install packages sequentially for now (can be parallelized later) for (const pkg of packages) { const packagePath = pkg.absolutePath; try { const result = await (0, install_1.installPackage)({ ...options, cwd: packagePath, config: pkg.config, }); totalRuleCount += result.installedRuleCount; totalCommandCount += result.installedCommandCount; totalAssetCount += result.installedAssetCount; totalHookCount += result.installedHookCount; totalSkillCount += result.installedSkillCount; totalAgentCount += result.installedAgentCount; results.push({ path: pkg.relativePath, success: result.success, error: result.error, installedRuleCount: result.installedRuleCount, installedCommandCount: result.installedCommandCount, installedAssetCount: result.installedAssetCount, installedHookCount: result.installedHookCount, installedSkillCount: result.installedSkillCount, installedAgentCount: result.installedAgentCount, }); } catch (error) { results.push({ path: pkg.relativePath, success: false, error: error instanceof Error ? error : new Error(String(error)), installedRuleCount: 0, installedCommandCount: 0, installedAssetCount: 0, installedHookCount: 0, installedSkillCount: 0, installedAgentCount: 0, }); } } const failedPackages = results.filter((r) => !r.success); return { success: failedPackages.length === 0, packages: results, totalRuleCount, totalCommandCount, totalAssetCount, totalHookCount, totalSkillCount, totalAgentCount, }; } /** * Install rules across multiple packages in a workspace */ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = false) { return (0, working_directory_1.withWorkingDirectory)(cwd, async () => { if (verbose) { console.log(chalk_1.default.blue("🔍 Discovering packages...")); } const allPackages = await (0, workspace_discovery_1.discoverPackagesWithAicm)(cwd); const packages = allPackages.filter((pkg) => { if (pkg.config.config.skipInstall === true) { return false; } const isRoot = pkg.relativePath === "."; if (!isRoot) return true; // For root directories, only keep if it has rules, commands, skills, agents, or presets const hasRules = pkg.config.rules && pkg.config.rules.length > 0; const hasCommands = pkg.config.commands && pkg.config.commands.length > 0; const hasSkills = pkg.config.skills && pkg.config.skills.length > 0; const hasAgents = pkg.config.agents && pkg.config.agents.length > 0; const hasPresets = pkg.config.config.presets && pkg.config.config.presets.length > 0; return hasRules || hasCommands || hasSkills || hasAgents || hasPresets; }); if (packages.length === 0) { return { success: false, error: new Error("No packages with aicm configurations found"), installedRuleCount: 0, installedCommandCount: 0, installedAssetCount: 0, installedHookCount: 0, installedSkillCount: 0, installedAgentCount: 0, packagesCount: 0, }; } if (verbose) { console.log(chalk_1.default.blue(`Found ${packages.length} packages with aicm configurations:`)); packages.forEach((pkg) => { console.log(chalk_1.default.gray(` - ${pkg.relativePath}`)); }); console.log(chalk_1.default.blue(`📦 Installing configurations...`)); } const result = await installWorkspacesPackages(packages, { installOnCI, verbose, dryRun, }); const workspaceCommands = mergeWorkspaceCommands(packages); const workspaceCommandTargets = collectWorkspaceCommandTargets(packages); if (workspaceCommands.length > 0) { (0, install_1.warnPresetCommandCollisions)(workspaceCommands); } if (!dryRun && workspaceCommands.length > 0 && workspaceCommandTargets.length > 0) { const dedupedWorkspaceCommands = (0, install_1.dedupeCommandsForInstall)(workspaceCommands); // Collect all assets from packages const allAssets = packages.flatMap((pkg) => { var _a; return (_a = pkg.config.assets) !== null && _a !== void 0 ? _a : []; }); // Copy assets to root (0, install_1.writeAssetsToTargets)(allAssets, workspaceCommandTargets); (0, install_1.writeCommandsToTargets)(dedupedWorkspaceCommands, workspaceCommandTargets); } // Merge and write skills for workspace const workspaceSkills = mergeWorkspaceSkills(packages); const workspaceSkillTargets = collectWorkspaceSkillTargets(packages); if (workspaceSkills.length > 0) { (0, install_1.warnPresetSkillCollisions)(workspaceSkills); } if (!dryRun && workspaceSkills.length > 0 && workspaceSkillTargets.length > 0) { const dedupedWorkspaceSkills = (0, install_1.dedupeSkillsForInstall)(workspaceSkills); (0, install_1.writeSkillsToTargets)(dedupedWorkspaceSkills, workspaceSkillTargets); } // Merge and write agents for workspace const workspaceAgents = mergeWorkspaceAgents(packages); const workspaceAgentTargets = collectWorkspaceAgentTargets(packages); if (workspaceAgents.length > 0) { (0, install_1.warnPresetAgentCollisions)(workspaceAgents); } if (!dryRun && workspaceAgents.length > 0 && workspaceAgentTargets.length > 0) { const dedupedWorkspaceAgents = (0, install_1.dedupeAgentsForInstall)(workspaceAgents); (0, install_1.writeAgentsToTargets)(dedupedWorkspaceAgents, workspaceAgentTargets); } const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages); const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor")); if (!dryRun && hasCursorTarget && Object.keys(rootMcp).length > 0) { const mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json"); (0, install_1.writeMcpServersToFile)(rootMcp, mcpPath); } for (const conflict of conflicts) { console.warn(`Warning: MCP configuration conflict detected\n Key: "${conflict.key}"\n Packages: ${conflict.packages.join(", ")}\n Using configuration from: ${conflict.chosen}`); } // Merge and write hooks for workspace const { merged: rootHooks, hookFiles: rootHookFiles } = mergeWorkspaceHooks(packages); const hasHooks = rootHooks.hooks && Object.keys(rootHooks.hooks).length > 0; if (!dryRun && hasCursorTarget && (hasHooks || rootHookFiles.length > 0)) { (0, hooks_1.writeHooksToCursor)(rootHooks, rootHookFiles, cwd); } if (verbose) { result.packages.forEach((pkg) => { if (pkg.success) { const summaryParts = [`${pkg.installedRuleCount} rules`]; if (pkg.installedCommandCount > 0) { summaryParts.push(`${pkg.installedCommandCount} command${pkg.installedCommandCount === 1 ? "" : "s"}`); } if (pkg.installedHookCount > 0) { summaryParts.push(`${pkg.installedHookCount} hook${pkg.installedHookCount === 1 ? "" : "s"}`); } if (pkg.installedSkillCount > 0) { summaryParts.push(`${pkg.installedSkillCount} skill${pkg.installedSkillCount === 1 ? "" : "s"}`); } if (pkg.installedAgentCount > 0) { summaryParts.push(`${pkg.installedAgentCount} agent${pkg.installedAgentCount === 1 ? "" : "s"}`); } console.log(chalk_1.default.green(`✅ ${pkg.path} (${summaryParts.join(", ")})`)); } else { console.log(chalk_1.default.red(`❌ ${pkg.path}: ${pkg.error}`)); } }); } const failedPackages = result.packages.filter((r) => !r.success); if (failedPackages.length > 0) { console.log(chalk_1.default.yellow(`Installation completed with errors`)); if (verbose) { const commandSummary = result.totalCommandCount > 0 ? `, ${result.totalCommandCount} command${result.totalCommandCount === 1 ? "" : "s"} total` : ""; const hookSummary = result.totalHookCount > 0 ? `, ${result.totalHookCount} hook${result.totalHookCount === 1 ? "" : "s"} total` : ""; const skillSummary = result.totalSkillCount > 0 ? `, ${result.totalSkillCount} skill${result.totalSkillCount === 1 ? "" : "s"} total` : ""; const agentSummary = result.totalAgentCount > 0 ? `, ${result.totalAgentCount} agent${result.totalAgentCount === 1 ? "" : "s"} total` : ""; console.log(chalk_1.default.green(`Successfully installed: ${result.packages.length - failedPackages.length}/${result.packages.length} packages (${result.totalRuleCount} rule${result.totalRuleCount === 1 ? "" : "s"} total${commandSummary}${hookSummary}${skillSummary}${agentSummary})`)); console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`)); } const errorDetails = failedPackages .map((p) => `${p.path}: ${p.error}`) .join("; "); return { success: false, error: new Error(`Package installation failed for ${failedPackages.length} package(s): ${errorDetails}`), installedRuleCount: result.totalRuleCount, installedCommandCount: result.totalCommandCount, installedAssetCount: result.totalAssetCount, installedHookCount: result.totalHookCount, installedSkillCount: result.totalSkillCount, installedAgentCount: result.totalAgentCount, packagesCount: result.packages.length, }; } return { success: true, installedRuleCount: result.totalRuleCount, installedCommandCount: result.totalCommandCount, installedAssetCount: result.totalAssetCount, installedHookCount: result.totalHookCount, installedSkillCount: result.totalSkillCount, installedAgentCount: result.totalAgentCount, packagesCount: result.packages.length, }; }); }