aicm 0.20.1 → 0.20.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,736 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.writeAssetsToTargets = writeAssetsToTargets;
7
- exports.writeCommandsToTargets = writeCommandsToTargets;
8
- exports.writeSkillsToTargets = writeSkillsToTargets;
9
- exports.warnPresetSkillCollisions = warnPresetSkillCollisions;
10
- exports.dedupeSkillsForInstall = dedupeSkillsForInstall;
11
- exports.writeAgentsToTargets = writeAgentsToTargets;
12
- exports.warnPresetAgentCollisions = warnPresetAgentCollisions;
13
- exports.dedupeAgentsForInstall = dedupeAgentsForInstall;
14
- exports.warnPresetCommandCollisions = warnPresetCommandCollisions;
15
- exports.dedupeCommandsForInstall = dedupeCommandsForInstall;
16
- exports.writeMcpServersToFile = writeMcpServersToFile;
17
- exports.installPackage = installPackage;
18
- exports.install = install;
19
- exports.installCommand = installCommand;
20
- const chalk_1 = __importDefault(require("chalk"));
21
- const fs_extra_1 = __importDefault(require("fs-extra"));
22
- const node_path_1 = __importDefault(require("node:path"));
23
- const config_1 = require("../utils/config");
24
- const hooks_1 = require("../utils/hooks");
25
- const working_directory_1 = require("../utils/working-directory");
26
- const is_ci_1 = require("../utils/is-ci");
27
- const rules_file_writer_1 = require("../utils/rules-file-writer");
28
- const install_workspaces_1 = require("./install-workspaces");
29
- /**
30
- * Rewrite asset references from source paths to installation paths
31
- * Only rewrites the ../assets/ pattern - everything else is preserved
32
- *
33
- * @param content - The file content to rewrite
34
- * @param presetName - The preset name if this file is from a preset
35
- * @param fileInstallDepth - The depth of the file's installation directory relative to .cursor/
36
- * For example: .cursor/commands/aicm/file.md has depth 2 (commands, aicm)
37
- * .cursor/rules/aicm/preset/file.mdc has depth 3 (rules, aicm, preset)
38
- */
39
- function rewriteAssetReferences(content, presetName, fileInstallDepth = 2) {
40
- // Calculate the relative path from the file to .cursor/assets/aicm/
41
- // We need to go up fileInstallDepth levels to reach .cursor/, then down to assets/aicm/
42
- const upLevels = "../".repeat(fileInstallDepth);
43
- // If this is from a preset, include the preset namespace in the asset path
44
- let assetBasePath = "assets/aicm/";
45
- if (presetName) {
46
- const namespace = (0, config_1.extractNamespaceFromPresetPath)(presetName);
47
- assetBasePath = node_path_1.default.posix.join("assets", "aicm", ...namespace) + "/";
48
- }
49
- const targetPath = upLevels + assetBasePath;
50
- // Replace ../assets/ with the calculated target path
51
- // Handles both forward slashes and backslashes for cross-platform compatibility
52
- return content.replace(/\.\.[\\/]assets[\\/]/g, targetPath);
53
- }
54
- function getTargetPaths() {
55
- const projectDir = process.cwd();
56
- return {
57
- cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
58
- assetsAicm: node_path_1.default.join(projectDir, ".cursor", "assets", "aicm"),
59
- aicm: node_path_1.default.join(projectDir, ".aicm"),
60
- };
61
- }
62
- function writeCursorRules(rules, cursorRulesDir) {
63
- fs_extra_1.default.emptyDirSync(cursorRulesDir);
64
- for (const rule of rules) {
65
- let rulePath;
66
- const ruleNameParts = rule.name.split(node_path_1.default.sep).filter(Boolean);
67
- if (rule.presetName) {
68
- // For rules from presets, create a namespaced directory structure
69
- const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
70
- // Path will be: cursorRulesDir/namespace/rule-name.mdc
71
- rulePath = node_path_1.default.join(cursorRulesDir, ...namespace, ...ruleNameParts);
72
- }
73
- else {
74
- // For local rules, maintain the original flat structure
75
- rulePath = node_path_1.default.join(cursorRulesDir, ...ruleNameParts);
76
- }
77
- const ruleFile = rulePath + ".mdc";
78
- fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(ruleFile));
79
- // Calculate the depth for asset path rewriting
80
- // cursorRulesDir is .cursor/rules/aicm (depth 2 from .cursor)
81
- // Add namespace depth if present
82
- let fileInstallDepth = 2; // rules, aicm
83
- if (rule.presetName) {
84
- const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
85
- fileInstallDepth += namespace.length;
86
- }
87
- // Add any subdirectories in the rule name
88
- fileInstallDepth += ruleNameParts.length - 1; // -1 because the last part is the filename
89
- // Rewrite asset references before writing
90
- const content = rewriteAssetReferences(rule.content, rule.presetName, fileInstallDepth);
91
- fs_extra_1.default.writeFileSync(ruleFile, content);
92
- }
93
- }
94
- function writeCursorCommands(commands, cursorCommandsDir) {
95
- fs_extra_1.default.removeSync(cursorCommandsDir);
96
- for (const command of commands) {
97
- const commandNameParts = command.name
98
- .replace(/\\/g, "/")
99
- .split("/")
100
- .filter(Boolean);
101
- const commandPath = node_path_1.default.join(cursorCommandsDir, ...commandNameParts);
102
- const commandFile = commandPath + ".md";
103
- fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(commandFile));
104
- // Calculate the depth for asset path rewriting
105
- // cursorCommandsDir is .cursor/commands/aicm (depth 2 from .cursor)
106
- // Commands are NOT namespaced by preset, but we still need to account for subdirectories
107
- let fileInstallDepth = 2; // commands, aicm
108
- // Add any subdirectories in the command name
109
- fileInstallDepth += commandNameParts.length - 1; // -1 because the last part is the filename
110
- // Rewrite asset references before writing
111
- const content = rewriteAssetReferences(command.content, command.presetName, fileInstallDepth);
112
- fs_extra_1.default.writeFileSync(commandFile, content);
113
- }
114
- }
115
- /**
116
- * Write rules to a shared directory and update the given rules file
117
- */
118
- function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
119
- fs_extra_1.default.emptyDirSync(ruleDir);
120
- const ruleFiles = rules.map((rule) => {
121
- let rulePath;
122
- const ruleNameParts = rule.name.split(node_path_1.default.sep).filter(Boolean);
123
- if (rule.presetName) {
124
- // For rules from presets, create a namespaced directory structure
125
- const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
126
- // Path will be: ruleDir/namespace/rule-name.md
127
- rulePath = node_path_1.default.join(ruleDir, ...namespace, ...ruleNameParts);
128
- }
129
- else {
130
- // For local rules, maintain the original flat structure
131
- rulePath = node_path_1.default.join(ruleDir, ...ruleNameParts);
132
- }
133
- // For windsurf/codex/claude, assets are installed at the same namespace level as rules
134
- // Example: .aicm/my-preset/rule.md and .aicm/my-preset/asset.json
135
- // So we need to remove the 'assets/' part from the path
136
- // ../assets/file.json -> ../file.json
137
- // ../../assets/file.json -> ../../file.json
138
- const content = rule.content.replace(/(\.\.[/\\])assets[/\\]/g, "$1");
139
- const physicalRulePath = rulePath + ".md";
140
- fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
141
- fs_extra_1.default.writeFileSync(physicalRulePath, content);
142
- const relativeRuleDir = node_path_1.default.basename(ruleDir);
143
- // For the rules file, maintain the same structure
144
- let windsurfPath;
145
- if (rule.presetName) {
146
- const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
147
- windsurfPath =
148
- node_path_1.default.join(relativeRuleDir, ...namespace, ...ruleNameParts) + ".md";
149
- }
150
- else {
151
- windsurfPath = node_path_1.default.join(relativeRuleDir, ...ruleNameParts) + ".md";
152
- }
153
- // Normalize to POSIX style for cross-platform compatibility
154
- const windsurfPathPosix = windsurfPath.replace(/\\/g, "/");
155
- return {
156
- name: rule.name,
157
- path: windsurfPathPosix,
158
- metadata: (0, rules_file_writer_1.parseRuleFrontmatter)(content),
159
- };
160
- });
161
- const rulesContent = (0, rules_file_writer_1.generateRulesFileContent)(ruleFiles);
162
- (0, rules_file_writer_1.writeRulesFile)(rulesContent, node_path_1.default.join(process.cwd(), rulesFile));
163
- }
164
- function writeAssetsToTargets(assets, targets) {
165
- const targetPaths = getTargetPaths();
166
- for (const target of targets) {
167
- let targetDir;
168
- switch (target) {
169
- case "cursor":
170
- targetDir = targetPaths.assetsAicm;
171
- break;
172
- case "windsurf":
173
- case "codex":
174
- case "claude":
175
- targetDir = targetPaths.aicm;
176
- break;
177
- default:
178
- continue;
179
- }
180
- for (const asset of assets) {
181
- let assetPath;
182
- if (asset.presetName) {
183
- const namespace = (0, config_1.extractNamespaceFromPresetPath)(asset.presetName);
184
- assetPath = node_path_1.default.join(targetDir, ...namespace, asset.name);
185
- }
186
- else {
187
- assetPath = node_path_1.default.join(targetDir, asset.name);
188
- }
189
- fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(assetPath));
190
- fs_extra_1.default.writeFileSync(assetPath, asset.content);
191
- }
192
- }
193
- }
194
- /**
195
- * Write all collected rules to their respective IDE targets
196
- */
197
- function writeRulesToTargets(rules, assets, targets) {
198
- const targetPaths = getTargetPaths();
199
- for (const target of targets) {
200
- switch (target) {
201
- case "cursor":
202
- if (rules.length > 0) {
203
- writeCursorRules(rules, targetPaths.cursor);
204
- }
205
- break;
206
- case "windsurf":
207
- if (rules.length > 0) {
208
- writeRulesForFile(rules, assets, targetPaths.aicm, ".windsurfrules");
209
- }
210
- break;
211
- case "codex":
212
- if (rules.length > 0) {
213
- writeRulesForFile(rules, assets, targetPaths.aicm, "AGENTS.md");
214
- }
215
- break;
216
- case "claude":
217
- if (rules.length > 0) {
218
- writeRulesForFile(rules, assets, targetPaths.aicm, "CLAUDE.md");
219
- }
220
- break;
221
- }
222
- }
223
- // Write assets after rules so they don't get wiped by emptyDirSync
224
- writeAssetsToTargets(assets, targets);
225
- }
226
- function writeCommandsToTargets(commands, targets) {
227
- const projectDir = process.cwd();
228
- const cursorRoot = node_path_1.default.join(projectDir, ".cursor");
229
- for (const target of targets) {
230
- if (target === "cursor") {
231
- const commandsDir = node_path_1.default.join(cursorRoot, "commands", "aicm");
232
- writeCursorCommands(commands, commandsDir);
233
- }
234
- // Other targets do not support commands yet
235
- }
236
- }
237
- /**
238
- * Get the skills installation path for a target
239
- * Returns null for targets that don't support skills
240
- */
241
- function getSkillsTargetPath(target) {
242
- const projectDir = process.cwd();
243
- switch (target) {
244
- case "cursor":
245
- return node_path_1.default.join(projectDir, ".cursor", "skills");
246
- case "claude":
247
- return node_path_1.default.join(projectDir, ".claude", "skills");
248
- case "codex":
249
- return node_path_1.default.join(projectDir, ".codex", "skills");
250
- case "windsurf":
251
- // Windsurf does not support skills
252
- return null;
253
- default:
254
- return null;
255
- }
256
- }
257
- /**
258
- * Write a single skill to the target directory
259
- * Copies the entire skill directory and writes .aicm.json metadata
260
- */
261
- function writeSkillToTarget(skill, targetSkillsDir) {
262
- const skillTargetPath = node_path_1.default.join(targetSkillsDir, skill.name);
263
- // Remove existing skill directory if it exists (to ensure clean install)
264
- if (fs_extra_1.default.existsSync(skillTargetPath)) {
265
- fs_extra_1.default.removeSync(skillTargetPath);
266
- }
267
- // Copy the entire skill directory
268
- fs_extra_1.default.copySync(skill.sourcePath, skillTargetPath);
269
- // Write .aicm.json metadata file
270
- // The presence of this file indicates the skill is managed by aicm
271
- const metadata = {
272
- source: skill.source,
273
- };
274
- if (skill.presetName) {
275
- metadata.presetName = skill.presetName;
276
- }
277
- const metadataPath = node_path_1.default.join(skillTargetPath, ".aicm.json");
278
- fs_extra_1.default.writeJsonSync(metadataPath, metadata, { spaces: 2 });
279
- }
280
- /**
281
- * Write skills to all supported target directories
282
- */
283
- function writeSkillsToTargets(skills, targets) {
284
- if (skills.length === 0)
285
- return;
286
- for (const target of targets) {
287
- const targetSkillsDir = getSkillsTargetPath(target);
288
- if (!targetSkillsDir) {
289
- // Target doesn't support skills
290
- continue;
291
- }
292
- // Ensure the skills directory exists
293
- fs_extra_1.default.ensureDirSync(targetSkillsDir);
294
- for (const skill of skills) {
295
- writeSkillToTarget(skill, targetSkillsDir);
296
- }
297
- }
298
- }
299
- /**
300
- * Warn about skill name collisions from different presets
301
- */
302
- function warnPresetSkillCollisions(skills) {
303
- const collisions = new Map();
304
- for (const skill of skills) {
305
- if (!skill.presetName)
306
- continue;
307
- const entry = collisions.get(skill.name);
308
- if (entry) {
309
- entry.presets.add(skill.presetName);
310
- entry.lastPreset = skill.presetName;
311
- }
312
- else {
313
- collisions.set(skill.name, {
314
- presets: new Set([skill.presetName]),
315
- lastPreset: skill.presetName,
316
- });
317
- }
318
- }
319
- for (const [skillName, { presets, lastPreset }] of collisions) {
320
- if (presets.size > 1) {
321
- const presetList = Array.from(presets).sort().join(", ");
322
- console.warn(chalk_1.default.yellow(`Warning: multiple presets provide the "${skillName}" skill (${presetList}). Using definition from ${lastPreset}.`));
323
- }
324
- }
325
- }
326
- /**
327
- * Dedupe skills by name (last one wins)
328
- */
329
- function dedupeSkillsForInstall(skills) {
330
- const unique = new Map();
331
- for (const skill of skills) {
332
- unique.set(skill.name, skill);
333
- }
334
- return Array.from(unique.values());
335
- }
336
- /**
337
- * Get the agents installation path for a target
338
- * Returns null for targets that don't support agents
339
- */
340
- function getAgentsTargetPath(target) {
341
- const projectDir = process.cwd();
342
- switch (target) {
343
- case "cursor":
344
- return node_path_1.default.join(projectDir, ".cursor", "agents");
345
- case "claude":
346
- return node_path_1.default.join(projectDir, ".claude", "agents");
347
- case "codex":
348
- case "windsurf":
349
- // Codex and Windsurf do not support agents
350
- return null;
351
- default:
352
- return null;
353
- }
354
- }
355
- /**
356
- * Write agents to all supported target directories
357
- * Similar to skills, agents are written directly to the agents directory
358
- * with a .aicm.json metadata file tracking which agents are managed
359
- */
360
- function writeAgentsToTargets(agents, targets) {
361
- if (agents.length === 0)
362
- return;
363
- for (const target of targets) {
364
- const targetAgentsDir = getAgentsTargetPath(target);
365
- if (!targetAgentsDir) {
366
- // Target doesn't support agents
367
- continue;
368
- }
369
- // Ensure the agents directory exists
370
- fs_extra_1.default.ensureDirSync(targetAgentsDir);
371
- // Read existing metadata to clean up old managed agents
372
- const metadataPath = node_path_1.default.join(targetAgentsDir, ".aicm.json");
373
- if (fs_extra_1.default.existsSync(metadataPath)) {
374
- try {
375
- const existingMetadata = fs_extra_1.default.readJsonSync(metadataPath);
376
- // Remove previously managed agents
377
- for (const agentName of existingMetadata.managedAgents || []) {
378
- // Skip invalid names containing path separators
379
- if (agentName.includes("/") || agentName.includes("\\")) {
380
- console.warn(chalk_1.default.yellow(`Warning: Skipping invalid agent name "${agentName}" (contains path separator)`));
381
- continue;
382
- }
383
- const fullPath = node_path_1.default.join(targetAgentsDir, agentName + ".md");
384
- if (fs_extra_1.default.existsSync(fullPath)) {
385
- fs_extra_1.default.removeSync(fullPath);
386
- }
387
- }
388
- }
389
- catch (_a) {
390
- // Ignore errors reading metadata
391
- }
392
- }
393
- const managedAgents = [];
394
- for (const agent of agents) {
395
- // Use base name only
396
- const agentName = node_path_1.default.basename(agent.name, node_path_1.default.extname(agent.name));
397
- const agentFile = node_path_1.default.join(targetAgentsDir, agentName + ".md");
398
- fs_extra_1.default.writeFileSync(agentFile, agent.content);
399
- managedAgents.push(agentName);
400
- }
401
- // Write metadata file to track managed agents
402
- const metadata = {
403
- managedAgents,
404
- };
405
- fs_extra_1.default.writeJsonSync(metadataPath, metadata, { spaces: 2 });
406
- }
407
- }
408
- /**
409
- * Warn about agent name collisions from different presets
410
- */
411
- function warnPresetAgentCollisions(agents) {
412
- const collisions = new Map();
413
- for (const agent of agents) {
414
- if (!agent.presetName)
415
- continue;
416
- const entry = collisions.get(agent.name);
417
- if (entry) {
418
- entry.presets.add(agent.presetName);
419
- entry.lastPreset = agent.presetName;
420
- }
421
- else {
422
- collisions.set(agent.name, {
423
- presets: new Set([agent.presetName]),
424
- lastPreset: agent.presetName,
425
- });
426
- }
427
- }
428
- for (const [agentName, { presets, lastPreset }] of collisions) {
429
- if (presets.size > 1) {
430
- const presetList = Array.from(presets).sort().join(", ");
431
- console.warn(chalk_1.default.yellow(`Warning: multiple presets provide the "${agentName}" agent (${presetList}). Using definition from ${lastPreset}.`));
432
- }
433
- }
434
- }
435
- /**
436
- * Dedupe agents by name (last one wins)
437
- */
438
- function dedupeAgentsForInstall(agents) {
439
- const unique = new Map();
440
- for (const agent of agents) {
441
- unique.set(agent.name, agent);
442
- }
443
- return Array.from(unique.values());
444
- }
445
- function warnPresetCommandCollisions(commands) {
446
- const collisions = new Map();
447
- for (const command of commands) {
448
- if (!command.presetName)
449
- continue;
450
- const entry = collisions.get(command.name);
451
- if (entry) {
452
- entry.presets.add(command.presetName);
453
- entry.lastPreset = command.presetName;
454
- }
455
- else {
456
- collisions.set(command.name, {
457
- presets: new Set([command.presetName]),
458
- lastPreset: command.presetName,
459
- });
460
- }
461
- }
462
- for (const [commandName, { presets, lastPreset }] of collisions) {
463
- if (presets.size > 1) {
464
- const presetList = Array.from(presets).sort().join(", ");
465
- console.warn(chalk_1.default.yellow(`Warning: multiple presets provide the "${commandName}" command (${presetList}). Using definition from ${lastPreset}.`));
466
- }
467
- }
468
- }
469
- function dedupeCommandsForInstall(commands) {
470
- const unique = new Map();
471
- for (const command of commands) {
472
- unique.set(command.name, command);
473
- }
474
- return Array.from(unique.values());
475
- }
476
- /**
477
- * Write MCP servers configuration to IDE targets
478
- */
479
- function writeMcpServersToTargets(mcpServers, targets, cwd) {
480
- if (!mcpServers || Object.keys(mcpServers).length === 0)
481
- return;
482
- for (const target of targets) {
483
- if (target === "cursor") {
484
- const mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json");
485
- writeMcpServersToFile(mcpServers, mcpPath);
486
- }
487
- // Windsurf and Codex do not support project mcpServers, so skip
488
- }
489
- }
490
- /**
491
- * Write hooks to IDE targets
492
- */
493
- function writeHooksToTargets(hooksConfig, hookFiles, targets, cwd) {
494
- const hasHooks = hooksConfig.hooks && Object.keys(hooksConfig.hooks).length > 0;
495
- if (!hasHooks && hookFiles.length === 0) {
496
- return;
497
- }
498
- for (const target of targets) {
499
- if (target === "cursor") {
500
- (0, hooks_1.writeHooksToCursor)(hooksConfig, hookFiles, cwd);
501
- }
502
- // Other targets do not support hooks yet
503
- }
504
- }
505
- /**
506
- * Write MCP servers configuration to a specific file
507
- */
508
- function writeMcpServersToFile(mcpServers, mcpPath) {
509
- var _a;
510
- fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(mcpPath));
511
- const existingConfig = fs_extra_1.default.existsSync(mcpPath)
512
- ? fs_extra_1.default.readJsonSync(mcpPath)
513
- : {};
514
- const existingMcpServers = (_a = existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.mcpServers) !== null && _a !== void 0 ? _a : {};
515
- // Filter out any existing aicm-managed servers (with aicm: true)
516
- // This removes stale aicm servers that are no longer in the configuration
517
- const userMcpServers = {};
518
- for (const [key, value] of Object.entries(existingMcpServers)) {
519
- if (typeof value === "object" && value !== null && value.aicm !== true) {
520
- userMcpServers[key] = value;
521
- }
522
- }
523
- // Mark new aicm servers as managed and filter out canceled servers
524
- const aicmMcpServers = {};
525
- for (const [key, value] of Object.entries(mcpServers)) {
526
- if (value !== false) {
527
- aicmMcpServers[key] = {
528
- ...value,
529
- aicm: true,
530
- };
531
- }
532
- }
533
- // Merge user servers with aicm servers (aicm servers override user servers with same key)
534
- const mergedMcpServers = {
535
- ...userMcpServers,
536
- ...aicmMcpServers,
537
- };
538
- const mergedConfig = {
539
- ...existingConfig,
540
- mcpServers: mergedMcpServers,
541
- };
542
- fs_extra_1.default.writeJsonSync(mcpPath, mergedConfig, { spaces: 2 });
543
- }
544
- /**
545
- * Install rules for a single package (used within workspaces and standalone installs)
546
- */
547
- async function installPackage(options = {}) {
548
- const cwd = options.cwd || process.cwd();
549
- return (0, working_directory_1.withWorkingDirectory)(cwd, async () => {
550
- let resolvedConfig;
551
- if (options.config) {
552
- resolvedConfig = options.config;
553
- }
554
- else {
555
- resolvedConfig = await (0, config_1.loadConfig)(cwd);
556
- }
557
- if (!resolvedConfig) {
558
- return {
559
- success: false,
560
- error: new Error("Configuration file not found"),
561
- installedRuleCount: 0,
562
- installedCommandCount: 0,
563
- installedAssetCount: 0,
564
- installedHookCount: 0,
565
- installedSkillCount: 0,
566
- installedAgentCount: 0,
567
- packagesCount: 0,
568
- };
569
- }
570
- const { config, rules, commands, assets, skills, agents, mcpServers, hooks, hookFiles, } = resolvedConfig;
571
- if (config.skipInstall === true) {
572
- return {
573
- success: true,
574
- installedRuleCount: 0,
575
- installedCommandCount: 0,
576
- installedAssetCount: 0,
577
- installedHookCount: 0,
578
- installedSkillCount: 0,
579
- installedAgentCount: 0,
580
- packagesCount: 0,
581
- };
582
- }
583
- warnPresetCommandCollisions(commands);
584
- const commandsToInstall = dedupeCommandsForInstall(commands);
585
- warnPresetSkillCollisions(skills);
586
- const skillsToInstall = dedupeSkillsForInstall(skills);
587
- warnPresetAgentCollisions(agents);
588
- const agentsToInstall = dedupeAgentsForInstall(agents);
589
- try {
590
- if (!options.dryRun) {
591
- writeRulesToTargets(rules, assets, config.targets);
592
- writeCommandsToTargets(commandsToInstall, config.targets);
593
- writeSkillsToTargets(skillsToInstall, config.targets);
594
- writeAgentsToTargets(agentsToInstall, config.targets);
595
- if (mcpServers && Object.keys(mcpServers).length > 0) {
596
- writeMcpServersToTargets(mcpServers, config.targets, cwd);
597
- }
598
- if (hooks && ((0, hooks_1.countHooks)(hooks) > 0 || hookFiles.length > 0)) {
599
- writeHooksToTargets(hooks, hookFiles, config.targets, cwd);
600
- }
601
- }
602
- const uniqueRuleCount = new Set(rules.map((rule) => rule.name)).size;
603
- const uniqueCommandCount = new Set(commandsToInstall.map((command) => command.name)).size;
604
- const uniqueHookCount = (0, hooks_1.countHooks)(hooks);
605
- const uniqueSkillCount = skillsToInstall.length;
606
- const uniqueAgentCount = agentsToInstall.length;
607
- return {
608
- success: true,
609
- installedRuleCount: uniqueRuleCount,
610
- installedCommandCount: uniqueCommandCount,
611
- installedAssetCount: assets.length,
612
- installedHookCount: uniqueHookCount,
613
- installedSkillCount: uniqueSkillCount,
614
- installedAgentCount: uniqueAgentCount,
615
- packagesCount: 1,
616
- };
617
- }
618
- catch (error) {
619
- return {
620
- success: false,
621
- error: error instanceof Error ? error : new Error(String(error)),
622
- installedRuleCount: 0,
623
- installedCommandCount: 0,
624
- installedAssetCount: 0,
625
- installedHookCount: 0,
626
- installedSkillCount: 0,
627
- installedAgentCount: 0,
628
- packagesCount: 0,
629
- };
630
- }
631
- });
632
- }
633
- /**
634
- * Core implementation of the rule installation logic
635
- */
636
- async function install(options = {}) {
637
- const cwd = options.cwd || process.cwd();
638
- const installOnCI = options.installOnCI === true; // Default to false if not specified
639
- const inCI = (0, is_ci_1.isCIEnvironment)();
640
- if (inCI && !installOnCI) {
641
- console.log(chalk_1.default.yellow("Detected CI environment, skipping install."));
642
- return {
643
- success: true,
644
- installedRuleCount: 0,
645
- installedCommandCount: 0,
646
- installedAssetCount: 0,
647
- installedHookCount: 0,
648
- installedSkillCount: 0,
649
- installedAgentCount: 0,
650
- packagesCount: 0,
651
- };
652
- }
653
- return (0, working_directory_1.withWorkingDirectory)(cwd, async () => {
654
- let resolvedConfig;
655
- if (options.config) {
656
- resolvedConfig = options.config;
657
- }
658
- else {
659
- resolvedConfig = await (0, config_1.loadConfig)(cwd);
660
- }
661
- const shouldUseWorkspaces = (resolvedConfig === null || resolvedConfig === void 0 ? void 0 : resolvedConfig.config.workspaces) ||
662
- (!resolvedConfig && (0, config_1.detectWorkspacesFromPackageJson)(cwd));
663
- if (shouldUseWorkspaces) {
664
- return await (0, install_workspaces_1.installWorkspaces)(cwd, installOnCI, options.verbose, options.dryRun);
665
- }
666
- return installPackage(options);
667
- });
668
- }
669
- /**
670
- * CLI command wrapper for install
671
- */
672
- async function installCommand(installOnCI, verbose, dryRun) {
673
- var _a;
674
- const result = await install({ installOnCI, verbose, dryRun });
675
- if (!result.success) {
676
- throw (_a = result.error) !== null && _a !== void 0 ? _a : new Error("Installation failed with unknown error");
677
- }
678
- else {
679
- const ruleCount = result.installedRuleCount;
680
- const commandCount = result.installedCommandCount;
681
- const hookCount = result.installedHookCount;
682
- const skillCount = result.installedSkillCount;
683
- const agentCount = result.installedAgentCount;
684
- const ruleMessage = ruleCount > 0 ? `${ruleCount} rule${ruleCount === 1 ? "" : "s"}` : null;
685
- const commandMessage = commandCount > 0
686
- ? `${commandCount} command${commandCount === 1 ? "" : "s"}`
687
- : null;
688
- const hookMessage = hookCount > 0 ? `${hookCount} hook${hookCount === 1 ? "" : "s"}` : null;
689
- const skillMessage = skillCount > 0
690
- ? `${skillCount} skill${skillCount === 1 ? "" : "s"}`
691
- : null;
692
- const agentMessage = agentCount > 0
693
- ? `${agentCount} agent${agentCount === 1 ? "" : "s"}`
694
- : null;
695
- const countsParts = [];
696
- if (ruleMessage) {
697
- countsParts.push(ruleMessage);
698
- }
699
- if (commandMessage) {
700
- countsParts.push(commandMessage);
701
- }
702
- if (hookMessage) {
703
- countsParts.push(hookMessage);
704
- }
705
- if (skillMessage) {
706
- countsParts.push(skillMessage);
707
- }
708
- if (agentMessage) {
709
- countsParts.push(agentMessage);
710
- }
711
- const countsMessage = countsParts.length > 0
712
- ? countsParts.join(", ").replace(/, ([^,]*)$/, " and $1")
713
- : "0 rules";
714
- if (dryRun) {
715
- if (result.packagesCount > 1) {
716
- console.log(`Dry run: validated ${countsMessage} across ${result.packagesCount} packages`);
717
- }
718
- else {
719
- console.log(`Dry run: validated ${countsMessage}`);
720
- }
721
- }
722
- else if (ruleCount === 0 &&
723
- commandCount === 0 &&
724
- hookCount === 0 &&
725
- skillCount === 0 &&
726
- agentCount === 0) {
727
- console.log("No rules, commands, hooks, skills, or agents installed");
728
- }
729
- else if (result.packagesCount > 1) {
730
- console.log(`Successfully installed ${countsMessage} across ${result.packagesCount} packages`);
731
- }
732
- else {
733
- console.log(`Successfully installed ${countsMessage}`);
734
- }
735
- }
736
- }