aicm
Version:
A TypeScript CLI tool for managing AI IDE rules across different projects and teams
422 lines (421 loc) • 18.4 kB
JavaScript
;
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,
};
});
}