aicm 0.16.1 → 0.17.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 +126 -118
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.js +80 -18
- package/dist/utils/config.d.ts +10 -0
- package/dist/utils/config.js +38 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,56 +2,76 @@
|
|
|
2
2
|
|
|
3
3
|
> AI Configuration Manager
|
|
4
4
|
|
|
5
|
-
A CLI tool for managing Agentic configurations across projects
|
|
5
|
+
A CLI tool for managing Agentic configurations across projects.
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Why](#why)
|
|
12
|
+
- [Supported Environments](#supported-environments)
|
|
13
|
+
- [Getting Started](#getting-started)
|
|
14
|
+
- [Creating a Preset](#creating-a-preset)
|
|
15
|
+
- [Using a Preset](#using-a-preset)
|
|
16
|
+
- [Features](#features)
|
|
17
|
+
- [Rules](#using-rules)
|
|
18
|
+
- [Commands](#using-commands)
|
|
19
|
+
- [MCP Servers](#mcp-servers)
|
|
20
|
+
- [Auxiliary Files](#referencing-auxiliary-files)
|
|
21
|
+
- [Overrides](#overrides)
|
|
22
|
+
- [Workspaces Support](#workspaces-support)
|
|
23
|
+
- [Configuration](#configuration)
|
|
24
|
+
- [CLI Commands](#cli-commands)
|
|
25
|
+
- [Node.js API](#nodejs-api)
|
|
26
|
+
|
|
9
27
|
## Why
|
|
10
28
|
|
|
11
|
-
Modern AI-powered IDEs like Cursor and Agents like Codex
|
|
29
|
+
Modern AI-powered IDEs like Cursor and Agents like Codex allow developers to add custom instructions, commands, and MCP servers. However, keeping these configurations consistent across a team or multiple projects is a challenge.
|
|
12
30
|
|
|
13
|
-
**aicm**
|
|
31
|
+
**aicm** enables **"Write Once, Use Everywhere"** for your AI configurations.
|
|
14
32
|
|
|
15
|
-
|
|
33
|
+
- **Team Consistency:** Ensure every developer on your team uses the same rules and best practices.
|
|
34
|
+
- **Reusable Presets:** Bundle your rules, commands & MCP configurations into npm packages (e.g., `@company/ai-preset`) to share them across your organization.
|
|
35
|
+
- **Multi-Target Support:** Write rules once in the comprehensive `.mdc` format, and automatically deploy them to Cursor, Windsurf, Codex, and Claude.
|
|
16
36
|
|
|
17
|
-
|
|
37
|
+
## Supported Environments
|
|
18
38
|
|
|
19
|
-
|
|
20
|
-
- **Windsurf**: Generates `.windsurfrules` file
|
|
21
|
-
- **Codex**: Generates `AGENTS.md` file
|
|
22
|
-
- **Claude**: Generates `CLAUDE.md` file
|
|
39
|
+
aicm acts as a bridge between your configuration and your AI tools. It accepts Cursor's `.mdc` format and can transform it for other environments:
|
|
23
40
|
|
|
24
|
-
|
|
41
|
+
| Target | Installation |
|
|
42
|
+
| ------------ | ------------------------------------------------------------------------------ |
|
|
43
|
+
| **Cursor** | Copies `.mdc` files to `.cursor/rules/aicm/` and configures `.cursor/mcp.json` |
|
|
44
|
+
| **Windsurf** | Generates a `.windsurfrules` file that links to rules in `.aicm/` |
|
|
45
|
+
| **Codex** | Generates an `AGENTS.md` file that references rules in `.aicm/` |
|
|
46
|
+
| **Claude** | Generates a `CLAUDE.md` file that references rules in `.aicm/` |
|
|
25
47
|
|
|
26
48
|
## Getting Started
|
|
27
49
|
|
|
28
50
|
The easiest way to get started with aicm is by using **presets** - npm packages containing rules and MCP configurations that you can install in any project.
|
|
29
51
|
|
|
30
|
-
###
|
|
52
|
+
### Demo
|
|
31
53
|
|
|
32
|
-
|
|
54
|
+
We'll install [an npm package](https://github.com/ranyitz/pirate-coding) containing a simple "Pirate Coding" preset to demonstrate how aicm works.
|
|
55
|
+
|
|
56
|
+
1. **Install the demo preset package**:
|
|
33
57
|
|
|
34
58
|
```bash
|
|
35
|
-
npm install --save-dev
|
|
59
|
+
npm install --save-dev pirate-coding
|
|
36
60
|
```
|
|
37
61
|
|
|
38
|
-
2. **Create an `aicm.json` file** in your project
|
|
62
|
+
2. **Create an `aicm.json` file** in your project:
|
|
39
63
|
|
|
40
|
-
```
|
|
41
|
-
{ "presets": ["
|
|
64
|
+
```bash
|
|
65
|
+
echo '{ "presets": ["pirate-coding"] }' > aicm.json
|
|
42
66
|
```
|
|
43
67
|
|
|
44
|
-
3. **
|
|
68
|
+
3. **Install all rules & MCPs from your configuration**:
|
|
45
69
|
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
"scripts": {
|
|
49
|
-
"prepare": "npx aicm -y install"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
70
|
+
```bash
|
|
71
|
+
npx aicm install
|
|
52
72
|
```
|
|
53
73
|
|
|
54
|
-
|
|
74
|
+
After installation, open Cursor and ask it to do something. Your AI assistant will respond with pirate-themed coding advice.
|
|
55
75
|
|
|
56
76
|
### Creating a Preset
|
|
57
77
|
|
|
@@ -85,111 +105,130 @@ The rules are now installed in `.cursor/rules/aicm/` and any MCP servers are con
|
|
|
85
105
|
|
|
86
106
|
> **Note:** This is syntactic sugar for `@team/ai-preset/aicm.json`.
|
|
87
107
|
|
|
88
|
-
### Using
|
|
108
|
+
### Using a Preset
|
|
89
109
|
|
|
90
|
-
|
|
110
|
+
To use a real preset in your production project:
|
|
91
111
|
|
|
92
|
-
|
|
93
|
-
{
|
|
94
|
-
"rulesDir": "path/to/rules/dir"
|
|
95
|
-
}
|
|
96
|
-
```
|
|
112
|
+
1. **Install a preset npm package**:
|
|
97
113
|
|
|
98
|
-
|
|
114
|
+
```bash
|
|
115
|
+
npm install --save-dev @team/ai-preset
|
|
116
|
+
```
|
|
99
117
|
|
|
100
|
-
|
|
101
|
-
alongside your rules and MCP configurations so they install automatically into Cursor.
|
|
118
|
+
2. **Create an `aicm.json` file** in your project root:
|
|
102
119
|
|
|
103
|
-
|
|
120
|
+
```json
|
|
121
|
+
{ "presets": ["@team/ai-preset"] }
|
|
122
|
+
```
|
|
104
123
|
|
|
105
|
-
Add a
|
|
124
|
+
3. **Add a prepare script** to your `package.json` to ensure rules are always up to date:
|
|
106
125
|
|
|
107
126
|
```json
|
|
108
127
|
{
|
|
109
|
-
"
|
|
110
|
-
|
|
128
|
+
"scripts": {
|
|
129
|
+
"prepare": "npx aicm -y install"
|
|
130
|
+
}
|
|
111
131
|
}
|
|
112
132
|
```
|
|
113
133
|
|
|
114
|
-
|
|
134
|
+
The rules are now installed in `.cursor/rules/aicm/` and any MCP servers are configured in `.cursor/mcp.json`.
|
|
135
|
+
|
|
136
|
+
### Notes
|
|
137
|
+
|
|
138
|
+
- Generated rules are always placed in a subdirectory for deterministic cleanup and easy gitignore.
|
|
139
|
+
- Users should add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf/Codex) to `.gitignore` to avoid tracking generated rules.
|
|
140
|
+
|
|
141
|
+
## Features
|
|
142
|
+
|
|
143
|
+
### Using Rules
|
|
115
144
|
|
|
116
|
-
|
|
145
|
+
aicm uses Cursor's `.mdc` files for rules. Read more about the format [here](https://cursor.com/docs/context/rules).
|
|
117
146
|
|
|
118
|
-
|
|
147
|
+
Add a rules directory to your project configuration:
|
|
119
148
|
|
|
120
149
|
```json
|
|
121
150
|
{
|
|
122
|
-
"rulesDir": "rules",
|
|
123
|
-
"
|
|
151
|
+
"rulesDir": "./rules",
|
|
152
|
+
"targets": ["cursor"]
|
|
124
153
|
}
|
|
125
154
|
```
|
|
126
155
|
|
|
127
|
-
|
|
128
|
-
If multiple presets provide a command at the same relative path, aicm will warn during installation and use the version from the
|
|
129
|
-
last preset listed in your configuration. Use `overrides` to explicitly choose a different definition when needed.
|
|
156
|
+
Rules are installed in `.cursor/rules/aicm/` and are loaded automatically by Cursor.
|
|
130
157
|
|
|
131
|
-
|
|
158
|
+
### Using Commands
|
|
159
|
+
|
|
160
|
+
Cursor supports custom commands that can be invoked directly in the chat interface. aicm can manage these command files alongside your rules and MCP configurations.
|
|
132
161
|
|
|
133
|
-
|
|
162
|
+
Add a commands directory to your project configuration:
|
|
134
163
|
|
|
135
164
|
```json
|
|
136
165
|
{
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
"legacy-command": false,
|
|
140
|
-
"custom-test": "./commands/test.md"
|
|
141
|
-
}
|
|
166
|
+
"commandsDir": "./commands",
|
|
167
|
+
"targets": ["cursor"]
|
|
142
168
|
}
|
|
143
169
|
```
|
|
144
170
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
- Generated rules are always placed in a subdirectory for deterministic cleanup and easy gitignore.
|
|
148
|
-
- Users may add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf/Codex) to `.gitignore` if they do not want to track generated rules.
|
|
171
|
+
Command files ending in `.md` are installed to `.cursor/commands/aicm/` and appear in Cursor under the `/` command menu.
|
|
149
172
|
|
|
150
|
-
###
|
|
173
|
+
### MCP Servers
|
|
151
174
|
|
|
152
|
-
You can
|
|
175
|
+
You can configure MCP servers directly in your `aicm.json`, which is useful for sharing mcp configurations across your team or bundling them into presets.
|
|
153
176
|
|
|
154
177
|
```json
|
|
155
178
|
{
|
|
156
|
-
"
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
179
|
+
"mcpServers": {
|
|
180
|
+
"Playwright": {
|
|
181
|
+
"command": "npx",
|
|
182
|
+
"args": ["@playwright/mcp"]
|
|
183
|
+
}
|
|
160
184
|
}
|
|
161
185
|
}
|
|
162
186
|
```
|
|
163
187
|
|
|
164
|
-
|
|
188
|
+
When installed, these servers are automatically added to your `.cursor/mcp.json`.
|
|
165
189
|
|
|
166
|
-
|
|
190
|
+
### Referencing Auxiliary Files
|
|
167
191
|
|
|
168
|
-
|
|
192
|
+
You can place any file (e.g., `example.ts`, `schema.json`, `guide.md`) in your `rulesDir` alongside your `.mdc` files. These assets are automatically copied to the target location. You can reference them in your rules using relative paths, and aicm will automatically rewrite the links to point to the correct location for each target IDE.
|
|
169
193
|
|
|
170
|
-
|
|
171
|
-
npm install --save-dev pirate-coding
|
|
172
|
-
```
|
|
194
|
+
Example `rules/my-rule.mdc`:
|
|
173
195
|
|
|
174
|
-
|
|
196
|
+
```markdown
|
|
197
|
+
# My Rule
|
|
175
198
|
|
|
176
|
-
|
|
177
|
-
echo '{ "presets": ["pirate-coding"] }' > aicm.json
|
|
199
|
+
See [Example](./example.ts) for details.
|
|
178
200
|
```
|
|
179
201
|
|
|
180
|
-
|
|
202
|
+
#### Commands Referencing Files
|
|
181
203
|
|
|
182
|
-
|
|
183
|
-
|
|
204
|
+
You can also use this feature to create commands that reference auxiliary files in your `rulesDir`. Since assets in `rulesDir` are copied to the target directory, your commands can link to them.
|
|
205
|
+
|
|
206
|
+
For example, if you have a schema file at `rules/schema.json` and a command at `commands/generate-schema.md`:
|
|
207
|
+
|
|
208
|
+
```markdown
|
|
209
|
+
# Generate Schema
|
|
210
|
+
|
|
211
|
+
Use the schema defined in [Schema Template](../rules/schema.json) to generate the response.
|
|
184
212
|
```
|
|
185
213
|
|
|
186
|
-
|
|
214
|
+
When installed, `aicm` will automatically rewrite the link to point to the correct location of `schema.json` in the target environment (e.g., `../../rules/aicm/schema.json` for Cursor).
|
|
187
215
|
|
|
188
|
-
|
|
216
|
+
> **Note:** Path rewriting works for any relative path format in your commands - markdown links, inline code references, or bare paths - as long as they point to actual files in your `rulesDir`.
|
|
189
217
|
|
|
190
|
-
|
|
218
|
+
### Overrides
|
|
191
219
|
|
|
192
|
-
|
|
220
|
+
You can disable or replace specific rules or commands provided by presets using the `overrides` field:
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"presets": ["@company/ai-rules"],
|
|
225
|
+
"overrides": {
|
|
226
|
+
"rule-from-preset-a": "./rules/override-rule.mdc",
|
|
227
|
+
"rule-from-preset-b": false,
|
|
228
|
+
"legacy-command": false
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
193
232
|
|
|
194
233
|
## Workspaces Support
|
|
195
234
|
|
|
@@ -203,17 +242,14 @@ You can enable workspaces mode by setting the `workspaces` property to `true` in
|
|
|
203
242
|
}
|
|
204
243
|
```
|
|
205
244
|
|
|
206
|
-
aicm automatically detects workspaces if your `package.json` contains a `workspaces` configuration
|
|
245
|
+
aicm automatically detects workspaces if your `package.json` contains a `workspaces` configuration.
|
|
207
246
|
|
|
208
247
|
### How It Works
|
|
209
248
|
|
|
210
|
-
1. **Discover packages**: Automatically find all directories containing `aicm.json` files in your repository
|
|
211
|
-
2. **Install per package**: Install rules and MCPs for each package individually in their respective directories
|
|
212
|
-
3. **Merge MCP servers**: Write a merged `.cursor/mcp.json` at the repository root containing all MCP servers from every package
|
|
213
|
-
|
|
214
|
-
### How It Works
|
|
215
|
-
|
|
216
|
-
Each directory containing an `aicm.json` file is treated as a separate package with its own configuration.
|
|
249
|
+
1. **Discover packages**: Automatically find all directories containing `aicm.json` files in your repository.
|
|
250
|
+
2. **Install per package**: Install rules and MCPs for each package individually in their respective directories.
|
|
251
|
+
3. **Merge MCP servers**: Write a merged `.cursor/mcp.json` at the repository root containing all MCP servers from every package.
|
|
252
|
+
4. **Merge commands**: Write a merged `.cursor/commands/aicm/` at the repository root containing all commands from every package.
|
|
217
253
|
|
|
218
254
|
For example, in a workspace structure like:
|
|
219
255
|
|
|
@@ -274,17 +310,7 @@ Create an `aicm.json` file in your project root, or an `aicm` key in your projec
|
|
|
274
310
|
- **workspaces**: Set to `true` to enable workspace mode. If not specified, aicm will automatically detect workspaces from your `package.json`.
|
|
275
311
|
- **skipInstall**: Set to `true` to skip rule installation for this package. Useful for preset packages that provide rules but shouldn't have rules installed into them.
|
|
276
312
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
- **Cursor**: MCP server configs are written to `.cursor/mcp.json`.
|
|
280
|
-
|
|
281
|
-
## Supported Targets
|
|
282
|
-
|
|
283
|
-
- **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/aicm/`), mcp servers are installed to `.cursor/mcp.json`
|
|
284
|
-
- **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.
|
|
285
|
-
- **Codex**: Rules are installed in the `.aicm` directory and referenced from `AGENTS.md`.
|
|
286
|
-
|
|
287
|
-
## Commands
|
|
313
|
+
## CLI Commands
|
|
288
314
|
|
|
289
315
|
### Global Options
|
|
290
316
|
|
|
@@ -347,27 +373,9 @@ install({
|
|
|
347
373
|
});
|
|
348
374
|
```
|
|
349
375
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
#### `install(options?: InstallOptions): Promise<InstallResult>`
|
|
353
|
-
|
|
354
|
-
Installs rules and MCP servers based on configuration.
|
|
355
|
-
|
|
356
|
-
**Options:**
|
|
357
|
-
|
|
358
|
-
- `cwd`: Base directory to use instead of `process.cwd()`
|
|
359
|
-
- `config`: Custom config object to use instead of loading from file
|
|
360
|
-
- `installOnCI`: Run installation on CI environments (default: `false`)
|
|
361
|
-
- `verbose`: Show verbose output and stack traces for debugging (default: `false`)
|
|
362
|
-
- `dryRun`: Simulate installation without writing files, useful for preset validation in CI (default: `false`)
|
|
363
|
-
|
|
364
|
-
**Returns:**
|
|
365
|
-
|
|
366
|
-
A Promise that resolves to an object with:
|
|
376
|
+
## Security Note
|
|
367
377
|
|
|
368
|
-
-
|
|
369
|
-
- `error`: Error object if the operation failed
|
|
370
|
-
- `installedRuleCount`: Number of rules installed
|
|
378
|
+
To prevent [prompt-injection](https://en.wikipedia.org/wiki/Prompt_injection), use only packages from trusted sources.
|
|
371
379
|
|
|
372
380
|
## Contributing
|
|
373
381
|
|
package/dist/commands/install.js
CHANGED
|
@@ -18,9 +18,7 @@ function getTargetPaths() {
|
|
|
18
18
|
const projectDir = process.cwd();
|
|
19
19
|
return {
|
|
20
20
|
cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
|
|
21
|
-
|
|
22
|
-
codex: node_path_1.default.join(projectDir, ".aicm"),
|
|
23
|
-
claude: node_path_1.default.join(projectDir, ".aicm"),
|
|
21
|
+
aicm: node_path_1.default.join(projectDir, ".aicm"),
|
|
24
22
|
};
|
|
25
23
|
}
|
|
26
24
|
function writeCursorRules(rules, cursorRulesDir) {
|
|
@@ -43,7 +41,7 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
43
41
|
fs_extra_1.default.writeFileSync(ruleFile, rule.content);
|
|
44
42
|
}
|
|
45
43
|
}
|
|
46
|
-
function writeCursorCommands(commands, cursorCommandsDir) {
|
|
44
|
+
function writeCursorCommands(commands, cursorCommandsDir, assets) {
|
|
47
45
|
fs_extra_1.default.removeSync(cursorCommandsDir);
|
|
48
46
|
for (const command of commands) {
|
|
49
47
|
const commandNameParts = command.name
|
|
@@ -53,9 +51,25 @@ function writeCursorCommands(commands, cursorCommandsDir) {
|
|
|
53
51
|
const commandPath = node_path_1.default.join(cursorCommandsDir, ...commandNameParts);
|
|
54
52
|
const commandFile = commandPath + ".md";
|
|
55
53
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(commandFile));
|
|
56
|
-
|
|
54
|
+
// If the command file references assets in the rules directory, we need to rewrite the links.
|
|
55
|
+
// Commands are installed in .cursor/commands/aicm/
|
|
56
|
+
// Rules/assets are installed in .cursor/rules/aicm/
|
|
57
|
+
// So a link like "../rules/asset.json" in source (from commands/ to rules/)
|
|
58
|
+
// needs to become "../../rules/aicm/asset.json" in target (from .cursor/commands/aicm/ to .cursor/rules/aicm/)
|
|
59
|
+
const content = rewriteCommandRelativeLinks(command.content, command.sourcePath, assets);
|
|
60
|
+
fs_extra_1.default.writeFileSync(commandFile, content);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
63
|
+
function rewriteCommandRelativeLinks(content, commandSourcePath, assets) {
|
|
64
|
+
const commandDir = node_path_1.default.dirname(commandSourcePath);
|
|
65
|
+
const assetMap = new Map(assets.map((a) => [node_path_1.default.normalize(a.sourcePath), a.name]));
|
|
66
|
+
return content.replace(/\.\.[/\\][\w\-/\\.]+/g, (match) => {
|
|
67
|
+
const resolved = node_path_1.default.normalize(node_path_1.default.resolve(commandDir, match));
|
|
68
|
+
return assetMap.has(resolved)
|
|
69
|
+
? `../../rules/aicm/${assetMap.get(resolved)}`
|
|
70
|
+
: match;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
59
73
|
function extractNamespaceFromPresetPath(presetPath) {
|
|
60
74
|
// Special case: npm package names always use forward slashes, regardless of platform
|
|
61
75
|
if (presetPath.startsWith("@")) {
|
|
@@ -68,7 +82,7 @@ function extractNamespaceFromPresetPath(presetPath) {
|
|
|
68
82
|
/**
|
|
69
83
|
* Write rules to a shared directory and update the given rules file
|
|
70
84
|
*/
|
|
71
|
-
function writeRulesForFile(rules, ruleDir, rulesFile) {
|
|
85
|
+
function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
|
|
72
86
|
fs_extra_1.default.emptyDirSync(ruleDir);
|
|
73
87
|
const ruleFiles = rules.map((rule) => {
|
|
74
88
|
let rulePath;
|
|
@@ -83,9 +97,10 @@ function writeRulesForFile(rules, ruleDir, rulesFile) {
|
|
|
83
97
|
// For local rules, maintain the original flat structure
|
|
84
98
|
rulePath = node_path_1.default.join(ruleDir, ...ruleNameParts);
|
|
85
99
|
}
|
|
100
|
+
const content = rule.content;
|
|
86
101
|
const physicalRulePath = rulePath + ".md";
|
|
87
102
|
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
|
|
88
|
-
fs_extra_1.default.writeFileSync(physicalRulePath,
|
|
103
|
+
fs_extra_1.default.writeFileSync(physicalRulePath, content);
|
|
89
104
|
const relativeRuleDir = node_path_1.default.basename(ruleDir);
|
|
90
105
|
// For the rules file, maintain the same structure
|
|
91
106
|
let windsurfPath;
|
|
@@ -102,16 +117,46 @@ function writeRulesForFile(rules, ruleDir, rulesFile) {
|
|
|
102
117
|
return {
|
|
103
118
|
name: rule.name,
|
|
104
119
|
path: windsurfPathPosix,
|
|
105
|
-
metadata: (0, rules_file_writer_1.parseRuleFrontmatter)(
|
|
120
|
+
metadata: (0, rules_file_writer_1.parseRuleFrontmatter)(content),
|
|
106
121
|
};
|
|
107
122
|
});
|
|
108
123
|
const rulesContent = (0, rules_file_writer_1.generateRulesFileContent)(ruleFiles);
|
|
109
124
|
(0, rules_file_writer_1.writeRulesFile)(rulesContent, node_path_1.default.join(process.cwd(), rulesFile));
|
|
110
125
|
}
|
|
126
|
+
function writeAssetsToTargets(assets, targets) {
|
|
127
|
+
const targetPaths = getTargetPaths();
|
|
128
|
+
for (const target of targets) {
|
|
129
|
+
let targetDir;
|
|
130
|
+
switch (target) {
|
|
131
|
+
case "cursor":
|
|
132
|
+
targetDir = targetPaths.cursor;
|
|
133
|
+
break;
|
|
134
|
+
case "windsurf":
|
|
135
|
+
case "codex":
|
|
136
|
+
case "claude":
|
|
137
|
+
targetDir = targetPaths.aicm;
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
for (const asset of assets) {
|
|
143
|
+
let assetPath;
|
|
144
|
+
if (asset.presetName) {
|
|
145
|
+
const namespace = extractNamespaceFromPresetPath(asset.presetName);
|
|
146
|
+
assetPath = node_path_1.default.join(targetDir, ...namespace, asset.name);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
assetPath = node_path_1.default.join(targetDir, asset.name);
|
|
150
|
+
}
|
|
151
|
+
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(assetPath));
|
|
152
|
+
fs_extra_1.default.writeFileSync(assetPath, asset.content);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
111
156
|
/**
|
|
112
157
|
* Write all collected rules to their respective IDE targets
|
|
113
158
|
*/
|
|
114
|
-
function writeRulesToTargets(rules, targets) {
|
|
159
|
+
function writeRulesToTargets(rules, assets, targets) {
|
|
115
160
|
const targetPaths = getTargetPaths();
|
|
116
161
|
for (const target of targets) {
|
|
117
162
|
switch (target) {
|
|
@@ -122,29 +167,31 @@ function writeRulesToTargets(rules, targets) {
|
|
|
122
167
|
break;
|
|
123
168
|
case "windsurf":
|
|
124
169
|
if (rules.length > 0) {
|
|
125
|
-
writeRulesForFile(rules, targetPaths.
|
|
170
|
+
writeRulesForFile(rules, assets, targetPaths.aicm, ".windsurfrules");
|
|
126
171
|
}
|
|
127
172
|
break;
|
|
128
173
|
case "codex":
|
|
129
174
|
if (rules.length > 0) {
|
|
130
|
-
writeRulesForFile(rules, targetPaths.
|
|
175
|
+
writeRulesForFile(rules, assets, targetPaths.aicm, "AGENTS.md");
|
|
131
176
|
}
|
|
132
177
|
break;
|
|
133
178
|
case "claude":
|
|
134
179
|
if (rules.length > 0) {
|
|
135
|
-
writeRulesForFile(rules, targetPaths.
|
|
180
|
+
writeRulesForFile(rules, assets, targetPaths.aicm, "CLAUDE.md");
|
|
136
181
|
}
|
|
137
182
|
break;
|
|
138
183
|
}
|
|
139
184
|
}
|
|
185
|
+
// Write assets after rules so they don't get wiped by emptyDirSync
|
|
186
|
+
writeAssetsToTargets(assets, targets);
|
|
140
187
|
}
|
|
141
|
-
function writeCommandsToTargets(commands, targets) {
|
|
188
|
+
function writeCommandsToTargets(commands, assets, targets) {
|
|
142
189
|
const projectDir = process.cwd();
|
|
143
190
|
const cursorRoot = node_path_1.default.join(projectDir, ".cursor");
|
|
144
191
|
for (const target of targets) {
|
|
145
192
|
if (target === "cursor") {
|
|
146
193
|
const commandsDir = node_path_1.default.join(cursorRoot, "commands", "aicm");
|
|
147
|
-
writeCursorCommands(commands, commandsDir);
|
|
194
|
+
writeCursorCommands(commands, commandsDir, assets);
|
|
148
195
|
}
|
|
149
196
|
// Other targets do not support commands yet
|
|
150
197
|
}
|
|
@@ -357,15 +404,17 @@ async function installPackage(options = {}) {
|
|
|
357
404
|
error: new Error("Configuration file not found"),
|
|
358
405
|
installedRuleCount: 0,
|
|
359
406
|
installedCommandCount: 0,
|
|
407
|
+
installedAssetCount: 0,
|
|
360
408
|
packagesCount: 0,
|
|
361
409
|
};
|
|
362
410
|
}
|
|
363
|
-
const { config, rules, commands, mcpServers } = resolvedConfig;
|
|
411
|
+
const { config, rules, commands, assets, mcpServers } = resolvedConfig;
|
|
364
412
|
if (config.skipInstall === true) {
|
|
365
413
|
return {
|
|
366
414
|
success: true,
|
|
367
415
|
installedRuleCount: 0,
|
|
368
416
|
installedCommandCount: 0,
|
|
417
|
+
installedAssetCount: 0,
|
|
369
418
|
packagesCount: 0,
|
|
370
419
|
};
|
|
371
420
|
}
|
|
@@ -373,8 +422,8 @@ async function installPackage(options = {}) {
|
|
|
373
422
|
const commandsToInstall = dedupeCommandsForInstall(commands);
|
|
374
423
|
try {
|
|
375
424
|
if (!options.dryRun) {
|
|
376
|
-
writeRulesToTargets(rules, config.targets);
|
|
377
|
-
writeCommandsToTargets(commandsToInstall, config.targets);
|
|
425
|
+
writeRulesToTargets(rules, assets, config.targets);
|
|
426
|
+
writeCommandsToTargets(commandsToInstall, assets, config.targets);
|
|
378
427
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
379
428
|
writeMcpServersToTargets(mcpServers, config.targets, cwd);
|
|
380
429
|
}
|
|
@@ -385,6 +434,7 @@ async function installPackage(options = {}) {
|
|
|
385
434
|
success: true,
|
|
386
435
|
installedRuleCount: uniqueRuleCount,
|
|
387
436
|
installedCommandCount: uniqueCommandCount,
|
|
437
|
+
installedAssetCount: assets.length,
|
|
388
438
|
packagesCount: 1,
|
|
389
439
|
};
|
|
390
440
|
}
|
|
@@ -394,6 +444,7 @@ async function installPackage(options = {}) {
|
|
|
394
444
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
395
445
|
installedRuleCount: 0,
|
|
396
446
|
installedCommandCount: 0,
|
|
447
|
+
installedAssetCount: 0,
|
|
397
448
|
packagesCount: 0,
|
|
398
449
|
};
|
|
399
450
|
}
|
|
@@ -406,6 +457,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
406
457
|
const results = [];
|
|
407
458
|
let totalRuleCount = 0;
|
|
408
459
|
let totalCommandCount = 0;
|
|
460
|
+
let totalAssetCount = 0;
|
|
409
461
|
// Install packages sequentially for now (can be parallelized later)
|
|
410
462
|
for (const pkg of packages) {
|
|
411
463
|
const packagePath = pkg.absolutePath;
|
|
@@ -417,12 +469,14 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
417
469
|
});
|
|
418
470
|
totalRuleCount += result.installedRuleCount;
|
|
419
471
|
totalCommandCount += result.installedCommandCount;
|
|
472
|
+
totalAssetCount += result.installedAssetCount;
|
|
420
473
|
results.push({
|
|
421
474
|
path: pkg.relativePath,
|
|
422
475
|
success: result.success,
|
|
423
476
|
error: result.error,
|
|
424
477
|
installedRuleCount: result.installedRuleCount,
|
|
425
478
|
installedCommandCount: result.installedCommandCount,
|
|
479
|
+
installedAssetCount: result.installedAssetCount,
|
|
426
480
|
});
|
|
427
481
|
}
|
|
428
482
|
catch (error) {
|
|
@@ -432,6 +486,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
432
486
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
433
487
|
installedRuleCount: 0,
|
|
434
488
|
installedCommandCount: 0,
|
|
489
|
+
installedAssetCount: 0,
|
|
435
490
|
});
|
|
436
491
|
}
|
|
437
492
|
}
|
|
@@ -441,6 +496,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
441
496
|
packages: results,
|
|
442
497
|
totalRuleCount,
|
|
443
498
|
totalCommandCount,
|
|
499
|
+
totalAssetCount,
|
|
444
500
|
};
|
|
445
501
|
}
|
|
446
502
|
/**
|
|
@@ -471,6 +527,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
471
527
|
error: new Error("No packages with aicm configurations found"),
|
|
472
528
|
installedRuleCount: 0,
|
|
473
529
|
installedCommandCount: 0,
|
|
530
|
+
installedAssetCount: 0,
|
|
474
531
|
packagesCount: 0,
|
|
475
532
|
};
|
|
476
533
|
}
|
|
@@ -495,7 +552,9 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
495
552
|
workspaceCommands.length > 0 &&
|
|
496
553
|
workspaceCommandTargets.length > 0) {
|
|
497
554
|
const dedupedWorkspaceCommands = dedupeCommandsForInstall(workspaceCommands);
|
|
498
|
-
|
|
555
|
+
// Collect all assets from packages for command path rewriting
|
|
556
|
+
const allAssets = packages.flatMap((pkg) => { var _a; return (_a = pkg.config.assets) !== null && _a !== void 0 ? _a : []; });
|
|
557
|
+
writeCommandsToTargets(dedupedWorkspaceCommands, allAssets, workspaceCommandTargets);
|
|
499
558
|
}
|
|
500
559
|
const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages);
|
|
501
560
|
const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor"));
|
|
@@ -538,6 +597,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
538
597
|
error: new Error(`Package installation failed for ${failedPackages.length} package(s): ${errorDetails}`),
|
|
539
598
|
installedRuleCount: result.totalRuleCount,
|
|
540
599
|
installedCommandCount: result.totalCommandCount,
|
|
600
|
+
installedAssetCount: result.totalAssetCount,
|
|
541
601
|
packagesCount: result.packages.length,
|
|
542
602
|
};
|
|
543
603
|
}
|
|
@@ -545,6 +605,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
545
605
|
success: true,
|
|
546
606
|
installedRuleCount: result.totalRuleCount,
|
|
547
607
|
installedCommandCount: result.totalCommandCount,
|
|
608
|
+
installedAssetCount: result.totalAssetCount,
|
|
548
609
|
packagesCount: result.packages.length,
|
|
549
610
|
};
|
|
550
611
|
});
|
|
@@ -562,6 +623,7 @@ async function install(options = {}) {
|
|
|
562
623
|
success: true,
|
|
563
624
|
installedRuleCount: 0,
|
|
564
625
|
installedCommandCount: 0,
|
|
626
|
+
installedAssetCount: 0,
|
|
565
627
|
packagesCount: 0,
|
|
566
628
|
};
|
|
567
629
|
}
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -40,6 +40,13 @@ export interface ManagedFile {
|
|
|
40
40
|
source: "local" | "preset";
|
|
41
41
|
presetName?: string;
|
|
42
42
|
}
|
|
43
|
+
export interface AssetFile {
|
|
44
|
+
name: string;
|
|
45
|
+
content: Buffer;
|
|
46
|
+
sourcePath: string;
|
|
47
|
+
source: "local" | "preset";
|
|
48
|
+
presetName?: string;
|
|
49
|
+
}
|
|
43
50
|
export type RuleFile = ManagedFile;
|
|
44
51
|
export type CommandFile = ManagedFile;
|
|
45
52
|
export interface RuleCollection {
|
|
@@ -49,6 +56,7 @@ export interface ResolvedConfig {
|
|
|
49
56
|
config: Config;
|
|
50
57
|
rules: RuleFile[];
|
|
51
58
|
commands: CommandFile[];
|
|
59
|
+
assets: AssetFile[];
|
|
52
60
|
mcpServers: MCPServers;
|
|
53
61
|
}
|
|
54
62
|
export declare const ALLOWED_CONFIG_KEYS: readonly ["rulesDir", "commandsDir", "targets", "presets", "overrides", "mcpServers", "workspaces", "skipInstall"];
|
|
@@ -60,6 +68,7 @@ export declare function applyDefaults(config: RawConfig, workspaces: boolean): C
|
|
|
60
68
|
export declare function validateConfig(config: unknown, configFilePath: string, cwd: string, isWorkspaceMode?: boolean): asserts config is Config;
|
|
61
69
|
export declare function loadRulesFromDirectory(rulesDir: string, source: "local" | "preset", presetName?: string): Promise<RuleFile[]>;
|
|
62
70
|
export declare function loadCommandsFromDirectory(commandsDir: string, source: "local" | "preset", presetName?: string): Promise<CommandFile[]>;
|
|
71
|
+
export declare function loadAssetsFromDirectory(rulesDir: string, source: "local" | "preset", presetName?: string): Promise<AssetFile[]>;
|
|
63
72
|
export declare function resolvePresetPath(presetPath: string, cwd: string): string | null;
|
|
64
73
|
export declare function loadPreset(presetPath: string, cwd: string): Promise<{
|
|
65
74
|
config: Config;
|
|
@@ -69,6 +78,7 @@ export declare function loadPreset(presetPath: string, cwd: string): Promise<{
|
|
|
69
78
|
export declare function loadAllRules(config: Config, cwd: string): Promise<{
|
|
70
79
|
rules: RuleFile[];
|
|
71
80
|
commands: CommandFile[];
|
|
81
|
+
assets: AssetFile[];
|
|
72
82
|
mcpServers: MCPServers;
|
|
73
83
|
}>;
|
|
74
84
|
export declare function applyOverrides<T extends ManagedFile>(files: T[], overrides: Record<string, string | false>, cwd: string): T[];
|
package/dist/utils/config.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.applyDefaults = applyDefaults;
|
|
|
10
10
|
exports.validateConfig = validateConfig;
|
|
11
11
|
exports.loadRulesFromDirectory = loadRulesFromDirectory;
|
|
12
12
|
exports.loadCommandsFromDirectory = loadCommandsFromDirectory;
|
|
13
|
+
exports.loadAssetsFromDirectory = loadAssetsFromDirectory;
|
|
13
14
|
exports.resolvePresetPath = resolvePresetPath;
|
|
14
15
|
exports.loadPreset = loadPreset;
|
|
15
16
|
exports.loadAllRules = loadAllRules;
|
|
@@ -175,6 +176,34 @@ async function loadCommandsFromDirectory(commandsDir, source, presetName) {
|
|
|
175
176
|
}
|
|
176
177
|
return commands;
|
|
177
178
|
}
|
|
179
|
+
async function loadAssetsFromDirectory(rulesDir, source, presetName) {
|
|
180
|
+
const assets = [];
|
|
181
|
+
if (!fs_extra_1.default.existsSync(rulesDir)) {
|
|
182
|
+
return assets;
|
|
183
|
+
}
|
|
184
|
+
// Find all files except .mdc files and hidden files
|
|
185
|
+
const pattern = node_path_1.default.join(rulesDir, "**/*").replace(/\\/g, "/");
|
|
186
|
+
const filePaths = await (0, fast_glob_1.default)(pattern, {
|
|
187
|
+
onlyFiles: true,
|
|
188
|
+
absolute: true,
|
|
189
|
+
ignore: ["**/*.mdc", "**/.*"],
|
|
190
|
+
});
|
|
191
|
+
for (const filePath of filePaths) {
|
|
192
|
+
const content = await fs_extra_1.default.readFile(filePath);
|
|
193
|
+
// Preserve directory structure by using relative path from rulesDir
|
|
194
|
+
const relativePath = node_path_1.default.relative(rulesDir, filePath);
|
|
195
|
+
// Keep extension for assets
|
|
196
|
+
const assetName = relativePath.replace(/\\/g, "/");
|
|
197
|
+
assets.push({
|
|
198
|
+
name: assetName,
|
|
199
|
+
content,
|
|
200
|
+
sourcePath: filePath,
|
|
201
|
+
source,
|
|
202
|
+
presetName,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return assets;
|
|
206
|
+
}
|
|
178
207
|
function resolvePresetPath(presetPath, cwd) {
|
|
179
208
|
// Support specifying aicm.json directory and load the config from it
|
|
180
209
|
if (!presetPath.endsWith(".json")) {
|
|
@@ -230,12 +259,15 @@ async function loadPreset(presetPath, cwd) {
|
|
|
230
259
|
async function loadAllRules(config, cwd) {
|
|
231
260
|
const allRules = [];
|
|
232
261
|
const allCommands = [];
|
|
262
|
+
const allAssets = [];
|
|
233
263
|
let mergedMcpServers = { ...config.mcpServers };
|
|
234
264
|
// Load local rules only if rulesDir is provided
|
|
235
265
|
if (config.rulesDir) {
|
|
236
266
|
const localRulesPath = node_path_1.default.resolve(cwd, config.rulesDir);
|
|
237
267
|
const localRules = await loadRulesFromDirectory(localRulesPath, "local");
|
|
268
|
+
const localAssets = await loadAssetsFromDirectory(localRulesPath, "local");
|
|
238
269
|
allRules.push(...localRules);
|
|
270
|
+
allAssets.push(...localAssets);
|
|
239
271
|
}
|
|
240
272
|
if (config.commandsDir) {
|
|
241
273
|
const localCommandsPath = node_path_1.default.resolve(cwd, config.commandsDir);
|
|
@@ -247,7 +279,9 @@ async function loadAllRules(config, cwd) {
|
|
|
247
279
|
const preset = await loadPreset(presetPath, cwd);
|
|
248
280
|
if (preset.rulesDir) {
|
|
249
281
|
const presetRules = await loadRulesFromDirectory(preset.rulesDir, "preset", presetPath);
|
|
282
|
+
const presetAssets = await loadAssetsFromDirectory(preset.rulesDir, "preset", presetPath);
|
|
250
283
|
allRules.push(...presetRules);
|
|
284
|
+
allAssets.push(...presetAssets);
|
|
251
285
|
}
|
|
252
286
|
if (preset.commandsDir) {
|
|
253
287
|
const presetCommands = await loadCommandsFromDirectory(preset.commandsDir, "preset", presetPath);
|
|
@@ -262,6 +296,7 @@ async function loadAllRules(config, cwd) {
|
|
|
262
296
|
return {
|
|
263
297
|
rules: allRules,
|
|
264
298
|
commands: allCommands,
|
|
299
|
+
assets: allAssets,
|
|
265
300
|
mcpServers: mergedMcpServers,
|
|
266
301
|
};
|
|
267
302
|
}
|
|
@@ -341,9 +376,10 @@ async function loadConfig(cwd) {
|
|
|
341
376
|
const isWorkspaces = resolveWorkspaces(config, configResult.filepath, workingDir);
|
|
342
377
|
validateConfig(config, configResult.filepath, workingDir, isWorkspaces);
|
|
343
378
|
const configWithDefaults = applyDefaults(config, isWorkspaces);
|
|
344
|
-
const { rules, commands, mcpServers } = await loadAllRules(configWithDefaults, workingDir);
|
|
379
|
+
const { rules, commands, assets, mcpServers } = await loadAllRules(configWithDefaults, workingDir);
|
|
345
380
|
let rulesWithOverrides = rules;
|
|
346
381
|
let commandsWithOverrides = commands;
|
|
382
|
+
// Note: Assets are not currently supported in overrides as they are binary/varied files
|
|
347
383
|
if (configWithDefaults.overrides) {
|
|
348
384
|
const overrides = configWithDefaults.overrides;
|
|
349
385
|
const ruleNames = new Set(rules.map((rule) => rule.name));
|
|
@@ -366,6 +402,7 @@ async function loadConfig(cwd) {
|
|
|
366
402
|
config: configWithDefaults,
|
|
367
403
|
rules: rulesWithOverrides,
|
|
368
404
|
commands: commandsWithOverrides,
|
|
405
|
+
assets,
|
|
369
406
|
mcpServers,
|
|
370
407
|
};
|
|
371
408
|
}
|