aicm 0.14.0 → 0.14.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
@@ -1,16 +1,25 @@
1
1
  # 🗂️ aicm
2
2
 
3
- > Agentic IDE Configuration Manager
3
+ > AI Configuration Manager
4
4
 
5
- A CLI tool for managing Agentic IDE 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
9
  ## Why
10
10
 
11
- Agentic IDEs like Cursor enable AI-driven development through custom rules and configurations. However, sharing these configurations across projects and teams is challenging.
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.
12
12
 
13
- **aicm** solves this by distributing AI rules and MCP servers through npm packages, automatically installing them to the correct IDE locations.
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.
14
+
15
+ ## How it works
16
+
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:
18
+
19
+ - **Cursor**: Native `.mdc` files with full feature support
20
+ - **Windsurf/Codex**: Generates `.windsurfrules`/`AGENTS.md` files with natural language adaptations
21
+
22
+ This approach ensures you write your rules once in the richest format available, while maintaining compatibility across different AI development environments.
14
23
 
15
24
  ## Getting Started
16
25
 
@@ -21,27 +30,21 @@ The easiest way to get started with aicm is by using **presets** - npm packages
21
30
  1. **Install a preset npm package**:
22
31
 
23
32
  ```bash
24
- npm install --save-dev @yourteam/ai-preset
33
+ npm install --save-dev @team/ai-preset
25
34
  ```
26
35
 
27
- 2. **Create an `aicm.json` file** in your project:
36
+ 2. **Create an `aicm.json` file** in your project root:
28
37
 
29
38
  ```json
30
- { "presets": ["@yourteam/ai-preset"] }
39
+ { "presets": ["@team/ai-preset"] }
31
40
  ```
32
41
 
33
- 3. **Install the rules and MCPs**:
34
-
35
- ```bash
36
- npx -y aicm install
37
- ```
38
-
39
- 4. **Add a prepare script** to your `package.json` for automatic installation:
42
+ 3. **Add a prepare script** to your `package.json` to install all preset rules and MCPs:
40
43
 
41
44
  ```json
42
45
  {
43
46
  "scripts": {
44
- "prepare": "npx aicm install"
47
+ "prepare": "npx aicm -y install"
45
48
  }
46
49
  }
47
50
  ```
@@ -50,12 +53,10 @@ The rules are now installed in `.cursor/rules/aicm/` and any MCP servers are con
50
53
 
51
54
  ### Creating a Preset
52
55
 
53
- To create a reusable preset for your team:
54
-
55
56
  1. **Create an npm package** with the following structure:
56
57
 
57
58
  ```
58
- @myteam/ai-tools
59
+ @team/ai-preset
59
60
  ├── package.json
60
61
  ├── aicm.json
61
62
  └── rules/
@@ -67,28 +68,28 @@ To create a reusable preset for your team:
67
68
 
68
69
  ```json
69
70
  {
70
- "rulesDir": "./rules",
71
+ "rulesDir": "rules",
71
72
  "mcpServers": {
72
73
  "my-mcp": { "url": "https://example.com/sse" }
73
74
  }
74
75
  }
75
76
  ```
76
77
 
77
- 3. **Publish the package** and reference it in your projects:
78
+ 3. **Publish the package** and use it in your project's `aicm.json`:
78
79
 
79
80
  ```json
80
- { "presets": ["@myteam/ai-tools"] }
81
+ { "presets": ["@team/ai-preset"] }
81
82
  ```
82
83
 
83
- > **Note:** This is syntactic sugar for `@myteam/ai-tools/aicm.json`. Both forms are equivalent.
84
+ > **Note:** This is syntactic sugar for `@team/ai-preset/aicm.json`.
84
85
 
85
86
  ### Using Local Rules
86
87
 
87
- 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:
88
+ 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.
88
89
 
89
90
  ```json
90
91
  {
91
- "rulesDir": "./rules"
92
+ "rulesDir": "path/to/rules/dir"
92
93
  }
93
94
  ```
94
95
 
@@ -264,11 +265,9 @@ install().then((result) => {
264
265
 
265
266
  // Install with custom options
266
267
  const customConfig = {
267
- ides: ["cursor"],
268
- rules: {
269
- typescript: "./rules/typescript.mdc",
270
- react: "./rules/react.mdc",
271
- },
268
+ targets: ["cursor"],
269
+ rulesDir: "rules",
270
+ presets: ["@team/ai-preset"],
272
271
  };
273
272
 
274
273
  install({
@@ -297,26 +296,19 @@ Installs rules and MCP servers based on configuration.
297
296
  A Promise that resolves to an object with:
298
297
 
299
298
  - `success`: Whether the operation was successful
300
- - `error`: Error message if the operation failed
299
+ - `error`: Error object if the operation failed
301
300
  - `installedRuleCount`: Number of rules installed
302
301
 
303
302
  ## Contributing
304
303
 
305
- Contributions are welcome! Please feel free to submit a Pull Request.
304
+ Contributions are welcome! Please feel free to open an issue or submit a Pull Request.
306
305
 
307
306
  ## Development
308
307
 
309
308
  ### Testing
310
309
 
311
310
  ```bash
312
- # Run all tests
313
- npm test
314
-
315
- # Run only unit tests
316
- npm run test:unit
317
-
318
- # Run only E2E tests
319
- npm run test:e2e
311
+ pnpm test
320
312
  ```
321
313
 
322
314
  ### Publishing
@@ -8,7 +8,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
10
  const defaultConfig = {
11
- rules: {},
11
+ rulesDir: "rules",
12
12
  };
13
13
  function initCommand() {
14
14
  const configPath = path_1.default.join(process.cwd(), "aicm.json");
@@ -479,7 +479,9 @@ async function install(options = {}) {
479
479
  else {
480
480
  resolvedConfig = await (0, config_1.loadConfig)(cwd);
481
481
  }
482
- if (resolvedConfig === null || resolvedConfig === void 0 ? void 0 : resolvedConfig.config.workspaces) {
482
+ const shouldUseWorkspaces = (resolvedConfig === null || resolvedConfig === void 0 ? void 0 : resolvedConfig.config.workspaces) ||
483
+ (!resolvedConfig && (0, config_1.detectWorkspacesFromPackageJson)(cwd));
484
+ if (shouldUseWorkspaces) {
483
485
  return await installWorkspaces(cwd, installOnCI, options.verbose);
484
486
  }
485
487
  return installPackage(options);
@@ -44,10 +44,13 @@ export interface ResolvedConfig {
44
44
  rules: RuleFile[];
45
45
  mcpServers: MCPServers;
46
46
  }
47
+ export declare const ALLOWED_CONFIG_KEYS: readonly ["rulesDir", "targets", "presets", "overrides", "mcpServers", "workspaces"];
47
48
  export declare const SUPPORTED_TARGETS: readonly ["cursor", "windsurf", "codex"];
48
49
  export type SupportedTarget = (typeof SUPPORTED_TARGETS)[number];
49
- export declare function applyDefaults(config: RawConfig, cwd?: string): Config;
50
- export declare function validateConfig(config: unknown, configFilePath: string, cwd: string): asserts config is Config;
50
+ export declare function detectWorkspacesFromPackageJson(cwd: string): boolean;
51
+ export declare function resolveWorkspaces(config: unknown, configFilePath: string, cwd: string): boolean;
52
+ export declare function applyDefaults(config: RawConfig, workspaces: boolean): Config;
53
+ export declare function validateConfig(config: unknown, configFilePath: string, cwd: string, isWorkspaceMode?: boolean): asserts config is Config;
51
54
  export declare function loadRulesFromDirectory(rulesDir: string, source: "local" | "preset", presetName?: string): Promise<RuleFile[]>;
52
55
  export declare function resolvePresetPath(presetPath: string, cwd: string): string | null;
53
56
  export declare function loadPreset(presetPath: string, cwd: string): Promise<{
@@ -3,7 +3,9 @@ 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.SUPPORTED_TARGETS = void 0;
6
+ exports.SUPPORTED_TARGETS = exports.ALLOWED_CONFIG_KEYS = void 0;
7
+ exports.detectWorkspacesFromPackageJson = detectWorkspacesFromPackageJson;
8
+ exports.resolveWorkspaces = resolveWorkspaces;
7
9
  exports.applyDefaults = applyDefaults;
8
10
  exports.validateConfig = validateConfig;
9
11
  exports.loadRulesFromDirectory = loadRulesFromDirectory;
@@ -18,6 +20,14 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
18
20
  const node_path_1 = __importDefault(require("node:path"));
19
21
  const cosmiconfig_1 = require("cosmiconfig");
20
22
  const fast_glob_1 = __importDefault(require("fast-glob"));
23
+ exports.ALLOWED_CONFIG_KEYS = [
24
+ "rulesDir",
25
+ "targets",
26
+ "presets",
27
+ "overrides",
28
+ "mcpServers",
29
+ "workspaces",
30
+ ];
21
31
  exports.SUPPORTED_TARGETS = ["cursor", "windsurf", "codex"];
22
32
  function detectWorkspacesFromPackageJson(cwd) {
23
33
  try {
@@ -32,12 +42,17 @@ function detectWorkspacesFromPackageJson(cwd) {
32
42
  return false;
33
43
  }
34
44
  }
35
- function applyDefaults(config, cwd) {
36
- const workingDir = cwd || process.cwd();
37
- // Auto-detect workspaces if not explicitly set
38
- const workspaces = config.workspaces !== undefined
39
- ? config.workspaces
40
- : detectWorkspacesFromPackageJson(workingDir);
45
+ function resolveWorkspaces(config, configFilePath, cwd) {
46
+ const hasConfigWorkspaces = typeof config === "object" && config !== null && "workspaces" in config;
47
+ if (hasConfigWorkspaces) {
48
+ if (typeof config.workspaces === "boolean") {
49
+ return config.workspaces;
50
+ }
51
+ throw new Error(`workspaces must be a boolean in config at ${configFilePath}`);
52
+ }
53
+ return detectWorkspacesFromPackageJson(cwd);
54
+ }
55
+ function applyDefaults(config, workspaces) {
41
56
  return {
42
57
  rulesDir: config.rulesDir,
43
58
  targets: config.targets || ["cursor"],
@@ -47,16 +62,22 @@ function applyDefaults(config, cwd) {
47
62
  workspaces,
48
63
  };
49
64
  }
50
- function validateConfig(config, configFilePath, cwd) {
65
+ function validateConfig(config, configFilePath, cwd, isWorkspaceMode = false) {
51
66
  if (typeof config !== "object" || config === null) {
52
67
  throw new Error(`Config is not an object at ${configFilePath}`);
53
68
  }
69
+ const unknownKeys = Object.keys(config).filter((key) => !exports.ALLOWED_CONFIG_KEYS.includes(key));
70
+ if (unknownKeys.length > 0) {
71
+ throw new Error(`Invalid configuration at ${configFilePath}: unknown keys: ${unknownKeys.join(", ")}`);
72
+ }
54
73
  // Validate that either rulesDir or presets is provided
55
74
  const hasRulesDir = "rulesDir" in config && typeof config.rulesDir === "string";
56
75
  const hasPresets = "presets" in config &&
57
76
  Array.isArray(config.presets) &&
58
77
  config.presets.length > 0;
59
- if (!hasRulesDir && !hasPresets) {
78
+ // In workspace mode, root config doesn't need rulesDir or presets
79
+ // since packages will have their own configurations
80
+ if (!isWorkspaceMode && !hasRulesDir && !hasPresets) {
60
81
  throw new Error(`Either rulesDir or presets must be specified in config at ${configFilePath}`);
61
82
  }
62
83
  // Validate rulesDir if provided
@@ -253,15 +274,17 @@ async function loadConfig(cwd) {
253
274
  if (!(configResult === null || configResult === void 0 ? void 0 : configResult.config)) {
254
275
  return null;
255
276
  }
256
- validateConfig(configResult.config, configResult.filepath, workingDir);
257
- const config = applyDefaults(configResult.config, workingDir);
258
- const { rules, mcpServers } = await loadAllRules(config, workingDir);
277
+ const config = configResult.config;
278
+ const isWorkspaces = resolveWorkspaces(config, configResult.filepath, workingDir);
279
+ validateConfig(config, configResult.filepath, workingDir, isWorkspaces);
280
+ const configWithDefaults = applyDefaults(config, isWorkspaces);
281
+ const { rules, mcpServers } = await loadAllRules(configWithDefaults, workingDir);
259
282
  let rulesWithOverrides = rules;
260
- if (config.overrides) {
261
- rulesWithOverrides = applyOverrides(rules, config.overrides, workingDir);
283
+ if (configWithDefaults.overrides) {
284
+ rulesWithOverrides = applyOverrides(rules, configWithDefaults.overrides, workingDir);
262
285
  }
263
286
  return {
264
- config,
287
+ config: configWithDefaults,
265
288
  rules: rulesWithOverrides,
266
289
  mcpServers,
267
290
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicm",
3
- "version": "0.14.0",
3
+ "version": "0.14.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",
@@ -12,9 +12,6 @@
12
12
  "README.md",
13
13
  "LICENSE"
14
14
  ],
15
- "np": {
16
- "tests": false
17
- },
18
15
  "keywords": [
19
16
  "ai",
20
17
  "ide",
@@ -27,7 +24,6 @@
27
24
  "license": "MIT",
28
25
  "dependencies": {
29
26
  "arg": "^5.0.2",
30
- "args": "^5.0.3",
31
27
  "chalk": "^4.1.2",
32
28
  "cosmiconfig": "^9.0.0",
33
29
  "fast-glob": "^3.3.3",
@@ -43,8 +39,6 @@
43
39
  "husky": "^8.0.3",
44
40
  "jest": "^29.7.0",
45
41
  "lint-staged": "^15.2.0",
46
- "mock-fs": "^5.2.0",
47
- "nock": "^13.3.8",
48
42
  "np": "^10.2.0",
49
43
  "prettier": "^3.1.0",
50
44
  "rimraf": "^5.0.5",
@@ -65,12 +59,10 @@
65
59
  "test": "jest",
66
60
  "test:watch": "jest --watch",
67
61
  "test:all": "npm run build && npm run test",
68
- "test:unit": "jest --config jest.unit.config.js",
69
- "test:e2e": "pnpm run build && jest tests/e2e",
70
62
  "format": "prettier --write .",
71
63
  "format:check": "prettier --check .",
72
64
  "lint": "eslint",
73
65
  "version": "auto-changelog -p && git add CHANGELOG.md",
74
- "release": "np"
66
+ "release": "np --no-tests"
75
67
  }
76
68
  }