aicm 0.18.0 → 0.19.0
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 +1 -0
- package/dist/commands/clean.js +61 -0
- 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 +62 -53
- package/dist/utils/config.d.ts +17 -13
- package/dist/utils/config.js +127 -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,25 @@ 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
|
+
function rewriteAssetReferences(content) {
|
|
28
|
+
// Replace ../assets/ with ../../assets/aicm/
|
|
29
|
+
// Handles both forward slashes and backslashes for cross-platform compatibility
|
|
30
|
+
return content.replace(/\.\.[\\/]assets[\\/]/g, "../../assets/aicm/");
|
|
31
|
+
}
|
|
23
32
|
function getTargetPaths() {
|
|
24
33
|
const projectDir = process.cwd();
|
|
25
34
|
return {
|
|
26
35
|
cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
|
|
36
|
+
assetsAicm: node_path_1.default.join(projectDir, ".cursor", "assets", "aicm"),
|
|
27
37
|
aicm: node_path_1.default.join(projectDir, ".aicm"),
|
|
28
38
|
};
|
|
29
39
|
}
|
|
@@ -34,7 +44,7 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
34
44
|
const ruleNameParts = rule.name.split(node_path_1.default.sep).filter(Boolean);
|
|
35
45
|
if (rule.presetName) {
|
|
36
46
|
// For rules from presets, create a namespaced directory structure
|
|
37
|
-
const namespace = extractNamespaceFromPresetPath(rule.presetName);
|
|
47
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
|
|
38
48
|
// Path will be: cursorRulesDir/namespace/rule-name.mdc
|
|
39
49
|
rulePath = node_path_1.default.join(cursorRulesDir, ...namespace, ...ruleNameParts);
|
|
40
50
|
}
|
|
@@ -44,10 +54,12 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
44
54
|
}
|
|
45
55
|
const ruleFile = rulePath + ".mdc";
|
|
46
56
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(ruleFile));
|
|
47
|
-
|
|
57
|
+
// Rewrite asset references before writing
|
|
58
|
+
const content = rewriteAssetReferences(rule.content);
|
|
59
|
+
fs_extra_1.default.writeFileSync(ruleFile, content);
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
|
-
function writeCursorCommands(commands, cursorCommandsDir
|
|
62
|
+
function writeCursorCommands(commands, cursorCommandsDir) {
|
|
51
63
|
fs_extra_1.default.removeSync(cursorCommandsDir);
|
|
52
64
|
for (const command of commands) {
|
|
53
65
|
const commandNameParts = command.name
|
|
@@ -57,46 +69,11 @@ function writeCursorCommands(commands, cursorCommandsDir, assets) {
|
|
|
57
69
|
const commandPath = node_path_1.default.join(cursorCommandsDir, ...commandNameParts);
|
|
58
70
|
const commandFile = commandPath + ".md";
|
|
59
71
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(commandFile));
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
// Rules/assets are installed in .cursor/rules/aicm/
|
|
63
|
-
// So a link like "../rules/asset.json" in source (from commands/ to rules/)
|
|
64
|
-
// needs to become "../../rules/aicm/asset.json" in target (from .cursor/commands/aicm/ to .cursor/rules/aicm/)
|
|
65
|
-
const content = rewriteCommandRelativeLinks(command.content, command.sourcePath, assets);
|
|
72
|
+
// Rewrite asset references before writing
|
|
73
|
+
const content = rewriteAssetReferences(command.content);
|
|
66
74
|
fs_extra_1.default.writeFileSync(commandFile, content);
|
|
67
75
|
}
|
|
68
76
|
}
|
|
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
77
|
/**
|
|
101
78
|
* Write rules to a shared directory and update the given rules file
|
|
102
79
|
*/
|
|
@@ -107,7 +84,7 @@ function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
|
|
|
107
84
|
const ruleNameParts = rule.name.split(node_path_1.default.sep).filter(Boolean);
|
|
108
85
|
if (rule.presetName) {
|
|
109
86
|
// For rules from presets, create a namespaced directory structure
|
|
110
|
-
const namespace = extractNamespaceFromPresetPath(rule.presetName);
|
|
87
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
|
|
111
88
|
// Path will be: ruleDir/namespace/rule-name.md
|
|
112
89
|
rulePath = node_path_1.default.join(ruleDir, ...namespace, ...ruleNameParts);
|
|
113
90
|
}
|
|
@@ -115,7 +92,8 @@ function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
|
|
|
115
92
|
// For local rules, maintain the original flat structure
|
|
116
93
|
rulePath = node_path_1.default.join(ruleDir, ...ruleNameParts);
|
|
117
94
|
}
|
|
118
|
-
|
|
95
|
+
// Rewrite asset references before writing
|
|
96
|
+
const content = rewriteAssetReferences(rule.content);
|
|
119
97
|
const physicalRulePath = rulePath + ".md";
|
|
120
98
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
|
|
121
99
|
fs_extra_1.default.writeFileSync(physicalRulePath, content);
|
|
@@ -123,7 +101,7 @@ function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
|
|
|
123
101
|
// For the rules file, maintain the same structure
|
|
124
102
|
let windsurfPath;
|
|
125
103
|
if (rule.presetName) {
|
|
126
|
-
const namespace = extractNamespaceFromPresetPath(rule.presetName);
|
|
104
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
|
|
127
105
|
windsurfPath =
|
|
128
106
|
node_path_1.default.join(relativeRuleDir, ...namespace, ...ruleNameParts) + ".md";
|
|
129
107
|
}
|
|
@@ -147,7 +125,7 @@ function writeAssetsToTargets(assets, targets) {
|
|
|
147
125
|
let targetDir;
|
|
148
126
|
switch (target) {
|
|
149
127
|
case "cursor":
|
|
150
|
-
targetDir = targetPaths.
|
|
128
|
+
targetDir = targetPaths.assetsAicm;
|
|
151
129
|
break;
|
|
152
130
|
case "windsurf":
|
|
153
131
|
case "codex":
|
|
@@ -160,7 +138,7 @@ function writeAssetsToTargets(assets, targets) {
|
|
|
160
138
|
for (const asset of assets) {
|
|
161
139
|
let assetPath;
|
|
162
140
|
if (asset.presetName) {
|
|
163
|
-
const namespace = extractNamespaceFromPresetPath(asset.presetName);
|
|
141
|
+
const namespace = (0, config_1.extractNamespaceFromPresetPath)(asset.presetName);
|
|
164
142
|
assetPath = node_path_1.default.join(targetDir, ...namespace, asset.name);
|
|
165
143
|
}
|
|
166
144
|
else {
|
|
@@ -203,13 +181,13 @@ function writeRulesToTargets(rules, assets, targets) {
|
|
|
203
181
|
// Write assets after rules so they don't get wiped by emptyDirSync
|
|
204
182
|
writeAssetsToTargets(assets, targets);
|
|
205
183
|
}
|
|
206
|
-
function writeCommandsToTargets(commands,
|
|
184
|
+
function writeCommandsToTargets(commands, targets) {
|
|
207
185
|
const projectDir = process.cwd();
|
|
208
186
|
const cursorRoot = node_path_1.default.join(projectDir, ".cursor");
|
|
209
187
|
for (const target of targets) {
|
|
210
188
|
if (target === "cursor") {
|
|
211
189
|
const commandsDir = node_path_1.default.join(cursorRoot, "commands", "aicm");
|
|
212
|
-
writeCursorCommands(commands, commandsDir
|
|
190
|
+
writeCursorCommands(commands, commandsDir);
|
|
213
191
|
}
|
|
214
192
|
// Other targets do not support commands yet
|
|
215
193
|
}
|
|
@@ -259,6 +237,21 @@ function writeMcpServersToTargets(mcpServers, targets, cwd) {
|
|
|
259
237
|
// Windsurf and Codex do not support project mcpServers, so skip
|
|
260
238
|
}
|
|
261
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Write hooks to IDE targets
|
|
242
|
+
*/
|
|
243
|
+
function writeHooksToTargets(hooksConfig, hookFiles, targets, cwd) {
|
|
244
|
+
const hasHooks = hooksConfig.hooks && Object.keys(hooksConfig.hooks).length > 0;
|
|
245
|
+
if (!hasHooks && hookFiles.length === 0) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
for (const target of targets) {
|
|
249
|
+
if (target === "cursor") {
|
|
250
|
+
(0, hooks_1.writeHooksToCursor)(hooksConfig, hookFiles, cwd);
|
|
251
|
+
}
|
|
252
|
+
// Other targets do not support hooks yet
|
|
253
|
+
}
|
|
254
|
+
}
|
|
262
255
|
/**
|
|
263
256
|
* Write MCP servers configuration to a specific file
|
|
264
257
|
*/
|
|
@@ -318,16 +311,18 @@ async function installPackage(options = {}) {
|
|
|
318
311
|
installedRuleCount: 0,
|
|
319
312
|
installedCommandCount: 0,
|
|
320
313
|
installedAssetCount: 0,
|
|
314
|
+
installedHookCount: 0,
|
|
321
315
|
packagesCount: 0,
|
|
322
316
|
};
|
|
323
317
|
}
|
|
324
|
-
const { config, rules, commands, assets, mcpServers } = resolvedConfig;
|
|
318
|
+
const { config, rules, commands, assets, mcpServers, hooks, hookFiles } = resolvedConfig;
|
|
325
319
|
if (config.skipInstall === true) {
|
|
326
320
|
return {
|
|
327
321
|
success: true,
|
|
328
322
|
installedRuleCount: 0,
|
|
329
323
|
installedCommandCount: 0,
|
|
330
324
|
installedAssetCount: 0,
|
|
325
|
+
installedHookCount: 0,
|
|
331
326
|
packagesCount: 0,
|
|
332
327
|
};
|
|
333
328
|
}
|
|
@@ -336,18 +331,23 @@ async function installPackage(options = {}) {
|
|
|
336
331
|
try {
|
|
337
332
|
if (!options.dryRun) {
|
|
338
333
|
writeRulesToTargets(rules, assets, config.targets);
|
|
339
|
-
writeCommandsToTargets(commandsToInstall,
|
|
334
|
+
writeCommandsToTargets(commandsToInstall, config.targets);
|
|
340
335
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
341
336
|
writeMcpServersToTargets(mcpServers, config.targets, cwd);
|
|
342
337
|
}
|
|
338
|
+
if (hooks && ((0, hooks_1.countHooks)(hooks) > 0 || hookFiles.length > 0)) {
|
|
339
|
+
writeHooksToTargets(hooks, hookFiles, config.targets, cwd);
|
|
340
|
+
}
|
|
343
341
|
}
|
|
344
342
|
const uniqueRuleCount = new Set(rules.map((rule) => rule.name)).size;
|
|
345
343
|
const uniqueCommandCount = new Set(commandsToInstall.map((command) => command.name)).size;
|
|
344
|
+
const uniqueHookCount = (0, hooks_1.countHooks)(hooks);
|
|
346
345
|
return {
|
|
347
346
|
success: true,
|
|
348
347
|
installedRuleCount: uniqueRuleCount,
|
|
349
348
|
installedCommandCount: uniqueCommandCount,
|
|
350
349
|
installedAssetCount: assets.length,
|
|
350
|
+
installedHookCount: uniqueHookCount,
|
|
351
351
|
packagesCount: 1,
|
|
352
352
|
};
|
|
353
353
|
}
|
|
@@ -358,6 +358,7 @@ async function installPackage(options = {}) {
|
|
|
358
358
|
installedRuleCount: 0,
|
|
359
359
|
installedCommandCount: 0,
|
|
360
360
|
installedAssetCount: 0,
|
|
361
|
+
installedHookCount: 0,
|
|
361
362
|
packagesCount: 0,
|
|
362
363
|
};
|
|
363
364
|
}
|
|
@@ -377,6 +378,7 @@ async function install(options = {}) {
|
|
|
377
378
|
installedRuleCount: 0,
|
|
378
379
|
installedCommandCount: 0,
|
|
379
380
|
installedAssetCount: 0,
|
|
381
|
+
installedHookCount: 0,
|
|
380
382
|
packagesCount: 0,
|
|
381
383
|
};
|
|
382
384
|
}
|
|
@@ -408,10 +410,12 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
408
410
|
else {
|
|
409
411
|
const ruleCount = result.installedRuleCount;
|
|
410
412
|
const commandCount = result.installedCommandCount;
|
|
413
|
+
const hookCount = result.installedHookCount;
|
|
411
414
|
const ruleMessage = ruleCount > 0 ? `${ruleCount} rule${ruleCount === 1 ? "" : "s"}` : null;
|
|
412
415
|
const commandMessage = commandCount > 0
|
|
413
416
|
? `${commandCount} command${commandCount === 1 ? "" : "s"}`
|
|
414
417
|
: null;
|
|
418
|
+
const hookMessage = hookCount > 0 ? `${hookCount} hook${hookCount === 1 ? "" : "s"}` : null;
|
|
415
419
|
const countsParts = [];
|
|
416
420
|
if (ruleMessage) {
|
|
417
421
|
countsParts.push(ruleMessage);
|
|
@@ -419,7 +423,12 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
419
423
|
if (commandMessage) {
|
|
420
424
|
countsParts.push(commandMessage);
|
|
421
425
|
}
|
|
422
|
-
|
|
426
|
+
if (hookMessage) {
|
|
427
|
+
countsParts.push(hookMessage);
|
|
428
|
+
}
|
|
429
|
+
const countsMessage = countsParts.length > 0
|
|
430
|
+
? countsParts.join(", ").replace(/, ([^,]*)$/, " and $1")
|
|
431
|
+
: "0 rules";
|
|
423
432
|
if (dryRun) {
|
|
424
433
|
if (result.packagesCount > 1) {
|
|
425
434
|
console.log(`Dry run: validated ${countsMessage} across ${result.packagesCount} packages`);
|
|
@@ -428,8 +437,8 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
428
437
|
console.log(`Dry run: validated ${countsMessage}`);
|
|
429
438
|
}
|
|
430
439
|
}
|
|
431
|
-
else if (ruleCount === 0 && commandCount === 0) {
|
|
432
|
-
console.log("No rules or
|
|
440
|
+
else if (ruleCount === 0 && commandCount === 0 && hookCount === 0) {
|
|
441
|
+
console.log("No rules, commands, or hooks installed");
|
|
433
442
|
}
|
|
434
443
|
else if (result.packagesCount > 1) {
|
|
435
444
|
console.log(`Successfully installed ${countsMessage} across ${result.packagesCount} packages`);
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { CosmiconfigResult } from "cosmiconfig";
|
|
2
|
+
import { HooksJson, HookFile } from "./hooks";
|
|
2
3
|
export interface RawConfig {
|
|
3
|
-
|
|
4
|
-
commandsDir?: string;
|
|
4
|
+
rootDir?: string;
|
|
5
5
|
targets?: string[];
|
|
6
6
|
presets?: string[];
|
|
7
|
-
overrides?: Record<string, string | false>;
|
|
8
7
|
mcpServers?: MCPServers;
|
|
9
8
|
workspaces?: boolean;
|
|
10
9
|
skipInstall?: boolean;
|
|
11
10
|
}
|
|
12
11
|
export interface Config {
|
|
13
|
-
|
|
14
|
-
commandsDir?: string;
|
|
12
|
+
rootDir?: string;
|
|
15
13
|
targets: string[];
|
|
16
14
|
presets?: string[];
|
|
17
|
-
overrides?: Record<string, string | false>;
|
|
18
15
|
mcpServers?: MCPServers;
|
|
19
16
|
workspaces?: boolean;
|
|
20
17
|
skipInstall?: boolean;
|
|
@@ -58,30 +55,37 @@ export interface ResolvedConfig {
|
|
|
58
55
|
commands: CommandFile[];
|
|
59
56
|
assets: AssetFile[];
|
|
60
57
|
mcpServers: MCPServers;
|
|
58
|
+
hooks: HooksJson;
|
|
59
|
+
hookFiles: HookFile[];
|
|
61
60
|
}
|
|
62
|
-
export declare const ALLOWED_CONFIG_KEYS: readonly ["
|
|
61
|
+
export declare const ALLOWED_CONFIG_KEYS: readonly ["rootDir", "targets", "presets", "mcpServers", "workspaces", "skipInstall"];
|
|
63
62
|
export declare const SUPPORTED_TARGETS: readonly ["cursor", "windsurf", "codex", "claude"];
|
|
64
63
|
export type SupportedTarget = (typeof SUPPORTED_TARGETS)[number];
|
|
65
64
|
export declare function detectWorkspacesFromPackageJson(cwd: string): boolean;
|
|
66
65
|
export declare function resolveWorkspaces(config: unknown, configFilePath: string, cwd: string): boolean;
|
|
67
66
|
export declare function applyDefaults(config: RawConfig, workspaces: boolean): Config;
|
|
68
67
|
export declare function validateConfig(config: unknown, configFilePath: string, cwd: string, isWorkspaceMode?: boolean): asserts config is Config;
|
|
69
|
-
export declare function loadRulesFromDirectory(
|
|
70
|
-
export declare function loadCommandsFromDirectory(
|
|
71
|
-
export declare function loadAssetsFromDirectory(
|
|
68
|
+
export declare function loadRulesFromDirectory(directoryPath: string, source: "local" | "preset", presetName?: string): Promise<RuleFile[]>;
|
|
69
|
+
export declare function loadCommandsFromDirectory(directoryPath: string, source: "local" | "preset", presetName?: string): Promise<CommandFile[]>;
|
|
70
|
+
export declare function loadAssetsFromDirectory(directoryPath: string, source: "local" | "preset", presetName?: string): Promise<AssetFile[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Extract namespace from preset path for directory structure
|
|
73
|
+
* Handles both npm packages and local paths consistently
|
|
74
|
+
*/
|
|
75
|
+
export declare function extractNamespaceFromPresetPath(presetPath: string): string[];
|
|
72
76
|
export declare function resolvePresetPath(presetPath: string, cwd: string): string | null;
|
|
73
77
|
export declare function loadPreset(presetPath: string, cwd: string): Promise<{
|
|
74
78
|
config: Config;
|
|
75
|
-
|
|
76
|
-
commandsDir?: string;
|
|
79
|
+
rootDir: string;
|
|
77
80
|
}>;
|
|
78
81
|
export declare function loadAllRules(config: Config, cwd: string): Promise<{
|
|
79
82
|
rules: RuleFile[];
|
|
80
83
|
commands: CommandFile[];
|
|
81
84
|
assets: AssetFile[];
|
|
82
85
|
mcpServers: MCPServers;
|
|
86
|
+
hooks: HooksJson;
|
|
87
|
+
hookFiles: HookFile[];
|
|
83
88
|
}>;
|
|
84
|
-
export declare function applyOverrides<T extends ManagedFile>(files: T[], overrides: Record<string, string | false>, cwd: string): T[];
|
|
85
89
|
export declare function loadConfigFile(searchFrom?: string): Promise<CosmiconfigResult>;
|
|
86
90
|
export declare function loadConfig(cwd?: string): Promise<ResolvedConfig | null>;
|
|
87
91
|
export declare function saveConfig(config: Config, cwd?: string): boolean;
|