aicm 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -3
- package/dist/commands/clean.js +60 -0
- package/dist/commands/install-workspaces.js +75 -3
- package/dist/commands/install.d.ts +19 -1
- package/dist/commands/install.js +132 -3
- package/dist/utils/config.d.ts +8 -0
- package/dist/utils/config.js +57 -4
- package/dist/utils/safe-path.d.ts +10 -0
- package/dist/utils/safe-path.js +28 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ A CLI tool for managing Agentic configurations across projects.
|
|
|
17
17
|
- [Rules](#rules)
|
|
18
18
|
- [Commands](#commands)
|
|
19
19
|
- [Skills](#skills)
|
|
20
|
+
- [Agents](#agents)
|
|
20
21
|
- [Hooks](#hooks)
|
|
21
22
|
- [MCP Servers](#mcp-servers)
|
|
22
23
|
- [Assets](#assets)
|
|
@@ -88,6 +89,7 @@ After installation, open Cursor and ask it to do something. Your AI assistant wi
|
|
|
88
89
|
│ └── react.mdc
|
|
89
90
|
├── commands/ # Command files (.md) [optional]
|
|
90
91
|
├── skills/ # Agent Skills [optional]
|
|
92
|
+
├── agents/ # Subagents (.md) [optional]
|
|
91
93
|
├── assets/ # Auxiliary files [optional]
|
|
92
94
|
└── hooks.json # Hook configuration [optional]
|
|
93
95
|
```
|
|
@@ -142,7 +144,7 @@ The rules are now installed in `.cursor/rules/aicm/` and any MCP servers are con
|
|
|
142
144
|
### Notes
|
|
143
145
|
|
|
144
146
|
- Generated files are always placed in subdirectories for deterministic cleanup and easy gitignore.
|
|
145
|
-
- Users
|
|
147
|
+
- Users may add `.cursor/*/aicm/`, `.cursor/skills/`, `.cursor/agents/`, `.claude/`, and `.codex/` to `.gitignore` to avoid tracking generated files.
|
|
146
148
|
|
|
147
149
|
## Features
|
|
148
150
|
|
|
@@ -255,6 +257,77 @@ When installed, each skill directory is copied in its entirety (including `scrip
|
|
|
255
257
|
|
|
256
258
|
In workspace mode, skills are installed both to each package and merged at the root level, similar to commands.
|
|
257
259
|
|
|
260
|
+
### Agents
|
|
261
|
+
|
|
262
|
+
aicm supports [Cursor Subagents](https://cursor.com/docs/context/subagents) and [Claude Code Subagents](https://code.claude.com/docs/en/sub-agents) - specialized AI assistants that can be delegated specific tasks. Agents are markdown files with YAML frontmatter that define custom prompts, descriptions, and model configurations.
|
|
263
|
+
|
|
264
|
+
Create an `agents/` directory in your project (at the `rootDir` location):
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
my-project/
|
|
268
|
+
├── aicm.json
|
|
269
|
+
└── agents/
|
|
270
|
+
├── code-reviewer.md
|
|
271
|
+
├── debugger.md
|
|
272
|
+
└── specialized/
|
|
273
|
+
└── security-auditor.md
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Each agent file should have YAML frontmatter with at least a `name` and `description`:
|
|
277
|
+
|
|
278
|
+
```markdown
|
|
279
|
+
---
|
|
280
|
+
name: code-reviewer
|
|
281
|
+
description: Reviews code for quality and best practices. Use after code changes.
|
|
282
|
+
model: inherit
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
You are a senior code reviewer ensuring high standards of code quality and security.
|
|
286
|
+
|
|
287
|
+
When invoked:
|
|
288
|
+
|
|
289
|
+
1. Run git diff to see recent changes
|
|
290
|
+
2. Focus on modified files
|
|
291
|
+
3. Begin review immediately
|
|
292
|
+
|
|
293
|
+
Review checklist:
|
|
294
|
+
|
|
295
|
+
- Code is clear and readable
|
|
296
|
+
- Functions and variables are well-named
|
|
297
|
+
- No duplicated code
|
|
298
|
+
- Proper error handling
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Configure your `aicm.json`:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"rootDir": "./",
|
|
306
|
+
"targets": ["cursor", "claude"]
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Agents are installed to different locations based on the target:
|
|
311
|
+
|
|
312
|
+
| Target | Agents Location |
|
|
313
|
+
| ---------- | ----------------- |
|
|
314
|
+
| **Cursor** | `.cursor/agents/` |
|
|
315
|
+
| **Claude** | `.claude/agents/` |
|
|
316
|
+
|
|
317
|
+
A `.aicm.json` metadata file is created in the agents directory to track which agents are managed by aicm. This allows the clean command to remove only aicm-managed agents while preserving any manually created agents.
|
|
318
|
+
|
|
319
|
+
**Supported Configuration Fields:**
|
|
320
|
+
|
|
321
|
+
Only fields that work in both Cursor and Claude Code are documented:
|
|
322
|
+
|
|
323
|
+
- `name` - Unique identifier (defaults to filename without extension)
|
|
324
|
+
- `description` - When the agent should be used for task delegation
|
|
325
|
+
- `model` - Model to use (`inherit`, or platform-specific values like `sonnet`, `haiku`, `fast`)
|
|
326
|
+
|
|
327
|
+
> **Note:** Users may include additional platform-specific fields (e.g., `tools`, `hooks` for Claude Code, or `readonly`, `is_background` for Cursor) - aicm will preserve them, but they only work on the respective platform.
|
|
328
|
+
|
|
329
|
+
In workspace mode, agents are installed both to each package and merged at the root level, similar to commands and skills.
|
|
330
|
+
|
|
258
331
|
### Hooks
|
|
259
332
|
|
|
260
333
|
aicm provides first-class support for [Cursor Agent Hooks](https://docs.cursor.com/advanced/hooks), allowing you to intercept and extend the agent's behavior. Hooks enable you to run custom scripts before/after shell execution, file edits, MCP calls, and more.
|
|
@@ -431,10 +504,11 @@ aicm automatically detects workspaces if your `package.json` contains a `workspa
|
|
|
431
504
|
### How It Works
|
|
432
505
|
|
|
433
506
|
1. **Discover packages**: Automatically find all directories containing `aicm.json` files in your repository.
|
|
434
|
-
2. **Install per package**: Install rules, commands, and
|
|
507
|
+
2. **Install per package**: Install rules, commands, skills, and agents for each package individually in their respective directories.
|
|
435
508
|
3. **Merge MCP servers**: Write a merged `.cursor/mcp.json` at the repository root containing all MCP servers from every package.
|
|
436
509
|
4. **Merge commands**: Write a merged `.cursor/commands/aicm/` at the repository root containing all commands from every package.
|
|
437
510
|
5. **Merge skills**: Write merged skills to the repository root (e.g., `.cursor/skills/`) containing all skills from every package.
|
|
511
|
+
6. **Merge agents**: Write merged agents to the repository root (e.g., `.cursor/agents/`) containing all agents from every package.
|
|
438
512
|
|
|
439
513
|
For example, in a workspace structure like:
|
|
440
514
|
|
|
@@ -492,7 +566,7 @@ Create an `aicm.json` file in your project root, or an `aicm` key in your projec
|
|
|
492
566
|
|
|
493
567
|
### Configuration Options
|
|
494
568
|
|
|
495
|
-
- **rootDir**: Directory containing your aicm structure. Must contain one or more of: `rules/`, `commands/`, `skills/`, `assets/`, `hooks/`, or `hooks.json`. If not specified, aicm will only install rules from presets and will not pick up any local directories.
|
|
569
|
+
- **rootDir**: Directory containing your aicm structure. Must contain one or more of: `rules/`, `commands/`, `skills/`, `agents/`, `assets/`, `hooks/`, or `hooks.json`. If not specified, aicm will only install rules from presets and will not pick up any local directories.
|
|
496
570
|
- **targets**: IDEs/Agent targets where rules should be installed. Defaults to `["cursor"]`. Supported targets: `cursor`, `windsurf`, `codex`, `claude`.
|
|
497
571
|
- **presets**: List of preset packages or paths to include.
|
|
498
572
|
- **mcpServers**: MCP server configurations.
|
|
@@ -542,6 +616,8 @@ my-project/
|
|
|
542
616
|
├── skills/ # Agent Skills [optional]
|
|
543
617
|
│ └── my-skill/
|
|
544
618
|
│ └── SKILL.md
|
|
619
|
+
├── agents/ # Subagents (.md) [optional]
|
|
620
|
+
│ └── code-reviewer.md
|
|
545
621
|
├── assets/ # Auxiliary files [optional]
|
|
546
622
|
│ ├── schema.json
|
|
547
623
|
│ └── examples/
|
package/dist/commands/clean.js
CHANGED
|
@@ -200,6 +200,62 @@ function cleanSkills(cwd, verbose) {
|
|
|
200
200
|
}
|
|
201
201
|
return cleanedCount;
|
|
202
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Clean aicm-managed agents from agents directories
|
|
205
|
+
* Only removes agents that are tracked in .aicm.json metadata file
|
|
206
|
+
*/
|
|
207
|
+
function cleanAgents(cwd, verbose) {
|
|
208
|
+
let cleanedCount = 0;
|
|
209
|
+
// Agents directories for each target
|
|
210
|
+
const agentsDirs = [
|
|
211
|
+
node_path_1.default.join(cwd, ".cursor", "agents"),
|
|
212
|
+
node_path_1.default.join(cwd, ".claude", "agents"),
|
|
213
|
+
];
|
|
214
|
+
for (const agentsDir of agentsDirs) {
|
|
215
|
+
const metadataPath = node_path_1.default.join(agentsDir, ".aicm.json");
|
|
216
|
+
if (!fs_extra_1.default.existsSync(metadataPath)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const metadata = fs_extra_1.default.readJsonSync(metadataPath);
|
|
221
|
+
// Remove all managed agents (names only)
|
|
222
|
+
for (const agentName of metadata.managedAgents || []) {
|
|
223
|
+
// Skip invalid names containing path separators (security check)
|
|
224
|
+
if (agentName.includes("/") || agentName.includes("\\")) {
|
|
225
|
+
console.warn(chalk_1.default.yellow(`Warning: Skipping invalid agent name "${agentName}" (contains path separator)`));
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const fullPath = node_path_1.default.join(agentsDir, agentName + ".md");
|
|
229
|
+
if (fs_extra_1.default.existsSync(fullPath)) {
|
|
230
|
+
fs_extra_1.default.removeSync(fullPath);
|
|
231
|
+
if (verbose) {
|
|
232
|
+
console.log(chalk_1.default.gray(` Removed agent ${fullPath}`));
|
|
233
|
+
}
|
|
234
|
+
cleanedCount++;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Remove the metadata file
|
|
238
|
+
fs_extra_1.default.removeSync(metadataPath);
|
|
239
|
+
if (verbose) {
|
|
240
|
+
console.log(chalk_1.default.gray(` Removed ${metadataPath}`));
|
|
241
|
+
}
|
|
242
|
+
// Remove the agents directory if it's now empty
|
|
243
|
+
if (fs_extra_1.default.existsSync(agentsDir)) {
|
|
244
|
+
const remainingEntries = fs_extra_1.default.readdirSync(agentsDir);
|
|
245
|
+
if (remainingEntries.length === 0) {
|
|
246
|
+
fs_extra_1.default.removeSync(agentsDir);
|
|
247
|
+
if (verbose) {
|
|
248
|
+
console.log(chalk_1.default.gray(` Removed empty directory ${agentsDir}`));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (_a) {
|
|
254
|
+
console.warn(chalk_1.default.yellow(`Warning: Failed to clean agents in ${agentsDir}`));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return cleanedCount;
|
|
258
|
+
}
|
|
203
259
|
function cleanEmptyDirectories(cwd, verbose) {
|
|
204
260
|
let cleanedCount = 0;
|
|
205
261
|
const dirsToCheck = [
|
|
@@ -208,8 +264,10 @@ function cleanEmptyDirectories(cwd, verbose) {
|
|
|
208
264
|
node_path_1.default.join(cwd, ".cursor", "assets"),
|
|
209
265
|
node_path_1.default.join(cwd, ".cursor", "hooks"),
|
|
210
266
|
node_path_1.default.join(cwd, ".cursor", "skills"),
|
|
267
|
+
node_path_1.default.join(cwd, ".cursor", "agents"),
|
|
211
268
|
node_path_1.default.join(cwd, ".cursor"),
|
|
212
269
|
node_path_1.default.join(cwd, ".claude", "skills"),
|
|
270
|
+
node_path_1.default.join(cwd, ".claude", "agents"),
|
|
213
271
|
node_path_1.default.join(cwd, ".claude"),
|
|
214
272
|
node_path_1.default.join(cwd, ".codex", "skills"),
|
|
215
273
|
node_path_1.default.join(cwd, ".codex"),
|
|
@@ -266,6 +324,8 @@ async function cleanPackage(options = {}) {
|
|
|
266
324
|
cleanedCount++;
|
|
267
325
|
// Clean skills
|
|
268
326
|
cleanedCount += cleanSkills(cwd, verbose);
|
|
327
|
+
// Clean agents
|
|
328
|
+
cleanedCount += cleanAgents(cwd, verbose);
|
|
269
329
|
// Clean empty directories
|
|
270
330
|
cleanedCount += cleanEmptyDirectories(cwd, verbose);
|
|
271
331
|
return {
|
|
@@ -87,6 +87,51 @@ function collectWorkspaceSkillTargets(packages) {
|
|
|
87
87
|
}
|
|
88
88
|
return Array.from(targets);
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Merge agents from multiple workspace packages
|
|
92
|
+
* Agents are merged flat (not namespaced by preset)
|
|
93
|
+
* Dedupes preset agents that appear in multiple packages
|
|
94
|
+
*/
|
|
95
|
+
function mergeWorkspaceAgents(packages) {
|
|
96
|
+
var _a;
|
|
97
|
+
const agents = [];
|
|
98
|
+
const seenPresetAgents = new Set();
|
|
99
|
+
for (const pkg of packages) {
|
|
100
|
+
// Agents are supported by cursor and claude targets
|
|
101
|
+
const hasAgentsTarget = pkg.config.config.targets.includes("cursor") ||
|
|
102
|
+
pkg.config.config.targets.includes("claude");
|
|
103
|
+
if (!hasAgentsTarget) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
for (const agent of (_a = pkg.config.agents) !== null && _a !== void 0 ? _a : []) {
|
|
107
|
+
if (agent.presetName) {
|
|
108
|
+
// Dedupe preset agents by preset+name combination
|
|
109
|
+
const presetKey = `${agent.presetName}::${agent.name}`;
|
|
110
|
+
if (seenPresetAgents.has(presetKey)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
seenPresetAgents.add(presetKey);
|
|
114
|
+
}
|
|
115
|
+
agents.push(agent);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return agents;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Collect all targets that support agents from workspace packages
|
|
122
|
+
*/
|
|
123
|
+
function collectWorkspaceAgentTargets(packages) {
|
|
124
|
+
const targets = new Set();
|
|
125
|
+
for (const pkg of packages) {
|
|
126
|
+
for (const target of pkg.config.config.targets) {
|
|
127
|
+
// Agents are supported by cursor and claude
|
|
128
|
+
if (target === "cursor" || target === "claude") {
|
|
129
|
+
targets.add(target);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return Array.from(targets);
|
|
134
|
+
}
|
|
90
135
|
function mergeWorkspaceMcpServers(packages) {
|
|
91
136
|
const merged = {};
|
|
92
137
|
const info = {};
|
|
@@ -148,6 +193,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
148
193
|
let totalAssetCount = 0;
|
|
149
194
|
let totalHookCount = 0;
|
|
150
195
|
let totalSkillCount = 0;
|
|
196
|
+
let totalAgentCount = 0;
|
|
151
197
|
// Install packages sequentially for now (can be parallelized later)
|
|
152
198
|
for (const pkg of packages) {
|
|
153
199
|
const packagePath = pkg.absolutePath;
|
|
@@ -162,6 +208,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
162
208
|
totalAssetCount += result.installedAssetCount;
|
|
163
209
|
totalHookCount += result.installedHookCount;
|
|
164
210
|
totalSkillCount += result.installedSkillCount;
|
|
211
|
+
totalAgentCount += result.installedAgentCount;
|
|
165
212
|
results.push({
|
|
166
213
|
path: pkg.relativePath,
|
|
167
214
|
success: result.success,
|
|
@@ -171,6 +218,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
171
218
|
installedAssetCount: result.installedAssetCount,
|
|
172
219
|
installedHookCount: result.installedHookCount,
|
|
173
220
|
installedSkillCount: result.installedSkillCount,
|
|
221
|
+
installedAgentCount: result.installedAgentCount,
|
|
174
222
|
});
|
|
175
223
|
}
|
|
176
224
|
catch (error) {
|
|
@@ -183,6 +231,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
183
231
|
installedAssetCount: 0,
|
|
184
232
|
installedHookCount: 0,
|
|
185
233
|
installedSkillCount: 0,
|
|
234
|
+
installedAgentCount: 0,
|
|
186
235
|
});
|
|
187
236
|
}
|
|
188
237
|
}
|
|
@@ -195,6 +244,7 @@ async function installWorkspacesPackages(packages, options = {}) {
|
|
|
195
244
|
totalAssetCount,
|
|
196
245
|
totalHookCount,
|
|
197
246
|
totalSkillCount,
|
|
247
|
+
totalAgentCount,
|
|
198
248
|
};
|
|
199
249
|
}
|
|
200
250
|
/**
|
|
@@ -213,12 +263,13 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
213
263
|
const isRoot = pkg.relativePath === ".";
|
|
214
264
|
if (!isRoot)
|
|
215
265
|
return true;
|
|
216
|
-
// For root directories, only keep if it has rules, commands, skills, or presets
|
|
266
|
+
// For root directories, only keep if it has rules, commands, skills, agents, or presets
|
|
217
267
|
const hasRules = pkg.config.rules && pkg.config.rules.length > 0;
|
|
218
268
|
const hasCommands = pkg.config.commands && pkg.config.commands.length > 0;
|
|
219
269
|
const hasSkills = pkg.config.skills && pkg.config.skills.length > 0;
|
|
270
|
+
const hasAgents = pkg.config.agents && pkg.config.agents.length > 0;
|
|
220
271
|
const hasPresets = pkg.config.config.presets && pkg.config.config.presets.length > 0;
|
|
221
|
-
return hasRules || hasCommands || hasSkills || hasPresets;
|
|
272
|
+
return hasRules || hasCommands || hasSkills || hasAgents || hasPresets;
|
|
222
273
|
});
|
|
223
274
|
if (packages.length === 0) {
|
|
224
275
|
return {
|
|
@@ -229,6 +280,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
229
280
|
installedAssetCount: 0,
|
|
230
281
|
installedHookCount: 0,
|
|
231
282
|
installedSkillCount: 0,
|
|
283
|
+
installedAgentCount: 0,
|
|
232
284
|
packagesCount: 0,
|
|
233
285
|
};
|
|
234
286
|
}
|
|
@@ -271,6 +323,18 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
271
323
|
const dedupedWorkspaceSkills = (0, install_1.dedupeSkillsForInstall)(workspaceSkills);
|
|
272
324
|
(0, install_1.writeSkillsToTargets)(dedupedWorkspaceSkills, workspaceSkillTargets);
|
|
273
325
|
}
|
|
326
|
+
// Merge and write agents for workspace
|
|
327
|
+
const workspaceAgents = mergeWorkspaceAgents(packages);
|
|
328
|
+
const workspaceAgentTargets = collectWorkspaceAgentTargets(packages);
|
|
329
|
+
if (workspaceAgents.length > 0) {
|
|
330
|
+
(0, install_1.warnPresetAgentCollisions)(workspaceAgents);
|
|
331
|
+
}
|
|
332
|
+
if (!dryRun &&
|
|
333
|
+
workspaceAgents.length > 0 &&
|
|
334
|
+
workspaceAgentTargets.length > 0) {
|
|
335
|
+
const dedupedWorkspaceAgents = (0, install_1.dedupeAgentsForInstall)(workspaceAgents);
|
|
336
|
+
(0, install_1.writeAgentsToTargets)(dedupedWorkspaceAgents, workspaceAgentTargets);
|
|
337
|
+
}
|
|
274
338
|
const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages);
|
|
275
339
|
const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor"));
|
|
276
340
|
if (!dryRun && hasCursorTarget && Object.keys(rootMcp).length > 0) {
|
|
@@ -299,6 +363,9 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
299
363
|
if (pkg.installedSkillCount > 0) {
|
|
300
364
|
summaryParts.push(`${pkg.installedSkillCount} skill${pkg.installedSkillCount === 1 ? "" : "s"}`);
|
|
301
365
|
}
|
|
366
|
+
if (pkg.installedAgentCount > 0) {
|
|
367
|
+
summaryParts.push(`${pkg.installedAgentCount} agent${pkg.installedAgentCount === 1 ? "" : "s"}`);
|
|
368
|
+
}
|
|
302
369
|
console.log(chalk_1.default.green(`✅ ${pkg.path} (${summaryParts.join(", ")})`));
|
|
303
370
|
}
|
|
304
371
|
else {
|
|
@@ -319,7 +386,10 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
319
386
|
const skillSummary = result.totalSkillCount > 0
|
|
320
387
|
? `, ${result.totalSkillCount} skill${result.totalSkillCount === 1 ? "" : "s"} total`
|
|
321
388
|
: "";
|
|
322
|
-
|
|
389
|
+
const agentSummary = result.totalAgentCount > 0
|
|
390
|
+
? `, ${result.totalAgentCount} agent${result.totalAgentCount === 1 ? "" : "s"} total`
|
|
391
|
+
: "";
|
|
392
|
+
console.log(chalk_1.default.green(`Successfully installed: ${result.packages.length - failedPackages.length}/${result.packages.length} packages (${result.totalRuleCount} rule${result.totalRuleCount === 1 ? "" : "s"} total${commandSummary}${hookSummary}${skillSummary}${agentSummary})`));
|
|
323
393
|
console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
|
|
324
394
|
}
|
|
325
395
|
const errorDetails = failedPackages
|
|
@@ -333,6 +403,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
333
403
|
installedAssetCount: result.totalAssetCount,
|
|
334
404
|
installedHookCount: result.totalHookCount,
|
|
335
405
|
installedSkillCount: result.totalSkillCount,
|
|
406
|
+
installedAgentCount: result.totalAgentCount,
|
|
336
407
|
packagesCount: result.packages.length,
|
|
337
408
|
};
|
|
338
409
|
}
|
|
@@ -343,6 +414,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
343
414
|
installedAssetCount: result.totalAssetCount,
|
|
344
415
|
installedHookCount: result.totalHookCount,
|
|
345
416
|
installedSkillCount: result.totalSkillCount,
|
|
417
|
+
installedAgentCount: result.totalAgentCount,
|
|
346
418
|
packagesCount: result.packages.length,
|
|
347
419
|
};
|
|
348
420
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ResolvedConfig, CommandFile, AssetFile, SkillFile, MCPServers, SupportedTarget } from "../utils/config";
|
|
1
|
+
import { ResolvedConfig, CommandFile, AssetFile, SkillFile, AgentFile, MCPServers, SupportedTarget } from "../utils/config";
|
|
2
2
|
export interface InstallOptions {
|
|
3
3
|
/**
|
|
4
4
|
* Base directory to use instead of process.cwd()
|
|
@@ -53,6 +53,10 @@ export interface InstallResult {
|
|
|
53
53
|
* Number of skills installed
|
|
54
54
|
*/
|
|
55
55
|
installedSkillCount: number;
|
|
56
|
+
/**
|
|
57
|
+
* Number of agents installed
|
|
58
|
+
*/
|
|
59
|
+
installedAgentCount: number;
|
|
56
60
|
/**
|
|
57
61
|
* Number of packages installed
|
|
58
62
|
*/
|
|
@@ -72,6 +76,20 @@ export declare function warnPresetSkillCollisions(skills: SkillFile[]): void;
|
|
|
72
76
|
* Dedupe skills by name (last one wins)
|
|
73
77
|
*/
|
|
74
78
|
export declare function dedupeSkillsForInstall(skills: SkillFile[]): SkillFile[];
|
|
79
|
+
/**
|
|
80
|
+
* Write agents to all supported target directories
|
|
81
|
+
* Similar to skills, agents are written directly to the agents directory
|
|
82
|
+
* with a .aicm.json metadata file tracking which agents are managed
|
|
83
|
+
*/
|
|
84
|
+
export declare function writeAgentsToTargets(agents: AgentFile[], targets: SupportedTarget[]): void;
|
|
85
|
+
/**
|
|
86
|
+
* Warn about agent name collisions from different presets
|
|
87
|
+
*/
|
|
88
|
+
export declare function warnPresetAgentCollisions(agents: AgentFile[]): void;
|
|
89
|
+
/**
|
|
90
|
+
* Dedupe agents by name (last one wins)
|
|
91
|
+
*/
|
|
92
|
+
export declare function dedupeAgentsForInstall(agents: AgentFile[]): AgentFile[];
|
|
75
93
|
export declare function warnPresetCommandCollisions(commands: CommandFile[]): void;
|
|
76
94
|
export declare function dedupeCommandsForInstall(commands: CommandFile[]): CommandFile[];
|
|
77
95
|
/**
|
package/dist/commands/install.js
CHANGED
|
@@ -8,6 +8,9 @@ exports.writeCommandsToTargets = writeCommandsToTargets;
|
|
|
8
8
|
exports.writeSkillsToTargets = writeSkillsToTargets;
|
|
9
9
|
exports.warnPresetSkillCollisions = warnPresetSkillCollisions;
|
|
10
10
|
exports.dedupeSkillsForInstall = dedupeSkillsForInstall;
|
|
11
|
+
exports.writeAgentsToTargets = writeAgentsToTargets;
|
|
12
|
+
exports.warnPresetAgentCollisions = warnPresetAgentCollisions;
|
|
13
|
+
exports.dedupeAgentsForInstall = dedupeAgentsForInstall;
|
|
11
14
|
exports.warnPresetCommandCollisions = warnPresetCommandCollisions;
|
|
12
15
|
exports.dedupeCommandsForInstall = dedupeCommandsForInstall;
|
|
13
16
|
exports.writeMcpServersToFile = writeMcpServersToFile;
|
|
@@ -330,6 +333,115 @@ function dedupeSkillsForInstall(skills) {
|
|
|
330
333
|
}
|
|
331
334
|
return Array.from(unique.values());
|
|
332
335
|
}
|
|
336
|
+
/**
|
|
337
|
+
* Get the agents installation path for a target
|
|
338
|
+
* Returns null for targets that don't support agents
|
|
339
|
+
*/
|
|
340
|
+
function getAgentsTargetPath(target) {
|
|
341
|
+
const projectDir = process.cwd();
|
|
342
|
+
switch (target) {
|
|
343
|
+
case "cursor":
|
|
344
|
+
return node_path_1.default.join(projectDir, ".cursor", "agents");
|
|
345
|
+
case "claude":
|
|
346
|
+
return node_path_1.default.join(projectDir, ".claude", "agents");
|
|
347
|
+
case "codex":
|
|
348
|
+
case "windsurf":
|
|
349
|
+
// Codex and Windsurf do not support agents
|
|
350
|
+
return null;
|
|
351
|
+
default:
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Write agents to all supported target directories
|
|
357
|
+
* Similar to skills, agents are written directly to the agents directory
|
|
358
|
+
* with a .aicm.json metadata file tracking which agents are managed
|
|
359
|
+
*/
|
|
360
|
+
function writeAgentsToTargets(agents, targets) {
|
|
361
|
+
if (agents.length === 0)
|
|
362
|
+
return;
|
|
363
|
+
for (const target of targets) {
|
|
364
|
+
const targetAgentsDir = getAgentsTargetPath(target);
|
|
365
|
+
if (!targetAgentsDir) {
|
|
366
|
+
// Target doesn't support agents
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
// Ensure the agents directory exists
|
|
370
|
+
fs_extra_1.default.ensureDirSync(targetAgentsDir);
|
|
371
|
+
// Read existing metadata to clean up old managed agents
|
|
372
|
+
const metadataPath = node_path_1.default.join(targetAgentsDir, ".aicm.json");
|
|
373
|
+
if (fs_extra_1.default.existsSync(metadataPath)) {
|
|
374
|
+
try {
|
|
375
|
+
const existingMetadata = fs_extra_1.default.readJsonSync(metadataPath);
|
|
376
|
+
// Remove previously managed agents
|
|
377
|
+
for (const agentName of existingMetadata.managedAgents || []) {
|
|
378
|
+
// Skip invalid names containing path separators
|
|
379
|
+
if (agentName.includes("/") || agentName.includes("\\")) {
|
|
380
|
+
console.warn(chalk_1.default.yellow(`Warning: Skipping invalid agent name "${agentName}" (contains path separator)`));
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const fullPath = node_path_1.default.join(targetAgentsDir, agentName + ".md");
|
|
384
|
+
if (fs_extra_1.default.existsSync(fullPath)) {
|
|
385
|
+
fs_extra_1.default.removeSync(fullPath);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch (_a) {
|
|
390
|
+
// Ignore errors reading metadata
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const managedAgents = [];
|
|
394
|
+
for (const agent of agents) {
|
|
395
|
+
// Use base name only
|
|
396
|
+
const agentName = node_path_1.default.basename(agent.name, node_path_1.default.extname(agent.name));
|
|
397
|
+
const agentFile = node_path_1.default.join(targetAgentsDir, agentName + ".md");
|
|
398
|
+
fs_extra_1.default.writeFileSync(agentFile, agent.content);
|
|
399
|
+
managedAgents.push(agentName);
|
|
400
|
+
}
|
|
401
|
+
// Write metadata file to track managed agents
|
|
402
|
+
const metadata = {
|
|
403
|
+
managedAgents,
|
|
404
|
+
};
|
|
405
|
+
fs_extra_1.default.writeJsonSync(metadataPath, metadata, { spaces: 2 });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Warn about agent name collisions from different presets
|
|
410
|
+
*/
|
|
411
|
+
function warnPresetAgentCollisions(agents) {
|
|
412
|
+
const collisions = new Map();
|
|
413
|
+
for (const agent of agents) {
|
|
414
|
+
if (!agent.presetName)
|
|
415
|
+
continue;
|
|
416
|
+
const entry = collisions.get(agent.name);
|
|
417
|
+
if (entry) {
|
|
418
|
+
entry.presets.add(agent.presetName);
|
|
419
|
+
entry.lastPreset = agent.presetName;
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
collisions.set(agent.name, {
|
|
423
|
+
presets: new Set([agent.presetName]),
|
|
424
|
+
lastPreset: agent.presetName,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
for (const [agentName, { presets, lastPreset }] of collisions) {
|
|
429
|
+
if (presets.size > 1) {
|
|
430
|
+
const presetList = Array.from(presets).sort().join(", ");
|
|
431
|
+
console.warn(chalk_1.default.yellow(`Warning: multiple presets provide the "${agentName}" agent (${presetList}). Using definition from ${lastPreset}.`));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Dedupe agents by name (last one wins)
|
|
437
|
+
*/
|
|
438
|
+
function dedupeAgentsForInstall(agents) {
|
|
439
|
+
const unique = new Map();
|
|
440
|
+
for (const agent of agents) {
|
|
441
|
+
unique.set(agent.name, agent);
|
|
442
|
+
}
|
|
443
|
+
return Array.from(unique.values());
|
|
444
|
+
}
|
|
333
445
|
function warnPresetCommandCollisions(commands) {
|
|
334
446
|
const collisions = new Map();
|
|
335
447
|
for (const command of commands) {
|
|
@@ -451,10 +563,11 @@ async function installPackage(options = {}) {
|
|
|
451
563
|
installedAssetCount: 0,
|
|
452
564
|
installedHookCount: 0,
|
|
453
565
|
installedSkillCount: 0,
|
|
566
|
+
installedAgentCount: 0,
|
|
454
567
|
packagesCount: 0,
|
|
455
568
|
};
|
|
456
569
|
}
|
|
457
|
-
const { config, rules, commands, assets, skills, mcpServers, hooks, hookFiles, } = resolvedConfig;
|
|
570
|
+
const { config, rules, commands, assets, skills, agents, mcpServers, hooks, hookFiles, } = resolvedConfig;
|
|
458
571
|
if (config.skipInstall === true) {
|
|
459
572
|
return {
|
|
460
573
|
success: true,
|
|
@@ -463,6 +576,7 @@ async function installPackage(options = {}) {
|
|
|
463
576
|
installedAssetCount: 0,
|
|
464
577
|
installedHookCount: 0,
|
|
465
578
|
installedSkillCount: 0,
|
|
579
|
+
installedAgentCount: 0,
|
|
466
580
|
packagesCount: 0,
|
|
467
581
|
};
|
|
468
582
|
}
|
|
@@ -470,11 +584,14 @@ async function installPackage(options = {}) {
|
|
|
470
584
|
const commandsToInstall = dedupeCommandsForInstall(commands);
|
|
471
585
|
warnPresetSkillCollisions(skills);
|
|
472
586
|
const skillsToInstall = dedupeSkillsForInstall(skills);
|
|
587
|
+
warnPresetAgentCollisions(agents);
|
|
588
|
+
const agentsToInstall = dedupeAgentsForInstall(agents);
|
|
473
589
|
try {
|
|
474
590
|
if (!options.dryRun) {
|
|
475
591
|
writeRulesToTargets(rules, assets, config.targets);
|
|
476
592
|
writeCommandsToTargets(commandsToInstall, config.targets);
|
|
477
593
|
writeSkillsToTargets(skillsToInstall, config.targets);
|
|
594
|
+
writeAgentsToTargets(agentsToInstall, config.targets);
|
|
478
595
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
479
596
|
writeMcpServersToTargets(mcpServers, config.targets, cwd);
|
|
480
597
|
}
|
|
@@ -486,6 +603,7 @@ async function installPackage(options = {}) {
|
|
|
486
603
|
const uniqueCommandCount = new Set(commandsToInstall.map((command) => command.name)).size;
|
|
487
604
|
const uniqueHookCount = (0, hooks_1.countHooks)(hooks);
|
|
488
605
|
const uniqueSkillCount = skillsToInstall.length;
|
|
606
|
+
const uniqueAgentCount = agentsToInstall.length;
|
|
489
607
|
return {
|
|
490
608
|
success: true,
|
|
491
609
|
installedRuleCount: uniqueRuleCount,
|
|
@@ -493,6 +611,7 @@ async function installPackage(options = {}) {
|
|
|
493
611
|
installedAssetCount: assets.length,
|
|
494
612
|
installedHookCount: uniqueHookCount,
|
|
495
613
|
installedSkillCount: uniqueSkillCount,
|
|
614
|
+
installedAgentCount: uniqueAgentCount,
|
|
496
615
|
packagesCount: 1,
|
|
497
616
|
};
|
|
498
617
|
}
|
|
@@ -505,6 +624,7 @@ async function installPackage(options = {}) {
|
|
|
505
624
|
installedAssetCount: 0,
|
|
506
625
|
installedHookCount: 0,
|
|
507
626
|
installedSkillCount: 0,
|
|
627
|
+
installedAgentCount: 0,
|
|
508
628
|
packagesCount: 0,
|
|
509
629
|
};
|
|
510
630
|
}
|
|
@@ -526,6 +646,7 @@ async function install(options = {}) {
|
|
|
526
646
|
installedAssetCount: 0,
|
|
527
647
|
installedHookCount: 0,
|
|
528
648
|
installedSkillCount: 0,
|
|
649
|
+
installedAgentCount: 0,
|
|
529
650
|
packagesCount: 0,
|
|
530
651
|
};
|
|
531
652
|
}
|
|
@@ -559,6 +680,7 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
559
680
|
const commandCount = result.installedCommandCount;
|
|
560
681
|
const hookCount = result.installedHookCount;
|
|
561
682
|
const skillCount = result.installedSkillCount;
|
|
683
|
+
const agentCount = result.installedAgentCount;
|
|
562
684
|
const ruleMessage = ruleCount > 0 ? `${ruleCount} rule${ruleCount === 1 ? "" : "s"}` : null;
|
|
563
685
|
const commandMessage = commandCount > 0
|
|
564
686
|
? `${commandCount} command${commandCount === 1 ? "" : "s"}`
|
|
@@ -567,6 +689,9 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
567
689
|
const skillMessage = skillCount > 0
|
|
568
690
|
? `${skillCount} skill${skillCount === 1 ? "" : "s"}`
|
|
569
691
|
: null;
|
|
692
|
+
const agentMessage = agentCount > 0
|
|
693
|
+
? `${agentCount} agent${agentCount === 1 ? "" : "s"}`
|
|
694
|
+
: null;
|
|
570
695
|
const countsParts = [];
|
|
571
696
|
if (ruleMessage) {
|
|
572
697
|
countsParts.push(ruleMessage);
|
|
@@ -580,6 +705,9 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
580
705
|
if (skillMessage) {
|
|
581
706
|
countsParts.push(skillMessage);
|
|
582
707
|
}
|
|
708
|
+
if (agentMessage) {
|
|
709
|
+
countsParts.push(agentMessage);
|
|
710
|
+
}
|
|
583
711
|
const countsMessage = countsParts.length > 0
|
|
584
712
|
? countsParts.join(", ").replace(/, ([^,]*)$/, " and $1")
|
|
585
713
|
: "0 rules";
|
|
@@ -594,8 +722,9 @@ async function installCommand(installOnCI, verbose, dryRun) {
|
|
|
594
722
|
else if (ruleCount === 0 &&
|
|
595
723
|
commandCount === 0 &&
|
|
596
724
|
hookCount === 0 &&
|
|
597
|
-
skillCount === 0
|
|
598
|
-
|
|
725
|
+
skillCount === 0 &&
|
|
726
|
+
agentCount === 0) {
|
|
727
|
+
console.log("No rules, commands, hooks, skills, or agents installed");
|
|
599
728
|
}
|
|
600
729
|
else if (result.packagesCount > 1) {
|
|
601
730
|
console.log(`Successfully installed ${countsMessage} across ${result.packagesCount} packages`);
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export interface SkillFile {
|
|
|
52
52
|
source: "local" | "preset";
|
|
53
53
|
presetName?: string;
|
|
54
54
|
}
|
|
55
|
+
export type AgentFile = ManagedFile;
|
|
55
56
|
export interface RuleCollection {
|
|
56
57
|
[target: string]: RuleFile[];
|
|
57
58
|
}
|
|
@@ -61,6 +62,7 @@ export interface ResolvedConfig {
|
|
|
61
62
|
commands: CommandFile[];
|
|
62
63
|
assets: AssetFile[];
|
|
63
64
|
skills: SkillFile[];
|
|
65
|
+
agents: AgentFile[];
|
|
64
66
|
mcpServers: MCPServers;
|
|
65
67
|
hooks: HooksJson;
|
|
66
68
|
hookFiles: HookFile[];
|
|
@@ -80,6 +82,11 @@ export declare function loadAssetsFromDirectory(directoryPath: string, source: "
|
|
|
80
82
|
* Each direct subdirectory containing a SKILL.md file is considered a skill
|
|
81
83
|
*/
|
|
82
84
|
export declare function loadSkillsFromDirectory(directoryPath: string, source: "local" | "preset", presetName?: string): Promise<SkillFile[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Load agents from an agents/ directory
|
|
87
|
+
* Agents are markdown files (.md) with YAML frontmatter
|
|
88
|
+
*/
|
|
89
|
+
export declare function loadAgentsFromDirectory(directoryPath: string, source: "local" | "preset", presetName?: string): Promise<AgentFile[]>;
|
|
83
90
|
/**
|
|
84
91
|
* Extract namespace from preset path for directory structure
|
|
85
92
|
* Handles both npm packages and local paths consistently
|
|
@@ -95,6 +102,7 @@ export declare function loadAllRules(config: Config, cwd: string): Promise<{
|
|
|
95
102
|
commands: CommandFile[];
|
|
96
103
|
assets: AssetFile[];
|
|
97
104
|
skills: SkillFile[];
|
|
105
|
+
agents: AgentFile[];
|
|
98
106
|
mcpServers: MCPServers;
|
|
99
107
|
hooks: HooksJson;
|
|
100
108
|
hookFiles: HookFile[];
|
package/dist/utils/config.js
CHANGED
|
@@ -12,6 +12,7 @@ exports.loadRulesFromDirectory = loadRulesFromDirectory;
|
|
|
12
12
|
exports.loadCommandsFromDirectory = loadCommandsFromDirectory;
|
|
13
13
|
exports.loadAssetsFromDirectory = loadAssetsFromDirectory;
|
|
14
14
|
exports.loadSkillsFromDirectory = loadSkillsFromDirectory;
|
|
15
|
+
exports.loadAgentsFromDirectory = loadAgentsFromDirectory;
|
|
15
16
|
exports.extractNamespaceFromPresetPath = extractNamespaceFromPresetPath;
|
|
16
17
|
exports.resolvePresetPath = resolvePresetPath;
|
|
17
18
|
exports.loadPreset = loadPreset;
|
|
@@ -98,6 +99,7 @@ function validateConfig(config, configFilePath, cwd, isWorkspaceMode = false) {
|
|
|
98
99
|
const hasCommands = fs_extra_1.default.existsSync(node_path_1.default.join(rootPath, "commands"));
|
|
99
100
|
const hasHooks = fs_extra_1.default.existsSync(node_path_1.default.join(rootPath, "hooks.json"));
|
|
100
101
|
const hasSkills = fs_extra_1.default.existsSync(node_path_1.default.join(rootPath, "skills"));
|
|
102
|
+
const hasAgents = fs_extra_1.default.existsSync(node_path_1.default.join(rootPath, "agents"));
|
|
101
103
|
// In workspace mode, root config doesn't need these directories
|
|
102
104
|
// since packages will have their own configurations
|
|
103
105
|
if (!isWorkspaceMode &&
|
|
@@ -105,8 +107,9 @@ function validateConfig(config, configFilePath, cwd, isWorkspaceMode = false) {
|
|
|
105
107
|
!hasCommands &&
|
|
106
108
|
!hasHooks &&
|
|
107
109
|
!hasSkills &&
|
|
110
|
+
!hasAgents &&
|
|
108
111
|
!hasPresets) {
|
|
109
|
-
throw new Error(`Root directory must contain at least one of: rules/, commands/, skills/, hooks.json, or have presets configured`);
|
|
112
|
+
throw new Error(`Root directory must contain at least one of: rules/, commands/, skills/, agents/, hooks.json, or have presets configured`);
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
else if (!isWorkspaceMode && !hasPresets) {
|
|
@@ -235,6 +238,35 @@ async function loadSkillsFromDirectory(directoryPath, source, presetName) {
|
|
|
235
238
|
}
|
|
236
239
|
return skills;
|
|
237
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Load agents from an agents/ directory
|
|
243
|
+
* Agents are markdown files (.md) with YAML frontmatter
|
|
244
|
+
*/
|
|
245
|
+
async function loadAgentsFromDirectory(directoryPath, source, presetName) {
|
|
246
|
+
const agents = [];
|
|
247
|
+
if (!fs_extra_1.default.existsSync(directoryPath)) {
|
|
248
|
+
return agents;
|
|
249
|
+
}
|
|
250
|
+
const pattern = node_path_1.default.join(directoryPath, "**/*.md").replace(/\\/g, "/");
|
|
251
|
+
const filePaths = await (0, fast_glob_1.default)(pattern, {
|
|
252
|
+
onlyFiles: true,
|
|
253
|
+
absolute: true,
|
|
254
|
+
});
|
|
255
|
+
filePaths.sort();
|
|
256
|
+
for (const filePath of filePaths) {
|
|
257
|
+
const content = await fs_extra_1.default.readFile(filePath, "utf8");
|
|
258
|
+
const relativePath = node_path_1.default.relative(directoryPath, filePath);
|
|
259
|
+
const agentName = relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
|
|
260
|
+
agents.push({
|
|
261
|
+
name: agentName,
|
|
262
|
+
content,
|
|
263
|
+
sourcePath: filePath,
|
|
264
|
+
source,
|
|
265
|
+
presetName,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return agents;
|
|
269
|
+
}
|
|
238
270
|
/**
|
|
239
271
|
* Extract namespace from preset path for directory structure
|
|
240
272
|
* Handles both npm packages and local paths consistently
|
|
@@ -292,8 +324,14 @@ async function loadPreset(presetPath, cwd) {
|
|
|
292
324
|
const hasHooks = fs_extra_1.default.existsSync(node_path_1.default.join(presetRootDir, "hooks.json"));
|
|
293
325
|
const hasAssets = fs_extra_1.default.existsSync(node_path_1.default.join(presetRootDir, "assets"));
|
|
294
326
|
const hasSkills = fs_extra_1.default.existsSync(node_path_1.default.join(presetRootDir, "skills"));
|
|
295
|
-
|
|
296
|
-
|
|
327
|
+
const hasAgents = fs_extra_1.default.existsSync(node_path_1.default.join(presetRootDir, "agents"));
|
|
328
|
+
if (!hasRules &&
|
|
329
|
+
!hasCommands &&
|
|
330
|
+
!hasHooks &&
|
|
331
|
+
!hasAssets &&
|
|
332
|
+
!hasSkills &&
|
|
333
|
+
!hasAgents) {
|
|
334
|
+
throw new Error(`Preset "${presetPath}" must have at least one of: rules/, commands/, skills/, agents/, hooks.json, or assets/`);
|
|
297
335
|
}
|
|
298
336
|
return {
|
|
299
337
|
config: presetConfig,
|
|
@@ -305,6 +343,7 @@ async function loadAllRules(config, cwd) {
|
|
|
305
343
|
const allCommands = [];
|
|
306
344
|
const allAssets = [];
|
|
307
345
|
const allSkills = [];
|
|
346
|
+
const allAgents = [];
|
|
308
347
|
const allHookFiles = [];
|
|
309
348
|
const allHooksConfigs = [];
|
|
310
349
|
let mergedMcpServers = { ...config.mcpServers };
|
|
@@ -342,6 +381,12 @@ async function loadAllRules(config, cwd) {
|
|
|
342
381
|
const localSkills = await loadSkillsFromDirectory(skillsPath, "local");
|
|
343
382
|
allSkills.push(...localSkills);
|
|
344
383
|
}
|
|
384
|
+
// Load agents from agents/ subdirectory
|
|
385
|
+
const agentsPath = node_path_1.default.join(rootPath, "agents");
|
|
386
|
+
if (fs_extra_1.default.existsSync(agentsPath)) {
|
|
387
|
+
const localAgents = await loadAgentsFromDirectory(agentsPath, "local");
|
|
388
|
+
allAgents.push(...localAgents);
|
|
389
|
+
}
|
|
345
390
|
}
|
|
346
391
|
// Load presets
|
|
347
392
|
if (config.presets) {
|
|
@@ -379,6 +424,12 @@ async function loadAllRules(config, cwd) {
|
|
|
379
424
|
const presetSkills = await loadSkillsFromDirectory(presetSkillsPath, "preset", presetPath);
|
|
380
425
|
allSkills.push(...presetSkills);
|
|
381
426
|
}
|
|
427
|
+
// Load preset agents from agents/ subdirectory
|
|
428
|
+
const presetAgentsPath = node_path_1.default.join(presetRootDir, "agents");
|
|
429
|
+
if (fs_extra_1.default.existsSync(presetAgentsPath)) {
|
|
430
|
+
const presetAgents = await loadAgentsFromDirectory(presetAgentsPath, "preset", presetPath);
|
|
431
|
+
allAgents.push(...presetAgents);
|
|
432
|
+
}
|
|
382
433
|
// Merge MCP servers from preset
|
|
383
434
|
if (preset.config.mcpServers) {
|
|
384
435
|
mergedMcpServers = mergePresetMcpServers(mergedMcpServers, preset.config.mcpServers);
|
|
@@ -392,6 +443,7 @@ async function loadAllRules(config, cwd) {
|
|
|
392
443
|
commands: allCommands,
|
|
393
444
|
assets: allAssets,
|
|
394
445
|
skills: allSkills,
|
|
446
|
+
agents: allAgents,
|
|
395
447
|
mcpServers: mergedMcpServers,
|
|
396
448
|
hooks: mergedHooks,
|
|
397
449
|
hookFiles: allHookFiles,
|
|
@@ -451,13 +503,14 @@ async function loadConfig(cwd) {
|
|
|
451
503
|
const isWorkspaces = resolveWorkspaces(config, configResult.filepath, workingDir);
|
|
452
504
|
validateConfig(config, configResult.filepath, workingDir, isWorkspaces);
|
|
453
505
|
const configWithDefaults = applyDefaults(config, isWorkspaces);
|
|
454
|
-
const { rules, commands, assets, skills, mcpServers, hooks, hookFiles } = await loadAllRules(configWithDefaults, workingDir);
|
|
506
|
+
const { rules, commands, assets, skills, agents, mcpServers, hooks, hookFiles, } = await loadAllRules(configWithDefaults, workingDir);
|
|
455
507
|
return {
|
|
456
508
|
config: configWithDefaults,
|
|
457
509
|
rules,
|
|
458
510
|
commands,
|
|
459
511
|
assets,
|
|
460
512
|
skills,
|
|
513
|
+
agents,
|
|
461
514
|
mcpServers,
|
|
462
515
|
hooks,
|
|
463
516
|
hookFiles,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a resolved path is safely within the specified base directory.
|
|
3
|
+
* This prevents path traversal attacks where malicious paths like "../../../etc"
|
|
4
|
+
* or absolute paths could escape the intended directory.
|
|
5
|
+
*
|
|
6
|
+
* @param baseDir - The directory that should contain the path
|
|
7
|
+
* @param relativePath - The potentially untrusted relative path
|
|
8
|
+
* @returns The safely resolved full path, or null if the path would escape baseDir
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveSafePath(baseDir: string, relativePath: string): string | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
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.resolveSafePath = resolveSafePath;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
/**
|
|
9
|
+
* Check if a resolved path is safely within the specified base directory.
|
|
10
|
+
* This prevents path traversal attacks where malicious paths like "../../../etc"
|
|
11
|
+
* or absolute paths could escape the intended directory.
|
|
12
|
+
*
|
|
13
|
+
* @param baseDir - The directory that should contain the path
|
|
14
|
+
* @param relativePath - The potentially untrusted relative path
|
|
15
|
+
* @returns The safely resolved full path, or null if the path would escape baseDir
|
|
16
|
+
*/
|
|
17
|
+
function resolveSafePath(baseDir, relativePath) {
|
|
18
|
+
// Resolve both to absolute paths
|
|
19
|
+
const resolvedBase = node_path_1.default.resolve(baseDir);
|
|
20
|
+
const resolvedTarget = node_path_1.default.resolve(baseDir, relativePath);
|
|
21
|
+
// The resolved path must start with the base directory + separator
|
|
22
|
+
// This ensures it's truly inside the directory, not a sibling with similar prefix
|
|
23
|
+
// e.g., /foo/bar should not match /foo/bar-other
|
|
24
|
+
if (!resolvedTarget.startsWith(resolvedBase + node_path_1.default.sep)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return resolvedTarget;
|
|
28
|
+
}
|