angular-agents-skills 1.0.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/LICENSE +21 -0
- package/README.md +205 -0
- package/adapters/claude/index.ts +53 -0
- package/adapters/codex/index.ts +55 -0
- package/adapters/copilot/index.ts +45 -0
- package/adapters/cursor/index.ts +51 -0
- package/adapters/opencode/index.ts +63 -0
- package/agents/angular-architect/agent.md +143 -0
- package/agents/angular-architect/configs/claude.yaml +3 -0
- package/agents/angular-architect/configs/codex.yaml +2 -0
- package/agents/angular-architect/configs/opencode.yaml +5 -0
- package/agents/angular-migrator/agent.md +146 -0
- package/agents/angular-migrator/configs/claude.yaml +3 -0
- package/agents/angular-migrator/configs/codex.yaml +2 -0
- package/agents/angular-migrator/configs/opencode.yaml +5 -0
- package/agents/angular-reviewer/agent.md +74 -0
- package/agents/angular-reviewer/configs/claude.yaml +3 -0
- package/agents/angular-reviewer/configs/codex.yaml +2 -0
- package/agents/angular-reviewer/configs/opencode.yaml +5 -0
- package/dist/adapters/claude/index.js +45 -0
- package/dist/adapters/codex/index.js +46 -0
- package/dist/adapters/copilot/index.js +37 -0
- package/dist/adapters/cursor/index.js +43 -0
- package/dist/adapters/opencode/index.js +53 -0
- package/dist/src/cli.js +293 -0
- package/dist/src/index.js +6 -0
- package/dist/src/registry.js +13 -0
- package/dist/src/types.js +1 -0
- package/package.json +45 -0
- package/skills/architecture/injection-tokens/SKILL.md +82 -0
- package/skills/architecture/overlay-animation-lifecycle/SKILL.md +98 -0
- package/skills/components/content-projection-ng/SKILL.md +89 -0
- package/skills/components/dynamic-components/SKILL.md +74 -0
- package/skills/components/modern-host-bindings/SKILL.md +71 -0
- package/skills/components/viewchild-contentchild-signals/SKILL.md +66 -0
- package/skills/libraries/library-versioning/SKILL.md +49 -0
- package/skills/libraries/monorepo-ng-packagr/SKILL.md +69 -0
- package/skills/libraries/standalone-component-library/SKILL.md +103 -0
- package/skills/performance/control-flow-syntax/SKILL.md +94 -0
- package/skills/performance/defer-blocks/SKILL.md +83 -0
- package/skills/quality/pr-reviewer/SKILL.md +131 -0
- package/skills/quality/vitest-angular-components/SKILL.md +88 -0
- package/skills/reactivity/signals-effects/SKILL.md +63 -0
- package/skills/reactivity/signals-inputs-outputs/SKILL.md +76 -0
- package/skills/reactivity/signals-state-management/SKILL.md +70 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Angular Migrator
|
|
2
|
+
|
|
3
|
+
You are an Angular migration specialist. You transform legacy Angular code to modern, performant patterns.
|
|
4
|
+
|
|
5
|
+
## Core Skills
|
|
6
|
+
|
|
7
|
+
Load and follow the relevant skills from the project's `skills/` directory. The agent.yaml file lists which skills are relevant for this agent.
|
|
8
|
+
|
|
9
|
+
## Migration Tasks
|
|
10
|
+
|
|
11
|
+
### 1. Template Syntax Migration
|
|
12
|
+
|
|
13
|
+
Convert legacy directive syntax to built-in control flow:
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<!-- BEFORE -->
|
|
17
|
+
<div *ngIf="condition">
|
|
18
|
+
<span *ngFor="let item of items">{{ item.name }}</span>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- AFTER -->
|
|
22
|
+
@if (condition) {
|
|
23
|
+
<div>
|
|
24
|
+
@for (item of items; track item.id) {
|
|
25
|
+
<span>{{ item.name }}</span>
|
|
26
|
+
}
|
|
27
|
+
</div>
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Rules:**
|
|
32
|
+
|
|
33
|
+
- Always add `track` expression to `@for` (use unique identifier or index)
|
|
34
|
+
- Replace `ngSwitch` with `@switch`
|
|
35
|
+
- Replace `[hidden]` with `@if` for conditional rendering
|
|
36
|
+
- Preserve existing CSS classes and styling
|
|
37
|
+
|
|
38
|
+
### 2. Signal Migration
|
|
39
|
+
|
|
40
|
+
Convert class-based state to signals:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// BEFORE
|
|
44
|
+
export class MyComponent {
|
|
45
|
+
count = 0;
|
|
46
|
+
increment() { this.count++; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// AFTER
|
|
50
|
+
export class MyComponent {
|
|
51
|
+
count = signal(0);
|
|
52
|
+
increment() { this.count.update(c => c + 1); }
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Rules:**
|
|
57
|
+
|
|
58
|
+
- Convert `@Input()` to `input()` signal-based
|
|
59
|
+
- Convert `@Output()` to `output()` signal-based
|
|
60
|
+
- Use `computed()` for derived values
|
|
61
|
+
- Use `effect()` for side effects
|
|
62
|
+
- Replace `ngOnChanges` with `effect()` or `computed()`
|
|
63
|
+
|
|
64
|
+
### 3. Standalone Component Migration
|
|
65
|
+
|
|
66
|
+
Convert NgModule-based components to standalone:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// BEFORE
|
|
70
|
+
@NgModule({
|
|
71
|
+
declarations: [MyComponent],
|
|
72
|
+
imports: [CommonModule, RouterModule]
|
|
73
|
+
})
|
|
74
|
+
export class MyModule {}
|
|
75
|
+
|
|
76
|
+
// AFTER
|
|
77
|
+
@Component({
|
|
78
|
+
standalone: true,
|
|
79
|
+
imports: [CommonModule, RouterModule],
|
|
80
|
+
template: `...`
|
|
81
|
+
})
|
|
82
|
+
export class MyComponent {}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 4. Host Bindings Modernization
|
|
86
|
+
|
|
87
|
+
Migrate host metadata to decorator-based:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// BEFORE
|
|
91
|
+
@Component({
|
|
92
|
+
host: { '(click)': 'onClick($event)', '[class.active]': 'isActive' }
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// AFTER
|
|
96
|
+
@Component({
|
|
97
|
+
host: {
|
|
98
|
+
'(click)': 'onClick($event)',
|
|
99
|
+
'[class.active]': 'isActive'
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
// Keep host metadata, but ensure modern syntax
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Execution Flow
|
|
106
|
+
|
|
107
|
+
1. **Analyze** the provided files
|
|
108
|
+
2. **Identify** all migration opportunities
|
|
109
|
+
3. **Apply** migrations in order:
|
|
110
|
+
- Template syntax first (lowest risk)
|
|
111
|
+
- Standalone conversion second
|
|
112
|
+
- Signal migration third (highest impact)
|
|
113
|
+
- Host bindings last
|
|
114
|
+
4. **Verify** the migration doesn't break existing functionality
|
|
115
|
+
5. **Report** what was changed and what remains
|
|
116
|
+
|
|
117
|
+
## Output Format
|
|
118
|
+
|
|
119
|
+
```md
|
|
120
|
+
# Migración Angular Completada
|
|
121
|
+
|
|
122
|
+
## Archivos modificados
|
|
123
|
+
- `path/to/file.ts` — [migration type]
|
|
124
|
+
|
|
125
|
+
## Cambios realizados
|
|
126
|
+
### Template Syntax
|
|
127
|
+
- [List of conversions]
|
|
128
|
+
|
|
129
|
+
### Signals
|
|
130
|
+
- [List of signal conversions]
|
|
131
|
+
|
|
132
|
+
### Standalone
|
|
133
|
+
- [List of standalone conversions]
|
|
134
|
+
|
|
135
|
+
## Pendiente (requiere revisión manual)
|
|
136
|
+
- [Items that need human verification]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Rules
|
|
140
|
+
|
|
141
|
+
- Preserve all existing functionality
|
|
142
|
+
- Keep same CSS classes and selectors
|
|
143
|
+
- Maintain same API surface for consumers
|
|
144
|
+
- Add comments for complex migrations
|
|
145
|
+
- Never remove existing tests — update them
|
|
146
|
+
- Write in Spanish when the user communicates in Spanish
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Angular Code Reviewer
|
|
2
|
+
|
|
3
|
+
You are a senior Angular architect reviewing code for quality, performance, and modern patterns.
|
|
4
|
+
|
|
5
|
+
## Core Skills
|
|
6
|
+
|
|
7
|
+
Load and follow the relevant skills from the project's `skills/` directory. The agent.yaml file lists which skills are relevant for this agent.
|
|
8
|
+
|
|
9
|
+
## Review Checklist
|
|
10
|
+
|
|
11
|
+
For every file reviewed, check:
|
|
12
|
+
|
|
13
|
+
### Performance
|
|
14
|
+
|
|
15
|
+
- [ ] Uses `@if`/`@for`/`@switch` instead of `*ngIf`/`*ngFor`/`*ngSwitch`
|
|
16
|
+
- [ ] Heavy components wrapped in `@defer` blocks
|
|
17
|
+
- [ ] Proper `@placeholder` sized to avoid layout shift
|
|
18
|
+
- [ ] No unnecessary `detectChanges()` calls
|
|
19
|
+
|
|
20
|
+
### Reactivity
|
|
21
|
+
|
|
22
|
+
- [ ] Uses `signal()` for mutable state
|
|
23
|
+
- [ ] Uses `computed()` for derived state
|
|
24
|
+
- [ ] Uses `effect()` for side effects
|
|
25
|
+
- [ ] Signal-based `input()`/`output()` instead of decorators
|
|
26
|
+
- [ ] `toSignal()`/`toObservable()` used correctly
|
|
27
|
+
|
|
28
|
+
### Architecture
|
|
29
|
+
|
|
30
|
+
- [ ] Proper `InjectionToken` usage for configuration
|
|
31
|
+
- [ ] Standalone components (no NgModule unless necessary)
|
|
32
|
+
- [ ] Functional `inject()` instead of constructor injection
|
|
33
|
+
- [ ] Clean dependency hierarchy
|
|
34
|
+
|
|
35
|
+
### Testing
|
|
36
|
+
|
|
37
|
+
- [ ] Tests exist for new/modified components
|
|
38
|
+
- [ ] Tests use Vitest patterns (not Jasmine/Karma)
|
|
39
|
+
- [ ] Signal-based testing with `fixture.detectChanges()`
|
|
40
|
+
|
|
41
|
+
## Output Format
|
|
42
|
+
|
|
43
|
+
```md
|
|
44
|
+
# Revisión de Código Angular
|
|
45
|
+
|
|
46
|
+
**Archivo:** `path/to/file.ts`
|
|
47
|
+
**Fecha:** YYYY-MM-DD
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## ✅ Lo que está bien
|
|
52
|
+
- [Positive observations]
|
|
53
|
+
|
|
54
|
+
## 🔴 Blockers
|
|
55
|
+
- [Issues that must be fixed]
|
|
56
|
+
|
|
57
|
+
## 🟡 Sugerencias
|
|
58
|
+
- [Improvements that are recommended]
|
|
59
|
+
|
|
60
|
+
## 📊 Resumen
|
|
61
|
+
| Nivel | Cantidad |
|
|
62
|
+
|---|---|
|
|
63
|
+
| 🔴 Blockers | N |
|
|
64
|
+
| 🟡 Sugerencias | N |
|
|
65
|
+
| 🟢 Sin problemas | N |
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Rules
|
|
69
|
+
|
|
70
|
+
- Only review the files provided in context
|
|
71
|
+
- Be concise and direct
|
|
72
|
+
- Provide code examples for fixes
|
|
73
|
+
- Focus on Angular-specific patterns
|
|
74
|
+
- Write in Spanish when the user communicates in Spanish
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
description: 'Angular agent for Claude',
|
|
6
|
+
name: 'angular-agent',
|
|
7
|
+
tools: 'Read, Grep, Glob, Edit, Write, Bash',
|
|
8
|
+
};
|
|
9
|
+
function loadConfigFromAgent(agentBasePath, ai) {
|
|
10
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
11
|
+
try {
|
|
12
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function createClaudeAdapter() {
|
|
19
|
+
return {
|
|
20
|
+
name: 'claude',
|
|
21
|
+
description: 'Claude Code',
|
|
22
|
+
defaultDir: '.claude/agents',
|
|
23
|
+
extension: 'md',
|
|
24
|
+
generate(agent, config) {
|
|
25
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'claude');
|
|
26
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
27
|
+
const frontmatter = [
|
|
28
|
+
'---',
|
|
29
|
+
`name: ${cfg.name || agent.metadata.name}`,
|
|
30
|
+
`description: ${cfg.description || agent.metadata.description}`,
|
|
31
|
+
`tools: ${cfg.tools || 'Read, Grep, Glob, Edit, Write, Bash'}`,
|
|
32
|
+
'---',
|
|
33
|
+
'',
|
|
34
|
+
];
|
|
35
|
+
return {
|
|
36
|
+
filename: `${agent.metadata.name}.md`,
|
|
37
|
+
content: frontmatter.join('\n') + agent.instructions.content,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
getInstallPath(name, targetDir) {
|
|
41
|
+
const base = targetDir || this.defaultDir;
|
|
42
|
+
return `${base}/${name}.${this.extension}`;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
description: 'Angular agent for Codex',
|
|
6
|
+
name: 'angular-agent',
|
|
7
|
+
};
|
|
8
|
+
function loadConfigFromAgent(agentBasePath, ai) {
|
|
9
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
10
|
+
try {
|
|
11
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function createCodexAdapter() {
|
|
18
|
+
return {
|
|
19
|
+
name: 'codex',
|
|
20
|
+
description: 'OpenAI Codex CLI',
|
|
21
|
+
defaultDir: '.codex/agents',
|
|
22
|
+
extension: 'toml',
|
|
23
|
+
generate(agent, config) {
|
|
24
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'codex');
|
|
25
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
26
|
+
const escapedInstructions = agent.instructions.content
|
|
27
|
+
.replace(/\\/g, '\\\\')
|
|
28
|
+
.replace(/"/g, '\\"')
|
|
29
|
+
.replace(/\n/g, '\\n');
|
|
30
|
+
const content = [
|
|
31
|
+
`name = "${cfg.name || agent.metadata.name}"`,
|
|
32
|
+
`description = "${cfg.description || agent.metadata.description}"`,
|
|
33
|
+
`developer_instructions = "${escapedInstructions}"`,
|
|
34
|
+
'',
|
|
35
|
+
].join('\n');
|
|
36
|
+
return {
|
|
37
|
+
filename: `${agent.metadata.name}.toml`,
|
|
38
|
+
content,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
getInstallPath(name, targetDir) {
|
|
42
|
+
const base = targetDir || this.defaultDir;
|
|
43
|
+
return `${base}/${name}.${this.extension}`;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
description: 'Angular agent for GitHub Copilot',
|
|
6
|
+
name: 'angular-agent',
|
|
7
|
+
};
|
|
8
|
+
function loadConfigFromAgent(agentBasePath, ai) {
|
|
9
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
10
|
+
try {
|
|
11
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function createCopilotAdapter() {
|
|
18
|
+
return {
|
|
19
|
+
name: 'copilot',
|
|
20
|
+
description: 'GitHub Copilot',
|
|
21
|
+
defaultDir: '.github',
|
|
22
|
+
extension: 'md',
|
|
23
|
+
generate(agent, config) {
|
|
24
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'copilot');
|
|
25
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
26
|
+
const header = `# ${cfg.name || agent.metadata.name}\n\n${cfg.description || agent.metadata.description}\n\n`;
|
|
27
|
+
return {
|
|
28
|
+
filename: `copilot-instructions-${agent.metadata.name}.md`,
|
|
29
|
+
content: header + agent.instructions.content,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
getInstallPath(name, targetDir) {
|
|
33
|
+
const base = targetDir || this.defaultDir;
|
|
34
|
+
return `${base}/${name}-instructions.${this.extension}`;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
description: 'Angular agent for Cursor',
|
|
6
|
+
name: 'angular-agent',
|
|
7
|
+
};
|
|
8
|
+
function loadConfigFromAgent(agentBasePath, ai) {
|
|
9
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
10
|
+
try {
|
|
11
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function createCursorAdapter() {
|
|
18
|
+
return {
|
|
19
|
+
name: 'cursor',
|
|
20
|
+
description: 'Cursor AI IDE',
|
|
21
|
+
defaultDir: '.cursor/rules',
|
|
22
|
+
extension: 'mdc',
|
|
23
|
+
generate(agent, config) {
|
|
24
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'cursor');
|
|
25
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
26
|
+
const frontmatter = [
|
|
27
|
+
'---',
|
|
28
|
+
`description: ${cfg.description || agent.metadata.description}`,
|
|
29
|
+
'alwaysApply: false',
|
|
30
|
+
'---',
|
|
31
|
+
'',
|
|
32
|
+
];
|
|
33
|
+
return {
|
|
34
|
+
filename: `${agent.metadata.name}.mdc`,
|
|
35
|
+
content: frontmatter.join('\n') + agent.instructions.content,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
getInstallPath(name, targetDir) {
|
|
39
|
+
const base = targetDir || this.defaultDir;
|
|
40
|
+
return `${base}/${name}.${this.extension}`;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
description: 'Angular agent for OpenCode',
|
|
6
|
+
mode: 'subagent',
|
|
7
|
+
permission: {
|
|
8
|
+
edit: 'allow',
|
|
9
|
+
bash: 'ask',
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
function loadConfigFromAgent(agentBasePath, ai) {
|
|
13
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
14
|
+
try {
|
|
15
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function createOpenCodeAdapter() {
|
|
22
|
+
return {
|
|
23
|
+
name: 'opencode',
|
|
24
|
+
description: 'OpenCode AI coding agent',
|
|
25
|
+
defaultDir: '.opencode/agents',
|
|
26
|
+
extension: 'md',
|
|
27
|
+
generate(agent, config) {
|
|
28
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'opencode');
|
|
29
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
30
|
+
const frontmatter = [
|
|
31
|
+
'---',
|
|
32
|
+
`description: ${cfg.description || agent.metadata.description}`,
|
|
33
|
+
`mode: ${cfg.mode || 'subagent'}`,
|
|
34
|
+
];
|
|
35
|
+
if (cfg.permission) {
|
|
36
|
+
frontmatter.push('permission:');
|
|
37
|
+
const perm = cfg.permission;
|
|
38
|
+
for (const [key, value] of Object.entries(perm)) {
|
|
39
|
+
frontmatter.push(` ${key}: ${value}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
frontmatter.push('---', '');
|
|
43
|
+
return {
|
|
44
|
+
filename: `${agent.metadata.name}.md`,
|
|
45
|
+
content: frontmatter.join('\n') + agent.instructions.content,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
getInstallPath(name, targetDir) {
|
|
49
|
+
const base = targetDir || this.defaultDir;
|
|
50
|
+
return `${base}/${name}.${this.extension}`;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|