aicm 0.9.1 → 0.10.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 CHANGED
@@ -147,6 +147,44 @@ After installation, open Cursor and ask it to do something. Your AI assistant wi
147
147
 
148
148
  To prevent [prompt-injection](https://en.wikipedia.org/wiki/Prompt_injection), use only packages from trusted sources.
149
149
 
150
+ ## Workspaces Support
151
+
152
+ aicm supports workspaces by automatically discovering and installing configurations across multiple packages in your repository.
153
+
154
+ To enable workspaces mode, use the `--workspaces` flag:
155
+
156
+ ```bash
157
+ npx aicm install --workspaces
158
+ ```
159
+
160
+ This will:
161
+
162
+ 1. **Discover packages**: Automatically find all directories containing `aicm.json` files in your repository
163
+ 2. **Install per package**: Install rules and MCPs for each package individually in their respective directories
164
+
165
+ ### How It Works
166
+
167
+ Each directory containing an `aicm.json` file is treated as a separate package with its own configuration.
168
+
169
+ For example, in a workspace structure like:
170
+
171
+ ```
172
+ ├── packages/
173
+ │ ├── frontend/
174
+ │ │ └── aicm.json
175
+ │ └── backend/
176
+ │ └── aicm.json
177
+ └── services/
178
+ └── api/
179
+ └── aicm.json
180
+ ```
181
+
182
+ Running `npx aicm install --workspaces` will install rules for each package in their respective directories:
183
+
184
+ - `packages/frontend/.cursor/rules/aicm/`
185
+ - `packages/backend/.cursor/rules/aicm/`
186
+ - `services/api/.cursor/rules/aicm/`
187
+
150
188
  ## Configuration
151
189
 
152
190
  To configure aicm, use either:
@@ -252,6 +290,8 @@ npx aicm install
252
290
  Options:
253
291
 
254
292
  - `--ci`: run in CI environments (default: `false`)
293
+ - `--workspaces`: enable workspaces mode to discover and install configurations across multiple packages
294
+ - `--verbose`: show detailed output during installation
255
295
 
256
296
  ## Node.js API
257
297
 
@@ -296,6 +336,8 @@ Installs rules and MCP servers based on configuration.
296
336
  - `cwd`: Base directory to use instead of `process.cwd()`
297
337
  - `config`: Custom config object to use instead of loading from file
298
338
  - `installOnCI`: Run installation on CI environments (default: `false`)
339
+ - `workspaces`: Enable workspaces mode (default: `false`)
340
+ - `verbose`: Show verbose output during installation (default: `false`)
299
341
 
300
342
  **Returns:**
301
343
 
@@ -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>;
@@ -13,6 +13,26 @@ const rule_writer_1 = require("../utils/rule-writer");
13
13
  const fs_extra_1 = __importDefault(require("fs-extra"));
14
14
  const node_path_1 = __importDefault(require("node:path"));
15
15
  const ci_info_1 = require("ci-info");
16
+ const discovery_1 = require("./workspaces/discovery");
17
+ const workspaces_install_1 = require("./workspaces/workspaces-install");
18
+ /**
19
+ * Helper function to execute a function within a specific working directory
20
+ * and ensure the original directory is always restored
21
+ */
22
+ async function withWorkingDirectory(targetDir, fn) {
23
+ const originalCwd = process.cwd();
24
+ if (targetDir !== originalCwd) {
25
+ process.chdir(targetDir);
26
+ }
27
+ try {
28
+ return await fn();
29
+ }
30
+ finally {
31
+ if (targetDir !== originalCwd) {
32
+ process.chdir(originalCwd);
33
+ }
34
+ }
35
+ }
16
36
  /**
17
37
  * Write MCP servers configuration to IDE targets
18
38
  * @param mcpServers The MCP servers configuration
@@ -47,6 +67,64 @@ function isInCIEnvironment() {
47
67
  // Fall back to ci-info's detection
48
68
  return ci_info_1.isCI;
49
69
  }
70
+ async function handleWorkspacesInstallation(cwd, installOnCI, verbose = false) {
71
+ return withWorkingDirectory(cwd, async () => {
72
+ if (verbose) {
73
+ console.log(chalk_1.default.blue("🔍 Discovering packages..."));
74
+ }
75
+ const packages = await (0, discovery_1.discoverPackagesWithAicm)(cwd);
76
+ if (packages.length === 0) {
77
+ return {
78
+ success: false,
79
+ error: "No packages with aicm configurations found",
80
+ installedRuleCount: 0,
81
+ packagesCount: 0,
82
+ };
83
+ }
84
+ if (verbose) {
85
+ console.log(chalk_1.default.blue(`Found ${packages.length} packages with aicm configurations:`));
86
+ packages.forEach((pkg) => {
87
+ console.log(chalk_1.default.gray(` - ${pkg.relativePath}`));
88
+ });
89
+ console.log(chalk_1.default.blue(`📦 Installing configurations...`));
90
+ }
91
+ const result = await (0, workspaces_install_1.installWorkspacesPackages)(packages, {
92
+ installOnCI,
93
+ });
94
+ if (verbose) {
95
+ result.packages.forEach((pkg) => {
96
+ if (pkg.success) {
97
+ console.log(chalk_1.default.green(`✅ ${pkg.path} (${pkg.installedRuleCount} rules)`));
98
+ }
99
+ else {
100
+ console.log(chalk_1.default.red(`❌ ${pkg.path}: ${pkg.error}`));
101
+ }
102
+ });
103
+ }
104
+ const failedPackages = result.packages.filter((r) => !r.success);
105
+ if (failedPackages.length > 0) {
106
+ console.log(chalk_1.default.yellow(`Installation completed with errors`));
107
+ if (verbose) {
108
+ console.log(chalk_1.default.green(`Successfully installed: ${result.packages.length - failedPackages.length}/${result.packages.length} packages (${result.totalRuleCount} rules total)`));
109
+ console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
110
+ }
111
+ const errorDetails = failedPackages
112
+ .map((p) => `${p.path}: ${p.error}`)
113
+ .join("; ");
114
+ return {
115
+ success: false,
116
+ error: `Package installation failed for ${failedPackages.length} package(s): ${errorDetails}`,
117
+ installedRuleCount: result.totalRuleCount,
118
+ packagesCount: result.packages.length,
119
+ };
120
+ }
121
+ return {
122
+ success: true,
123
+ installedRuleCount: result.totalRuleCount,
124
+ packagesCount: result.packages.length,
125
+ };
126
+ });
127
+ }
50
128
  /**
51
129
  * Core implementation of the rule installation logic
52
130
  * @param options Install options
@@ -55,45 +133,38 @@ function isInCIEnvironment() {
55
133
  async function install(options = {}) {
56
134
  const cwd = options.cwd || process.cwd();
57
135
  const installOnCI = options.installOnCI === true; // Default to false if not specified
58
- try {
59
- const originalCwd = process.cwd();
60
- if (cwd !== originalCwd) {
61
- process.chdir(cwd);
136
+ return withWorkingDirectory(cwd, async () => {
137
+ if (options.workspaces) {
138
+ return await handleWorkspacesInstallation(cwd, installOnCI, options.verbose);
62
139
  }
63
- const ruleCollection = (0, rule_collector_1.initRuleCollection)();
64
140
  const config = options.config || (0, config_1.getConfig)();
141
+ const ruleCollection = (0, rule_collector_1.initRuleCollection)();
65
142
  if (!config) {
66
- if (cwd !== originalCwd) {
67
- process.chdir(originalCwd);
68
- }
69
143
  return {
70
144
  success: false,
71
- error: "Configuration file not found!",
145
+ error: "Configuration file not found",
72
146
  installedRuleCount: 0,
147
+ packagesCount: 0,
73
148
  };
74
149
  }
75
150
  const inCI = isInCIEnvironment();
76
151
  if (inCI && !installOnCI && !config.installOnCI) {
77
- if (cwd !== originalCwd) {
78
- process.chdir(originalCwd);
79
- }
80
152
  console.log(chalk_1.default.yellow("Detected CI environment, skipping install."));
81
153
  return {
82
154
  success: true,
83
155
  installedRuleCount: 0,
156
+ packagesCount: 0,
84
157
  };
85
158
  }
86
159
  // Check if rules are defined (either directly or through presets)
87
160
  if (!config.rules || Object.keys(config.rules).length === 0) {
88
161
  // If there are no presets defined either, show a message
89
162
  if (!config.presets || config.presets.length === 0) {
90
- if (cwd !== originalCwd) {
91
- process.chdir(originalCwd);
92
- }
93
163
  return {
94
164
  success: false,
95
- error: "No rules defined in configuration.",
165
+ error: "No rules defined in configuration",
96
166
  installedRuleCount: 0,
167
+ packagesCount: 0,
97
168
  };
98
169
  }
99
170
  }
@@ -139,13 +210,11 @@ async function install(options = {}) {
139
210
  }
140
211
  // If there were errors, exit with error
141
212
  if (hasErrors) {
142
- if (cwd !== originalCwd) {
143
- process.chdir(originalCwd);
144
- }
145
213
  return {
146
214
  success: false,
147
215
  error: errorMessages.join("; "),
148
216
  installedRuleCount,
217
+ packagesCount: 0,
149
218
  };
150
219
  }
151
220
  // Write all collected rules to their targets
@@ -156,37 +225,30 @@ async function install(options = {}) {
156
225
  const filteredMcpServers = Object.fromEntries(Object.entries(config.mcpServers).filter(([, v]) => v !== false));
157
226
  writeMcpServersToTargets(filteredMcpServers, config.ides, cwd);
158
227
  }
159
- // Restore original cwd
160
- if (cwd !== originalCwd) {
161
- process.chdir(originalCwd);
162
- }
163
228
  return {
164
229
  success: true,
165
230
  installedRuleCount,
231
+ packagesCount: 1,
166
232
  };
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
- }
233
+ });
180
234
  }
181
- async function installCommand(installOnCI) {
235
+ async function installCommand(installOnCI, workspaces, verbose) {
182
236
  try {
183
- const result = await install({ installOnCI });
237
+ const result = await install({ installOnCI, workspaces, verbose });
184
238
  if (!result.success) {
185
239
  console.error(chalk_1.default.red(result.error));
186
240
  process.exit(1);
187
241
  }
188
242
  else {
189
- console.log("Rules installation completed");
243
+ if (result.packagesCount > 1) {
244
+ console.log(`Successfully installed ${result.installedRuleCount} rules across ${result.packagesCount} packages`);
245
+ }
246
+ else if (workspaces) {
247
+ console.log(`Successfully installed ${result.installedRuleCount} rules across ${result.packagesCount} packages`);
248
+ }
249
+ else {
250
+ console.log("Rules installation completed");
251
+ }
190
252
  }
191
253
  }
192
254
  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
  }
@@ -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
+ }
@@ -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;
@@ -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
- // If it's a local file with .json extension and exists, return as is
19
- if (presetPath.endsWith(".json") && fs_extra_1.default.pathExistsSync(presetPath)) {
20
- return { fullPath: presetPath, originalPath: presetPath };
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, process.cwd()],
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, process.cwd()],
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, process.cwd()],
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 config = loadAicmConfigCosmiconfig();
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 ${process.cwd()}, create one using "aicm init"`);
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 configPath = node_path_1.default.join(process.cwd(), CONFIG_FILE);
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,6 @@
1
+ /**
2
+ * Detect the type of package based on files present in the directory
3
+ * @param packageDir The directory to check
4
+ * @returns The detected package type
5
+ */
6
+ export declare function detectPackageType(packageDir: string): "npm" | "bazel" | "unknown";
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicm",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
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",