aicm 0.17.0 → 0.17.2

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 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
  ![aicm](https://github.com/user-attachments/assets/ca38f2d6-ece6-43ad-a127-6f4fce8b2a5a)
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 enable developers to write custom instructions to maintain context across coding sessions. They also support MCPs for enhanced functionality. However, sharing these configurations across multiple projects is a challenge.
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** solves this by enabling you to create reusable presets that bundle rules and MCP configurations together. With multi-target support, you can write your rules once and deploy them consistently across different AI tools and IDEs.
31
+ **aicm** enables **"Write Once, Use Everywhere"** for your AI configurations.
14
32
 
15
- ## How it works
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
- aicm accepts Cursor's `.mdc` format as it provides the most comprehensive feature set. For other AI tools and IDEs, aicm automatically generates compatible formats:
37
+ ## Supported Environments
18
38
 
19
- - **Cursor**: Native `.mdc` files with full feature support
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
- This approach ensures you write your rules once in the richest format available, while maintaining compatibility across different AI development environments.
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
- ### Using a preset
52
+ ### Demo
31
53
 
32
- 1. **Install a preset npm package**:
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 @team/ai-preset
59
+ npm install --save-dev pirate-coding
36
60
  ```
37
61
 
38
- 2. **Create an `aicm.json` file** in your project root:
62
+ 2. **Create an `aicm.json` file** in your project:
39
63
 
40
- ```json
41
- { "presets": ["@team/ai-preset"] }
64
+ ```bash
65
+ echo '{ "presets": ["pirate-coding"] }' > aicm.json
42
66
  ```
43
67
 
44
- 3. **Add a prepare script** to your `package.json` to install all preset rules and MCPs:
68
+ 3. **Install all rules & MCPs from your configuration**:
45
69
 
46
- ```json
47
- {
48
- "scripts": {
49
- "prepare": "npx aicm -y install"
50
- }
51
- }
70
+ ```bash
71
+ npx aicm install
52
72
  ```
53
73
 
54
- The rules are now installed in `.cursor/rules/aicm/` and any MCP servers are configured in `.cursor/mcp.json`.
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,48 +105,59 @@ 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 Local Rules
108
+ ### Using a Preset
89
109
 
90
- For project-specific rules, you can specify `rulesDir` in your `aicm.json` config. This approach allows you to write rules once and automatically generate them for all configured targets.
110
+ To use a real preset in your production project:
111
+
112
+ 1. **Install a preset npm package**:
113
+
114
+ ```bash
115
+ npm install --save-dev @team/ai-preset
116
+ ```
117
+
118
+ 2. **Create an `aicm.json` file** in your project root:
91
119
 
92
120
  ```json
93
- {
94
- "rulesDir": "path/to/rules/dir"
95
- }
121
+ { "presets": ["@team/ai-preset"] }
96
122
  ```
97
123
 
98
- **Referencing Auxiliary Files**
124
+ 3. **Add a prepare script** to your `package.json` to ensure rules are always up to date:
99
125
 
100
- 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.
126
+ ```json
127
+ {
128
+ "scripts": {
129
+ "prepare": "npx aicm -y install"
130
+ }
131
+ }
132
+ ```
101
133
 
102
- Example `rules/my-rule.mdc`:
134
+ The rules are now installed in `.cursor/rules/aicm/` and any MCP servers are configured in `.cursor/mcp.json`.
103
135
 
104
- ```markdown
105
- # My Rule
136
+ ### Notes
106
137
 
107
- See [Example](./example.ts) for details.
108
- ```
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.
109
140
 
110
- **Commands referencing files**
141
+ ## Features
111
142
 
112
- 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.
143
+ ### Using Rules
113
144
 
114
- For example, if you have a schema file at `rules/schema.json` and a command at `commands/generate-schema.md`:
145
+ aicm uses Cursor's `.mdc` files for rules. Read more about the format [here](https://cursor.com/docs/context/rules).
115
146
 
116
- ```markdown
117
- # Generate Schema
147
+ Add a rules directory to your project configuration:
118
148
 
119
- Use the schema defined in [Schema Template](../rules/schema.json) to generate the response.
149
+ ```json
150
+ {
151
+ "rulesDir": "./rules",
152
+ "targets": ["cursor"]
153
+ }
120
154
  ```
121
155
 
122
- 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).
156
+ Rules are installed in `.cursor/rules/aicm/` and are loaded automatically by Cursor.
123
157
 
124
158
  ### Using Commands
125
159
 
126
- Cursor supports custom commands that can be invoked directly in the chat interface. aicm can manage these command files
127
- alongside your rules and MCP configurations so they install automatically into Cursor.
128
-
129
- #### Local Commands
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.
130
161
 
131
162
  Add a commands directory to your project configuration:
132
163
 
@@ -139,84 +170,66 @@ Add a commands directory to your project configuration:
139
170
 
140
171
  Command files ending in `.md` are installed to `.cursor/commands/aicm/` and appear in Cursor under the `/` command menu.
141
172
 
142
- #### Commands in Presets
173
+ ### MCP Servers
143
174
 
144
- Presets can ship reusable command libraries in addition to rules:
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.
145
176
 
146
177
  ```json
147
178
  {
148
- "rulesDir": "rules",
149
- "commandsDir": "commands"
179
+ "mcpServers": {
180
+ "Playwright": {
181
+ "command": "npx",
182
+ "args": ["@playwright/mcp"]
183
+ }
184
+ }
150
185
  }
151
186
  ```
152
187
 
153
- Preset command files install alongside local ones in `.cursor/commands/aicm/` so they appear with concise paths inside Cursor.
154
- If multiple presets provide a command at the same relative path, aicm will warn during installation and use the version from the
155
- last preset listed in your configuration. Use `overrides` to explicitly choose a different definition when needed.
188
+ When installed, these servers are automatically added to your `.cursor/mcp.json`.
156
189
 
157
- #### Command Overrides
190
+ ### Referencing Auxiliary Files
158
191
 
159
- Use the existing `overrides` field to customize or disable commands provided by presets:
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.
160
193
 
161
- ```json
162
- {
163
- "presets": ["@team/dev-preset"],
164
- "overrides": {
165
- "legacy-command": false,
166
- "custom-test": "./commands/test.md"
167
- }
168
- }
194
+ Example `rules/my-rule.mdc`:
195
+
196
+ ```markdown
197
+ # My Rule
198
+
199
+ See [Example](./example.ts) for details.
169
200
  ```
170
201
 
171
- ### Notes
202
+ #### Commands Referencing Files
172
203
 
173
- - Generated rules are always placed in a subdirectory for deterministic cleanup and easy gitignore.
174
- - Users may add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf/Codex) to `.gitignore` if they do not want to track generated rules.
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.
212
+ ```
213
+
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).
215
+
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`.
175
217
 
176
218
  ### Overrides
177
219
 
178
- You can disable or replace specific rules provided by presets using the `overrides` field:
220
+ You can disable or replace specific rules or commands provided by presets using the `overrides` field:
179
221
 
180
222
  ```json
181
223
  {
182
224
  "presets": ["@company/ai-rules"],
183
225
  "overrides": {
184
226
  "rule-from-preset-a": "./rules/override-rule.mdc",
185
- "rule-from-preset-b": false
227
+ "rule-from-preset-b": false,
228
+ "legacy-command": false
186
229
  }
187
230
  }
188
231
  ```
189
232
 
190
- ### Demo
191
-
192
- We'll install [an npm package](https://github.com/ranyitz/pirate-coding) containing a simple preset to demonstrate how aicm works.
193
-
194
- 1. **Install the demo preset package**:
195
-
196
- ```bash
197
- npm install --save-dev pirate-coding
198
- ```
199
-
200
- 2. **Create an `aicm.json` file** in your project:
201
-
202
- ```bash
203
- echo '{ "presets": ["pirate-coding"] }' > aicm.json
204
- ```
205
-
206
- 3. **Install all rules & MCPs from your configuration**:
207
-
208
- ```bash
209
- npx aicm install
210
- ```
211
-
212
- This command installs all configured rules and MCPs to their IDE-specific locations.
213
-
214
- 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 advice based on the latest documentation.
215
-
216
- ## Security Note
217
-
218
- To prevent [prompt-injection](https://en.wikipedia.org/wiki/Prompt_injection), use only packages from trusted sources.
219
-
220
233
  ## Workspaces Support
221
234
 
222
235
  aicm supports workspaces by automatically discovering and installing configurations across multiple packages in your repository.
@@ -229,17 +242,14 @@ You can enable workspaces mode by setting the `workspaces` property to `true` in
229
242
  }
230
243
  ```
231
244
 
232
- aicm automatically detects workspaces if your `package.json` contains a `workspaces` configuration:
233
-
234
- ### How It Works
235
-
236
- 1. **Discover packages**: Automatically find all directories containing `aicm.json` files in your repository
237
- 2. **Install per package**: Install rules and MCPs for each package individually in their respective directories
238
- 3. **Merge MCP servers**: Write a merged `.cursor/mcp.json` at the repository root containing all MCP servers from every package
245
+ aicm automatically detects workspaces if your `package.json` contains a `workspaces` configuration.
239
246
 
240
247
  ### How It Works
241
248
 
242
- 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.
243
253
 
244
254
  For example, in a workspace structure like:
245
255
 
@@ -300,17 +310,7 @@ Create an `aicm.json` file in your project root, or an `aicm` key in your projec
300
310
  - **workspaces**: Set to `true` to enable workspace mode. If not specified, aicm will automatically detect workspaces from your `package.json`.
301
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.
302
312
 
303
- ### MCP Server Installation
304
-
305
- - **Cursor**: MCP server configs are written to `.cursor/mcp.json`.
306
-
307
- ## Supported Targets
308
-
309
- - **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/aicm/`), mcp servers are installed to `.cursor/mcp.json`
310
- - **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.
311
- - **Codex**: Rules are installed in the `.aicm` directory and referenced from `AGENTS.md`.
312
-
313
- ## Commands
313
+ ## CLI Commands
314
314
 
315
315
  ### Global Options
316
316
 
@@ -343,6 +343,14 @@ Options:
343
343
  - `--verbose`: show detailed output and stack traces for debugging
344
344
  - `--dry-run`: simulate installation without writing files, useful for validating presets in CI
345
345
 
346
+ ### `clean`
347
+
348
+ Removes all files, directories & changes made by aicm.
349
+
350
+ ```bash
351
+ npx aicm clean
352
+ ```
353
+
346
354
  ## Node.js API
347
355
 
348
356
  In addition to the CLI, aicm can be used programmatically in Node.js applications:
@@ -373,27 +381,9 @@ install({
373
381
  });
374
382
  ```
375
383
 
376
- ### API Reference
377
-
378
- #### `install(options?: InstallOptions): Promise<InstallResult>`
379
-
380
- Installs rules and MCP servers based on configuration.
381
-
382
- **Options:**
383
-
384
- - `cwd`: Base directory to use instead of `process.cwd()`
385
- - `config`: Custom config object to use instead of loading from file
386
- - `installOnCI`: Run installation on CI environments (default: `false`)
387
- - `verbose`: Show verbose output and stack traces for debugging (default: `false`)
388
- - `dryRun`: Simulate installation without writing files, useful for preset validation in CI (default: `false`)
389
-
390
- **Returns:**
391
-
392
- A Promise that resolves to an object with:
384
+ ## Security Note
393
385
 
394
- - `success`: Whether the operation was successful
395
- - `error`: Error object if the operation failed
396
- - `installedRuleCount`: Number of rules installed
386
+ To prevent [prompt-injection](https://en.wikipedia.org/wiki/Prompt_injection), use only packages from trusted sources.
397
387
 
398
388
  ## Contributing
399
389
 
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ const chalk_1 = __importDefault(require("chalk"));
10
10
  const init_1 = require("./commands/init");
11
11
  const install_1 = require("./commands/install");
12
12
  const list_1 = require("./commands/list");
13
+ const clean_1 = require("./commands/clean");
13
14
  // Define version from package.json
14
15
  // eslint-disable-next-line @typescript-eslint/no-require-imports
15
16
  const pkg = require("../package.json");
@@ -48,6 +49,9 @@ async function runCli() {
48
49
  case "list":
49
50
  await (0, list_1.listCommand)();
50
51
  break;
52
+ case "clean":
53
+ await (0, clean_1.cleanCommand)(args["--verbose"]);
54
+ break;
51
55
  default:
52
56
  showHelp();
53
57
  break;
@@ -69,6 +73,7 @@ ${chalk_1.default.bold("COMMANDS")}
69
73
  init Initialize a new aicm configuration file
70
74
  install Install rules from configured sources
71
75
  list List all configured rules and their status
76
+ clean Remove all files and directories created by aicm
72
77
 
73
78
  ${chalk_1.default.bold("OPTIONS")}
74
79
  -h, --help Show this help message
@@ -0,0 +1,19 @@
1
+ export interface CleanOptions {
2
+ /**
3
+ * Base directory to use instead of process.cwd()
4
+ */
5
+ cwd?: string;
6
+ /**
7
+ * Show verbose output
8
+ */
9
+ verbose?: boolean;
10
+ }
11
+ export interface CleanResult {
12
+ success: boolean;
13
+ error?: Error;
14
+ cleanedCount: number;
15
+ }
16
+ export declare function cleanPackage(options?: CleanOptions): Promise<CleanResult>;
17
+ export declare function cleanWorkspaces(cwd: string, verbose?: boolean): Promise<CleanResult>;
18
+ export declare function clean(options?: CleanOptions): Promise<CleanResult>;
19
+ export declare function cleanCommand(verbose?: boolean): Promise<void>;
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.cleanPackage = cleanPackage;
7
+ exports.cleanWorkspaces = cleanWorkspaces;
8
+ exports.clean = clean;
9
+ exports.cleanCommand = cleanCommand;
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const config_1 = require("../utils/config");
14
+ const working_directory_1 = require("../utils/working-directory");
15
+ const rules_file_writer_1 = require("../utils/rules-file-writer");
16
+ const workspace_discovery_1 = require("../utils/workspace-discovery");
17
+ function cleanFile(filePath, verbose) {
18
+ if (!fs_extra_1.default.existsSync(filePath))
19
+ return false;
20
+ try {
21
+ fs_extra_1.default.removeSync(filePath);
22
+ if (verbose)
23
+ console.log(chalk_1.default.gray(` Removed ${filePath}`));
24
+ return true;
25
+ }
26
+ catch (_a) {
27
+ console.warn(chalk_1.default.yellow(`Warning: Failed to remove ${filePath}`));
28
+ return false;
29
+ }
30
+ }
31
+ function cleanRulesBlock(filePath, verbose) {
32
+ if (!fs_extra_1.default.existsSync(filePath))
33
+ return false;
34
+ try {
35
+ const content = fs_extra_1.default.readFileSync(filePath, "utf8");
36
+ const cleanedContent = (0, rules_file_writer_1.removeRulesBlock)(content);
37
+ if (content === cleanedContent)
38
+ return false;
39
+ if (cleanedContent.trim() === "") {
40
+ fs_extra_1.default.removeSync(filePath);
41
+ if (verbose)
42
+ console.log(chalk_1.default.gray(` Removed empty file ${filePath}`));
43
+ }
44
+ else {
45
+ fs_extra_1.default.writeFileSync(filePath, cleanedContent);
46
+ if (verbose)
47
+ console.log(chalk_1.default.gray(` Cleaned rules block from ${filePath}`));
48
+ }
49
+ return true;
50
+ }
51
+ catch (_a) {
52
+ console.warn(chalk_1.default.yellow(`Warning: Failed to clean ${filePath}`));
53
+ return false;
54
+ }
55
+ }
56
+ function cleanMcpServers(cwd, verbose) {
57
+ const mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json");
58
+ if (!fs_extra_1.default.existsSync(mcpPath))
59
+ return false;
60
+ try {
61
+ const content = fs_extra_1.default.readJsonSync(mcpPath);
62
+ const mcpServers = content.mcpServers;
63
+ if (!mcpServers)
64
+ return false;
65
+ let hasChanges = false;
66
+ const newMcpServers = {};
67
+ for (const [key, value] of Object.entries(mcpServers)) {
68
+ if (typeof value === "object" &&
69
+ value !== null &&
70
+ "aicm" in value &&
71
+ value.aicm === true) {
72
+ hasChanges = true;
73
+ }
74
+ else {
75
+ newMcpServers[key] = value;
76
+ }
77
+ }
78
+ if (!hasChanges)
79
+ return false;
80
+ content.mcpServers = newMcpServers;
81
+ fs_extra_1.default.writeJsonSync(mcpPath, content, { spaces: 2 });
82
+ if (verbose)
83
+ console.log(chalk_1.default.gray(` Cleaned aicm MCP servers from ${mcpPath}`));
84
+ return true;
85
+ }
86
+ catch (_a) {
87
+ console.warn(chalk_1.default.yellow(`Warning: Failed to clean MCP servers`));
88
+ return false;
89
+ }
90
+ }
91
+ async function cleanPackage(options = {}) {
92
+ const cwd = options.cwd || process.cwd();
93
+ const verbose = options.verbose || false;
94
+ return (0, working_directory_1.withWorkingDirectory)(cwd, async () => {
95
+ let cleanedCount = 0;
96
+ const filesToClean = [
97
+ node_path_1.default.join(cwd, ".cursor", "rules", "aicm"),
98
+ node_path_1.default.join(cwd, ".cursor", "commands", "aicm"),
99
+ node_path_1.default.join(cwd, ".aicm"),
100
+ ];
101
+ const rulesFilesToClean = [
102
+ node_path_1.default.join(cwd, ".windsurfrules"),
103
+ node_path_1.default.join(cwd, "AGENTS.md"),
104
+ node_path_1.default.join(cwd, "CLAUDE.md"),
105
+ ];
106
+ // Clean directories and files
107
+ for (const file of filesToClean) {
108
+ if (cleanFile(file, verbose))
109
+ cleanedCount++;
110
+ }
111
+ // Clean rules blocks from files
112
+ for (const file of rulesFilesToClean) {
113
+ if (cleanRulesBlock(file, verbose))
114
+ cleanedCount++;
115
+ }
116
+ // Clean MCP servers
117
+ if (cleanMcpServers(cwd, verbose))
118
+ cleanedCount++;
119
+ return {
120
+ success: true,
121
+ cleanedCount,
122
+ };
123
+ });
124
+ }
125
+ async function cleanWorkspaces(cwd, verbose = false) {
126
+ if (verbose)
127
+ console.log(chalk_1.default.blue("🔍 Discovering packages..."));
128
+ const packages = await (0, workspace_discovery_1.discoverPackagesWithAicm)(cwd);
129
+ if (verbose && packages.length > 0) {
130
+ console.log(chalk_1.default.blue(`Found ${packages.length} packages with aicm configurations.`));
131
+ }
132
+ let totalCleaned = 0;
133
+ // Clean all discovered packages
134
+ for (const pkg of packages) {
135
+ if (verbose)
136
+ console.log(chalk_1.default.blue(`Cleaning package: ${pkg.relativePath}`));
137
+ const result = await cleanPackage({
138
+ cwd: pkg.absolutePath,
139
+ verbose,
140
+ });
141
+ totalCleaned += result.cleanedCount;
142
+ }
143
+ // Always clean root directory (for merged artifacts like mcp.json and commands)
144
+ const rootPackage = packages.find((p) => p.absolutePath === cwd);
145
+ if (!rootPackage) {
146
+ if (verbose)
147
+ console.log(chalk_1.default.blue(`Cleaning root workspace artifacts...`));
148
+ const rootResult = await cleanPackage({ cwd, verbose });
149
+ totalCleaned += rootResult.cleanedCount;
150
+ }
151
+ return {
152
+ success: true,
153
+ cleanedCount: totalCleaned,
154
+ };
155
+ }
156
+ async function clean(options = {}) {
157
+ var _a;
158
+ const cwd = options.cwd || process.cwd();
159
+ const verbose = options.verbose || false;
160
+ const shouldUseWorkspaces = ((_a = (await (0, config_1.loadConfig)(cwd))) === null || _a === void 0 ? void 0 : _a.config.workspaces) ||
161
+ (0, config_1.detectWorkspacesFromPackageJson)(cwd);
162
+ if (shouldUseWorkspaces) {
163
+ return cleanWorkspaces(cwd, verbose);
164
+ }
165
+ return cleanPackage(options);
166
+ }
167
+ async function cleanCommand(verbose) {
168
+ const result = await clean({ verbose });
169
+ if (result.cleanedCount === 0) {
170
+ console.log("Nothing to clean.");
171
+ }
172
+ else {
173
+ console.log(chalk_1.default.green(`Successfully cleaned ${result.cleanedCount} file(s)/director(y/ies).`));
174
+ }
175
+ }
@@ -9,11 +9,11 @@ exports.installCommand = installCommand;
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
10
  const fs_extra_1 = __importDefault(require("fs-extra"));
11
11
  const node_path_1 = __importDefault(require("node:path"));
12
- const child_process_1 = require("child_process");
13
12
  const config_1 = require("../utils/config");
14
13
  const working_directory_1 = require("../utils/working-directory");
15
14
  const is_ci_1 = require("../utils/is-ci");
16
15
  const rules_file_writer_1 = require("../utils/rules-file-writer");
16
+ const workspace_discovery_1 = require("../utils/workspace-discovery");
17
17
  function getTargetPaths() {
18
18
  const projectDir = process.cwd();
19
19
  return {
@@ -41,7 +41,7 @@ function writeCursorRules(rules, cursorRulesDir) {
41
41
  fs_extra_1.default.writeFileSync(ruleFile, rule.content);
42
42
  }
43
43
  }
44
- function writeCursorCommands(commands, cursorCommandsDir) {
44
+ function writeCursorCommands(commands, cursorCommandsDir, assets) {
45
45
  fs_extra_1.default.removeSync(cursorCommandsDir);
46
46
  for (const command of commands) {
47
47
  const commandNameParts = command.name
@@ -56,22 +56,18 @@ function writeCursorCommands(commands, cursorCommandsDir) {
56
56
  // Rules/assets are installed in .cursor/rules/aicm/
57
57
  // So a link like "../rules/asset.json" in source (from commands/ to rules/)
58
58
  // needs to become "../../rules/aicm/asset.json" in target (from .cursor/commands/aicm/ to .cursor/rules/aicm/)
59
- const content = rewriteCommandRelativeLinks(command.content);
59
+ const content = rewriteCommandRelativeLinks(command.content, command.sourcePath, assets);
60
60
  fs_extra_1.default.writeFileSync(commandFile, content);
61
61
  }
62
62
  }
63
- function rewriteCommandRelativeLinks(content) {
64
- return content.replace(/\[([^\]]*)\]\(([^)]+)\)/g, (match, text, url) => {
65
- if (url.startsWith("http") || url.startsWith("#") || url.startsWith("/")) {
66
- return match;
67
- }
68
- // Check if it's a link to the rules directory
69
- if (url.includes("rules/")) {
70
- const parts = url.split("/");
71
- const filename = parts[parts.length - 1];
72
- return `[${text}](../../rules/aicm/${filename})`;
73
- }
74
- return match;
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;
75
71
  });
76
72
  }
77
73
  function extractNamespaceFromPresetPath(presetPath) {
@@ -189,13 +185,13 @@ function writeRulesToTargets(rules, assets, targets) {
189
185
  // Write assets after rules so they don't get wiped by emptyDirSync
190
186
  writeAssetsToTargets(assets, targets);
191
187
  }
192
- function writeCommandsToTargets(commands, targets) {
188
+ function writeCommandsToTargets(commands, assets, targets) {
193
189
  const projectDir = process.cwd();
194
190
  const cursorRoot = node_path_1.default.join(projectDir, ".cursor");
195
191
  for (const target of targets) {
196
192
  if (target === "cursor") {
197
193
  const commandsDir = node_path_1.default.join(cursorRoot, "commands", "aicm");
198
- writeCursorCommands(commands, commandsDir);
194
+ writeCursorCommands(commands, commandsDir, assets);
199
195
  }
200
196
  // Other targets do not support commands yet
201
197
  }
@@ -346,49 +342,6 @@ function mergeWorkspaceMcpServers(packages) {
346
342
  }
347
343
  return { merged, conflicts };
348
344
  }
349
- /**
350
- * Discover all packages with aicm configurations using git ls-files
351
- */
352
- function findAicmFiles(rootDir) {
353
- try {
354
- const output = (0, child_process_1.execSync)("git ls-files --cached --others --exclude-standard aicm.json **/aicm.json", {
355
- cwd: rootDir,
356
- encoding: "utf8",
357
- });
358
- return output
359
- .trim()
360
- .split("\n")
361
- .filter(Boolean)
362
- .map((file) => node_path_1.default.resolve(rootDir, file));
363
- }
364
- catch (_a) {
365
- // Fallback to manual search if git is not available
366
- return [];
367
- }
368
- }
369
- /**
370
- * Discover all packages with aicm configurations
371
- */
372
- async function discoverPackagesWithAicm(rootDir) {
373
- const aicmFiles = findAicmFiles(rootDir);
374
- const packages = [];
375
- for (const aicmFile of aicmFiles) {
376
- const packageDir = node_path_1.default.dirname(aicmFile);
377
- const relativePath = node_path_1.default.relative(rootDir, packageDir);
378
- // Normalize to forward slashes for cross-platform compatibility
379
- const normalizedRelativePath = relativePath.replace(/\\/g, "/");
380
- const config = await (0, config_1.loadConfig)(packageDir);
381
- if (config) {
382
- packages.push({
383
- relativePath: normalizedRelativePath || ".",
384
- absolutePath: packageDir,
385
- config,
386
- });
387
- }
388
- }
389
- // Sort packages by relativePath for deterministic order
390
- return packages.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
391
- }
392
345
  /**
393
346
  * Install rules for a single package (used within workspaces and standalone installs)
394
347
  */
@@ -427,7 +380,7 @@ async function installPackage(options = {}) {
427
380
  try {
428
381
  if (!options.dryRun) {
429
382
  writeRulesToTargets(rules, assets, config.targets);
430
- writeCommandsToTargets(commandsToInstall, config.targets);
383
+ writeCommandsToTargets(commandsToInstall, assets, config.targets);
431
384
  if (mcpServers && Object.keys(mcpServers).length > 0) {
432
385
  writeMcpServersToTargets(mcpServers, config.targets, cwd);
433
386
  }
@@ -511,7 +464,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
511
464
  if (verbose) {
512
465
  console.log(chalk_1.default.blue("🔍 Discovering packages..."));
513
466
  }
514
- const allPackages = await discoverPackagesWithAicm(cwd);
467
+ const allPackages = await (0, workspace_discovery_1.discoverPackagesWithAicm)(cwd);
515
468
  const packages = allPackages.filter((pkg) => {
516
469
  if (pkg.config.config.skipInstall === true) {
517
470
  return false;
@@ -556,7 +509,9 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
556
509
  workspaceCommands.length > 0 &&
557
510
  workspaceCommandTargets.length > 0) {
558
511
  const dedupedWorkspaceCommands = dedupeCommandsForInstall(workspaceCommands);
559
- writeCommandsToTargets(dedupedWorkspaceCommands, workspaceCommandTargets);
512
+ // Collect all assets from packages for command path rewriting
513
+ const allAssets = packages.flatMap((pkg) => { var _a; return (_a = pkg.config.assets) !== null && _a !== void 0 ? _a : []; });
514
+ writeCommandsToTargets(dedupedWorkspaceCommands, allAssets, workspaceCommandTargets);
560
515
  }
561
516
  const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages);
562
517
  const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor"));
@@ -3,6 +3,10 @@ export type RuleMetadata = Record<string, string | boolean | string[]>;
3
3
  * Parse YAML frontmatter blocks from a rule file and return a flat metadata object
4
4
  */
5
5
  export declare function parseRuleFrontmatter(content: string): RuleMetadata;
6
+ /**
7
+ * Remove the rules block from the content
8
+ */
9
+ export declare function removeRulesBlock(content: string): string;
6
10
  /**
7
11
  * Write rules to the .windsurfrules file
8
12
  * This will update the content between the RULES_BEGIN and RULES_END markers
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.parseRuleFrontmatter = parseRuleFrontmatter;
7
+ exports.removeRulesBlock = removeRulesBlock;
7
8
  exports.writeRulesFile = writeRulesFile;
8
9
  exports.generateRulesFileContent = generateRulesFileContent;
9
10
  const fs_extra_1 = __importDefault(require("fs-extra"));
@@ -55,6 +56,20 @@ function parseRuleFrontmatter(content) {
55
56
  const RULES_BEGIN = "<!-- AICM:BEGIN -->";
56
57
  const RULES_END = "<!-- AICM:END -->";
57
58
  const WARNING = "<!-- WARNING: Everything between these markers will be overwritten during installation -->";
59
+ /**
60
+ * Remove the rules block from the content
61
+ */
62
+ function removeRulesBlock(content) {
63
+ // Check if our markers exist
64
+ if (content.includes(RULES_BEGIN) && content.includes(RULES_END)) {
65
+ const parts = content.split(RULES_BEGIN);
66
+ const beforeMarker = parts[0];
67
+ const afterParts = parts[1].split(RULES_END);
68
+ const afterMarker = afterParts.slice(1).join(RULES_END); // In case RULES_END appears multiple times (unlikely but safe)
69
+ return (beforeMarker + afterMarker).trim();
70
+ }
71
+ return content;
72
+ }
58
73
  /**
59
74
  * Create a formatted block of content with rules markers
60
75
  */
@@ -0,0 +1,13 @@
1
+ import { ResolvedConfig } from "./config";
2
+ /**
3
+ * Discover all packages with aicm configurations using git ls-files
4
+ */
5
+ export declare function findAicmFiles(rootDir: string): string[];
6
+ /**
7
+ * Discover all packages with aicm configurations
8
+ */
9
+ export declare function discoverPackagesWithAicm(rootDir: string): Promise<Array<{
10
+ relativePath: string;
11
+ absolutePath: string;
12
+ config: ResolvedConfig;
13
+ }>>;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findAicmFiles = findAicmFiles;
7
+ exports.discoverPackagesWithAicm = discoverPackagesWithAicm;
8
+ const child_process_1 = require("child_process");
9
+ const path_1 = __importDefault(require("path"));
10
+ const config_1 = require("./config");
11
+ /**
12
+ * Discover all packages with aicm configurations using git ls-files
13
+ */
14
+ function findAicmFiles(rootDir) {
15
+ try {
16
+ const output = (0, child_process_1.execSync)("git ls-files --cached --others --exclude-standard aicm.json **/aicm.json", {
17
+ cwd: rootDir,
18
+ encoding: "utf8",
19
+ });
20
+ return output
21
+ .trim()
22
+ .split("\n")
23
+ .filter(Boolean)
24
+ .map((file) => path_1.default.resolve(rootDir, file));
25
+ }
26
+ catch (_a) {
27
+ // Fallback to manual search if git is not available
28
+ return [];
29
+ }
30
+ }
31
+ /**
32
+ * Discover all packages with aicm configurations
33
+ */
34
+ async function discoverPackagesWithAicm(rootDir) {
35
+ const aicmFiles = findAicmFiles(rootDir);
36
+ const packages = [];
37
+ for (const aicmFile of aicmFiles) {
38
+ const packageDir = path_1.default.dirname(aicmFile);
39
+ const relativePath = path_1.default.relative(rootDir, packageDir);
40
+ // Normalize to forward slashes for cross-platform compatibility
41
+ const normalizedRelativePath = relativePath.replace(/\\/g, "/");
42
+ const config = await (0, config_1.loadConfig)(packageDir);
43
+ if (config) {
44
+ packages.push({
45
+ relativePath: normalizedRelativePath || ".",
46
+ absolutePath: packageDir,
47
+ config,
48
+ });
49
+ }
50
+ }
51
+ // Sort packages by relativePath for deterministic order
52
+ return packages.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicm",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "A TypeScript CLI tool for managing AI IDE rules across different projects and teams",
5
5
  "main": "dist/api.js",
6
6
  "types": "dist/api.d.ts",