aicm 0.4.0 → 0.5.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 +62 -18
- package/dist/commands/install.js +15 -81
- package/dist/commands/list.js +2 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/config.js +29 -6
- package/dist/utils/rule-collector.d.ts +2 -2
- package/dist/utils/rule-collector.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,9 +66,9 @@ Now the rules will be linked to `.cursor/rules/` when you run `npm install`.
|
|
|
66
66
|
|
|
67
67
|
Presets allow you to bundle multiple rules into a single configuration that can be shared across projects.
|
|
68
68
|
|
|
69
|
-
1. **Create a preset
|
|
69
|
+
1. **Create a preset package or directory**
|
|
70
70
|
|
|
71
|
-
Create
|
|
71
|
+
Create an npm package with your rule definitions in an `aicm.json` file:
|
|
72
72
|
|
|
73
73
|
```json
|
|
74
74
|
{
|
|
@@ -81,34 +81,83 @@ Create a JSON file with your rule definitions:
|
|
|
81
81
|
|
|
82
82
|
2. **Reference the preset in your project**
|
|
83
83
|
|
|
84
|
-
In your project's `aicm.json`, reference the preset:
|
|
84
|
+
In your project's `aicm.json`, reference the preset by its npm package or directory name:
|
|
85
85
|
|
|
86
86
|
```json
|
|
87
87
|
{
|
|
88
88
|
"ides": ["cursor"],
|
|
89
|
-
"presets": ["@myteam/ai-tools
|
|
89
|
+
"presets": ["@myteam/ai-tools"]
|
|
90
90
|
}
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
When you run `npx aicm install`, all rules from the preset will be installed to `.cursor/rules/`.
|
|
94
94
|
|
|
95
|
+
### Overriding and Canceling Rules and MCP Servers from Presets
|
|
96
|
+
|
|
97
|
+
When you use a preset, you can override or cancel any rule or mcpServer from the preset in your own `aicm.json` configuration:
|
|
98
|
+
|
|
99
|
+
- **Override**: To override a rule or mcpServer, specify the same key in your config with a new value. The value in your config will take precedence over the preset.
|
|
100
|
+
- **Cancel**: To cancel (remove) a rule or mcpServer from a preset, set its value to `false` in your config.
|
|
101
|
+
|
|
102
|
+
**Example:**
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"ides": ["cursor"],
|
|
107
|
+
"presets": ["@company/ai-rules/aicm.json"],
|
|
108
|
+
"rules": {
|
|
109
|
+
"rule-from-preset-a": "./rules/override-rule.mdc",
|
|
110
|
+
"rule-from-preset-b": false
|
|
111
|
+
},
|
|
112
|
+
"mcpServers": {
|
|
113
|
+
"mcp-from-preset-a": {
|
|
114
|
+
"command": "./scripts/override-mcp.sh",
|
|
115
|
+
"env": { "MCP_TOKEN": "override" }
|
|
116
|
+
},
|
|
117
|
+
"mcp-from-preset-b": false
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- In this example, `npm-rule` is overridden with a local rule, and `preset-rule` is canceled.
|
|
123
|
+
- The `preset-mcp` server is overridden, and `another-mcp` is canceled.
|
|
124
|
+
|
|
125
|
+
This allows you to fully customize or selectively disable rules and servers from any preset you use.
|
|
126
|
+
|
|
95
127
|
### Demo
|
|
96
128
|
|
|
97
129
|
Here is a package to demonstrate how aicm works:
|
|
98
130
|
|
|
131
|
+
1. Install a package containing a rule
|
|
132
|
+
|
|
99
133
|
```bash
|
|
100
|
-
# Install a package containing a rule
|
|
101
134
|
npm install --save-dev pirate-coding-rule
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
2. Initialize aicm config
|
|
102
138
|
|
|
103
|
-
|
|
104
|
-
npx -y aicm
|
|
139
|
+
```bash
|
|
140
|
+
npx -y aicm init
|
|
105
141
|
```
|
|
106
142
|
|
|
107
|
-
|
|
143
|
+
3. Add the rule to your config file: `aicm.json`
|
|
108
144
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"ides": ["cursor"],
|
|
148
|
+
"rules": {
|
|
149
|
+
"pirate-coding": "pirate-coding-rule/rule.mdc"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
4. Install all rules from your configuration
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npx -y aicm install
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This command installs all configured rules and MCPs to their IDE-specific locations.
|
|
112
161
|
|
|
113
162
|
After installation, open Cursor and ask it to do something. Your AI assistant will respond with pirate-themed coding advice.
|
|
114
163
|
|
|
@@ -162,7 +211,6 @@ Example `aicm.json`:
|
|
|
162
211
|
|
|
163
212
|
- **Cursor**: MCP server configs are written to `.cursor/mcp.json` (see Cursor docs for latest path).
|
|
164
213
|
- **Windsurf**: Windsurf does not support project mcpServers. MCP server configuration is not installed for Windsurf projects.
|
|
165
|
-
- All installations are per-project.
|
|
166
214
|
|
|
167
215
|
### Rule Source Types
|
|
168
216
|
|
|
@@ -211,22 +259,18 @@ npx aicm init
|
|
|
211
259
|
Installs rules from your configuration to the appropriate IDE locations.
|
|
212
260
|
|
|
213
261
|
```bash
|
|
214
|
-
npx aicm install
|
|
262
|
+
npx aicm install
|
|
215
263
|
```
|
|
216
264
|
|
|
217
265
|
**Options:**
|
|
218
266
|
|
|
219
|
-
-
|
|
220
|
-
- `[rule-source]`: Optional - Source of the rule (npm package or local path)
|
|
267
|
+
- No arguments are supported. All rules are installed from your configuration and any referenced presets.
|
|
221
268
|
|
|
222
269
|
**Examples:**
|
|
223
270
|
|
|
224
271
|
```bash
|
|
225
272
|
# Install all configured rules
|
|
226
273
|
npx -y aicm install
|
|
227
|
-
|
|
228
|
-
# Install a rule from an npm package and update configuration
|
|
229
|
-
npx -y aicm install react-best-practices @my-team/ai-tools/react-best-practices.mdc
|
|
230
274
|
```
|
|
231
275
|
|
|
232
276
|
## Contributing
|
package/dist/commands/install.js
CHANGED
|
@@ -5,19 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.installCommand = installCommand;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const arg_1 = __importDefault(require("arg"));
|
|
9
8
|
const config_1 = require("../utils/config");
|
|
10
9
|
const rule_detector_1 = require("../utils/rule-detector");
|
|
11
10
|
const rule_collector_1 = require("../utils/rule-collector");
|
|
12
11
|
const rule_writer_1 = require("../utils/rule-writer");
|
|
13
12
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
14
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
-
// Default configuration
|
|
16
|
-
const defaultConfig = {
|
|
17
|
-
ides: ["cursor", "windsurf"],
|
|
18
|
-
rules: {},
|
|
19
|
-
presets: [],
|
|
20
|
-
};
|
|
21
14
|
function writeMcpServersToTargets(mcpServers, ides) {
|
|
22
15
|
if (!mcpServers)
|
|
23
16
|
return;
|
|
@@ -34,74 +27,22 @@ function writeMcpServersToTargets(mcpServers, ides) {
|
|
|
34
27
|
}
|
|
35
28
|
}
|
|
36
29
|
async function installCommand() {
|
|
37
|
-
// Parse command-specific arguments
|
|
38
|
-
const args = (0, arg_1.default)({
|
|
39
|
-
// Removed flags as we'll infer the type from the source
|
|
40
|
-
}, {
|
|
41
|
-
permissive: true,
|
|
42
|
-
argv: process.argv.slice(3), // Skip the first two args and the command name
|
|
43
|
-
});
|
|
44
|
-
// Get rule name and source if provided
|
|
45
|
-
const ruleName = args._.length > 0 ? args._[0] : null;
|
|
46
|
-
const ruleSource = args._.length > 1 ? args._[1] : null;
|
|
47
30
|
try {
|
|
48
31
|
// Initialize rule collection
|
|
49
32
|
const ruleCollection = (0, rule_collector_1.initRuleCollection)();
|
|
50
|
-
// If a rule name and source are provided, install directly
|
|
51
|
-
if (ruleName && ruleSource) {
|
|
52
|
-
// Detect rule type from the source string
|
|
53
|
-
const ruleType = (0, rule_detector_1.detectRuleType)(ruleSource);
|
|
54
|
-
// Create config file if it doesn't exist
|
|
55
|
-
let config = (0, config_1.getConfig)();
|
|
56
|
-
if (!config) {
|
|
57
|
-
config = { ...defaultConfig };
|
|
58
|
-
console.log(chalk_1.default.blue("Configuration file not found. Creating a new one..."));
|
|
59
|
-
}
|
|
60
|
-
// Add the rule to the config
|
|
61
|
-
config.rules[ruleName] = ruleSource;
|
|
62
|
-
// Save the updated config
|
|
63
|
-
(0, config_1.saveConfig)(config);
|
|
64
|
-
console.log(chalk_1.default.green("Configuration updated successfully!"));
|
|
65
|
-
// Collect the rule based on its type
|
|
66
|
-
let ruleContent;
|
|
67
|
-
switch (ruleType) {
|
|
68
|
-
case "npm":
|
|
69
|
-
ruleContent = (0, rule_collector_1.collectNpmRule)(ruleName, ruleSource);
|
|
70
|
-
break;
|
|
71
|
-
case "local":
|
|
72
|
-
ruleContent = (0, rule_collector_1.collectLocalRule)(ruleName, ruleSource);
|
|
73
|
-
break;
|
|
74
|
-
default:
|
|
75
|
-
console.log(chalk_1.default.yellow(`Unknown rule type: ${ruleType}`));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
// Add rule to collection
|
|
79
|
-
(0, rule_collector_1.addRuleToCollection)(ruleCollection, ruleContent, config.ides);
|
|
80
|
-
// Write rules to targets
|
|
81
|
-
(0, rule_writer_1.writeRulesToTargets)(ruleCollection);
|
|
82
|
-
// Write mcpServers config to IDE targets
|
|
83
|
-
writeMcpServersToTargets(config.mcpServers, config.ides);
|
|
84
|
-
console.log(chalk_1.default.green("\nRules installation completed!"));
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
33
|
// Load configuration
|
|
88
|
-
|
|
89
|
-
// If config doesn't exist,
|
|
34
|
+
const config = (0, config_1.getConfig)();
|
|
35
|
+
// If config doesn't exist, print error and exit
|
|
90
36
|
if (!config) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
(0, config_1.saveConfig)(config);
|
|
94
|
-
console.log(chalk_1.default.green("Empty configuration file created successfully!"));
|
|
95
|
-
console.log(chalk_1.default.yellow("No rules defined in configuration."));
|
|
96
|
-
console.log(`Edit your ${chalk_1.default.blue("aicm.json")} file to add rules or use the direct install command: npx aicm install <rule-name> <rule-source>`);
|
|
97
|
-
return;
|
|
37
|
+
console.error(chalk_1.default.red("Configuration file not found! Please run 'npx aicm init' to create one."));
|
|
38
|
+
process.exit(1);
|
|
98
39
|
}
|
|
99
40
|
// Check if rules are defined (either directly or through presets)
|
|
100
41
|
if (!config.rules || Object.keys(config.rules).length === 0) {
|
|
101
42
|
// If there are no presets defined either, show a message
|
|
102
43
|
if (!config.presets || config.presets.length === 0) {
|
|
103
44
|
console.log(chalk_1.default.yellow("No rules defined in configuration."));
|
|
104
|
-
console.log(`Edit your ${chalk_1.default.blue("aicm.json")} file to add rules
|
|
45
|
+
console.log(`Edit your ${chalk_1.default.blue("aicm.json")} file to add rules.`);
|
|
105
46
|
return;
|
|
106
47
|
}
|
|
107
48
|
}
|
|
@@ -114,13 +55,11 @@ async function installCommand() {
|
|
|
114
55
|
return;
|
|
115
56
|
}
|
|
116
57
|
}
|
|
117
|
-
// Process each rule
|
|
58
|
+
// Process each rule
|
|
118
59
|
let hasErrors = false;
|
|
119
60
|
for (const [name, source] of Object.entries(config.rules)) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
61
|
+
if (source === false)
|
|
62
|
+
continue; // skip canceled rules
|
|
124
63
|
// Detect rule type from the source string
|
|
125
64
|
const ruleType = (0, rule_detector_1.detectRuleType)(source);
|
|
126
65
|
// Get the base path of the preset file if this rule came from a preset
|
|
@@ -145,25 +84,20 @@ async function installCommand() {
|
|
|
145
84
|
catch (error) {
|
|
146
85
|
hasErrors = true;
|
|
147
86
|
console.error(chalk_1.default.red(`Error processing rule ${name}: ${error instanceof Error ? error.message : String(error)}`));
|
|
148
|
-
// If a specific rule was requested and it failed, exit immediately
|
|
149
|
-
if (ruleName) {
|
|
150
|
-
throw error;
|
|
151
|
-
}
|
|
152
87
|
}
|
|
153
88
|
}
|
|
154
|
-
// If there were errors
|
|
155
|
-
if (hasErrors
|
|
89
|
+
// If there were errors, exit with error
|
|
90
|
+
if (hasErrors) {
|
|
156
91
|
throw new Error("One or more rules failed to process");
|
|
157
92
|
}
|
|
158
|
-
// If a specific rule was requested but not found
|
|
159
|
-
if (ruleName && !Object.keys(config.rules).includes(ruleName)) {
|
|
160
|
-
console.log(chalk_1.default.yellow(`Rule "${ruleName}" not found in configuration.`));
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
93
|
// Write all collected rules to their targets
|
|
164
94
|
(0, rule_writer_1.writeRulesToTargets)(ruleCollection);
|
|
165
95
|
// Write mcpServers config to IDE targets
|
|
166
|
-
|
|
96
|
+
if (config.mcpServers) {
|
|
97
|
+
// Filter out canceled servers
|
|
98
|
+
const filteredMcpServers = Object.fromEntries(Object.entries(config.mcpServers).filter(([, v]) => v !== false));
|
|
99
|
+
writeMcpServersToTargets(filteredMcpServers, config.ides);
|
|
100
|
+
}
|
|
167
101
|
console.log(chalk_1.default.green("\nRules installation completed!"));
|
|
168
102
|
}
|
|
169
103
|
catch (error) {
|
package/dist/commands/list.js
CHANGED
|
@@ -23,6 +23,8 @@ function listCommand() {
|
|
|
23
23
|
console.log(chalk_1.default.blue("Configured Rules:"));
|
|
24
24
|
console.log(chalk_1.default.dim("─".repeat(50)));
|
|
25
25
|
for (const [ruleName, source] of Object.entries(config.rules)) {
|
|
26
|
+
if (source === false)
|
|
27
|
+
continue; // skip canceled rules
|
|
26
28
|
const ruleType = (0, rule_detector_1.detectRuleType)(source);
|
|
27
29
|
const status = (0, rule_status_1.checkRuleStatus)(ruleName, ruleType, config.ides);
|
|
28
30
|
const statusColor = status
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type Rule = string;
|
|
1
|
+
export type Rule = string | false;
|
|
2
2
|
export interface Rules {
|
|
3
3
|
[ruleName: string]: Rule;
|
|
4
4
|
}
|
|
@@ -12,7 +12,7 @@ export type MCPServer = {
|
|
|
12
12
|
env?: Record<string, string>;
|
|
13
13
|
command?: never;
|
|
14
14
|
args?: never;
|
|
15
|
-
};
|
|
15
|
+
} | false;
|
|
16
16
|
export interface MCPServers {
|
|
17
17
|
[serverName: string]: MCPServer;
|
|
18
18
|
}
|
package/dist/utils/config.js
CHANGED
|
@@ -23,14 +23,24 @@ function getFullPresetPath(presetPath) {
|
|
|
23
23
|
let fullPresetPath = presetPath;
|
|
24
24
|
if (ruleType === "npm") {
|
|
25
25
|
try {
|
|
26
|
+
// Try to resolve as a file first
|
|
26
27
|
fullPresetPath = require.resolve(presetPath, {
|
|
27
28
|
paths: [process.cwd()],
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
catch (_a) {
|
|
32
|
+
// If not a file, check if it's a directory in node_modules
|
|
31
33
|
const directPath = node_path_1.default.join(process.cwd(), "node_modules", presetPath);
|
|
32
34
|
if (fs_extra_1.default.existsSync(directPath)) {
|
|
33
|
-
|
|
35
|
+
// If it's a directory, look for aicm.json inside
|
|
36
|
+
const aicmJsonPath = node_path_1.default.join(directPath, "aicm.json");
|
|
37
|
+
if (fs_extra_1.default.existsSync(aicmJsonPath)) {
|
|
38
|
+
fullPresetPath = aicmJsonPath;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// If aicm.json doesn't exist, treat the directory as invalid
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
34
44
|
}
|
|
35
45
|
else {
|
|
36
46
|
return null;
|
|
@@ -93,12 +103,18 @@ function processPresets(config) {
|
|
|
93
103
|
* Merge preset rules into the config
|
|
94
104
|
*/
|
|
95
105
|
function mergePresetRules(config, presetRules, presetPath) {
|
|
96
|
-
// Add preset rules, but don't override existing rules
|
|
97
106
|
for (const [ruleName, rulePath] of Object.entries(presetRules)) {
|
|
98
|
-
//
|
|
99
|
-
if (
|
|
107
|
+
// Cancel if set to false in config
|
|
108
|
+
if (Object.prototype.hasOwnProperty.call(config.rules, ruleName) &&
|
|
109
|
+
config.rules[ruleName] === false) {
|
|
110
|
+
delete config.rules[ruleName];
|
|
111
|
+
if (config.__ruleSources)
|
|
112
|
+
delete config.__ruleSources[ruleName];
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Only add if not already defined in config (override handled by config)
|
|
116
|
+
if (!Object.prototype.hasOwnProperty.call(config.rules, ruleName)) {
|
|
100
117
|
config.rules[ruleName] = rulePath;
|
|
101
|
-
// Store the source preset path in metadata
|
|
102
118
|
config.__ruleSources = config.__ruleSources || {};
|
|
103
119
|
config.__ruleSources[ruleName] = presetPath;
|
|
104
120
|
}
|
|
@@ -111,7 +127,14 @@ function mergePresetMcpServers(config, presetMcpServers) {
|
|
|
111
127
|
if (!config.mcpServers)
|
|
112
128
|
config.mcpServers = {};
|
|
113
129
|
for (const [serverName, serverConfig] of Object.entries(presetMcpServers)) {
|
|
114
|
-
if
|
|
130
|
+
// Cancel if set to false in config
|
|
131
|
+
if (Object.prototype.hasOwnProperty.call(config.mcpServers, serverName) &&
|
|
132
|
+
config.mcpServers[serverName] === false) {
|
|
133
|
+
delete config.mcpServers[serverName];
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// Only add if not already defined in config (override handled by config)
|
|
137
|
+
if (!Object.prototype.hasOwnProperty.call(config.mcpServers, serverName)) {
|
|
115
138
|
config.mcpServers[serverName] = serverConfig;
|
|
116
139
|
}
|
|
117
140
|
}
|
|
@@ -23,11 +23,11 @@ export declare function addRuleToCollection(collection: RuleCollection, rule: Ru
|
|
|
23
23
|
* @param ruleBasePath Optional base path for resolving relative paths
|
|
24
24
|
* @returns The rule content
|
|
25
25
|
*/
|
|
26
|
-
export declare function collectLocalRule(ruleName: string, source: string, ruleBasePath?: string): RuleContent;
|
|
26
|
+
export declare function collectLocalRule(ruleName: string, source: string | false, ruleBasePath?: string): RuleContent;
|
|
27
27
|
/**
|
|
28
28
|
* Collect a rule from an npm package
|
|
29
29
|
* @param ruleName The name of the rule
|
|
30
30
|
* @param source The npm package source (can include path)
|
|
31
31
|
* @returns The rule content
|
|
32
32
|
*/
|
|
33
|
-
export declare function collectNpmRule(ruleName: string, source: string): RuleContent;
|
|
33
|
+
export declare function collectNpmRule(ruleName: string, source: string | false): RuleContent;
|
|
@@ -87,6 +87,8 @@ function addRuleToCollection(collection, rule, ides) {
|
|
|
87
87
|
* @returns The rule content
|
|
88
88
|
*/
|
|
89
89
|
function collectLocalRule(ruleName, source, ruleBasePath) {
|
|
90
|
+
if (source === false)
|
|
91
|
+
throw new Error(`Rule '${ruleName}' is canceled and should not be processed.`);
|
|
90
92
|
// Resolve path relative to base path or current directory
|
|
91
93
|
let sourcePath = source;
|
|
92
94
|
if (!node_path_1.default.isAbsolute(source)) {
|
|
@@ -118,6 +120,8 @@ function collectLocalRule(ruleName, source, ruleBasePath) {
|
|
|
118
120
|
* @returns The rule content
|
|
119
121
|
*/
|
|
120
122
|
function collectNpmRule(ruleName, source) {
|
|
123
|
+
if (source === false)
|
|
124
|
+
throw new Error(`Rule '${ruleName}' is canceled and should not be processed.`);
|
|
121
125
|
// Parse source into package and file path
|
|
122
126
|
let packageName;
|
|
123
127
|
let packagePath;
|