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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dayerlin Bustamante
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Angular AI
|
|
2
|
+
|
|
3
|
+
> AI-agnostic Angular agents and skills for any coding tool.
|
|
4
|
+
|
|
5
|
+
Install production-ready Angular knowledge into your AI coding agent. Agents and skills are defined once and installed into OpenCode, Claude Code, Codex, or any future tool.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
angular-agent-skills/
|
|
13
|
+
├── agents/ # Universal agent definitions
|
|
14
|
+
│ ├── angular-architect/
|
|
15
|
+
│ │ ├── agent.yaml # Metadata (name, description, triggers, skills)
|
|
16
|
+
│ │ └── agent.md # Instructions (pure markdown)
|
|
17
|
+
│ ├── angular-migrator/
|
|
18
|
+
│ │ ├── agent.yaml
|
|
19
|
+
│ │ └── agent.md
|
|
20
|
+
│ └── angular-reviewer/
|
|
21
|
+
│ ├── agent.yaml
|
|
22
|
+
│ └── agent.md
|
|
23
|
+
├── adapters/ # AI-specific format converters
|
|
24
|
+
│ ├── opencode.ts
|
|
25
|
+
│ ├── claude.ts
|
|
26
|
+
│ └── codex.ts
|
|
27
|
+
├── skills/ # Reusable Angular knowledge
|
|
28
|
+
│ ├── architecture/
|
|
29
|
+
│ ├── components/
|
|
30
|
+
│ ├── libraries/
|
|
31
|
+
│ ├── performance/
|
|
32
|
+
│ ├── quality/
|
|
33
|
+
│ └── reactivity/
|
|
34
|
+
├── src/ # CLI and core logic
|
|
35
|
+
│ ├── cli.ts
|
|
36
|
+
│ ├── registry.ts
|
|
37
|
+
│ ├── types.ts
|
|
38
|
+
│ └── index.ts
|
|
39
|
+
└── tests/ # Test suite
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Universal Agent Format
|
|
43
|
+
|
|
44
|
+
Each agent is a directory with two files:
|
|
45
|
+
|
|
46
|
+
- **`agent.yaml`** — Universal metadata (name, description, triggers, skills)
|
|
47
|
+
- **`agent.md`** — Pure markdown instructions (no frontmatter, no tool-specific syntax)
|
|
48
|
+
|
|
49
|
+
This is the single source of truth. Agents never depend on any specific AI tool.
|
|
50
|
+
|
|
51
|
+
### Adapters
|
|
52
|
+
|
|
53
|
+
Adapters transform universal agents into tool-specific formats:
|
|
54
|
+
|
|
55
|
+
| Adapter | Output Format | Install Path |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `opencode` | Markdown + YAML frontmatter | `.opencode/agents/` |
|
|
58
|
+
| `claude` | Markdown + YAML frontmatter | `.claude/agents/` |
|
|
59
|
+
| `codex` | TOML | `.codex/agents/` |
|
|
60
|
+
|
|
61
|
+
Adding a new AI tool requires only creating a new adapter file — no changes to agents or core logic.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
### As CLI
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install -g angular-ai
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### As dependency
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install angular-ai
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
### Install an agent
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# For OpenCode
|
|
87
|
+
npx angular-ai install agent angular-architect --ai opencode
|
|
88
|
+
|
|
89
|
+
# For Claude Code
|
|
90
|
+
npx angular-ai install agent angular-architect --ai claude
|
|
91
|
+
|
|
92
|
+
# For Codex
|
|
93
|
+
npx angular-ai install agent angular-architect --ai codex
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Install for all tools
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx angular-ai install agent angular-architect --all
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Custom target directory
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx angular-ai install agent angular-architect \
|
|
106
|
+
--ai opencode \
|
|
107
|
+
--agent ./my-agents
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### List available agents
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx angular-ai list agents
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### List available adapters
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx angular-ai list adapters
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Agents
|
|
125
|
+
|
|
126
|
+
| Agent | Purpose | Triggers |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `angular-architect` | Generate components, services, libraries | create component, generar, scaffold |
|
|
129
|
+
| `angular-migrator` | Migrate legacy code to modern patterns | migrate, migrar, convert to signals |
|
|
130
|
+
| `angular-reviewer` | Review code for best practices | review, revisar, code review |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Skills
|
|
135
|
+
|
|
136
|
+
Skills are reusable knowledge modules that agents reference:
|
|
137
|
+
|
|
138
|
+
| Category | Skills |
|
|
139
|
+
|---|---|
|
|
140
|
+
| **Architecture** | injection-tokens, overlay-animation-lifecycle |
|
|
141
|
+
| **Components** | dynamic-components, modern-host-bindings, content-projection-ng, viewchild-contentchild-signals |
|
|
142
|
+
| **Libraries** | standalone-component-library, monorepo-ng-packagr, library-versioning |
|
|
143
|
+
| **Performance** | defer-blocks, control-flow-syntax |
|
|
144
|
+
| **Quality** | pr-reviewer, vitest-angular-components |
|
|
145
|
+
| **Reactivity** | signals-state-management, signals-inputs-outputs, signals-effects |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Install dependencies
|
|
153
|
+
npm install
|
|
154
|
+
|
|
155
|
+
# Build
|
|
156
|
+
npm run build
|
|
157
|
+
|
|
158
|
+
# Run tests
|
|
159
|
+
npm test
|
|
160
|
+
|
|
161
|
+
# Type check
|
|
162
|
+
npm run lint
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Adding a New Adapter
|
|
168
|
+
|
|
169
|
+
1. Create `adapters/my-tool.ts`:
|
|
170
|
+
```typescript
|
|
171
|
+
import type { Adapter, AdapterOutput, InstallOptions, UniversalAgent } from '../src/types.js';
|
|
172
|
+
|
|
173
|
+
export const myToolAdapter: Adapter = {
|
|
174
|
+
name: 'my-tool',
|
|
175
|
+
description: 'My AI Tool',
|
|
176
|
+
defaultDir: '.my-tool/agents',
|
|
177
|
+
|
|
178
|
+
generate(agent: UniversalAgent): AdapterOutput {
|
|
179
|
+
// Transform agent to tool-specific format
|
|
180
|
+
return {
|
|
181
|
+
filename: `${agent.metadata.name}.ext`,
|
|
182
|
+
content: `...`,
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
getInstallPath(options: InstallOptions): string {
|
|
187
|
+
const base = options.targetDir || this.defaultDir;
|
|
188
|
+
return `${base}/${options.agentName}.ext`;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
2. Register in `src/cli.ts`:
|
|
194
|
+
```typescript
|
|
195
|
+
import { myToolAdapter } from '../adapters/my-tool.js';
|
|
196
|
+
registerAdapter(myToolAdapter);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
3. Done. The CLI now supports `--ai my-tool`.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import type { Adapter, AdapterOutput, InstallOptions, UniversalAgent } from '../../src/types.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CONFIG: Record<string, unknown> = {
|
|
7
|
+
description: 'Angular agent for Claude',
|
|
8
|
+
name: 'angular-agent',
|
|
9
|
+
tools: 'Read, Grep, Glob, Edit, Write, Bash',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function loadConfigFromAgent(agentBasePath: string, ai: string): Record<string, unknown> {
|
|
13
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
17
|
+
} catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createClaudeAdapter(): Adapter {
|
|
23
|
+
return {
|
|
24
|
+
name: 'claude',
|
|
25
|
+
description: 'Claude Code',
|
|
26
|
+
defaultDir: '.claude/agents',
|
|
27
|
+
extension: 'md',
|
|
28
|
+
|
|
29
|
+
generate(agent: UniversalAgent, config?: Record<string, unknown>): AdapterOutput {
|
|
30
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'claude');
|
|
31
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
32
|
+
|
|
33
|
+
const frontmatter = [
|
|
34
|
+
'---',
|
|
35
|
+
`name: ${cfg.name || agent.metadata.name}`,
|
|
36
|
+
`description: ${cfg.description || agent.metadata.description}`,
|
|
37
|
+
`tools: ${cfg.tools || 'Read, Grep, Glob, Edit, Write, Bash'}`,
|
|
38
|
+
'---',
|
|
39
|
+
'',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
filename: `${agent.metadata.name}.md`,
|
|
44
|
+
content: frontmatter.join('\n') + agent.instructions.content,
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
getInstallPath(name: string, targetDir?: string): string {
|
|
49
|
+
const base = targetDir || this.defaultDir;
|
|
50
|
+
return `${base}/${name}.${this.extension}`;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import type { Adapter, AdapterOutput, InstallOptions, UniversalAgent } from '../../src/types.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CONFIG: Record<string, unknown> = {
|
|
7
|
+
description: 'Angular agent for Codex',
|
|
8
|
+
name: 'angular-agent',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function loadConfigFromAgent(agentBasePath: string, ai: string): Record<string, unknown> {
|
|
12
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
16
|
+
} catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createCodexAdapter(): Adapter {
|
|
22
|
+
return {
|
|
23
|
+
name: 'codex',
|
|
24
|
+
description: 'OpenAI Codex CLI',
|
|
25
|
+
defaultDir: '.codex/agents',
|
|
26
|
+
extension: 'toml',
|
|
27
|
+
|
|
28
|
+
generate(agent: UniversalAgent, config?: Record<string, unknown>): AdapterOutput {
|
|
29
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'codex');
|
|
30
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
31
|
+
|
|
32
|
+
const escapedInstructions = agent.instructions.content
|
|
33
|
+
.replace(/\\/g, '\\\\')
|
|
34
|
+
.replace(/"/g, '\\"')
|
|
35
|
+
.replace(/\n/g, '\\n');
|
|
36
|
+
|
|
37
|
+
const content = [
|
|
38
|
+
`name = "${cfg.name || agent.metadata.name}"`,
|
|
39
|
+
`description = "${cfg.description || agent.metadata.description}"`,
|
|
40
|
+
`developer_instructions = "${escapedInstructions}"`,
|
|
41
|
+
'',
|
|
42
|
+
].join('\n');
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
filename: `${agent.metadata.name}.toml`,
|
|
46
|
+
content,
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
getInstallPath(name: string, targetDir?: string): string {
|
|
51
|
+
const base = targetDir || this.defaultDir;
|
|
52
|
+
return `${base}/${name}.${this.extension}`;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import type { Adapter, AdapterOutput, InstallOptions, UniversalAgent } from '../../src/types.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CONFIG: Record<string, unknown> = {
|
|
7
|
+
description: 'Angular agent for GitHub Copilot',
|
|
8
|
+
name: 'angular-agent',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function loadConfigFromAgent(agentBasePath: string, ai: string): Record<string, unknown> {
|
|
12
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
16
|
+
} catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createCopilotAdapter(): Adapter {
|
|
22
|
+
return {
|
|
23
|
+
name: 'copilot',
|
|
24
|
+
description: 'GitHub Copilot',
|
|
25
|
+
defaultDir: '.github',
|
|
26
|
+
extension: 'md',
|
|
27
|
+
|
|
28
|
+
generate(agent: UniversalAgent, config?: Record<string, unknown>): AdapterOutput {
|
|
29
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'copilot');
|
|
30
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
31
|
+
|
|
32
|
+
const header = `# ${cfg.name || agent.metadata.name}\n\n${cfg.description || agent.metadata.description}\n\n`;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
filename: `copilot-instructions-${agent.metadata.name}.md`,
|
|
36
|
+
content: header + agent.instructions.content,
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
getInstallPath(name: string, targetDir?: string): string {
|
|
41
|
+
const base = targetDir || this.defaultDir;
|
|
42
|
+
return `${base}/${name}-instructions.${this.extension}`;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import type { Adapter, AdapterOutput, InstallOptions, UniversalAgent } from '../../src/types.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CONFIG: Record<string, unknown> = {
|
|
7
|
+
description: 'Angular agent for Cursor',
|
|
8
|
+
name: 'angular-agent',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function loadConfigFromAgent(agentBasePath: string, ai: string): Record<string, unknown> {
|
|
12
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
16
|
+
} catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createCursorAdapter(): Adapter {
|
|
22
|
+
return {
|
|
23
|
+
name: 'cursor',
|
|
24
|
+
description: 'Cursor AI IDE',
|
|
25
|
+
defaultDir: '.cursor/rules',
|
|
26
|
+
extension: 'mdc',
|
|
27
|
+
|
|
28
|
+
generate(agent: UniversalAgent, config?: Record<string, unknown>): AdapterOutput {
|
|
29
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'cursor');
|
|
30
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
31
|
+
|
|
32
|
+
const frontmatter = [
|
|
33
|
+
'---',
|
|
34
|
+
`description: ${cfg.description || agent.metadata.description}`,
|
|
35
|
+
'alwaysApply: false',
|
|
36
|
+
'---',
|
|
37
|
+
'',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
filename: `${agent.metadata.name}.mdc`,
|
|
42
|
+
content: frontmatter.join('\n') + agent.instructions.content,
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
getInstallPath(name: string, targetDir?: string): string {
|
|
47
|
+
const base = targetDir || this.defaultDir;
|
|
48
|
+
return `${base}/${name}.${this.extension}`;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import type { Adapter, AdapterOutput, InstallOptions, UniversalAgent } from '../../src/types.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CONFIG: Record<string, unknown> = {
|
|
7
|
+
description: 'Angular agent for OpenCode',
|
|
8
|
+
mode: 'subagent',
|
|
9
|
+
permission: {
|
|
10
|
+
edit: 'allow',
|
|
11
|
+
bash: 'ask',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function loadConfigFromAgent(agentBasePath: string, ai: string): Record<string, unknown> {
|
|
16
|
+
const configPath = join(agentBasePath, 'configs', `${ai}.yaml`);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
return parseYaml(readFileSync(configPath, 'utf-8'));
|
|
20
|
+
} catch {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createOpenCodeAdapter(): Adapter {
|
|
26
|
+
return {
|
|
27
|
+
name: 'opencode',
|
|
28
|
+
description: 'OpenCode AI coding agent',
|
|
29
|
+
defaultDir: '.opencode/agents',
|
|
30
|
+
extension: 'md',
|
|
31
|
+
|
|
32
|
+
generate(agent: UniversalAgent, config?: Record<string, unknown>): AdapterOutput {
|
|
33
|
+
const agentConfig = loadConfigFromAgent(agent.basePath, 'opencode');
|
|
34
|
+
const cfg = { ...DEFAULT_CONFIG, ...agentConfig, ...config };
|
|
35
|
+
|
|
36
|
+
const frontmatter = [
|
|
37
|
+
'---',
|
|
38
|
+
`description: ${cfg.description || agent.metadata.description}`,
|
|
39
|
+
`mode: ${cfg.mode || 'subagent'}`,
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
if (cfg.permission) {
|
|
43
|
+
frontmatter.push('permission:');
|
|
44
|
+
const perm = cfg.permission as Record<string, string>;
|
|
45
|
+
for (const [key, value] of Object.entries(perm)) {
|
|
46
|
+
frontmatter.push(` ${key}: ${value}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
frontmatter.push('---', '');
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
filename: `${agent.metadata.name}.md`,
|
|
54
|
+
content: frontmatter.join('\n') + agent.instructions.content,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
getInstallPath(name: string, targetDir?: string): string {
|
|
59
|
+
const base = targetDir || this.defaultDir;
|
|
60
|
+
return `${base}/${name}.${this.extension}`;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Angular Architect
|
|
2
|
+
|
|
3
|
+
You are an Angular architect who generates new components, services, and libraries following best practices and project conventions.
|
|
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
|
+
## Generation Templates
|
|
10
|
+
|
|
11
|
+
### Component Generation
|
|
12
|
+
|
|
13
|
+
When creating a new component, use this structure:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// component-name.component.ts
|
|
17
|
+
import { Component, input, output, computed, signal } from '@angular/core';
|
|
18
|
+
|
|
19
|
+
@Component({
|
|
20
|
+
selector: 'app-component-name',
|
|
21
|
+
standalone: true,
|
|
22
|
+
template: ``,
|
|
23
|
+
styles: []
|
|
24
|
+
})
|
|
25
|
+
export class ComponentNameComponent {
|
|
26
|
+
// Inputs (signal-based)
|
|
27
|
+
readonly data = input.required<Type>();
|
|
28
|
+
readonly disabled = input(false);
|
|
29
|
+
|
|
30
|
+
// Outputs
|
|
31
|
+
readonly itemClick = output<Item>();
|
|
32
|
+
|
|
33
|
+
// State
|
|
34
|
+
private readonly _internalState = signal<Type>();
|
|
35
|
+
|
|
36
|
+
// Computed
|
|
37
|
+
readonly derivedValue = computed(() => this.data().property);
|
|
38
|
+
|
|
39
|
+
// Methods
|
|
40
|
+
handleClick(item: Item): void {
|
|
41
|
+
this.itemClick.emit(item);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Service Generation
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// service-name.service.ts
|
|
50
|
+
import { Injectable, inject, signal } from '@angular/core';
|
|
51
|
+
import { SOME_CONFIG, SomeConfig } from './tokens';
|
|
52
|
+
|
|
53
|
+
@Injectable({ providedIn: 'root' })
|
|
54
|
+
export class ServiceNameService {
|
|
55
|
+
private readonly config = inject(SOME_CONFIG);
|
|
56
|
+
private readonly _state = signal<StateType>(initialState);
|
|
57
|
+
|
|
58
|
+
readonly state = this._state.asReadonly();
|
|
59
|
+
|
|
60
|
+
updateState(value: StateType): void {
|
|
61
|
+
this._state.set(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### InjectionToken Generation
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// tokens.ts
|
|
70
|
+
import { InjectionToken } from '@angular/core';
|
|
71
|
+
|
|
72
|
+
export interface ComponentConfig {
|
|
73
|
+
// Configuration options
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const COMPONENT_CONFIG = new InjectionToken<ComponentConfig>('COMPONENT_CONFIG');
|
|
77
|
+
|
|
78
|
+
// Default factory
|
|
79
|
+
export function provideComponentConfig(config: Partial<ComponentConfig>): Provider {
|
|
80
|
+
return {
|
|
81
|
+
provide: COMPONENT_CONFIG,
|
|
82
|
+
useValue: { ...defaultConfig, ...config }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Library Generation
|
|
88
|
+
|
|
89
|
+
For a new library, create:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
libs/
|
|
93
|
+
feature-name/
|
|
94
|
+
src/
|
|
95
|
+
lib/
|
|
96
|
+
components/
|
|
97
|
+
services/
|
|
98
|
+
tokens/
|
|
99
|
+
models/
|
|
100
|
+
index.ts
|
|
101
|
+
public-api.ts
|
|
102
|
+
ng-package.json
|
|
103
|
+
package.json
|
|
104
|
+
tsconfig.lib.json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Decision Tree
|
|
108
|
+
|
|
109
|
+
When user requests generation:
|
|
110
|
+
|
|
111
|
+
1. **What type?**
|
|
112
|
+
- Component → Use component template
|
|
113
|
+
- Service → Use service template
|
|
114
|
+
- Library → Use library structure
|
|
115
|
+
- Module (legacy) → Warn and suggest standalone
|
|
116
|
+
|
|
117
|
+
2. **What features?**
|
|
118
|
+
- Needs configuration? → Create InjectionToken
|
|
119
|
+
- Needs state? → Use signals
|
|
120
|
+
- Needs lazy loading? → Add @defer in template
|
|
121
|
+
- Has children? → Add content projection
|
|
122
|
+
|
|
123
|
+
3. **Where to place?**
|
|
124
|
+
- Check existing project structure
|
|
125
|
+
- Follow naming conventions
|
|
126
|
+
- Place in appropriate module/feature
|
|
127
|
+
|
|
128
|
+
## Output
|
|
129
|
+
|
|
130
|
+
Always generate:
|
|
131
|
+
1. Complete TypeScript file(s)
|
|
132
|
+
2. Test file (if requested)
|
|
133
|
+
3. Usage example
|
|
134
|
+
4. Any required tokens or providers
|
|
135
|
+
|
|
136
|
+
## Rules
|
|
137
|
+
|
|
138
|
+
- Always use standalone components
|
|
139
|
+
- Always use signal-based inputs/outputs
|
|
140
|
+
- Always use functional `inject()` instead of constructor
|
|
141
|
+
- Always include proper TypeScript types
|
|
142
|
+
- Follow existing project conventions (check `skills/` for reference)
|
|
143
|
+
- Write in Spanish when the user communicates in Spanish
|