aicm 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -5
- package/dist/commands/install.d.ts +13 -1
- package/dist/commands/install.js +98 -58
- package/dist/commands/monorepo/discovery.d.ts +7 -0
- package/dist/commands/monorepo/discovery.js +50 -0
- package/dist/commands/monorepo/installer.d.ts +9 -0
- package/dist/commands/monorepo/installer.js +70 -0
- package/dist/commands/monorepo/monorepo-install.d.ts +9 -0
- package/dist/commands/monorepo/monorepo-install.js +46 -0
- package/dist/commands/monorepo/types.d.ts +1 -0
- package/dist/commands/monorepo/types.js +17 -0
- package/dist/commands/workspaces/discovery.d.ts +7 -0
- package/dist/commands/workspaces/discovery.js +50 -0
- package/dist/commands/workspaces/workspaces-install.d.ts +9 -0
- package/dist/commands/workspaces/workspaces-install.js +46 -0
- package/dist/index.js +6 -1
- package/dist/types/index.d.ts +15 -0
- package/dist/utils/config.d.ts +5 -5
- package/dist/utils/config.js +31 -23
- package/dist/utils/mcp-writer.d.ts +14 -0
- package/dist/utils/mcp-writer.js +69 -0
- package/dist/utils/package-detector.d.ts +6 -0
- package/dist/utils/package-detector.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
> Agentic IDE Configuration Manager
|
|
4
4
|
|
|
5
|
-
A CLI tool for
|
|
5
|
+
A CLI tool for managing Agentic IDE configurations across projects
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
https://github.com/user-attachments/assets/e80dedbc-89c4-4747-9acf-b7ecb7493fcc
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Why
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
With the rise of Agentic IDEs, we have an opportunity to enforce best practices through rules. However, these rules are typically isolated within individual projects.
|
|
12
|
+
**aicm** is a CLI tool for distributing Agentic IDE configurations, rules, and MCPs across projects. It leverages node package managers, copy configurations from node_modules to the correct locations in your file system.
|
|
12
13
|
|
|
13
14
|
## Getting Started
|
|
14
15
|
|
|
@@ -33,6 +34,11 @@ In your project's `aicm.json`, reference the package and the specific rule:
|
|
|
33
34
|
"rules": {
|
|
34
35
|
"typescript": "@myteam/ai-tools/rules/typescript.mdc",
|
|
35
36
|
"react": "@myteam/ai-tools/rules/react.mdc"
|
|
37
|
+
},
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"my-mcp": {
|
|
40
|
+
"url": "https://example.com/sse"
|
|
41
|
+
}
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
```
|
|
@@ -47,7 +53,7 @@ In your project's `aicm.json`, reference the package and the specific rule:
|
|
|
47
53
|
}
|
|
48
54
|
```
|
|
49
55
|
|
|
50
|
-
Now the rules will be
|
|
56
|
+
Now, when you run `npm install`, the rules will be added to `.cursor/rules/aicm/` and the mcps to `.cursor/mcp.json`.
|
|
51
57
|
|
|
52
58
|
### Using Presets
|
|
53
59
|
|
|
@@ -147,6 +153,44 @@ After installation, open Cursor and ask it to do something. Your AI assistant wi
|
|
|
147
153
|
|
|
148
154
|
To prevent [prompt-injection](https://en.wikipedia.org/wiki/Prompt_injection), use only packages from trusted sources.
|
|
149
155
|
|
|
156
|
+
## Workspaces Support
|
|
157
|
+
|
|
158
|
+
aicm supports workspaces by automatically discovering and installing configurations across multiple packages in your repository.
|
|
159
|
+
|
|
160
|
+
To enable workspaces mode, use the `--workspaces` flag:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npx aicm install --workspaces
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
This will:
|
|
167
|
+
|
|
168
|
+
1. **Discover packages**: Automatically find all directories containing `aicm.json` files in your repository
|
|
169
|
+
2. **Install per package**: Install rules and MCPs for each package individually in their respective directories
|
|
170
|
+
|
|
171
|
+
### How It Works
|
|
172
|
+
|
|
173
|
+
Each directory containing an `aicm.json` file is treated as a separate package with its own configuration.
|
|
174
|
+
|
|
175
|
+
For example, in a workspace structure like:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
├── packages/
|
|
179
|
+
│ ├── frontend/
|
|
180
|
+
│ │ └── aicm.json
|
|
181
|
+
│ └── backend/
|
|
182
|
+
│ └── aicm.json
|
|
183
|
+
└── services/
|
|
184
|
+
└── api/
|
|
185
|
+
└── aicm.json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Running `npx aicm install --workspaces` will install rules for each package in their respective directories:
|
|
189
|
+
|
|
190
|
+
- `packages/frontend/.cursor/rules/aicm/`
|
|
191
|
+
- `packages/backend/.cursor/rules/aicm/`
|
|
192
|
+
- `services/api/.cursor/rules/aicm/`
|
|
193
|
+
|
|
150
194
|
## Configuration
|
|
151
195
|
|
|
152
196
|
To configure aicm, use either:
|
|
@@ -252,6 +296,8 @@ npx aicm install
|
|
|
252
296
|
Options:
|
|
253
297
|
|
|
254
298
|
- `--ci`: run in CI environments (default: `false`)
|
|
299
|
+
- `--workspaces`: enable workspaces mode to discover and install configurations across multiple packages
|
|
300
|
+
- `--verbose`: show detailed output during installation
|
|
255
301
|
|
|
256
302
|
## Node.js API
|
|
257
303
|
|
|
@@ -296,6 +342,8 @@ Installs rules and MCP servers based on configuration.
|
|
|
296
342
|
- `cwd`: Base directory to use instead of `process.cwd()`
|
|
297
343
|
- `config`: Custom config object to use instead of loading from file
|
|
298
344
|
- `installOnCI`: Run installation on CI environments (default: `false`)
|
|
345
|
+
- `workspaces`: Enable workspaces mode (default: `false`)
|
|
346
|
+
- `verbose`: Show verbose output during installation (default: `false`)
|
|
299
347
|
|
|
300
348
|
**Returns:**
|
|
301
349
|
|
|
@@ -15,6 +15,14 @@ export interface InstallOptions {
|
|
|
15
15
|
* allow installation on CI environments
|
|
16
16
|
*/
|
|
17
17
|
installOnCI?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Enable workspaces mode
|
|
20
|
+
*/
|
|
21
|
+
workspaces?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Show verbose output during installation
|
|
24
|
+
*/
|
|
25
|
+
verbose?: boolean;
|
|
18
26
|
}
|
|
19
27
|
/**
|
|
20
28
|
* Result of the install operation
|
|
@@ -32,6 +40,10 @@ export interface InstallResult {
|
|
|
32
40
|
* Number of rules installed
|
|
33
41
|
*/
|
|
34
42
|
installedRuleCount: number;
|
|
43
|
+
/**
|
|
44
|
+
* Number of packages installed
|
|
45
|
+
*/
|
|
46
|
+
packagesCount: number;
|
|
35
47
|
}
|
|
36
48
|
/**
|
|
37
49
|
* Core implementation of the rule installation logic
|
|
@@ -39,4 +51,4 @@ export interface InstallResult {
|
|
|
39
51
|
* @returns Result of the install operation
|
|
40
52
|
*/
|
|
41
53
|
export declare function install(options?: InstallOptions): Promise<InstallResult>;
|
|
42
|
-
export declare function installCommand(installOnCI?: boolean): Promise<void>;
|
|
54
|
+
export declare function installCommand(installOnCI?: boolean, workspaces?: boolean, verbose?: boolean): Promise<void>;
|
package/dist/commands/install.js
CHANGED
|
@@ -10,27 +10,25 @@ const config_1 = require("../utils/config");
|
|
|
10
10
|
const rule_detector_1 = require("../utils/rule-detector");
|
|
11
11
|
const rule_collector_1 = require("../utils/rule-collector");
|
|
12
12
|
const rule_writer_1 = require("../utils/rule-writer");
|
|
13
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
14
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
15
13
|
const ci_info_1 = require("ci-info");
|
|
14
|
+
const discovery_1 = require("./workspaces/discovery");
|
|
15
|
+
const workspaces_install_1 = require("./workspaces/workspaces-install");
|
|
16
|
+
const mcp_writer_1 = require("../utils/mcp-writer");
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* @param ides The IDEs to write to
|
|
20
|
-
* @param cwd The current working directory
|
|
18
|
+
* Helper function to execute a function within a specific working directory
|
|
19
|
+
* and ensure the original directory is always restored
|
|
21
20
|
*/
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
fs_extra_1.default.writeJsonSync(mcpPath, { mcpServers }, { spaces: 2 });
|
|
21
|
+
async function withWorkingDirectory(targetDir, fn) {
|
|
22
|
+
const originalCwd = process.cwd();
|
|
23
|
+
if (targetDir !== originalCwd) {
|
|
24
|
+
process.chdir(targetDir);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return await fn();
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
if (targetDir !== originalCwd) {
|
|
31
|
+
process.chdir(originalCwd);
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
34
|
}
|
|
@@ -47,6 +45,64 @@ function isInCIEnvironment() {
|
|
|
47
45
|
// Fall back to ci-info's detection
|
|
48
46
|
return ci_info_1.isCI;
|
|
49
47
|
}
|
|
48
|
+
async function handleWorkspacesInstallation(cwd, installOnCI, verbose = false) {
|
|
49
|
+
return withWorkingDirectory(cwd, async () => {
|
|
50
|
+
if (verbose) {
|
|
51
|
+
console.log(chalk_1.default.blue("🔍 Discovering packages..."));
|
|
52
|
+
}
|
|
53
|
+
const packages = await (0, discovery_1.discoverPackagesWithAicm)(cwd);
|
|
54
|
+
if (packages.length === 0) {
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: "No packages with aicm configurations found",
|
|
58
|
+
installedRuleCount: 0,
|
|
59
|
+
packagesCount: 0,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (verbose) {
|
|
63
|
+
console.log(chalk_1.default.blue(`Found ${packages.length} packages with aicm configurations:`));
|
|
64
|
+
packages.forEach((pkg) => {
|
|
65
|
+
console.log(chalk_1.default.gray(` - ${pkg.relativePath}`));
|
|
66
|
+
});
|
|
67
|
+
console.log(chalk_1.default.blue(`📦 Installing configurations...`));
|
|
68
|
+
}
|
|
69
|
+
const result = await (0, workspaces_install_1.installWorkspacesPackages)(packages, {
|
|
70
|
+
installOnCI,
|
|
71
|
+
});
|
|
72
|
+
if (verbose) {
|
|
73
|
+
result.packages.forEach((pkg) => {
|
|
74
|
+
if (pkg.success) {
|
|
75
|
+
console.log(chalk_1.default.green(`✅ ${pkg.path} (${pkg.installedRuleCount} rules)`));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log(chalk_1.default.red(`❌ ${pkg.path}: ${pkg.error}`));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const failedPackages = result.packages.filter((r) => !r.success);
|
|
83
|
+
if (failedPackages.length > 0) {
|
|
84
|
+
console.log(chalk_1.default.yellow(`Installation completed with errors`));
|
|
85
|
+
if (verbose) {
|
|
86
|
+
console.log(chalk_1.default.green(`Successfully installed: ${result.packages.length - failedPackages.length}/${result.packages.length} packages (${result.totalRuleCount} rules total)`));
|
|
87
|
+
console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
|
|
88
|
+
}
|
|
89
|
+
const errorDetails = failedPackages
|
|
90
|
+
.map((p) => `${p.path}: ${p.error}`)
|
|
91
|
+
.join("; ");
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: `Package installation failed for ${failedPackages.length} package(s): ${errorDetails}`,
|
|
95
|
+
installedRuleCount: result.totalRuleCount,
|
|
96
|
+
packagesCount: result.packages.length,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
installedRuleCount: result.totalRuleCount,
|
|
102
|
+
packagesCount: result.packages.length,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}
|
|
50
106
|
/**
|
|
51
107
|
* Core implementation of the rule installation logic
|
|
52
108
|
* @param options Install options
|
|
@@ -55,45 +111,38 @@ function isInCIEnvironment() {
|
|
|
55
111
|
async function install(options = {}) {
|
|
56
112
|
const cwd = options.cwd || process.cwd();
|
|
57
113
|
const installOnCI = options.installOnCI === true; // Default to false if not specified
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
process.chdir(cwd);
|
|
114
|
+
return withWorkingDirectory(cwd, async () => {
|
|
115
|
+
if (options.workspaces) {
|
|
116
|
+
return await handleWorkspacesInstallation(cwd, installOnCI, options.verbose);
|
|
62
117
|
}
|
|
63
|
-
const ruleCollection = (0, rule_collector_1.initRuleCollection)();
|
|
64
118
|
const config = options.config || (0, config_1.getConfig)();
|
|
119
|
+
const ruleCollection = (0, rule_collector_1.initRuleCollection)();
|
|
65
120
|
if (!config) {
|
|
66
|
-
if (cwd !== originalCwd) {
|
|
67
|
-
process.chdir(originalCwd);
|
|
68
|
-
}
|
|
69
121
|
return {
|
|
70
122
|
success: false,
|
|
71
|
-
error: "Configuration file not found
|
|
123
|
+
error: "Configuration file not found",
|
|
72
124
|
installedRuleCount: 0,
|
|
125
|
+
packagesCount: 0,
|
|
73
126
|
};
|
|
74
127
|
}
|
|
75
128
|
const inCI = isInCIEnvironment();
|
|
76
129
|
if (inCI && !installOnCI && !config.installOnCI) {
|
|
77
|
-
if (cwd !== originalCwd) {
|
|
78
|
-
process.chdir(originalCwd);
|
|
79
|
-
}
|
|
80
130
|
console.log(chalk_1.default.yellow("Detected CI environment, skipping install."));
|
|
81
131
|
return {
|
|
82
132
|
success: true,
|
|
83
133
|
installedRuleCount: 0,
|
|
134
|
+
packagesCount: 0,
|
|
84
135
|
};
|
|
85
136
|
}
|
|
86
137
|
// Check if rules are defined (either directly or through presets)
|
|
87
138
|
if (!config.rules || Object.keys(config.rules).length === 0) {
|
|
88
139
|
// If there are no presets defined either, show a message
|
|
89
140
|
if (!config.presets || config.presets.length === 0) {
|
|
90
|
-
if (cwd !== originalCwd) {
|
|
91
|
-
process.chdir(originalCwd);
|
|
92
|
-
}
|
|
93
141
|
return {
|
|
94
142
|
success: false,
|
|
95
|
-
error: "No rules defined in configuration
|
|
143
|
+
error: "No rules defined in configuration",
|
|
96
144
|
installedRuleCount: 0,
|
|
145
|
+
packagesCount: 0,
|
|
97
146
|
};
|
|
98
147
|
}
|
|
99
148
|
}
|
|
@@ -139,13 +188,11 @@ async function install(options = {}) {
|
|
|
139
188
|
}
|
|
140
189
|
// If there were errors, exit with error
|
|
141
190
|
if (hasErrors) {
|
|
142
|
-
if (cwd !== originalCwd) {
|
|
143
|
-
process.chdir(originalCwd);
|
|
144
|
-
}
|
|
145
191
|
return {
|
|
146
192
|
success: false,
|
|
147
193
|
error: errorMessages.join("; "),
|
|
148
194
|
installedRuleCount,
|
|
195
|
+
packagesCount: 0,
|
|
149
196
|
};
|
|
150
197
|
}
|
|
151
198
|
// Write all collected rules to their targets
|
|
@@ -154,39 +201,32 @@ async function install(options = {}) {
|
|
|
154
201
|
if (config.mcpServers) {
|
|
155
202
|
// Filter out canceled servers
|
|
156
203
|
const filteredMcpServers = Object.fromEntries(Object.entries(config.mcpServers).filter(([, v]) => v !== false));
|
|
157
|
-
writeMcpServersToTargets(filteredMcpServers, config.ides, cwd);
|
|
158
|
-
}
|
|
159
|
-
// Restore original cwd
|
|
160
|
-
if (cwd !== originalCwd) {
|
|
161
|
-
process.chdir(originalCwd);
|
|
204
|
+
(0, mcp_writer_1.writeMcpServersToTargets)(filteredMcpServers, config.ides, cwd);
|
|
162
205
|
}
|
|
163
206
|
return {
|
|
164
207
|
success: true,
|
|
165
208
|
installedRuleCount,
|
|
209
|
+
packagesCount: 1,
|
|
166
210
|
};
|
|
167
|
-
}
|
|
168
|
-
catch (e) {
|
|
169
|
-
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
170
|
-
// If cwd was changed, restore it
|
|
171
|
-
if (cwd !== process.cwd()) {
|
|
172
|
-
process.chdir(cwd);
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
success: false,
|
|
176
|
-
error: errorMessage,
|
|
177
|
-
installedRuleCount: 0,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
211
|
+
});
|
|
180
212
|
}
|
|
181
|
-
async function installCommand(installOnCI) {
|
|
213
|
+
async function installCommand(installOnCI, workspaces, verbose) {
|
|
182
214
|
try {
|
|
183
|
-
const result = await install({ installOnCI });
|
|
215
|
+
const result = await install({ installOnCI, workspaces, verbose });
|
|
184
216
|
if (!result.success) {
|
|
185
217
|
console.error(chalk_1.default.red(result.error));
|
|
186
218
|
process.exit(1);
|
|
187
219
|
}
|
|
188
220
|
else {
|
|
189
|
-
|
|
221
|
+
if (result.packagesCount > 1) {
|
|
222
|
+
console.log(`Successfully installed ${result.installedRuleCount} rules across ${result.packagesCount} packages`);
|
|
223
|
+
}
|
|
224
|
+
else if (workspaces) {
|
|
225
|
+
console.log(`Successfully installed ${result.installedRuleCount} rules across ${result.packagesCount} packages`);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
console.log("Rules installation completed");
|
|
229
|
+
}
|
|
190
230
|
}
|
|
191
231
|
}
|
|
192
232
|
catch (error) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PackageInfo } from "../../types";
|
|
2
|
+
/**
|
|
3
|
+
* Discover all packages with aicm configurations in a monorepo
|
|
4
|
+
* @param rootDir The root directory to search from
|
|
5
|
+
* @returns Array of discovered packages
|
|
6
|
+
*/
|
|
7
|
+
export declare function discoverPackagesWithAicm(rootDir: string): Promise<PackageInfo[]>;
|
|
@@ -0,0 +1,50 @@
|
|
|
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.discoverPackagesWithAicm = discoverPackagesWithAicm;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const config_1 = require("../../utils/config");
|
|
10
|
+
/**
|
|
11
|
+
* Discover aicm.json files using git ls-files
|
|
12
|
+
* @param rootDir The root directory to search from
|
|
13
|
+
* @returns Array of aicm.json file paths
|
|
14
|
+
*/
|
|
15
|
+
function findAicmFiles(rootDir) {
|
|
16
|
+
const output = (0, node_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) => node_path_1.default.resolve(rootDir, file));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Discover all packages with aicm configurations in a monorepo
|
|
28
|
+
* @param rootDir The root directory to search from
|
|
29
|
+
* @returns Array of discovered packages
|
|
30
|
+
*/
|
|
31
|
+
async function discoverPackagesWithAicm(rootDir) {
|
|
32
|
+
const aicmFiles = findAicmFiles(rootDir);
|
|
33
|
+
const packages = [];
|
|
34
|
+
for (const aicmFile of aicmFiles) {
|
|
35
|
+
const packageDir = node_path_1.default.dirname(aicmFile);
|
|
36
|
+
const relativePath = node_path_1.default.relative(rootDir, packageDir);
|
|
37
|
+
// Normalize to forward slashes for cross-platform compatibility
|
|
38
|
+
const normalizedRelativePath = relativePath.replace(/\\/g, "/");
|
|
39
|
+
const config = (0, config_1.getConfig)(packageDir);
|
|
40
|
+
if (config) {
|
|
41
|
+
packages.push({
|
|
42
|
+
relativePath: normalizedRelativePath || ".",
|
|
43
|
+
absolutePath: packageDir,
|
|
44
|
+
config,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Sort packages by relativePath for deterministic order
|
|
49
|
+
return packages.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PackageInfo, MonorepoInstallResult } from "./types";
|
|
2
|
+
import { InstallOptions } from "../install";
|
|
3
|
+
/**
|
|
4
|
+
* Install aicm configurations for all packages in a monorepo
|
|
5
|
+
* @param packages The packages to install configurations for
|
|
6
|
+
* @param options Install options
|
|
7
|
+
* @returns Result of the monorepo installation
|
|
8
|
+
*/
|
|
9
|
+
export declare function installMonorepoPackages(packages: PackageInfo[], options?: InstallOptions): Promise<MonorepoInstallResult>;
|
|
@@ -0,0 +1,70 @@
|
|
|
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.installMonorepoPackages = installMonorepoPackages;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const install_1 = require("../install");
|
|
10
|
+
/**
|
|
11
|
+
* Install aicm configurations for all packages in a monorepo
|
|
12
|
+
* @param packages The packages to install configurations for
|
|
13
|
+
* @param options Install options
|
|
14
|
+
* @returns Result of the monorepo installation
|
|
15
|
+
*/
|
|
16
|
+
async function installMonorepoPackages(packages, options = {}) {
|
|
17
|
+
console.log(chalk_1.default.blue(`📦 Installing configurations...`));
|
|
18
|
+
const results = [];
|
|
19
|
+
let totalRuleCount = 0;
|
|
20
|
+
// Install packages sequentially for now (can be parallelized later)
|
|
21
|
+
for (const pkg of packages) {
|
|
22
|
+
const packagePath = node_path_1.default.resolve(process.cwd(), pkg.path);
|
|
23
|
+
try {
|
|
24
|
+
console.log(chalk_1.default.gray(` Installing ${pkg.path}...`));
|
|
25
|
+
const result = await (0, install_1.install)({
|
|
26
|
+
...options,
|
|
27
|
+
cwd: packagePath,
|
|
28
|
+
});
|
|
29
|
+
if (result.success) {
|
|
30
|
+
console.log(chalk_1.default.green(`✅ ${pkg.path} (${result.installedRuleCount} rules)`));
|
|
31
|
+
totalRuleCount += result.installedRuleCount;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.log(chalk_1.default.red(`❌ ${pkg.path}: ${result.error}`));
|
|
35
|
+
}
|
|
36
|
+
results.push({
|
|
37
|
+
path: pkg.path,
|
|
38
|
+
success: result.success,
|
|
39
|
+
error: result.error,
|
|
40
|
+
installedRuleCount: result.installedRuleCount,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
45
|
+
console.log(chalk_1.default.red(`❌ ${pkg.path}: ${errorMessage}`));
|
|
46
|
+
results.push({
|
|
47
|
+
path: pkg.path,
|
|
48
|
+
success: false,
|
|
49
|
+
error: errorMessage,
|
|
50
|
+
installedRuleCount: 0,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const successfulPackages = results.filter((r) => r.success);
|
|
55
|
+
const failedPackages = results.filter((r) => !r.success);
|
|
56
|
+
// Print summary
|
|
57
|
+
if (failedPackages.length > 0) {
|
|
58
|
+
console.log(chalk_1.default.yellow(`\n⚠️ Installation completed with errors`));
|
|
59
|
+
console.log(chalk_1.default.green(`Successfully installed: ${successfulPackages.length}/${packages.length} packages (${totalRuleCount} rules total)`));
|
|
60
|
+
console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
console.log(chalk_1.default.green(`\n🎉 Successfully installed ${totalRuleCount} rules across ${packages.length} packages`));
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
success: failedPackages.length === 0,
|
|
67
|
+
packages: results,
|
|
68
|
+
totalRuleCount,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PackageInfo, MonorepoInstallResult } from "../../types";
|
|
2
|
+
import { InstallOptions } from "../install";
|
|
3
|
+
/**
|
|
4
|
+
* Install aicm configurations for all packages in a monorepo
|
|
5
|
+
* @param packages The packages to install configurations for
|
|
6
|
+
* @param options Install options
|
|
7
|
+
* @returns Result of the monorepo installation
|
|
8
|
+
*/
|
|
9
|
+
export declare function installMonorepoPackages(packages: PackageInfo[], options?: InstallOptions): Promise<MonorepoInstallResult>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installMonorepoPackages = installMonorepoPackages;
|
|
4
|
+
const install_1 = require("../install");
|
|
5
|
+
/**
|
|
6
|
+
* Install aicm configurations for all packages in a monorepo
|
|
7
|
+
* @param packages The packages to install configurations for
|
|
8
|
+
* @param options Install options
|
|
9
|
+
* @returns Result of the monorepo installation
|
|
10
|
+
*/
|
|
11
|
+
async function installMonorepoPackages(packages, options = {}) {
|
|
12
|
+
const results = [];
|
|
13
|
+
let totalRuleCount = 0;
|
|
14
|
+
// Install packages sequentially for now (can be parallelized later)
|
|
15
|
+
for (const pkg of packages) {
|
|
16
|
+
const packagePath = pkg.absolutePath;
|
|
17
|
+
try {
|
|
18
|
+
const result = await (0, install_1.install)({
|
|
19
|
+
...options,
|
|
20
|
+
cwd: packagePath,
|
|
21
|
+
});
|
|
22
|
+
totalRuleCount += result.installedRuleCount;
|
|
23
|
+
results.push({
|
|
24
|
+
path: pkg.relativePath,
|
|
25
|
+
success: result.success,
|
|
26
|
+
error: result.error,
|
|
27
|
+
installedRuleCount: result.installedRuleCount,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
32
|
+
results.push({
|
|
33
|
+
path: pkg.relativePath,
|
|
34
|
+
success: false,
|
|
35
|
+
error: errorMessage,
|
|
36
|
+
installedRuleCount: 0,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const failedPackages = results.filter((r) => !r.success);
|
|
41
|
+
return {
|
|
42
|
+
success: failedPackages.length === 0,
|
|
43
|
+
packages: results,
|
|
44
|
+
totalRuleCount,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../types";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("../../types"), exports);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PackageInfo } from "../../types";
|
|
2
|
+
/**
|
|
3
|
+
* Discover all packages with aicm configurations
|
|
4
|
+
* @param rootDir The root directory to search from
|
|
5
|
+
* @returns Array of discovered packages
|
|
6
|
+
*/
|
|
7
|
+
export declare function discoverPackagesWithAicm(rootDir: string): Promise<PackageInfo[]>;
|
|
@@ -0,0 +1,50 @@
|
|
|
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.discoverPackagesWithAicm = discoverPackagesWithAicm;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const config_1 = require("../../utils/config");
|
|
10
|
+
/**
|
|
11
|
+
* Discover aicm.json files using git ls-files
|
|
12
|
+
* @param rootDir The root directory to search from
|
|
13
|
+
* @returns Array of aicm.json file paths
|
|
14
|
+
*/
|
|
15
|
+
function findAicmFiles(rootDir) {
|
|
16
|
+
const output = (0, node_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) => node_path_1.default.resolve(rootDir, file));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Discover all packages with aicm configurations
|
|
28
|
+
* @param rootDir The root directory to search from
|
|
29
|
+
* @returns Array of discovered packages
|
|
30
|
+
*/
|
|
31
|
+
async function discoverPackagesWithAicm(rootDir) {
|
|
32
|
+
const aicmFiles = findAicmFiles(rootDir);
|
|
33
|
+
const packages = [];
|
|
34
|
+
for (const aicmFile of aicmFiles) {
|
|
35
|
+
const packageDir = node_path_1.default.dirname(aicmFile);
|
|
36
|
+
const relativePath = node_path_1.default.relative(rootDir, packageDir);
|
|
37
|
+
// Normalize to forward slashes for cross-platform compatibility
|
|
38
|
+
const normalizedRelativePath = relativePath.replace(/\\/g, "/");
|
|
39
|
+
const config = (0, config_1.getConfig)(packageDir);
|
|
40
|
+
if (config) {
|
|
41
|
+
packages.push({
|
|
42
|
+
relativePath: normalizedRelativePath || ".",
|
|
43
|
+
absolutePath: packageDir,
|
|
44
|
+
config,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Sort packages by relativePath for deterministic order
|
|
49
|
+
return packages.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PackageInfo, WorkspacesInstallResult } from "../../types";
|
|
2
|
+
import { InstallOptions } from "../install";
|
|
3
|
+
/**
|
|
4
|
+
* Install aicm configurations for all packages in a workspace
|
|
5
|
+
* @param packages The packages to install configurations for
|
|
6
|
+
* @param options Install options
|
|
7
|
+
* @returns Result of the workspace installation
|
|
8
|
+
*/
|
|
9
|
+
export declare function installWorkspacesPackages(packages: PackageInfo[], options?: InstallOptions): Promise<WorkspacesInstallResult>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installWorkspacesPackages = installWorkspacesPackages;
|
|
4
|
+
const install_1 = require("../install");
|
|
5
|
+
/**
|
|
6
|
+
* Install aicm configurations for all packages in a workspace
|
|
7
|
+
* @param packages The packages to install configurations for
|
|
8
|
+
* @param options Install options
|
|
9
|
+
* @returns Result of the workspace installation
|
|
10
|
+
*/
|
|
11
|
+
async function installWorkspacesPackages(packages, options = {}) {
|
|
12
|
+
const results = [];
|
|
13
|
+
let totalRuleCount = 0;
|
|
14
|
+
// Install packages sequentially for now (can be parallelized later)
|
|
15
|
+
for (const pkg of packages) {
|
|
16
|
+
const packagePath = pkg.absolutePath;
|
|
17
|
+
try {
|
|
18
|
+
const result = await (0, install_1.install)({
|
|
19
|
+
...options,
|
|
20
|
+
cwd: packagePath,
|
|
21
|
+
});
|
|
22
|
+
totalRuleCount += result.installedRuleCount;
|
|
23
|
+
results.push({
|
|
24
|
+
path: pkg.relativePath,
|
|
25
|
+
success: result.success,
|
|
26
|
+
error: result.error,
|
|
27
|
+
installedRuleCount: result.installedRuleCount,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
32
|
+
results.push({
|
|
33
|
+
path: pkg.relativePath,
|
|
34
|
+
success: false,
|
|
35
|
+
error: errorMessage,
|
|
36
|
+
installedRuleCount: 0,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const failedPackages = results.filter((r) => !r.success);
|
|
41
|
+
return {
|
|
42
|
+
success: failedPackages.length === 0,
|
|
43
|
+
packages: results,
|
|
44
|
+
totalRuleCount,
|
|
45
|
+
};
|
|
46
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,8 @@ const args = (0, arg_1.default)({
|
|
|
17
17
|
"--help": Boolean,
|
|
18
18
|
"--version": Boolean,
|
|
19
19
|
"--ci": Boolean,
|
|
20
|
+
"--workspaces": Boolean,
|
|
21
|
+
"--verbose": Boolean,
|
|
20
22
|
"-h": "--help",
|
|
21
23
|
"-v": "--version",
|
|
22
24
|
}, {
|
|
@@ -36,7 +38,7 @@ switch (command) {
|
|
|
36
38
|
(0, init_1.initCommand)();
|
|
37
39
|
break;
|
|
38
40
|
case "install":
|
|
39
|
-
(0, install_1.installCommand)(args["--ci"]);
|
|
41
|
+
(0, install_1.installCommand)(args["--ci"], args["--workspaces"], args["--verbose"]);
|
|
40
42
|
break;
|
|
41
43
|
case "list":
|
|
42
44
|
(0, list_1.listCommand)();
|
|
@@ -62,10 +64,13 @@ ${chalk_1.default.bold("OPTIONS")}
|
|
|
62
64
|
-h, --help Show this help message
|
|
63
65
|
-v, --version Show version number
|
|
64
66
|
--ci Run in CI environments (default: \`false\`)
|
|
67
|
+
--workspaces Install rules across all workspaces
|
|
68
|
+
--verbose Show detailed output during installation
|
|
65
69
|
|
|
66
70
|
${chalk_1.default.bold("EXAMPLES")}
|
|
67
71
|
$ aicm init
|
|
68
72
|
$ aicm install
|
|
73
|
+
$ aicm install --workspaces
|
|
69
74
|
$ aicm list
|
|
70
75
|
`);
|
|
71
76
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -41,3 +41,18 @@ export interface RuleCollection {
|
|
|
41
41
|
cursor: RuleContent[];
|
|
42
42
|
windsurf: RuleContent[];
|
|
43
43
|
}
|
|
44
|
+
export interface PackageInfo {
|
|
45
|
+
relativePath: string;
|
|
46
|
+
absolutePath: string;
|
|
47
|
+
config: Config;
|
|
48
|
+
}
|
|
49
|
+
export interface WorkspacesInstallResult {
|
|
50
|
+
success: boolean;
|
|
51
|
+
packages: Array<{
|
|
52
|
+
path: string;
|
|
53
|
+
success: boolean;
|
|
54
|
+
error?: string;
|
|
55
|
+
installedRuleCount: number;
|
|
56
|
+
}>;
|
|
57
|
+
totalRuleCount: number;
|
|
58
|
+
}
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -11,11 +11,11 @@ export interface PresetPathInfo {
|
|
|
11
11
|
fullPath: string;
|
|
12
12
|
originalPath: string;
|
|
13
13
|
}
|
|
14
|
-
export declare function getFullPresetPath(presetPath: string): PresetPathInfo | null;
|
|
14
|
+
export declare function getFullPresetPath(presetPath: string, cwd?: string): PresetPathInfo | null;
|
|
15
15
|
/**
|
|
16
16
|
* Load a preset file and return its contents
|
|
17
17
|
*/
|
|
18
|
-
export declare function loadPreset(presetPath: string): {
|
|
18
|
+
export declare function loadPreset(presetPath: string, cwd?: string): {
|
|
19
19
|
rules: Rules;
|
|
20
20
|
mcpServers?: import("../types").MCPServers;
|
|
21
21
|
presets?: string[];
|
|
@@ -24,11 +24,11 @@ export declare function loadPreset(presetPath: string): {
|
|
|
24
24
|
* Load the aicm config using cosmiconfigSync, supporting both aicm.json and package.json.
|
|
25
25
|
* Returns the config object or null if not found.
|
|
26
26
|
*/
|
|
27
|
-
export declare function loadAicmConfigCosmiconfig(): Config | null;
|
|
27
|
+
export declare function loadAicmConfigCosmiconfig(searchFrom?: string): Config | null;
|
|
28
28
|
/**
|
|
29
29
|
* Get the configuration from aicm.json or package.json (using cosmiconfigSync) and merge with any presets
|
|
30
30
|
*/
|
|
31
|
-
export declare function getConfig(): Config | null;
|
|
31
|
+
export declare function getConfig(cwd?: string): Config | null;
|
|
32
32
|
/**
|
|
33
33
|
* Get the source preset path for a rule if it came from a preset
|
|
34
34
|
*/
|
|
@@ -40,4 +40,4 @@ export declare function getOriginalPresetPath(config: Config, ruleName: string):
|
|
|
40
40
|
/**
|
|
41
41
|
* Save the configuration to the aicm.json file
|
|
42
42
|
*/
|
|
43
|
-
export declare function saveConfig(config: Config): boolean;
|
|
43
|
+
export declare function saveConfig(config: Config, cwd?: string): boolean;
|
package/dist/utils/config.js
CHANGED
|
@@ -14,17 +14,23 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
14
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
15
|
const cosmiconfig_1 = require("cosmiconfig");
|
|
16
16
|
const CONFIG_FILE = "aicm.json";
|
|
17
|
-
function getFullPresetPath(presetPath) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
function getFullPresetPath(presetPath, cwd) {
|
|
18
|
+
const workingDir = cwd || process.cwd();
|
|
19
|
+
// If it's a local file with .json extension, check relative to the working directory
|
|
20
|
+
if (presetPath.endsWith(".json")) {
|
|
21
|
+
const absolutePath = node_path_1.default.isAbsolute(presetPath)
|
|
22
|
+
? presetPath
|
|
23
|
+
: node_path_1.default.resolve(workingDir, presetPath);
|
|
24
|
+
if (fs_extra_1.default.pathExistsSync(absolutePath)) {
|
|
25
|
+
return { fullPath: absolutePath, originalPath: presetPath };
|
|
26
|
+
}
|
|
21
27
|
}
|
|
22
28
|
try {
|
|
23
29
|
let absolutePresetPath;
|
|
24
30
|
// Handle npm package with explicit JSON path
|
|
25
31
|
if (presetPath.endsWith(".json")) {
|
|
26
32
|
absolutePresetPath = require.resolve(presetPath, {
|
|
27
|
-
paths: [__dirname,
|
|
33
|
+
paths: [__dirname, workingDir],
|
|
28
34
|
});
|
|
29
35
|
}
|
|
30
36
|
// Handle npm package without explicit JSON path (add aicm.json)
|
|
@@ -33,13 +39,13 @@ function getFullPresetPath(presetPath) {
|
|
|
33
39
|
const presetPathWithConfig = node_path_1.default.join(presetPath, "aicm.json");
|
|
34
40
|
try {
|
|
35
41
|
absolutePresetPath = require.resolve(presetPathWithConfig, {
|
|
36
|
-
paths: [__dirname,
|
|
42
|
+
paths: [__dirname, workingDir],
|
|
37
43
|
});
|
|
38
44
|
}
|
|
39
45
|
catch (_a) {
|
|
40
46
|
// If direct resolution fails, try as a package name
|
|
41
47
|
absolutePresetPath = require.resolve(presetPath, {
|
|
42
|
-
paths: [__dirname,
|
|
48
|
+
paths: [__dirname, workingDir],
|
|
43
49
|
});
|
|
44
50
|
// If we found the package but not the config file, look for aicm.json
|
|
45
51
|
if (fs_extra_1.default.existsSync(absolutePresetPath)) {
|
|
@@ -62,8 +68,8 @@ function getFullPresetPath(presetPath) {
|
|
|
62
68
|
/**
|
|
63
69
|
* Load a preset file and return its contents
|
|
64
70
|
*/
|
|
65
|
-
function loadPreset(presetPath) {
|
|
66
|
-
const pathInfo = getFullPresetPath(presetPath);
|
|
71
|
+
function loadPreset(presetPath, cwd) {
|
|
72
|
+
const pathInfo = getFullPresetPath(presetPath, cwd);
|
|
67
73
|
if (!pathInfo) {
|
|
68
74
|
throw new Error(`Error loading preset: "${presetPath}". Make sure the package is installed in your project.`);
|
|
69
75
|
}
|
|
@@ -92,7 +98,7 @@ const processedPresets = new Set();
|
|
|
92
98
|
/**
|
|
93
99
|
* Process presets and return a new config with merged rules and metadata
|
|
94
100
|
*/
|
|
95
|
-
function processPresets(config) {
|
|
101
|
+
function processPresets(config, cwd) {
|
|
96
102
|
// Create a deep copy of the config to avoid mutations
|
|
97
103
|
const newConfig = JSON.parse(JSON.stringify(config));
|
|
98
104
|
const metadata = {
|
|
@@ -101,17 +107,17 @@ function processPresets(config) {
|
|
|
101
107
|
};
|
|
102
108
|
// Clear processed presets tracking set when starting from the top level
|
|
103
109
|
processedPresets.clear();
|
|
104
|
-
return processPresetsInternal(newConfig, metadata);
|
|
110
|
+
return processPresetsInternal(newConfig, metadata, cwd);
|
|
105
111
|
}
|
|
106
112
|
/**
|
|
107
113
|
* Internal function to process presets recursively
|
|
108
114
|
*/
|
|
109
|
-
function processPresetsInternal(config, metadata) {
|
|
115
|
+
function processPresetsInternal(config, metadata, cwd) {
|
|
110
116
|
if (!config.presets || !Array.isArray(config.presets)) {
|
|
111
117
|
return { config, metadata };
|
|
112
118
|
}
|
|
113
119
|
for (const presetPath of config.presets) {
|
|
114
|
-
const pathInfo = getFullPresetPath(presetPath);
|
|
120
|
+
const pathInfo = getFullPresetPath(presetPath, cwd);
|
|
115
121
|
if (!pathInfo) {
|
|
116
122
|
throw new Error(`Error loading preset: "${presetPath}". Make sure the package is installed in your project.`);
|
|
117
123
|
}
|
|
@@ -122,7 +128,7 @@ function processPresetsInternal(config, metadata) {
|
|
|
122
128
|
}
|
|
123
129
|
// Mark this preset as processed
|
|
124
130
|
processedPresets.add(pathInfo.fullPath);
|
|
125
|
-
const preset = loadPreset(presetPath);
|
|
131
|
+
const preset = loadPreset(presetPath, cwd);
|
|
126
132
|
if (!preset)
|
|
127
133
|
continue;
|
|
128
134
|
// Process nested presets first (depth-first)
|
|
@@ -134,7 +140,7 @@ function processPresetsInternal(config, metadata) {
|
|
|
134
140
|
ides: [],
|
|
135
141
|
};
|
|
136
142
|
// Recursively process the nested presets
|
|
137
|
-
const { config: nestedConfig } = processPresetsInternal(presetConfig, metadata);
|
|
143
|
+
const { config: nestedConfig } = processPresetsInternal(presetConfig, metadata, cwd);
|
|
138
144
|
Object.assign(preset.rules, nestedConfig.rules);
|
|
139
145
|
}
|
|
140
146
|
const { updatedConfig, updatedMetadata } = mergePresetRules(config, preset.rules, pathInfo, metadata);
|
|
@@ -200,12 +206,12 @@ function mergePresetMcpServers(configMcpServers, presetMcpServers) {
|
|
|
200
206
|
* Load the aicm config using cosmiconfigSync, supporting both aicm.json and package.json.
|
|
201
207
|
* Returns the config object or null if not found.
|
|
202
208
|
*/
|
|
203
|
-
function loadAicmConfigCosmiconfig() {
|
|
209
|
+
function loadAicmConfigCosmiconfig(searchFrom) {
|
|
204
210
|
const explorer = (0, cosmiconfig_1.cosmiconfigSync)("aicm", {
|
|
205
211
|
searchPlaces: ["package.json", "aicm.json"],
|
|
206
212
|
});
|
|
207
213
|
try {
|
|
208
|
-
const result = explorer.search();
|
|
214
|
+
const result = explorer.search(searchFrom);
|
|
209
215
|
if (!result || !result.config)
|
|
210
216
|
return null;
|
|
211
217
|
const config = result.config;
|
|
@@ -222,12 +228,13 @@ function loadAicmConfigCosmiconfig() {
|
|
|
222
228
|
/**
|
|
223
229
|
* Get the configuration from aicm.json or package.json (using cosmiconfigSync) and merge with any presets
|
|
224
230
|
*/
|
|
225
|
-
function getConfig() {
|
|
226
|
-
const
|
|
231
|
+
function getConfig(cwd) {
|
|
232
|
+
const workingDir = cwd || process.cwd();
|
|
233
|
+
const config = loadAicmConfigCosmiconfig(workingDir);
|
|
227
234
|
if (!config) {
|
|
228
|
-
throw new Error(`No config found in ${
|
|
235
|
+
throw new Error(`No config found in ${workingDir}, create one using "aicm init"`);
|
|
229
236
|
}
|
|
230
|
-
const { config: processedConfig, metadata } = processPresets(config);
|
|
237
|
+
const { config: processedConfig, metadata } = processPresets(config, workingDir);
|
|
231
238
|
// Store metadata for later access
|
|
232
239
|
currentMetadata = metadata;
|
|
233
240
|
return processedConfig;
|
|
@@ -249,8 +256,9 @@ function getOriginalPresetPath(config, ruleName) {
|
|
|
249
256
|
/**
|
|
250
257
|
* Save the configuration to the aicm.json file
|
|
251
258
|
*/
|
|
252
|
-
function saveConfig(config) {
|
|
253
|
-
const
|
|
259
|
+
function saveConfig(config, cwd) {
|
|
260
|
+
const workingDir = cwd || process.cwd();
|
|
261
|
+
const configPath = node_path_1.default.join(workingDir, CONFIG_FILE);
|
|
254
262
|
try {
|
|
255
263
|
fs_extra_1.default.writeJsonSync(configPath, config, { spaces: 2 });
|
|
256
264
|
return true;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Config } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Write MCP servers configuration to IDE targets
|
|
4
|
+
* @param mcpServers The MCP servers configuration
|
|
5
|
+
* @param ides The IDEs to write to
|
|
6
|
+
* @param cwd The current working directory
|
|
7
|
+
*/
|
|
8
|
+
export declare function writeMcpServersToTargets(mcpServers: Config["mcpServers"], ides: string[], cwd: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Write MCP servers configuration to a specific file
|
|
11
|
+
* @param mcpServers The MCP servers configuration
|
|
12
|
+
* @param mcpPath The path to the mcp.json file
|
|
13
|
+
*/
|
|
14
|
+
export declare function writeMcpServersToFile(mcpServers: Config["mcpServers"], mcpPath: string): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
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.writeMcpServersToTargets = writeMcpServersToTargets;
|
|
7
|
+
exports.writeMcpServersToFile = writeMcpServersToFile;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
/**
|
|
11
|
+
* Write MCP servers configuration to IDE targets
|
|
12
|
+
* @param mcpServers The MCP servers configuration
|
|
13
|
+
* @param ides The IDEs to write to
|
|
14
|
+
* @param cwd The current working directory
|
|
15
|
+
*/
|
|
16
|
+
function writeMcpServersToTargets(mcpServers, ides, cwd) {
|
|
17
|
+
if (!mcpServers)
|
|
18
|
+
return;
|
|
19
|
+
for (const ide of ides) {
|
|
20
|
+
if (ide === "cursor") {
|
|
21
|
+
const mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json");
|
|
22
|
+
writeMcpServersToFile(mcpServers, mcpPath);
|
|
23
|
+
}
|
|
24
|
+
// Windsurf does not support project mcpServers, so skip
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Write MCP servers configuration to a specific file
|
|
29
|
+
* @param mcpServers The MCP servers configuration
|
|
30
|
+
* @param mcpPath The path to the mcp.json file
|
|
31
|
+
*/
|
|
32
|
+
function writeMcpServersToFile(mcpServers, mcpPath) {
|
|
33
|
+
var _a;
|
|
34
|
+
if (!mcpServers)
|
|
35
|
+
return;
|
|
36
|
+
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(mcpPath));
|
|
37
|
+
const existingConfig = fs_extra_1.default.existsSync(mcpPath)
|
|
38
|
+
? fs_extra_1.default.readJsonSync(mcpPath)
|
|
39
|
+
: {};
|
|
40
|
+
const existingMcpServers = (_a = existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.mcpServers) !== null && _a !== void 0 ? _a : {};
|
|
41
|
+
// Filter out any existing aicm-managed servers (with aicm: true)
|
|
42
|
+
// This removes stale aicm servers that are no longer in the configuration
|
|
43
|
+
const userMcpServers = {};
|
|
44
|
+
for (const [key, value] of Object.entries(existingMcpServers)) {
|
|
45
|
+
if (typeof value === "object" && value !== null && value.aicm !== true) {
|
|
46
|
+
userMcpServers[key] = value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Mark new aicm servers as managed and filter out canceled servers
|
|
50
|
+
const aicmMcpServers = {};
|
|
51
|
+
for (const [key, value] of Object.entries(mcpServers)) {
|
|
52
|
+
if (value !== false) {
|
|
53
|
+
aicmMcpServers[key] = {
|
|
54
|
+
...value,
|
|
55
|
+
aicm: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Merge user servers with aicm servers (aicm servers override user servers with same key)
|
|
60
|
+
const mergedMcpServers = {
|
|
61
|
+
...userMcpServers,
|
|
62
|
+
...aicmMcpServers,
|
|
63
|
+
};
|
|
64
|
+
const mergedConfig = {
|
|
65
|
+
...existingConfig,
|
|
66
|
+
mcpServers: mergedMcpServers,
|
|
67
|
+
};
|
|
68
|
+
fs_extra_1.default.writeJsonSync(mcpPath, mergedConfig, { spaces: 2 });
|
|
69
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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.detectPackageType = detectPackageType;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
/**
|
|
10
|
+
* Detect the type of package based on files present in the directory
|
|
11
|
+
* @param packageDir The directory to check
|
|
12
|
+
* @returns The detected package type
|
|
13
|
+
*/
|
|
14
|
+
function detectPackageType(packageDir) {
|
|
15
|
+
// Check for npm package
|
|
16
|
+
if (fs_extra_1.default.existsSync(node_path_1.default.join(packageDir, "package.json"))) {
|
|
17
|
+
return "npm";
|
|
18
|
+
}
|
|
19
|
+
// Check for Bazel package
|
|
20
|
+
if (fs_extra_1.default.existsSync(node_path_1.default.join(packageDir, "BUILD")) ||
|
|
21
|
+
fs_extra_1.default.existsSync(node_path_1.default.join(packageDir, "BUILD.bazel"))) {
|
|
22
|
+
return "bazel";
|
|
23
|
+
}
|
|
24
|
+
return "unknown";
|
|
25
|
+
}
|