aicm 0.5.0 → 0.6.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 +83 -50
- package/dist/api.d.ts +8 -0
- package/dist/api.js +12 -0
- package/dist/commands/init.js +2 -3
- package/dist/commands/install.d.ts +41 -0
- package/dist/commands/install.js +98 -26
- package/dist/utils/rule-status.js +3 -7
- package/dist/utils/rule-writer.js +14 -15
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -6,23 +6,13 @@ A CLI tool for syncing and managing Agentic IDE rules across projects
|
|
|
6
6
|
|
|
7
7
|
## Why
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
With the rise of Agentic IDEs like cursor and windsurf, we have an opportunity to enforce best practices through rules. However, these rules are typically isolated within individual developers or projects.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- **Knowledge Silos**: Best practices remain trapped in individual projects
|
|
13
|
-
- **Change Management**: No efficient way to update and distribute new standards
|
|
14
|
-
|
|
15
|
-
As developers increasingly adopt AI-powered IDEs like Cursor and Windsurf, we have an opportunity to enforce best practices through rules. However, these rules are typically isolated within individual developers or projects.
|
|
16
|
-
|
|
17
|
-
**aicm** is a CLI tool that helps with distribution of agentic IDE configurations, rules and mcps:
|
|
18
|
-
|
|
19
|
-
- 🏛️ **Single Source of Truth**: Define, maintain and version-control all AI IDE rules in one central repository
|
|
20
|
-
- 📦 **Seamless Distribution**: Automatically synchronize the latest rules to developers' local projects using npm packages
|
|
21
|
-
- 🌐 **Cross-IDE Support**: Supports multiple AI-powered IDEs (Cursor, Windsurf)
|
|
11
|
+
**aicm** is a CLI tool for distributing Agentic IDE configurations, rules, and MCPs across projects. It leverages package managers to copy configurations from node_modules to the correct locations in your file system.
|
|
22
12
|
|
|
23
13
|
## Getting Started
|
|
24
14
|
|
|
25
|
-
|
|
15
|
+
Since aicm is not a package manager, begin by creating an npm package that contains your rules and MCP configurations.
|
|
26
16
|
|
|
27
17
|
Consider the following npm package structure:
|
|
28
18
|
|
|
@@ -31,8 +21,7 @@ Consider the following npm package structure:
|
|
|
31
21
|
├── package.json
|
|
32
22
|
└── rules/
|
|
33
23
|
├── typescript.mdc
|
|
34
|
-
|
|
35
|
-
└── general.mdc
|
|
24
|
+
└── react.mdc
|
|
36
25
|
```
|
|
37
26
|
|
|
38
27
|
1. **Point to the path within the npm package**
|
|
@@ -44,8 +33,7 @@ In your project's `aicm.json`, reference the package and the specific rule:
|
|
|
44
33
|
"ides": ["cursor"],
|
|
45
34
|
"rules": {
|
|
46
35
|
"typescript": "@myteam/ai-tools/rules/typescript.mdc",
|
|
47
|
-
"react": "@myteam/ai-tools/rules/react.mdc"
|
|
48
|
-
"general": "@myteam/ai-tools/rules/general.mdc"
|
|
36
|
+
"react": "@myteam/ai-tools/rules/react.mdc"
|
|
49
37
|
}
|
|
50
38
|
}
|
|
51
39
|
```
|
|
@@ -60,21 +48,28 @@ In your project's `aicm.json`, reference the package and the specific rule:
|
|
|
60
48
|
}
|
|
61
49
|
```
|
|
62
50
|
|
|
63
|
-
Now the rules will be linked to `.cursor/rules/` when you run `npm install`.
|
|
51
|
+
Now the rules will be linked to `.cursor/rules/aicm/` when you run `npm install`.
|
|
64
52
|
|
|
65
53
|
### Using Presets
|
|
66
54
|
|
|
67
|
-
Presets allow you to bundle multiple rules into a single configuration that can be shared across projects.
|
|
55
|
+
Presets allow you to bundle multiple rules & mcps into a single configuration that can be shared across projects.
|
|
68
56
|
|
|
69
57
|
1. **Create a preset package or directory**
|
|
70
58
|
|
|
71
59
|
Create an npm package with your rule definitions in an `aicm.json` file:
|
|
72
60
|
|
|
61
|
+
> `@myteam/ai-tools/aicm.json`
|
|
62
|
+
|
|
73
63
|
```json
|
|
74
64
|
{
|
|
75
65
|
"rules": {
|
|
76
66
|
"typescript": "./rules/typescript.mdc",
|
|
77
67
|
"react": "./rules/react.mdc"
|
|
68
|
+
},
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"my-mcp": {
|
|
71
|
+
"url": "https://example.com/sse"
|
|
72
|
+
}
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
75
|
```
|
|
@@ -90,14 +85,19 @@ In your project's `aicm.json`, reference the preset by its npm package or direct
|
|
|
90
85
|
}
|
|
91
86
|
```
|
|
92
87
|
|
|
93
|
-
When you run `npx aicm install`, all rules from the preset will be installed to `.cursor/rules
|
|
88
|
+
When you run `npx aicm install`, all rules from the preset will be installed to `.cursor/rules/aicm/` and all mcps from the preset will be installed to `.cursor/mcp.json`.
|
|
94
89
|
|
|
95
|
-
###
|
|
90
|
+
### Notes
|
|
96
91
|
|
|
97
|
-
|
|
92
|
+
- Generated rules are always placed in a subdirectory for deterministic cleanup and easy gitignore.
|
|
93
|
+
- Users may add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf) to their `.gitignore` if they do not want to track generated rules.
|
|
94
|
+
|
|
95
|
+
### Overriding and Disabling Rules and MCP Servers from Presets
|
|
96
|
+
|
|
97
|
+
When you use a preset, you can override or disable any rule or mcpServer from the preset in your own `aicm.json` configuration:
|
|
98
98
|
|
|
99
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
|
-
- **
|
|
100
|
+
- **Disable**: To disable a rule or mcpServer from a preset, set its value to `false` in your config.
|
|
101
101
|
|
|
102
102
|
**Example:**
|
|
103
103
|
|
|
@@ -119,19 +119,14 @@ When you use a preset, you can override or cancel any rule or mcpServer from the
|
|
|
119
119
|
}
|
|
120
120
|
```
|
|
121
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
|
-
|
|
127
122
|
### Demo
|
|
128
123
|
|
|
129
|
-
|
|
124
|
+
We'll install [an npm package](https://github.com/ranyitz/pirate-coding) containing a simple preset to demonstrate how aicm works.
|
|
130
125
|
|
|
131
|
-
1. Install
|
|
126
|
+
1. Install an npm package containing a preset
|
|
132
127
|
|
|
133
128
|
```bash
|
|
134
|
-
npm install --save-dev pirate-coding
|
|
129
|
+
npm install --save-dev pirate-coding
|
|
135
130
|
```
|
|
136
131
|
|
|
137
132
|
2. Initialize aicm config
|
|
@@ -145,13 +140,11 @@ npx -y aicm init
|
|
|
145
140
|
```json
|
|
146
141
|
{
|
|
147
142
|
"ides": ["cursor"],
|
|
148
|
-
"
|
|
149
|
-
"pirate-coding": "pirate-coding-rule/rule.mdc"
|
|
150
|
-
}
|
|
143
|
+
"presets": ["pirate-coding"]
|
|
151
144
|
}
|
|
152
145
|
```
|
|
153
146
|
|
|
154
|
-
4. Install all rules from your configuration
|
|
147
|
+
4. Install all rules & mcps from your configuration
|
|
155
148
|
|
|
156
149
|
```bash
|
|
157
150
|
npx -y aicm install
|
|
@@ -159,7 +152,7 @@ npx -y aicm install
|
|
|
159
152
|
|
|
160
153
|
This command installs all configured rules and MCPs to their IDE-specific locations.
|
|
161
154
|
|
|
162
|
-
After installation, open Cursor and ask it to do something. Your AI assistant will respond with pirate-themed coding advice.
|
|
155
|
+
After installation, open Cursor and ask it to do something. Your AI assistant will respond with pirate-themed coding advice. You can also ask it about the aicm library which uses https://gitmcp.io/ to give you advise based on the latest documentation.
|
|
163
156
|
|
|
164
157
|
## Security Note
|
|
165
158
|
|
|
@@ -179,11 +172,11 @@ Example `aicm.json`:
|
|
|
179
172
|
"ides": ["cursor"],
|
|
180
173
|
"presets": ["@my-team/ai-tools/my-aicm.json"],
|
|
181
174
|
"rules": {
|
|
182
|
-
"team-standards": "@my-team/ai-tools/rules/team-standards.mdc"
|
|
175
|
+
"team-rules/team-standards": "@my-team/ai-tools/rules/team-standards.mdc"
|
|
183
176
|
},
|
|
184
177
|
"mcpServers": {
|
|
185
178
|
"remote-mcp": {
|
|
186
|
-
"url": "https://example.com/
|
|
179
|
+
"url": "https://example.com/sse"
|
|
187
180
|
}
|
|
188
181
|
}
|
|
189
182
|
}
|
|
@@ -195,7 +188,7 @@ Example `aicm.json`:
|
|
|
195
188
|
|
|
196
189
|
- **rules**: Object containing rule configurations
|
|
197
190
|
|
|
198
|
-
- **rule-name**: A unique identifier for the rule
|
|
191
|
+
- **rule-name**: A unique identifier for the rule. Can include a directory path to install the rule to a specific directory.
|
|
199
192
|
- **source-location**: Location of the rule file (path within an npm package or local path)
|
|
200
193
|
|
|
201
194
|
- **mcpServers**: Object containing MCP server configurations. Each key is a unique server name, and the value is an object with either:
|
|
@@ -234,8 +227,8 @@ Rules stored locally in your project or filesystem. Any path containing slashes
|
|
|
234
227
|
|
|
235
228
|
## Supported IDEs
|
|
236
229
|
|
|
237
|
-
- **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/`), mcp servers are installed to `.cursor/mcp.json`
|
|
238
|
-
- **Windsurf**: Rules are installed in the `.
|
|
230
|
+
- **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/aicm/`), mcp servers are installed to `.cursor/mcp.json`
|
|
231
|
+
- **Windsurf**: Rules are installed in the `.aicm` directory which should be added to your `.gitignore` file. Our approach for Windsurf is to create links from the `.windsurfrules` file to the respective rules in the `.aicm` directory. There is no support for local mcp servers at the moment.
|
|
239
232
|
|
|
240
233
|
## Commands
|
|
241
234
|
|
|
@@ -256,23 +249,65 @@ npx aicm init
|
|
|
256
249
|
|
|
257
250
|
### `install`
|
|
258
251
|
|
|
259
|
-
Installs rules
|
|
252
|
+
Installs all rules and MCPs configured in your `aicm.json`.
|
|
260
253
|
|
|
261
254
|
```bash
|
|
262
255
|
npx aicm install
|
|
263
256
|
```
|
|
264
257
|
|
|
265
|
-
|
|
258
|
+
## Node.js API
|
|
266
259
|
|
|
267
|
-
|
|
260
|
+
In addition to the CLI, aicm can be used programmatically in Node.js applications:
|
|
268
261
|
|
|
269
|
-
|
|
262
|
+
```javascript
|
|
263
|
+
const { install, Config } = require("aicm");
|
|
270
264
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
265
|
+
install().then((result) => {
|
|
266
|
+
if (result.success) {
|
|
267
|
+
console.log(`Successfully installed ${result.installedRuleCount} rules`);
|
|
268
|
+
} else {
|
|
269
|
+
console.error(`Error: ${result.error}`);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Install with custom options
|
|
274
|
+
const customConfig = {
|
|
275
|
+
ides: ["cursor"],
|
|
276
|
+
rules: {
|
|
277
|
+
typescript: "./rules/typescript.mdc",
|
|
278
|
+
react: "@org/rules/react.mdc",
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
install({
|
|
283
|
+
config: customConfig,
|
|
284
|
+
cwd: "/path/to/project",
|
|
285
|
+
silent: true,
|
|
286
|
+
}).then((result) => {
|
|
287
|
+
// Handle result
|
|
288
|
+
});
|
|
274
289
|
```
|
|
275
290
|
|
|
291
|
+
### API Reference
|
|
292
|
+
|
|
293
|
+
#### `install(options?: InstallOptions): Promise<InstallResult>`
|
|
294
|
+
|
|
295
|
+
Installs rules and MCP servers based on configuration.
|
|
296
|
+
|
|
297
|
+
**Options:**
|
|
298
|
+
|
|
299
|
+
- `cwd`: Base directory to use instead of `process.cwd()`
|
|
300
|
+
- `config`: Custom config object to use instead of loading from file
|
|
301
|
+
- `silent`: Whether to suppress console output
|
|
302
|
+
|
|
303
|
+
**Returns:**
|
|
304
|
+
|
|
305
|
+
A Promise that resolves to an object with:
|
|
306
|
+
|
|
307
|
+
- `success`: Whether the operation was successful
|
|
308
|
+
- `error`: Error message if the operation failed
|
|
309
|
+
- `installedRuleCount`: Number of rules installed
|
|
310
|
+
|
|
276
311
|
## Contributing
|
|
277
312
|
|
|
278
313
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -281,8 +316,6 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
281
316
|
|
|
282
317
|
### Testing
|
|
283
318
|
|
|
284
|
-
The project includes both unit tests and end-to-end (E2E) tests:
|
|
285
|
-
|
|
286
319
|
```bash
|
|
287
320
|
# Run all tests
|
|
288
321
|
npm test
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { InstallOptions, InstallResult } from "./commands/install";
|
|
2
|
+
/**
|
|
3
|
+
* Install AICM rules based on configuration
|
|
4
|
+
* @param options Install options
|
|
5
|
+
* @returns Result of the install operation
|
|
6
|
+
*/
|
|
7
|
+
export declare function install(options?: InstallOptions): Promise<InstallResult>;
|
|
8
|
+
export { Config, Rule, Rules, RuleMetadata, RuleContent, RuleCollection, } from "./types";
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.install = install;
|
|
4
|
+
const install_1 = require("./commands/install");
|
|
5
|
+
/**
|
|
6
|
+
* Install AICM rules based on configuration
|
|
7
|
+
* @param options Install options
|
|
8
|
+
* @returns Result of the install operation
|
|
9
|
+
*/
|
|
10
|
+
async function install(options = {}) {
|
|
11
|
+
return (0, install_1.install)(options);
|
|
12
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -20,11 +20,10 @@ function initCommand() {
|
|
|
20
20
|
}
|
|
21
21
|
try {
|
|
22
22
|
fs_extra_1.default.writeJsonSync(configPath, defaultConfig, { spaces: 2 });
|
|
23
|
-
console.log(chalk_1.default.green("Configuration file created successfully!"));
|
|
24
23
|
console.log(`Configuration file location: ${chalk_1.default.blue(configPath)}`);
|
|
25
24
|
console.log(`\nNext steps:`);
|
|
26
|
-
console.log(` 1. Edit ${chalk_1.default.blue("aicm.json")} to configure your rules`);
|
|
27
|
-
console.log(` 2. Run ${chalk_1.default.blue("npx aicm install")} to install rules`);
|
|
25
|
+
console.log(` 1. Edit ${chalk_1.default.blue("aicm.json")} to configure your rules & presets`);
|
|
26
|
+
console.log(` 2. Run ${chalk_1.default.blue("npx aicm install")} to install rules & mcps`);
|
|
28
27
|
}
|
|
29
28
|
catch (error) {
|
|
30
29
|
console.error(chalk_1.default.red("Error creating configuration file:"), error);
|
|
@@ -1 +1,42 @@
|
|
|
1
|
+
import { Config } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Options for the installCore function
|
|
4
|
+
*/
|
|
5
|
+
export interface InstallOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Base directory to use instead of process.cwd()
|
|
8
|
+
*/
|
|
9
|
+
cwd?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Custom config object to use instead of loading from file
|
|
12
|
+
*/
|
|
13
|
+
config?: Config;
|
|
14
|
+
/**
|
|
15
|
+
* Whether to log progress to console
|
|
16
|
+
*/
|
|
17
|
+
silent?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Result of the install operation
|
|
21
|
+
*/
|
|
22
|
+
export interface InstallResult {
|
|
23
|
+
/**
|
|
24
|
+
* Whether the operation was successful
|
|
25
|
+
*/
|
|
26
|
+
success: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Error message if the operation failed
|
|
29
|
+
*/
|
|
30
|
+
error?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Number of rules installed
|
|
33
|
+
*/
|
|
34
|
+
installedRuleCount: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Core implementation of the rule installation logic
|
|
38
|
+
* @param options Install options
|
|
39
|
+
* @returns Result of the install operation
|
|
40
|
+
*/
|
|
41
|
+
export declare function install(options?: InstallOptions): Promise<InstallResult>;
|
|
1
42
|
export declare function installCommand(): Promise<void>;
|
package/dist/commands/install.js
CHANGED
|
@@ -3,6 +3,7 @@ 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.install = install;
|
|
6
7
|
exports.installCommand = installCommand;
|
|
7
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
9
|
const config_1 = require("../utils/config");
|
|
@@ -11,13 +12,19 @@ const rule_collector_1 = require("../utils/rule-collector");
|
|
|
11
12
|
const rule_writer_1 = require("../utils/rule-writer");
|
|
12
13
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
13
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Write MCP servers configuration to IDE targets
|
|
17
|
+
* @param mcpServers The MCP servers configuration
|
|
18
|
+
* @param ides The IDEs to write to
|
|
19
|
+
* @param cwd The current working directory
|
|
20
|
+
*/
|
|
21
|
+
function writeMcpServersToTargets(mcpServers, ides, cwd) {
|
|
15
22
|
if (!mcpServers)
|
|
16
23
|
return;
|
|
17
24
|
for (const ide of ides) {
|
|
18
25
|
let mcpPath = null;
|
|
19
26
|
if (ide === "cursor") {
|
|
20
|
-
mcpPath = node_path_1.default.join(
|
|
27
|
+
mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json");
|
|
21
28
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(mcpPath));
|
|
22
29
|
}
|
|
23
30
|
// Windsurf does not support project mcpServers, so skip
|
|
@@ -26,37 +33,59 @@ function writeMcpServersToTargets(mcpServers, ides) {
|
|
|
26
33
|
}
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Core implementation of the rule installation logic
|
|
38
|
+
* @param options Install options
|
|
39
|
+
* @returns Result of the install operation
|
|
40
|
+
*/
|
|
41
|
+
async function install(options = {}) {
|
|
42
|
+
const cwd = options.cwd || process.cwd();
|
|
43
|
+
const silent = options.silent || false;
|
|
44
|
+
const log = silent ? () => { } : console.log;
|
|
45
|
+
const error = silent ? () => { } : console.error;
|
|
30
46
|
try {
|
|
47
|
+
// Save original process.cwd() and change to the specified cwd
|
|
48
|
+
const originalCwd = process.cwd();
|
|
49
|
+
if (cwd !== originalCwd) {
|
|
50
|
+
process.chdir(cwd);
|
|
51
|
+
}
|
|
31
52
|
// Initialize rule collection
|
|
32
53
|
const ruleCollection = (0, rule_collector_1.initRuleCollection)();
|
|
33
|
-
//
|
|
34
|
-
const config = (0, config_1.getConfig)();
|
|
35
|
-
// If config doesn't exist,
|
|
54
|
+
// Use provided config or load from file
|
|
55
|
+
const config = options.config || (0, config_1.getConfig)();
|
|
56
|
+
// If config doesn't exist, return error
|
|
36
57
|
if (!config) {
|
|
37
|
-
|
|
38
|
-
|
|
58
|
+
error("Configuration file not found!");
|
|
59
|
+
// Restore original cwd
|
|
60
|
+
if (cwd !== originalCwd) {
|
|
61
|
+
process.chdir(originalCwd);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: "Configuration file not found!",
|
|
66
|
+
installedRuleCount: 0,
|
|
67
|
+
};
|
|
39
68
|
}
|
|
40
69
|
// Check if rules are defined (either directly or through presets)
|
|
41
70
|
if (!config.rules || Object.keys(config.rules).length === 0) {
|
|
42
71
|
// If there are no presets defined either, show a message
|
|
43
72
|
if (!config.presets || config.presets.length === 0) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.log(chalk_1.default.yellow("\nNo valid rules found in configuration or presets."));
|
|
55
|
-
return;
|
|
73
|
+
error("No rules defined in configuration.");
|
|
74
|
+
// Restore original cwd
|
|
75
|
+
if (cwd !== originalCwd) {
|
|
76
|
+
process.chdir(originalCwd);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: "No rules defined in configuration.",
|
|
81
|
+
installedRuleCount: 0,
|
|
82
|
+
};
|
|
56
83
|
}
|
|
57
84
|
}
|
|
58
85
|
// Process each rule
|
|
59
86
|
let hasErrors = false;
|
|
87
|
+
const errorMessages = [];
|
|
88
|
+
let installedRuleCount = 0;
|
|
60
89
|
for (const [name, source] of Object.entries(config.rules)) {
|
|
61
90
|
if (source === false)
|
|
62
91
|
continue; // skip canceled rules
|
|
@@ -75,20 +104,32 @@ async function installCommand() {
|
|
|
75
104
|
ruleContent = (0, rule_collector_1.collectLocalRule)(name, source, ruleBasePath);
|
|
76
105
|
break;
|
|
77
106
|
default:
|
|
78
|
-
|
|
107
|
+
error(`Unknown rule type: ${ruleType}`);
|
|
108
|
+
errorMessages.push(`Unknown rule type: ${ruleType}`);
|
|
79
109
|
continue;
|
|
80
110
|
}
|
|
81
111
|
// Add rule to collection
|
|
82
112
|
(0, rule_collector_1.addRuleToCollection)(ruleCollection, ruleContent, config.ides);
|
|
113
|
+
installedRuleCount++;
|
|
83
114
|
}
|
|
84
|
-
catch (
|
|
115
|
+
catch (e) {
|
|
85
116
|
hasErrors = true;
|
|
86
|
-
|
|
117
|
+
const errorMessage = `Error processing rule ${name}: ${e instanceof Error ? e.message : String(e)}`;
|
|
118
|
+
error(errorMessage);
|
|
119
|
+
errorMessages.push(errorMessage);
|
|
87
120
|
}
|
|
88
121
|
}
|
|
89
122
|
// If there were errors, exit with error
|
|
90
123
|
if (hasErrors) {
|
|
91
|
-
|
|
124
|
+
// Restore original cwd
|
|
125
|
+
if (cwd !== originalCwd) {
|
|
126
|
+
process.chdir(originalCwd);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
error: errorMessages.join("; "),
|
|
131
|
+
installedRuleCount,
|
|
132
|
+
};
|
|
92
133
|
}
|
|
93
134
|
// Write all collected rules to their targets
|
|
94
135
|
(0, rule_writer_1.writeRulesToTargets)(ruleCollection);
|
|
@@ -96,9 +137,40 @@ async function installCommand() {
|
|
|
96
137
|
if (config.mcpServers) {
|
|
97
138
|
// Filter out canceled servers
|
|
98
139
|
const filteredMcpServers = Object.fromEntries(Object.entries(config.mcpServers).filter(([, v]) => v !== false));
|
|
99
|
-
writeMcpServersToTargets(filteredMcpServers, config.ides);
|
|
140
|
+
writeMcpServersToTargets(filteredMcpServers, config.ides, cwd);
|
|
141
|
+
}
|
|
142
|
+
log("Rules installation completed!");
|
|
143
|
+
// Restore original cwd
|
|
144
|
+
if (cwd !== originalCwd) {
|
|
145
|
+
process.chdir(originalCwd);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
installedRuleCount,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
const errorMessage = `Error during rule installation: ${e instanceof Error ? e.message : String(e)}`;
|
|
154
|
+
error(errorMessage);
|
|
155
|
+
// If cwd was changed, restore it
|
|
156
|
+
if (cwd !== process.cwd()) {
|
|
157
|
+
process.chdir(cwd);
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
error: errorMessage,
|
|
162
|
+
installedRuleCount: 0,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function installCommand() {
|
|
167
|
+
try {
|
|
168
|
+
const result = await install({ silent: false });
|
|
169
|
+
if (!result.success) {
|
|
170
|
+
console.error(chalk_1.default.red(result.error));
|
|
171
|
+
process.exit(1);
|
|
100
172
|
}
|
|
101
|
-
console.log(chalk_1.default.green("
|
|
173
|
+
console.log(chalk_1.default.green("Rules installation completed!"));
|
|
102
174
|
}
|
|
103
175
|
catch (error) {
|
|
104
176
|
console.error(chalk_1.default.red(`Error during rule installation: ${error instanceof Error ? error.message : String(error)}`));
|
|
@@ -13,8 +13,8 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
13
13
|
function getIdePaths() {
|
|
14
14
|
const projectDir = process.cwd(); // Get current working directory (project root)
|
|
15
15
|
return {
|
|
16
|
-
cursor: node_path_1.default.join(projectDir, ".cursor", "rules"),
|
|
17
|
-
windsurf: node_path_1.default.join(projectDir, ".
|
|
16
|
+
cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
|
|
17
|
+
windsurf: node_path_1.default.join(projectDir, ".aicm"),
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
@@ -22,7 +22,6 @@ function getIdePaths() {
|
|
|
22
22
|
*/
|
|
23
23
|
function checkRuleStatus(ruleName, ruleType, ides) {
|
|
24
24
|
const idePaths = getIdePaths();
|
|
25
|
-
// Check if rule is installed in all specified IDEs
|
|
26
25
|
return ides.every((ide) => {
|
|
27
26
|
if (!idePaths[ide]) {
|
|
28
27
|
return false;
|
|
@@ -31,14 +30,11 @@ function checkRuleStatus(ruleName, ruleType, ides) {
|
|
|
31
30
|
return fs_extra_1.default.existsSync(node_path_1.default.join(idePaths[ide], `${ruleName}.mdc`));
|
|
32
31
|
}
|
|
33
32
|
if (ide === "windsurf") {
|
|
34
|
-
// For Windsurf, check if the rule exists in .rules directory
|
|
35
|
-
// and if it's referenced in .windsurfrules
|
|
36
33
|
const ruleExists = fs_extra_1.default.existsSync(node_path_1.default.join(idePaths[ide], `${ruleName}.md`));
|
|
37
|
-
// Check if .windsurfrules exists and contains a reference to this rule
|
|
38
34
|
const windsurfRulesPath = node_path_1.default.join(process.cwd(), ".windsurfrules");
|
|
39
35
|
if (fs_extra_1.default.existsSync(windsurfRulesPath)) {
|
|
40
36
|
const windsurfRulesContent = fs_extra_1.default.readFileSync(windsurfRulesPath, "utf8");
|
|
41
|
-
return (ruleExists && windsurfRulesContent.includes(`.
|
|
37
|
+
return (ruleExists && windsurfRulesContent.includes(`.aicm/${ruleName}.md`));
|
|
42
38
|
}
|
|
43
39
|
return false;
|
|
44
40
|
}
|
|
@@ -20,7 +20,7 @@ function writeRulesToTargets(collection) {
|
|
|
20
20
|
}
|
|
21
21
|
// Write Windsurf rules
|
|
22
22
|
if (collection.windsurf.length > 0) {
|
|
23
|
-
writeWindsurfRulesFromCollection(collection.windsurf);
|
|
23
|
+
writeWindsurfRulesFromCollection(collection.windsurf, idePaths.windsurf);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
@@ -29,16 +29,14 @@ function writeRulesToTargets(collection) {
|
|
|
29
29
|
* @param cursorRulesDir The path to Cursor's rules directory
|
|
30
30
|
*/
|
|
31
31
|
function writeCursorRules(rules, cursorRulesDir) {
|
|
32
|
-
fs_extra_1.default.
|
|
32
|
+
fs_extra_1.default.emptyDirSync(cursorRulesDir);
|
|
33
33
|
for (const rule of rules) {
|
|
34
|
-
const ruleFile = node_path_1.default.join(cursorRulesDir,
|
|
35
|
-
|
|
34
|
+
const ruleFile = node_path_1.default.join(cursorRulesDir, ...rule.name.split("/")) + ".mdc";
|
|
35
|
+
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(ruleFile));
|
|
36
36
|
if (fs_extra_1.default.existsSync(rule.sourcePath)) {
|
|
37
|
-
// Copy the file (safer than symlink)
|
|
38
37
|
fs_extra_1.default.copyFileSync(rule.sourcePath, ruleFile);
|
|
39
38
|
}
|
|
40
39
|
else {
|
|
41
|
-
// If source path doesn't exist (shouldn't happen), write content directly
|
|
42
40
|
const mdcContent = `---\n${JSON.stringify(rule.metadata, null, 2)}\n---\n\n${rule.content}`;
|
|
43
41
|
fs_extra_1.default.writeFileSync(ruleFile, mdcContent);
|
|
44
42
|
}
|
|
@@ -48,21 +46,22 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
48
46
|
* Write rules to Windsurf's rules directory and update .windsurfrules file
|
|
49
47
|
* @param rules The rules to write
|
|
50
48
|
*/
|
|
51
|
-
function writeWindsurfRulesFromCollection(rules) {
|
|
52
|
-
|
|
53
|
-
const ruleDir = idePaths.windsurf;
|
|
54
|
-
fs_extra_1.default.ensureDirSync(ruleDir);
|
|
55
|
-
// First write individual rule files
|
|
49
|
+
function writeWindsurfRulesFromCollection(rules, ruleDir) {
|
|
50
|
+
fs_extra_1.default.emptyDirSync(ruleDir);
|
|
56
51
|
const ruleFiles = rules.map((rule) => {
|
|
57
|
-
const
|
|
58
|
-
fs_extra_1.default.
|
|
52
|
+
const physicalRulePath = node_path_1.default.join(ruleDir, ...rule.name.split("/")) + ".md";
|
|
53
|
+
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
|
|
54
|
+
fs_extra_1.default.writeFileSync(physicalRulePath, rule.content);
|
|
55
|
+
const relativeRuleDir = node_path_1.default.basename(ruleDir); // Gets '.rules'
|
|
56
|
+
const windsurfPath = node_path_1.default.join(relativeRuleDir, ...rule.name.split("/")) + ".md";
|
|
57
|
+
// Normalize to POSIX style for cross-platform compatibility in .windsurfrules
|
|
58
|
+
const windsurfPathPosix = windsurfPath.replace(/\\/g, "/");
|
|
59
59
|
return {
|
|
60
60
|
name: rule.name,
|
|
61
|
-
path:
|
|
61
|
+
path: windsurfPathPosix,
|
|
62
62
|
metadata: rule.metadata,
|
|
63
63
|
};
|
|
64
64
|
});
|
|
65
|
-
// Then generate and write the .windsurfrules file
|
|
66
65
|
const windsurfRulesContent = (0, windsurf_writer_1.generateWindsurfRulesContent)(ruleFiles);
|
|
67
66
|
(0, windsurf_writer_1.writeWindsurfRules)(windsurfRulesContent);
|
|
68
67
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "A TypeScript CLI tool for managing AI IDE rules across different projects and teams",
|
|
5
|
-
"main": "dist/
|
|
5
|
+
"main": "dist/api.js",
|
|
6
|
+
"types": "dist/api.d.ts",
|
|
6
7
|
"bin": {
|
|
7
8
|
"aicm": "./dist/index.js"
|
|
8
9
|
},
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"format": "prettier --write .",
|
|
25
26
|
"format:check": "prettier --check .",
|
|
26
27
|
"lint": "eslint",
|
|
27
|
-
"prepare": "husky && npx ts-node src/index.ts install",
|
|
28
|
+
"prepare": "husky install && npx ts-node src/index.ts install",
|
|
28
29
|
"version": "auto-changelog -p && git add CHANGELOG.md"
|
|
29
30
|
},
|
|
30
31
|
"keywords": [
|