aicm 0.18.0 → 0.19.1
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.
- package/README.md +222 -65
- package/dist/api.d.ts +7 -0
- package/dist/api.js +10 -0
- package/dist/commands/clean.js +62 -3
- package/dist/commands/init.js +23 -3
- package/dist/commands/install-workspaces.js +45 -136
- package/dist/commands/install.d.ts +5 -2
- package/dist/commands/install.js +98 -53
- package/dist/utils/config.d.ts +22 -13
- package/dist/utils/config.js +140 -132
- package/dist/utils/hooks.d.ts +50 -0
- package/dist/utils/hooks.js +346 -0
- package/package.json +1 -1
|
@@ -5,133 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.installWorkspaces = installWorkspaces;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const hooks_1 = require("../utils/hooks");
|
|
10
10
|
const working_directory_1 = require("../utils/working-directory");
|
|
11
|
-
const rules_file_writer_1 = require("../utils/rules-file-writer");
|
|
12
11
|
const workspace_discovery_1 = require("../utils/workspace-discovery");
|
|
13
12
|
const install_1 = require("./install");
|
|
14
|
-
/**
|
|
15
|
-
* Extract .mdc file references from a command for warning purposes
|
|
16
|
-
* Returns absolute paths to .mdc files that are referenced
|
|
17
|
-
*/
|
|
18
|
-
function extractMdcReferences(content, commandSourcePath) {
|
|
19
|
-
const commandDir = node_path_1.default.dirname(commandSourcePath);
|
|
20
|
-
const mdcFiles = [];
|
|
21
|
-
const seenPaths = new Set();
|
|
22
|
-
// Same regex pattern as rewriteCommandRelativeLinks
|
|
23
|
-
const matches = content.matchAll(/\.\.[/\\][\w\-/\\.]+/g);
|
|
24
|
-
for (const match of matches) {
|
|
25
|
-
const relativePath = match[0];
|
|
26
|
-
const resolved = node_path_1.default.normalize(node_path_1.default.resolve(commandDir, relativePath));
|
|
27
|
-
// Only process .mdc files that exist
|
|
28
|
-
if (resolved.endsWith(".mdc") &&
|
|
29
|
-
!seenPaths.has(resolved) &&
|
|
30
|
-
fs_extra_1.default.existsSync(resolved) &&
|
|
31
|
-
fs_extra_1.default.statSync(resolved).isFile()) {
|
|
32
|
-
seenPaths.add(resolved);
|
|
33
|
-
mdcFiles.push(resolved);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return mdcFiles;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Check if a .mdc file is a manual rule (not automatic/auto-attached/agent-requested)
|
|
40
|
-
*/
|
|
41
|
-
function isManualRule(content) {
|
|
42
|
-
const metadata = (0, rules_file_writer_1.parseRuleFrontmatter)(content);
|
|
43
|
-
// Check for always rules
|
|
44
|
-
if (metadata.type === "always" ||
|
|
45
|
-
metadata.alwaysApply === true ||
|
|
46
|
-
metadata.alwaysApply === "true") {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
// Check for auto-attached rules
|
|
50
|
-
if (metadata.type === "auto-attached" || metadata.globs) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
// Check for agent-requested rules
|
|
54
|
-
if (metadata.type === "agent-requested" || metadata.description) {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
// Default to manual rule
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Process .mdc files referenced by commands in workspace mode
|
|
62
|
-
* Warns about non-manual rules and copies them to root for command access
|
|
63
|
-
*/
|
|
64
|
-
function processMdcFilesForWorkspace(mdcFilePaths, commands, packages, rootDir) {
|
|
65
|
-
const mdcAssets = [];
|
|
66
|
-
// Build a map of command source paths to their preset names
|
|
67
|
-
const commandPresetMap = new Map();
|
|
68
|
-
for (const command of commands) {
|
|
69
|
-
const commandDir = node_path_1.default.dirname(command.sourcePath);
|
|
70
|
-
commandPresetMap.set(commandDir, command.presetName);
|
|
71
|
-
}
|
|
72
|
-
for (const mdcPath of mdcFilePaths) {
|
|
73
|
-
const content = fs_extra_1.default.readFileSync(mdcPath, "utf8");
|
|
74
|
-
if (!isManualRule(content)) {
|
|
75
|
-
const relativePath = node_path_1.default.basename(mdcPath);
|
|
76
|
-
console.warn(chalk_1.default.yellow(`Warning: Command references non-manual rule file "${relativePath}". ` +
|
|
77
|
-
`This may cause the rule to be included twice in the context. ` +
|
|
78
|
-
`Consider using manual rules (without alwaysApply, globs, or description metadata) ` +
|
|
79
|
-
`when referencing from commands.`));
|
|
80
|
-
}
|
|
81
|
-
// Find which command references this .mdc file and get its preset name
|
|
82
|
-
let mdcAssetName = "";
|
|
83
|
-
let presetName;
|
|
84
|
-
let found = false;
|
|
85
|
-
// First check packages for local files
|
|
86
|
-
for (const pkg of packages) {
|
|
87
|
-
const rulesDir = pkg.config.config.rulesDir;
|
|
88
|
-
if (rulesDir) {
|
|
89
|
-
const pkgRulesPath = node_path_1.default.join(pkg.absolutePath, rulesDir);
|
|
90
|
-
if (mdcPath.startsWith(pkgRulesPath)) {
|
|
91
|
-
mdcAssetName = node_path_1.default.relative(pkgRulesPath, mdcPath);
|
|
92
|
-
found = true;
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// If not found in local packages, it might be from a preset
|
|
98
|
-
if (!found) {
|
|
99
|
-
// Find the command that references this .mdc file
|
|
100
|
-
for (const command of commands) {
|
|
101
|
-
if (command.presetName) {
|
|
102
|
-
const commandDir = node_path_1.default.dirname(command.sourcePath);
|
|
103
|
-
const rulesDir = node_path_1.default.join(node_path_1.default.dirname(commandDir), "rules");
|
|
104
|
-
if (mdcPath.startsWith(rulesDir)) {
|
|
105
|
-
mdcAssetName = node_path_1.default.relative(rulesDir, mdcPath);
|
|
106
|
-
presetName = command.presetName;
|
|
107
|
-
found = true;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (found) {
|
|
114
|
-
// Build the final asset name with preset namespace if applicable
|
|
115
|
-
const finalAssetName = presetName
|
|
116
|
-
? node_path_1.default.posix.join(...(0, install_1.extractNamespaceFromPresetPath)(presetName), mdcAssetName.replace(/\\/g, "/"))
|
|
117
|
-
: mdcAssetName.replace(/\\/g, "/");
|
|
118
|
-
// Create an AssetFile entry for the .mdc file
|
|
119
|
-
mdcAssets.push({
|
|
120
|
-
name: finalAssetName,
|
|
121
|
-
content: Buffer.from(content),
|
|
122
|
-
sourcePath: mdcPath,
|
|
123
|
-
source: presetName ? "preset" : "local",
|
|
124
|
-
presetName,
|
|
125
|
-
});
|
|
126
|
-
// Copy to root .cursor/rules/aicm/
|
|
127
|
-
const cursorRulesDir = node_path_1.default.join(rootDir, ".cursor", "rules", "aicm");
|
|
128
|
-
const targetPath = node_path_1.default.join(cursorRulesDir, finalAssetName);
|
|
129
|
-
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(targetPath));
|
|
130
|
-
fs_extra_1.default.writeFileSync(targetPath, content);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return mdcAssets;
|
|
134
|
-
}
|
|
135
13
|
function mergeWorkspaceCommands(packages) {
|
|
136
14
|
var _a;
|
|
137
15
|
const commands = [];
|
|
@@ -194,6 +72,26 @@ function mergeWorkspaceMcpServers(packages) {
|
|
|
194
72
|
}
|
|
195
73
|
return { merged, conflicts };
|
|
196
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Merge hooks from multiple workspace packages
|
|
77
|
+
*/
|
|
78
|
+
function mergeWorkspaceHooks(packages) {
|
|
79
|
+
const allHooksConfigs = [];
|
|
80
|
+
const allHookFiles = [];
|
|
81
|
+
for (const pkg of packages) {
|
|
82
|
+
// Collect hooks configs
|
|
83
|
+
if (pkg.config.hooks) {
|
|
84
|
+
allHooksConfigs.push(pkg.config.hooks);
|
|
85
|
+
}
|
|
86
|
+
// Collect hook files
|
|
87
|
+
allHookFiles.push(...pkg.config.hookFiles);
|
|
88
|
+
}
|
|
89
|
+
// Merge hooks configs
|
|
90
|
+
const merged = (0, hooks_1.mergeHooksConfigs)(allHooksConfigs);
|
|
91
|
+
// Dedupe hook files by basename with MD5 checking
|
|
92
|
+
const dedupedHookFiles = (0, hooks_1.dedupeHookFiles)(allHookFiles);
|
|
93
|
+
return { merged, hookFiles: dedupedHookFiles };
|
|
94
|
+
}
|
|
197
95
|
/**
|
|
198
96
|
* Install aicm configurations for all packages in a workspace
|
|
199
97
|
*/
|
|
@@ -202,6 +100,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
202
100
|
let totalRuleCount = 0;
|
|
203
101
|
let totalCommandCount = 0;
|
|
204
102
|
let totalAssetCount = 0;
|
|
103
|
+
let totalHookCount = 0;
|
|
205
104
|
// Install packages sequentially for now (can be parallelized later)
|
|
206
105
|
for (const pkg of packages) {
|
|
207
106
|
const packagePath = pkg.absolutePath;
|
|
@@ -214,6 +113,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
214
113
|
totalRuleCount += result.installedRuleCount;
|
|
215
114
|
totalCommandCount += result.installedCommandCount;
|
|
216
115
|
totalAssetCount += result.installedAssetCount;
|
|
116
|
+
totalHookCount += result.installedHookCount;
|
|
217
117
|
results.push({
|
|
218
118
|
path: pkg.relativePath,
|
|
219
119
|
success: result.success,
|
|
@@ -221,6 +121,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
221
121
|
installedRuleCount: result.installedRuleCount,
|
|
222
122
|
installedCommandCount: result.installedCommandCount,
|
|
223
123
|
installedAssetCount: result.installedAssetCount,
|
|
124
|
+
installedHookCount: result.installedHookCount,
|
|
224
125
|
});
|
|
225
126
|
}
|
|
226
127
|
catch (error) {
|
|
@@ -231,6 +132,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
231
132
|
installedRuleCount: 0,
|
|
232
133
|
installedCommandCount: 0,
|
|
233
134
|
installedAssetCount: 0,
|
|
135
|
+
installedHookCount: 0,
|
|
234
136
|
});
|
|
235
137
|
}
|
|
236
138
|
}
|
|
@@ -241,6 +143,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
241
143
|
totalRuleCount,
|
|
242
144
|
totalCommandCount,
|
|
243
145
|
totalAssetCount,
|
|
146
|
+
totalHookCount,
|
|
244
147
|
};
|
|
245
148
|
}
|
|
246
149
|
/**
|
|
@@ -272,6 +175,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
272
175
|
installedRuleCount: 0,
|
|
273
176
|
installedCommandCount: 0,
|
|
274
177
|
installedAssetCount: 0,
|
|
178
|
+
installedHookCount: 0,
|
|
275
179
|
packagesCount: 0,
|
|
276
180
|
};
|
|
277
181
|
}
|
|
@@ -296,20 +200,11 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
296
200
|
workspaceCommands.length > 0 &&
|
|
297
201
|
workspaceCommandTargets.length > 0) {
|
|
298
202
|
const dedupedWorkspaceCommands = (0, install_1.dedupeCommandsForInstall)(workspaceCommands);
|
|
299
|
-
// Collect all assets from packages
|
|
203
|
+
// Collect all assets from packages
|
|
300
204
|
const allAssets = packages.flatMap((pkg) => { var _a; return (_a = pkg.config.assets) !== null && _a !== void 0 ? _a : []; });
|
|
301
|
-
// Copy assets to root
|
|
205
|
+
// Copy assets to root
|
|
302
206
|
(0, install_1.writeAssetsToTargets)(allAssets, workspaceCommandTargets);
|
|
303
|
-
|
|
304
|
-
const mdcFilesSet = new Set();
|
|
305
|
-
for (const command of dedupedWorkspaceCommands) {
|
|
306
|
-
const mdcRefs = extractMdcReferences(command.content, command.sourcePath);
|
|
307
|
-
mdcRefs.forEach((ref) => mdcFilesSet.add(ref));
|
|
308
|
-
}
|
|
309
|
-
const mdcAssets = processMdcFilesForWorkspace(mdcFilesSet, dedupedWorkspaceCommands, packages, cwd);
|
|
310
|
-
// Merge .mdc assets with regular assets for link rewriting
|
|
311
|
-
const allAssetsWithMdc = [...allAssets, ...mdcAssets];
|
|
312
|
-
(0, install_1.writeCommandsToTargets)(dedupedWorkspaceCommands, allAssetsWithMdc, workspaceCommandTargets);
|
|
207
|
+
(0, install_1.writeCommandsToTargets)(dedupedWorkspaceCommands, workspaceCommandTargets);
|
|
313
208
|
}
|
|
314
209
|
const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages);
|
|
315
210
|
const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor"));
|
|
@@ -320,6 +215,12 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
320
215
|
for (const conflict of conflicts) {
|
|
321
216
|
console.warn(`Warning: MCP configuration conflict detected\n Key: "${conflict.key}"\n Packages: ${conflict.packages.join(", ")}\n Using configuration from: ${conflict.chosen}`);
|
|
322
217
|
}
|
|
218
|
+
// Merge and write hooks for workspace
|
|
219
|
+
const { merged: rootHooks, hookFiles: rootHookFiles } = mergeWorkspaceHooks(packages);
|
|
220
|
+
const hasHooks = rootHooks.hooks && Object.keys(rootHooks.hooks).length > 0;
|
|
221
|
+
if (!dryRun && hasCursorTarget && (hasHooks || rootHookFiles.length > 0)) {
|
|
222
|
+
(0, hooks_1.writeHooksToCursor)(rootHooks, rootHookFiles, cwd);
|
|
223
|
+
}
|
|
323
224
|
if (verbose) {
|
|
324
225
|
result.packages.forEach((pkg) => {
|
|
325
226
|
if (pkg.success) {
|
|
@@ -327,6 +228,9 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
327
228
|
if (pkg.installedCommandCount > 0) {
|
|
328
229
|
summaryParts.push(`${pkg.installedCommandCount} command${pkg.installedCommandCount === 1 ? "" : "s"}`);
|
|
329
230
|
}
|
|
231
|
+
if (pkg.installedHookCount > 0) {
|
|
232
|
+
summaryParts.push(`${pkg.installedHookCount} hook${pkg.installedHookCount === 1 ? "" : "s"}`);
|
|
233
|
+
}
|
|
330
234
|
console.log(chalk_1.default.green(`✅ ${pkg.path} (${summaryParts.join(", ")})`));
|
|
331
235
|
}
|
|
332
236
|
else {
|
|
@@ -341,7 +245,10 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
341
245
|
const commandSummary = result.totalCommandCount > 0
|
|
342
246
|
? `, ${result.totalCommandCount} command${result.totalCommandCount === 1 ? "" : "s"} total`
|
|
343
247
|
: "";
|
|
344
|
-
|
|
248
|
+
const hookSummary = result.totalHookCount > 0
|
|
249
|
+
? `, ${result.totalHookCount} hook${result.totalHookCount === 1 ? "" : "s"} total`
|
|
250
|
+
: "";
|
|
251
|
+
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})`));
|
|
345
252
|
console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
|
|
346
253
|
}
|
|
347
254
|
const errorDetails = failedPackages
|
|
@@ -353,6 +260,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
353
260
|
installedRuleCount: result.totalRuleCount,
|
|
354
261
|
installedCommandCount: result.totalCommandCount,
|
|
355
262
|
installedAssetCount: result.totalAssetCount,
|
|
263
|
+
installedHookCount: result.totalHookCount,
|
|
356
264
|
packagesCount: result.packages.length,
|
|
357
265
|
};
|
|
358
266
|
}
|
|
@@ -361,6 +269,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
361
269
|
installedRuleCount: result.totalRuleCount,
|
|
362
270
|
installedCommandCount: result.totalCommandCount,
|
|
363
271
|
installedAssetCount: result.totalAssetCount,
|
|
272
|
+
installedHookCount: result.totalHookCount,
|
|
364
273
|
packagesCount: result.packages.length,
|
|
365
274
|
};
|
|
366
275
|
});
|
|
@@ -45,14 +45,17 @@ export interface InstallResult {
|
|
|
45
45
|
* Number of assets installed
|
|
46
46
|
*/
|
|
47
47
|
installedAssetCount: number;
|
|
48
|
+
/**
|
|
49
|
+
* Number of hooks installed
|
|
50
|
+
*/
|
|
51
|
+
installedHookCount: number;
|
|
48
52
|
/**
|
|
49
53
|
* Number of packages installed
|
|
50
54
|
*/
|
|
51
55
|
packagesCount: number;
|
|
52
56
|
}
|
|
53
|
-
export declare function extractNamespaceFromPresetPath(presetPath: string): string[];
|
|
54
57
|
export declare function writeAssetsToTargets(assets: AssetFile[], targets: SupportedTarget[]): void;
|
|
55
|
-
export declare function writeCommandsToTargets(commands: CommandFile[],
|
|
58
|
+
export declare function writeCommandsToTargets(commands: CommandFile[], targets: SupportedTarget[]): void;
|
|
56
59
|
export declare function warnPresetCommandCollisions(commands: CommandFile[]): void;
|
|
57
60
|
export declare function dedupeCommandsForInstall(commands: CommandFile[]): CommandFile[];
|
|
58
61
|
/**
|
package/dist/commands/install.js
CHANGED
|
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.extractNamespaceFromPresetPath = extractNamespaceFromPresetPath;
|
|
7
6
|
exports.writeAssetsToTargets = writeAssetsToTargets;
|
|
8
7
|
exports.writeCommandsToTargets = writeCommandsToTargets;
|
|
9
8
|
exports.warnPresetCommandCollisions = warnPresetCommandCollisions;
|
|
@@ -16,14 +15,41 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
16
15
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
17
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
18
17
|
const config_1 = require("../utils/config");
|
|
18
|
+
const hooks_1 = require("../utils/hooks");
|
|
19
19
|
const working_directory_1 = require("../utils/working-directory");
|
|
20
20
|
const is_ci_1 = require("../utils/is-ci");
|
|
21
21
|
const rules_file_writer_1 = require("../utils/rules-file-writer");
|
|
22
22
|
const install_workspaces_1 = require("./install-workspaces");
|
|
23
|
+
/**
|
|
24
|
+
* Rewrite asset references from source paths to installation paths
|
|
25
|
+
* Only rewrites the ../assets/ pattern - everything else is preserved
|
|
26
|
+
*
|
|
27
|
+
* @param content - The file content to rewrite
|
|
28
|
+
* @param presetName - The preset name if this file is from a preset
|
|
29
|
+
* @param fileInstallDepth - The depth of the file's installation directory relative to .cursor/
|
|
30
|
+
* For example: .cursor/commands/aicm/file.md has depth 2 (commands, aicm)
|
|
31
|
+
* .cursor/rules/aicm/preset/file.mdc has depth 3 (rules, aicm, preset)
|
|
32
|
+
*/
|
|
33
|
+
function rewriteAssetReferences(content, presetName, fileInstallDepth = 2) {
|
|
34
|
+
// Calculate the relative path from the file to .cursor/assets/aicm/
|
|
35
|
+
// We need to go up fileInstallDepth levels to reach .cursor/, then down to assets/aicm/
|
|
36
|
+
const upLevels = "../".repeat(fileInstallDepth);
|
|
37
|
+
// If this is from a preset, include the preset namespace in the asset path
|
|
38
|
+
let assetBasePath = "assets/aicm/";
|
|
39
|
+
if (presetName) {
|
|
40
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(presetName);
|
|
41
|
+
assetBasePath = node_path_1.default.posix.join("assets", "aicm", ...namespace) + "/";
|
|
42
|
+
}
|
|
43
|
+
const targetPath = upLevels + assetBasePath;
|
|
44
|
+
// Replace ../assets/ with the calculated target path
|
|
45
|
+
// Handles both forward slashes and backslashes for cross-platform compatibility
|
|
46
|
+
return content.replace(/\.\.[\\/]assets[\\/]/g, targetPath);
|
|
47
|
+
}
|
|
23
48
|
function getTargetPaths() {
|
|
24
49
|
const projectDir = process.cwd();
|
|
25
50
|
return {
|
|
26
51
|
cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
|
|
52
|
+
assetsAicm: node_path_1.default.join(projectDir, ".cursor", "assets", "aicm"),
|
|
27
53
|
aicm: node_path_1.default.join(projectDir, ".aicm"),
|
|
28
54
|
};
|
|
29
55
|
}
|
|
@@ -34,7 +60,7 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
34
60
|
const ruleNameParts = rule.name.split(node_path_1.default.sep).filter(Boolean);
|
|
35
61
|
if (rule.presetName) {
|
|
36
62
|
// For rules from presets, create a namespaced directory structure
|
|
37
|
-
const namespace = extractNamespaceFromPresetPath(rule.presetName);
|
|
63
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
|
|
38
64
|
// Path will be: cursorRulesDir/namespace/rule-name.mdc
|
|
39
65
|
rulePath = node_path_1.default.join(cursorRulesDir, ...namespace, ...ruleNameParts);
|
|
40
66
|
}
|
|
@@ -44,10 +70,22 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
44
70
|
}
|
|
45
71
|
const ruleFile = rulePath + ".mdc";
|
|
46
72
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(ruleFile));
|
|
47
|
-
|
|
73
|
+
// Calculate the depth for asset path rewriting
|
|
74
|
+
// cursorRulesDir is .cursor/rules/aicm (depth 2 from .cursor)
|
|
75
|
+
// Add namespace depth if present
|
|
76
|
+
let fileInstallDepth = 2; // rules, aicm
|
|
77
|
+
if (rule.presetName) {
|
|
78
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
|
|
79
|
+
fileInstallDepth += namespace.length;
|
|
80
|
+
}
|
|
81
|
+
// Add any subdirectories in the rule name
|
|
82
|
+
fileInstallDepth += ruleNameParts.length - 1; // -1 because the last part is the filename
|
|
83
|
+
// Rewrite asset references before writing
|
|
84
|
+
const content = rewriteAssetReferences(rule.content, rule.presetName, fileInstallDepth);
|
|
85
|
+
fs_extra_1.default.writeFileSync(ruleFile, content);
|
|
48
86
|
}
|
|
49
87
|
}
|
|
50
|
-
function writeCursorCommands(commands, cursorCommandsDir
|
|
88
|
+
function writeCursorCommands(commands, cursorCommandsDir) {
|
|
51
89
|
fs_extra_1.default.removeSync(cursorCommandsDir);
|
|
52
90
|
for (const command of commands) {
|
|
53
91
|
const commandNameParts = command.name
|
|
@@ -57,46 +95,17 @@ function writeCursorCommands(commands, cursorCommandsDir, assets) {
|
|
|
57
95
|
const commandPath = node_path_1.default.join(cursorCommandsDir, ...commandNameParts);
|
|
58
96
|
const commandFile = commandPath + ".md";
|
|
59
97
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(commandFile));
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
98
|
+
// Calculate the depth for asset path rewriting
|
|
99
|
+
// cursorCommandsDir is .cursor/commands/aicm (depth 2 from .cursor)
|
|
100
|
+
// Commands are NOT namespaced by preset, but we still need to account for subdirectories
|
|
101
|
+
let fileInstallDepth = 2; // commands, aicm
|
|
102
|
+
// Add any subdirectories in the command name
|
|
103
|
+
fileInstallDepth += commandNameParts.length - 1; // -1 because the last part is the filename
|
|
104
|
+
// Rewrite asset references before writing
|
|
105
|
+
const content = rewriteAssetReferences(command.content, command.presetName, fileInstallDepth);
|
|
66
106
|
fs_extra_1.default.writeFileSync(commandFile, content);
|
|
67
107
|
}
|
|
68
108
|
}
|
|
69
|
-
function rewriteCommandRelativeLinks(content, commandSourcePath, assets) {
|
|
70
|
-
const commandDir = node_path_1.default.dirname(commandSourcePath);
|
|
71
|
-
const assetMap = new Map(assets.map((a) => {
|
|
72
|
-
let targetPath;
|
|
73
|
-
if (a.presetName) {
|
|
74
|
-
const namespace = extractNamespaceFromPresetPath(a.presetName);
|
|
75
|
-
// Use posix paths for URLs/links (always forward slashes)
|
|
76
|
-
targetPath = node_path_1.default.posix.join(...namespace, a.name);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
// Normalize to posix for consistent forward slashes in links
|
|
80
|
-
targetPath = a.name.split(node_path_1.default.sep).join(node_path_1.default.posix.sep);
|
|
81
|
-
}
|
|
82
|
-
return [node_path_1.default.normalize(a.sourcePath), targetPath];
|
|
83
|
-
}));
|
|
84
|
-
return content.replace(/\.\.[/\\][\w\-/\\.]+/g, (match) => {
|
|
85
|
-
const resolved = node_path_1.default.normalize(node_path_1.default.resolve(commandDir, match));
|
|
86
|
-
return assetMap.has(resolved)
|
|
87
|
-
? `../../rules/aicm/${assetMap.get(resolved)}`
|
|
88
|
-
: match;
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
function extractNamespaceFromPresetPath(presetPath) {
|
|
92
|
-
// Special case: npm package names always use forward slashes, regardless of platform
|
|
93
|
-
if (presetPath.startsWith("@")) {
|
|
94
|
-
// For scoped packages like @scope/package/subdir, create nested directories
|
|
95
|
-
return presetPath.split("/");
|
|
96
|
-
}
|
|
97
|
-
const parts = presetPath.split(node_path_1.default.sep);
|
|
98
|
-
return parts.filter((part) => part.length > 0 && part !== "." && part !== "..");
|
|
99
|
-
}
|
|
100
109
|
/**
|
|
101
110
|
* Write rules to a shared directory and update the given rules file
|
|
102
111
|
*/
|
|
@@ -107,7 +116,7 @@ function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
|
|
|
107
116
|
const ruleNameParts = rule.name.split(node_path_1.default.sep).filter(Boolean);
|
|
108
117
|
if (rule.presetName) {
|
|
109
118
|
// For rules from presets, create a namespaced directory structure
|
|
110
|
-
const namespace = extractNamespaceFromPresetPath(rule.presetName);
|
|
119
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
|
|
111
120
|
// Path will be: ruleDir/namespace/rule-name.md
|
|
112
121
|
rulePath = node_path_1.default.join(ruleDir, ...namespace, ...ruleNameParts);
|
|
113
122
|
}
|
|
@@ -115,7 +124,12 @@ function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
|
|
|
115
124
|
// For local rules, maintain the original flat structure
|
|
116
125
|
rulePath = node_path_1.default.join(ruleDir, ...ruleNameParts);
|
|
117
126
|
}
|
|
118
|
-
|
|
127
|
+
// For windsurf/codex/claude, assets are installed at the same namespace level as rules
|
|
128
|
+
// Example: .aicm/my-preset/rule.md and .aicm/my-preset/asset.json
|
|
129
|
+
// So we need to remove the 'assets/' part from the path
|
|
130
|
+
// ../assets/file.json -> ../file.json
|
|
131
|
+
// ../../assets/file.json -> ../../file.json
|
|
132
|
+
const content = rule.content.replace(/(\.\.[/\\])assets[/\\]/g, "$1");
|
|
119
133
|
const physicalRulePath = rulePath + ".md";
|
|
120
134
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
|
|
121
135
|
fs_extra_1.default.writeFileSync(physicalRulePath, content);
|
|
@@ -123,7 +137,7 @@ function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
|
|
|
123
137
|
// For the rules file, maintain the same structure
|
|
124
138
|
let windsurfPath;
|
|
125
139
|
if (rule.presetName) {
|
|
126
|
-
const namespace = extractNamespaceFromPresetPath(rule.presetName);
|
|
140
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
|
|
127
141
|
windsurfPath =
|
|
128
142
|
node_path_1.default.join(relativeRuleDir, ...namespace, ...ruleNameParts) + ".md";
|
|
129
143
|
}
|
|
@@ -147,7 +161,7 @@ function writeAssetsToTargets(assets, targets) {
|
|
|
147
161
|
let targetDir;
|
|
148
162
|
switch (target) {
|
|
149
163
|
case "cursor":
|
|
150
|
-
targetDir = targetPaths.
|
|
164
|
+
targetDir = targetPaths.assetsAicm;
|
|
151
165
|
break;
|
|
152
166
|
case "windsurf":
|
|
153
167
|
case "codex":
|
|
@@ -160,7 +174,7 @@ function writeAssetsToTargets(assets, targets) {
|
|
|
160
174
|
for (const asset of assets) {
|
|
161
175
|
let assetPath;
|
|
162
176
|
if (asset.presetName) {
|
|
163
|
-
const namespace = extractNamespaceFromPresetPath(asset.presetName);
|
|
177
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(asset.presetName);
|
|
164
178
|
assetPath = node_path_1.default.join(targetDir, ...namespace, asset.name);
|
|
165
179
|
}
|
|
166
180
|
else {
|
|
@@ -203,13 +217,13 @@ function writeRulesToTargets(rules, assets, targets) {
|
|
|
203
217
|
// Write assets after rules so they don't get wiped by emptyDirSync
|
|
204
218
|
writeAssetsToTargets(assets, targets);
|
|
205
219
|
}
|
|
206
|
-
function writeCommandsToTargets(commands,
|
|
220
|
+
function writeCommandsToTargets(commands, targets) {
|
|
207
221
|
const projectDir = process.cwd();
|
|
208
222
|
const cursorRoot = node_path_1.default.join(projectDir, ".cursor");
|
|
209
223
|
for (const target of targets) {
|
|
210
224
|
if (target === "cursor") {
|
|
211
225
|
const commandsDir = node_path_1.default.join(cursorRoot, "commands", "aicm");
|
|
212
|
-
writeCursorCommands(commands, commandsDir
|
|
226
|
+
writeCursorCommands(commands, commandsDir);
|
|
213
227
|
}
|
|
214
228
|
// Other targets do not support commands yet
|
|
215
229
|
}
|
|
@@ -259,6 +273,21 @@ function writeMcpServersToTargets(mcpServers, targets, cwd) {
|
|
|
259
273
|
// Windsurf and Codex do not support project mcpServers, so skip
|
|
260
274
|
}
|
|
261
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Write hooks to IDE targets
|
|
278
|
+
*/
|
|
279
|
+
function writeHooksToTargets(hooksConfig, hookFiles, targets, cwd) {
|
|
280
|
+
const hasHooks = hooksConfig.hooks && Object.keys(hooksConfig.hooks).length > 0;
|
|
281
|
+
if (!hasHooks && hookFiles.length === 0) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
for (const target of targets) {
|
|
285
|
+
if (target === "cursor") {
|
|
286
|
+
(0, hooks_1.writeHooksToCursor)(hooksConfig, hookFiles, cwd);
|
|
287
|
+
}
|
|
288
|
+
// Other targets do not support hooks yet
|
|
289
|
+
}
|
|
290
|
+
}
|
|
262
291
|
/**
|
|
263
292
|
* Write MCP servers configuration to a specific file
|
|
264
293
|
*/
|
|
@@ -318,16 +347,18 @@ async function installPackage(options = {}) {
|
|
|
318
347
|
installedRuleCount: 0,
|
|
319
348
|
installedCommandCount: 0,
|
|
320
349
|
installedAssetCount: 0,
|
|
350
|
+
installedHookCount: 0,
|
|
321
351
|
packagesCount: 0,
|
|
322
352
|
};
|
|
323
353
|
}
|
|
324
|
-
const { config, rules, commands, assets, mcpServers } = resolvedConfig;
|
|
354
|
+
const { config, rules, commands, assets, mcpServers, hooks, hookFiles } = resolvedConfig;
|
|
325
355
|
if (config.skipInstall === true) {
|
|
326
356
|
return {
|
|
327
357
|
success: true,
|
|
328
358
|
installedRuleCount: 0,
|
|
329
359
|
installedCommandCount: 0,
|
|
330
360
|
installedAssetCount: 0,
|
|
361
|
+
installedHookCount: 0,
|
|
331
362
|
packagesCount: 0,
|
|
332
363
|
};
|
|
333
364
|
}
|
|
@@ -336,18 +367,23 @@ async function installPackage(options = {}) {
|
|
|
336
367
|
try {
|
|
337
368
|
if (!options.dryRun) {
|
|
338
369
|
writeRulesToTargets(rules, assets, config.targets);
|
|
339
|
-
writeCommandsToTargets(commandsToInstall,
|
|
370
|
+
writeCommandsToTargets(commandsToInstall, config.targets);
|
|
340
371
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
341
372
|
writeMcpServersToTargets(mcpServers, config.targets, cwd);
|
|
342
373
|
}
|
|
374
|
+
if (hooks && ((0, hooks_1.countHooks)(hooks) > 0 || hookFiles.length > 0)) {
|
|
375
|
+
writeHooksToTargets(hooks, hookFiles, config.targets, cwd);
|
|
376
|
+
}
|
|
343
377
|
}
|
|
344
378
|
const uniqueRuleCount = new Set(rules.map((rule) => rule.name)).size;
|
|
345
379
|
const uniqueCommandCount = new Set(commandsToInstall.map((command) => command.name)).size;
|
|
380
|
+
const uniqueHookCount = (0, hooks_1.countHooks)(hooks);
|
|
346
381
|
return {
|
|
347
382
|
success: true,
|
|
348
383
|
installedRuleCount: uniqueRuleCount,
|
|
349
384
|
installedCommandCount: uniqueCommandCount,
|
|
350
385
|
installedAssetCount: assets.length,
|
|
386
|
+
installedHookCount: uniqueHookCount,
|
|
351
387
|
packagesCount: 1,
|
|
352
388
|
};
|
|
353
389
|
}
|
|
@@ -358,6 +394,7 @@ async function installPackage(options = {}) {
|
|
|
358
394
|
installedRuleCount: 0,
|
|
359
395
|
installedCommandCount: 0,
|
|
360
396
|
installedAssetCount: 0,
|
|
397
|
+
installedHookCount: 0,
|
|
361
398
|
packagesCount: 0,
|
|
362
399
|
};
|
|
363
400
|
}
|
|
@@ -377,6 +414,7 @@ async function install(options = {}) {
|
|
|
377
414
|
installedRuleCount: 0,
|
|
378
415
|
installedCommandCount: 0,
|
|
379
416
|
installedAssetCount: 0,
|
|
417
|
+
installedHookCount: 0,
|
|
380
418
|
packagesCount: 0,
|
|
381
419
|
};
|
|
382
420
|
}
|
|
@@ -408,10 +446,12 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
408
446
|
else {
|
|
409
447
|
const ruleCount = result.installedRuleCount;
|
|
410
448
|
const commandCount = result.installedCommandCount;
|
|
449
|
+
const hookCount = result.installedHookCount;
|
|
411
450
|
const ruleMessage = ruleCount > 0 ? `${ruleCount} rule${ruleCount === 1 ? "" : "s"}` : null;
|
|
412
451
|
const commandMessage = commandCount > 0
|
|
413
452
|
? `${commandCount} command${commandCount === 1 ? "" : "s"}`
|
|
414
453
|
: null;
|
|
454
|
+
const hookMessage = hookCount > 0 ? `${hookCount} hook${hookCount === 1 ? "" : "s"}` : null;
|
|
415
455
|
const countsParts = [];
|
|
416
456
|
if (ruleMessage) {
|
|
417
457
|
countsParts.push(ruleMessage);
|
|
@@ -419,7 +459,12 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
419
459
|
if (commandMessage) {
|
|
420
460
|
countsParts.push(commandMessage);
|
|
421
461
|
}
|
|
422
|
-
|
|
462
|
+
if (hookMessage) {
|
|
463
|
+
countsParts.push(hookMessage);
|
|
464
|
+
}
|
|
465
|
+
const countsMessage = countsParts.length > 0
|
|
466
|
+
? countsParts.join(", ").replace(/, ([^,]*)$/, " and $1")
|
|
467
|
+
: "0 rules";
|
|
423
468
|
if (dryRun) {
|
|
424
469
|
if (result.packagesCount > 1) {
|
|
425
470
|
console.log(`Dry run: validated ${countsMessage} across ${result.packagesCount} packages`);
|
|
@@ -428,8 +473,8 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
428
473
|
console.log(`Dry run: validated ${countsMessage}`);
|
|
429
474
|
}
|
|
430
475
|
}
|
|
431
|
-
else if (ruleCount === 0 && commandCount === 0) {
|
|
432
|
-
console.log("No rules or
|
|
476
|
+
else if (ruleCount === 0 && commandCount === 0 && hookCount === 0) {
|
|
477
|
+
console.log("No rules, commands, or hooks installed");
|
|
433
478
|
}
|
|
434
479
|
else if (result.packagesCount > 1) {
|
|
435
480
|
console.log(`Successfully installed ${countsMessage} across ${result.packagesCount} packages`);
|