ccsetup 1.1.1 → 1.2.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 +96 -363
- package/bin/create-project.js +1616 -60
- package/bin/lib/claudeInterface.js +209 -0
- package/bin/lib/contextGenerator.js +287 -0
- package/bin/lib/scanner/index.js +28 -0
- package/bin/scan.js +367 -0
- package/lib/aiAgentSelector.js +155 -0
- package/lib/aiMergeHelper.js +112 -0
- package/lib/contextGenerator.js +574 -0
- package/lib/contextMerger.js +812 -0
- package/lib/progressReporter.js +88 -0
- package/lib/scanConfig.js +200 -0
- package/lib/scanner/fileAnalyzer.js +605 -0
- package/lib/scanner/index.js +164 -0
- package/lib/scanner/patterns.js +277 -0
- package/lib/scanner/projectDetector.js +147 -0
- package/lib/templates/README.md +176 -0
- package/lib/templates/catalog.js +230 -0
- package/lib/templates/filter.js +257 -0
- package/lib/templates/index.js +45 -0
- package/lib/templates/metadata/agents.json +413 -0
- package/lib/templates/metadata-extractor.js +329 -0
- package/lib/templates/search.js +356 -0
- package/package.json +11 -4
- package/template/{agents → .claude/agents}/checker.md +29 -0
- package/template/.claude/settings.json +15 -0
- package/template/.claude/skills/prd/SKILL.md +343 -0
- package/template/.claude/skills/ralph/SKILL.md +339 -0
- package/template/CLAUDE.md +39 -21
- package/template/CONTRIBUTING.md +37 -0
- package/template/agents/README.md +15 -171
- package/template/docs/ROADMAP.md +0 -36
- package/template/docs/agent-orchestration.md +24 -141
- package/template/hooks/workflow-selector/index.js +398 -0
- package/template/scripts/ralph/CLAUDE.md +174 -0
- package/template/scripts/ralph/ralph.sh +127 -0
- package/template/tickets/ticket-list.md +17 -68
- package/template/agents/ai-engineer.md +0 -31
- package/template/agents/api-documenter.md +0 -31
- package/template/agents/architect-review.md +0 -42
- package/template/agents/backend-architect.md +0 -29
- package/template/agents/business-analyst.md +0 -34
- package/template/agents/c-pro.md +0 -34
- package/template/agents/cloud-architect.md +0 -31
- package/template/agents/code-reviewer.md +0 -28
- package/template/agents/content-marketer.md +0 -34
- package/template/agents/context-manager.md +0 -63
- package/template/agents/cpp-pro.md +0 -37
- package/template/agents/customer-support.md +0 -34
- package/template/agents/data-engineer.md +0 -31
- package/template/agents/data-scientist.md +0 -28
- package/template/agents/database-admin.md +0 -31
- package/template/agents/database-optimizer.md +0 -31
- package/template/agents/debugger.md +0 -29
- package/template/agents/deployment-engineer.md +0 -31
- package/template/agents/devops-troubleshooter.md +0 -31
- package/template/agents/dx-optimizer.md +0 -62
- package/template/agents/error-detective.md +0 -31
- package/template/agents/frontend-developer.md +0 -30
- package/template/agents/golang-pro.md +0 -31
- package/template/agents/graphql-architect.md +0 -31
- package/template/agents/incident-responder.md +0 -73
- package/template/agents/javascript-pro.md +0 -34
- package/template/agents/legacy-modernizer.md +0 -31
- package/template/agents/ml-engineer.md +0 -31
- package/template/agents/mlops-engineer.md +0 -56
- package/template/agents/mobile-developer.md +0 -31
- package/template/agents/network-engineer.md +0 -31
- package/template/agents/payment-integration.md +0 -31
- package/template/agents/performance-engineer.md +0 -31
- package/template/agents/prompt-engineer.md +0 -58
- package/template/agents/python-pro.md +0 -31
- package/template/agents/quant-analyst.md +0 -31
- package/template/agents/risk-manager.md +0 -40
- package/template/agents/rust-pro.md +0 -34
- package/template/agents/sales-automator.md +0 -34
- package/template/agents/search-specialist.md +0 -58
- package/template/agents/security-auditor.md +0 -31
- package/template/agents/sql-pro.md +0 -34
- package/template/agents/terraform-specialist.md +0 -34
- package/template/agents/test-automator.md +0 -31
- /package/template/{agents → .claude/agents}/backend.md +0 -0
- /package/template/{agents → .claude/agents}/blockchain.md +0 -0
- /package/template/{agents → .claude/agents}/coder.md +0 -0
- /package/template/{agents → .claude/agents}/frontend.md +0 -0
- /package/template/{agents → .claude/agents}/planner.md +0 -0
- /package/template/{agents → .claude/agents}/researcher.md +0 -0
- /package/template/{agents → .claude/agents}/shadcn.md +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Template Catalog System
|
|
2
|
+
|
|
3
|
+
The template catalog system provides a powerful foundation for browsing, filtering, and selecting from ccsetup's collection of 50+ agent templates. This system implements Phase 1 of the template selection feature as outlined in PLAN-009.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The template catalog system consists of:
|
|
8
|
+
- **Metadata Extraction**: Automatically extracts metadata from agent frontmatter
|
|
9
|
+
- **Template Catalog**: Provides filtering, search, and discovery capabilities
|
|
10
|
+
- **Category Management**: Organizes templates into logical categories
|
|
11
|
+
- **Caching**: Efficient caching for fast template discovery
|
|
12
|
+
|
|
13
|
+
## Components
|
|
14
|
+
|
|
15
|
+
### MetadataExtractor (`metadata-extractor.js`)
|
|
16
|
+
- Parses agent frontmatter to extract metadata
|
|
17
|
+
- Automatically categorizes agents based on content analysis
|
|
18
|
+
- Generates tags from descriptions and tools
|
|
19
|
+
- Creates structured metadata JSON files
|
|
20
|
+
|
|
21
|
+
### TemplateCatalog (`catalog.js`)
|
|
22
|
+
- Template discovery and loading
|
|
23
|
+
- Filtering by category, tags, and search queries
|
|
24
|
+
- Template validation
|
|
25
|
+
- Caching for performance
|
|
26
|
+
|
|
27
|
+
### Generated Metadata (`metadata/agents.json`)
|
|
28
|
+
- Contains metadata for all 52 agents
|
|
29
|
+
- Organized by categories: Development, Backend, Planning, AI/ML, etc.
|
|
30
|
+
- Includes tags, tools, examples, and workflows for each agent
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Basic Catalog Operations
|
|
35
|
+
```javascript
|
|
36
|
+
const { TemplateCatalog } = require('./lib/templates');
|
|
37
|
+
|
|
38
|
+
const catalog = new TemplateCatalog();
|
|
39
|
+
|
|
40
|
+
// Load all templates
|
|
41
|
+
const data = await catalog.load();
|
|
42
|
+
|
|
43
|
+
// Get templates by category
|
|
44
|
+
const planningAgents = await catalog.getTemplates('planning');
|
|
45
|
+
const frontendAgents = await catalog.getTemplates('frontend');
|
|
46
|
+
|
|
47
|
+
// Filter by tags
|
|
48
|
+
const testingAgents = await catalog.getTemplates(null, ['testing']);
|
|
49
|
+
|
|
50
|
+
// Search templates
|
|
51
|
+
const reactAgents = await catalog.searchTemplates('react');
|
|
52
|
+
|
|
53
|
+
// Get categories
|
|
54
|
+
const categories = await catalog.getCategories();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Template Filtering
|
|
58
|
+
```javascript
|
|
59
|
+
// Multiple filters can be combined
|
|
60
|
+
const templates = await catalog.getTemplates(
|
|
61
|
+
'development', // category
|
|
62
|
+
['frontend', 'ui'], // tags
|
|
63
|
+
'react' // search query
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Advanced search with options
|
|
67
|
+
const results = await catalog.searchTemplates('API', {
|
|
68
|
+
category: 'backend',
|
|
69
|
+
tags: ['api'],
|
|
70
|
+
limit: 10,
|
|
71
|
+
sortBy: 'name'
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Template Validation
|
|
76
|
+
```javascript
|
|
77
|
+
// Validate a template
|
|
78
|
+
const validation = await catalog.validateTemplate(template);
|
|
79
|
+
if (!validation.valid) {
|
|
80
|
+
console.log('Errors:', validation.errors);
|
|
81
|
+
console.log('Warnings:', validation.warnings);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Categories
|
|
86
|
+
|
|
87
|
+
The system automatically categorizes agents into:
|
|
88
|
+
|
|
89
|
+
- **Planning & Architecture** (8 agents) - Strategic planning and system design
|
|
90
|
+
- **Development & Implementation** (26 agents) - Code implementation and development
|
|
91
|
+
- **Frontend Development** (13 agents) - UI/UX and frontend frameworks
|
|
92
|
+
- **Backend Development** (2 agents) - API design and server-side development
|
|
93
|
+
- **Quality Assurance & Testing** (9 agents) - Testing and code review
|
|
94
|
+
- **Security & Auditing** (5 agents) - Security analysis and auditing
|
|
95
|
+
- **DevOps & Infrastructure** (2 agents) - Deployment and infrastructure
|
|
96
|
+
- **Database & Data** (3 agents) - Database management and data analysis
|
|
97
|
+
- **AI & Machine Learning** (5 agents) - AI/ML development and operations
|
|
98
|
+
- **Mobile Development** (1 agent) - Mobile app development
|
|
99
|
+
- **General Purpose** (4 agents) - Utility and general-purpose agents
|
|
100
|
+
|
|
101
|
+
## Popular Tags
|
|
102
|
+
|
|
103
|
+
Most common tags across all templates:
|
|
104
|
+
- `development` (35 agents)
|
|
105
|
+
- `implementation` (35 agents)
|
|
106
|
+
- `ml`, `ai` (19 agents each)
|
|
107
|
+
- `performance` (19 agents)
|
|
108
|
+
- `frontend`, `ui` (13 agents each)
|
|
109
|
+
- `backend`, `api` (9 agents each)
|
|
110
|
+
- `testing` (9 agents)
|
|
111
|
+
|
|
112
|
+
## Scripts
|
|
113
|
+
|
|
114
|
+
### Generate Metadata
|
|
115
|
+
```bash
|
|
116
|
+
npm run metadata:generate
|
|
117
|
+
```
|
|
118
|
+
Scans all agent templates and generates fresh metadata.
|
|
119
|
+
|
|
120
|
+
### Test Catalog
|
|
121
|
+
```bash
|
|
122
|
+
npm run catalog:test
|
|
123
|
+
```
|
|
124
|
+
Runs comprehensive tests of catalog functionality.
|
|
125
|
+
|
|
126
|
+
### Demo System
|
|
127
|
+
```bash
|
|
128
|
+
node scripts/demo-template-selection.js
|
|
129
|
+
```
|
|
130
|
+
Demonstrates browsing, filtering, and search capabilities.
|
|
131
|
+
|
|
132
|
+
## Data Structure
|
|
133
|
+
|
|
134
|
+
Each template has the following metadata:
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"id": "agent-planner",
|
|
138
|
+
"name": "Strategic Planner Agent",
|
|
139
|
+
"category": "agents",
|
|
140
|
+
"subcategory": "planning",
|
|
141
|
+
"description": "Strategic planning specialist...",
|
|
142
|
+
"tags": ["planning", "architecture", "roadmap"],
|
|
143
|
+
"tools": ["Read", "Grep", "Glob", "TodoWrite"],
|
|
144
|
+
"version": "1.0.0",
|
|
145
|
+
"dependencies": [],
|
|
146
|
+
"files": ["planner.md"],
|
|
147
|
+
"author": "ccsetup",
|
|
148
|
+
"examples": ["Breaking down feature implementations"],
|
|
149
|
+
"workflows": ["Feature Development", "Refactoring"]
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Performance
|
|
154
|
+
|
|
155
|
+
- **Caching**: 5-minute cache validity for fast repeated access
|
|
156
|
+
- **Lazy Loading**: Metadata loaded only when needed
|
|
157
|
+
- **Efficient Filtering**: Fast category and tag-based filtering
|
|
158
|
+
- **Search Optimization**: Text search across name, description, tags, and examples
|
|
159
|
+
|
|
160
|
+
## Future Enhancements
|
|
161
|
+
|
|
162
|
+
Phase 1 provides the foundation for:
|
|
163
|
+
- Interactive CLI template selection (Phase 2)
|
|
164
|
+
- Browse command implementation (Phase 3)
|
|
165
|
+
- Additional template categories (Phase 4)
|
|
166
|
+
- Template marketplace features (Future)
|
|
167
|
+
|
|
168
|
+
## Files
|
|
169
|
+
|
|
170
|
+
- `metadata-extractor.js` - Extracts metadata from agent templates
|
|
171
|
+
- `catalog.js` - Main catalog class with filtering and search
|
|
172
|
+
- `index.js` - Public API exports
|
|
173
|
+
- `metadata/agents.json` - Generated metadata for all agents
|
|
174
|
+
- `README.md` - This documentation
|
|
175
|
+
|
|
176
|
+
The template catalog system provides a robust foundation for the enhanced template selection system outlined in PLAN-009.
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const MetadataExtractor = require('./metadata-extractor');
|
|
4
|
+
|
|
5
|
+
class TemplateCatalog {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.cache = null;
|
|
8
|
+
this.cacheTimestamp = null;
|
|
9
|
+
this.cacheValidityMs = 5 * 60 * 1000; // 5 minutes
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async load(forceRefresh = false) {
|
|
13
|
+
if (!forceRefresh && this.isCacheValid()) {
|
|
14
|
+
return this.cache;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const metadataPath = path.join(__dirname, 'metadata/agents.json');
|
|
19
|
+
|
|
20
|
+
if (fs.existsSync(metadataPath) && !forceRefresh) {
|
|
21
|
+
const content = fs.readFileSync(metadataPath, 'utf8');
|
|
22
|
+
this.cache = JSON.parse(content);
|
|
23
|
+
this.cacheTimestamp = Date.now();
|
|
24
|
+
return this.cache;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log('Generating fresh catalog from agent templates...');
|
|
28
|
+
const catalog = MetadataExtractor.generateCatalogFromAgents();
|
|
29
|
+
|
|
30
|
+
MetadataExtractor.saveCatalogToFile(catalog, metadataPath);
|
|
31
|
+
|
|
32
|
+
this.cache = catalog;
|
|
33
|
+
this.cacheTimestamp = Date.now();
|
|
34
|
+
|
|
35
|
+
return this.cache;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Error loading template catalog:', error.message);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
agents: [],
|
|
41
|
+
categories: {},
|
|
42
|
+
stats: {
|
|
43
|
+
totalAgents: 0,
|
|
44
|
+
categories: 0,
|
|
45
|
+
lastUpdated: new Date().toISOString(),
|
|
46
|
+
error: error.message
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
isCacheValid() {
|
|
53
|
+
return this.cache &&
|
|
54
|
+
this.cacheTimestamp &&
|
|
55
|
+
(Date.now() - this.cacheTimestamp) < this.cacheValidityMs;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getTemplates(category = null, tags = [], searchQuery = null) {
|
|
59
|
+
const catalog = await this.load();
|
|
60
|
+
let templates = catalog.agents || [];
|
|
61
|
+
|
|
62
|
+
if (category && category !== 'all') {
|
|
63
|
+
if (category === 'agents') {
|
|
64
|
+
// Already filtered to agents
|
|
65
|
+
} else {
|
|
66
|
+
templates = templates.filter(t => t.subcategory === category);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (tags && tags.length > 0) {
|
|
71
|
+
templates = templates.filter(t =>
|
|
72
|
+
tags.some(tag => t.tags.includes(tag.toLowerCase()))
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (searchQuery && searchQuery.trim()) {
|
|
77
|
+
const query = searchQuery.toLowerCase().trim();
|
|
78
|
+
templates = templates.filter(t => {
|
|
79
|
+
const searchableText = [
|
|
80
|
+
t.name,
|
|
81
|
+
t.description,
|
|
82
|
+
...t.tags,
|
|
83
|
+
...t.examples
|
|
84
|
+
].join(' ').toLowerCase();
|
|
85
|
+
|
|
86
|
+
return searchableText.includes(query);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return templates;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getTemplate(id) {
|
|
94
|
+
const catalog = await this.load();
|
|
95
|
+
return catalog.agents.find(agent => agent.id === id);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getCategories() {
|
|
99
|
+
const catalog = await this.load();
|
|
100
|
+
return catalog.categories || {};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getStats() {
|
|
104
|
+
const catalog = await this.load();
|
|
105
|
+
return catalog.stats || {};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async searchTemplates(query, options = {}) {
|
|
109
|
+
const {
|
|
110
|
+
category = null,
|
|
111
|
+
tags = [],
|
|
112
|
+
limit = 50,
|
|
113
|
+
sortBy = 'name'
|
|
114
|
+
} = options;
|
|
115
|
+
|
|
116
|
+
let templates = await this.getTemplates(category, tags, query);
|
|
117
|
+
|
|
118
|
+
if (sortBy === 'name') {
|
|
119
|
+
templates.sort((a, b) => a.name.localeCompare(b.name));
|
|
120
|
+
} else if (sortBy === 'category') {
|
|
121
|
+
templates.sort((a, b) => {
|
|
122
|
+
const categoryCompare = a.subcategory.localeCompare(b.subcategory);
|
|
123
|
+
return categoryCompare !== 0 ? categoryCompare : a.name.localeCompare(b.name);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return templates.slice(0, limit);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async validateTemplate(template) {
|
|
131
|
+
const requiredFields = ['id', 'name', 'category', 'description'];
|
|
132
|
+
const validation = {
|
|
133
|
+
valid: true,
|
|
134
|
+
errors: [],
|
|
135
|
+
warnings: []
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
for (const field of requiredFields) {
|
|
139
|
+
if (!template[field]) {
|
|
140
|
+
validation.valid = false;
|
|
141
|
+
validation.errors.push(`Missing required field: ${field}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (template.category !== 'agents') {
|
|
146
|
+
validation.warnings.push(`Unexpected category: ${template.category}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!template.files || template.files.length === 0) {
|
|
150
|
+
validation.valid = false;
|
|
151
|
+
validation.errors.push('Template must specify at least one file');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (template.files) {
|
|
155
|
+
const templateDir = path.join(__dirname, '../../template/.claude/agents');
|
|
156
|
+
for (const file of template.files) {
|
|
157
|
+
const filePath = path.join(templateDir, file);
|
|
158
|
+
if (!fs.existsSync(filePath)) {
|
|
159
|
+
validation.valid = false;
|
|
160
|
+
validation.errors.push(`Template file not found: ${file}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!template.tags || template.tags.length === 0) {
|
|
166
|
+
validation.warnings.push('Template has no tags - may be hard to discover');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return validation;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async refreshCatalog() {
|
|
173
|
+
console.log('Refreshing template catalog...');
|
|
174
|
+
|
|
175
|
+
const catalog = MetadataExtractor.generateCatalogFromAgents();
|
|
176
|
+
const metadataPath = path.join(__dirname, 'metadata/agents.json');
|
|
177
|
+
|
|
178
|
+
const saved = MetadataExtractor.saveCatalogToFile(catalog, metadataPath);
|
|
179
|
+
|
|
180
|
+
if (saved) {
|
|
181
|
+
this.cache = catalog;
|
|
182
|
+
this.cacheTimestamp = Date.now();
|
|
183
|
+
console.log(`Catalog refreshed: ${catalog.stats.totalAgents} agents processed`);
|
|
184
|
+
return catalog;
|
|
185
|
+
} else {
|
|
186
|
+
throw new Error('Failed to save refreshed catalog');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async getCatalogBySubcategory() {
|
|
191
|
+
const catalog = await this.load();
|
|
192
|
+
const result = {};
|
|
193
|
+
|
|
194
|
+
for (const agent of catalog.agents) {
|
|
195
|
+
if (!result[agent.subcategory]) {
|
|
196
|
+
result[agent.subcategory] = [];
|
|
197
|
+
}
|
|
198
|
+
result[agent.subcategory].push(agent);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Object.keys(result).forEach(category => {
|
|
202
|
+
result[category].sort((a, b) => a.name.localeCompare(b.name));
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async getTagCloud() {
|
|
209
|
+
const catalog = await this.load();
|
|
210
|
+
const tagCounts = {};
|
|
211
|
+
|
|
212
|
+
for (const agent of catalog.agents) {
|
|
213
|
+
for (const tag of agent.tags) {
|
|
214
|
+
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const sortedTags = Object.entries(tagCounts)
|
|
219
|
+
.sort((a, b) => b[1] - a[1])
|
|
220
|
+
.map(([tag, count]) => ({ tag, count }));
|
|
221
|
+
|
|
222
|
+
return sortedTags;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static createInstance() {
|
|
226
|
+
return new TemplateCatalog();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = TemplateCatalog;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
class TemplateFilter {
|
|
2
|
+
constructor(templates = []) {
|
|
3
|
+
this.templates = templates;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
setTemplates(templates) {
|
|
7
|
+
this.templates = templates;
|
|
8
|
+
return this;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
byCategory(category) {
|
|
12
|
+
if (!category || category === 'all') {
|
|
13
|
+
return new TemplateFilter(this.templates);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const filtered = this.templates.filter(template => {
|
|
17
|
+
if (category === 'agents') {
|
|
18
|
+
return template.category === 'agents';
|
|
19
|
+
}
|
|
20
|
+
return template.subcategory === category;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return new TemplateFilter(filtered);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
byTags(tags) {
|
|
27
|
+
if (!tags || tags.length === 0) {
|
|
28
|
+
return new TemplateFilter(this.templates);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const normalizedTags = tags.map(tag => tag.toLowerCase());
|
|
32
|
+
const filtered = this.templates.filter(template => {
|
|
33
|
+
if (!template.tags || template.tags.length === 0) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return normalizedTags.some(tag =>
|
|
38
|
+
template.tags.some(templateTag =>
|
|
39
|
+
templateTag.toLowerCase().includes(tag)
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return new TemplateFilter(filtered);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
byExactTags(tags) {
|
|
48
|
+
if (!tags || tags.length === 0) {
|
|
49
|
+
return new TemplateFilter(this.templates);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalizedTags = tags.map(tag => tag.toLowerCase());
|
|
53
|
+
const filtered = this.templates.filter(template => {
|
|
54
|
+
if (!template.tags || template.tags.length === 0) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return normalizedTags.every(tag =>
|
|
59
|
+
template.tags.some(templateTag =>
|
|
60
|
+
templateTag.toLowerCase() === tag
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return new TemplateFilter(filtered);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
byAnyTag(tags) {
|
|
69
|
+
if (!tags || tags.length === 0) {
|
|
70
|
+
return new TemplateFilter(this.templates);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const normalizedTags = tags.map(tag => tag.toLowerCase());
|
|
74
|
+
const filtered = this.templates.filter(template => {
|
|
75
|
+
if (!template.tags || template.tags.length === 0) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return normalizedTags.some(tag =>
|
|
80
|
+
template.tags.some(templateTag =>
|
|
81
|
+
templateTag.toLowerCase() === tag
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return new TemplateFilter(filtered);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
byAllTags(tags) {
|
|
90
|
+
if (!tags || tags.length === 0) {
|
|
91
|
+
return new TemplateFilter(this.templates);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const normalizedTags = tags.map(tag => tag.toLowerCase());
|
|
95
|
+
const filtered = this.templates.filter(template => {
|
|
96
|
+
if (!template.tags || template.tags.length === 0) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return normalizedTags.every(tag =>
|
|
101
|
+
template.tags.some(templateTag =>
|
|
102
|
+
templateTag.toLowerCase() === tag
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return new TemplateFilter(filtered);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
combine(filters = {}) {
|
|
111
|
+
let result = this.templates;
|
|
112
|
+
|
|
113
|
+
if (filters.category) {
|
|
114
|
+
result = new TemplateFilter(result).byCategory(filters.category).getResults();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
118
|
+
const tagMode = filters.tagMode || 'any';
|
|
119
|
+
if (tagMode === 'all') {
|
|
120
|
+
result = new TemplateFilter(result).byAllTags(filters.tags).getResults();
|
|
121
|
+
} else if (tagMode === 'exact') {
|
|
122
|
+
result = new TemplateFilter(result).byExactTags(filters.tags).getResults();
|
|
123
|
+
} else {
|
|
124
|
+
result = new TemplateFilter(result).byAnyTag(filters.tags).getResults();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (filters.subcategories && filters.subcategories.length > 0) {
|
|
129
|
+
result = result.filter(template =>
|
|
130
|
+
filters.subcategories.includes(template.subcategory)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return new TemplateFilter(result);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
getResults() {
|
|
138
|
+
return [...this.templates];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getCount() {
|
|
142
|
+
return this.templates.length;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
getCategories() {
|
|
146
|
+
const categories = new Set();
|
|
147
|
+
this.templates.forEach(template => {
|
|
148
|
+
if (template.category) {
|
|
149
|
+
categories.add(template.category);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return Array.from(categories).sort();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
getSubcategories() {
|
|
156
|
+
const subcategories = new Set();
|
|
157
|
+
this.templates.forEach(template => {
|
|
158
|
+
if (template.subcategory) {
|
|
159
|
+
subcategories.add(template.subcategory);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return Array.from(subcategories).sort();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getAllTags() {
|
|
166
|
+
const tags = new Set();
|
|
167
|
+
this.templates.forEach(template => {
|
|
168
|
+
if (template.tags && Array.isArray(template.tags)) {
|
|
169
|
+
template.tags.forEach(tag => tags.add(tag.toLowerCase()));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return Array.from(tags).sort();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getTagsByFrequency() {
|
|
176
|
+
const tagCounts = {};
|
|
177
|
+
this.templates.forEach(template => {
|
|
178
|
+
if (template.tags && Array.isArray(template.tags)) {
|
|
179
|
+
template.tags.forEach(tag => {
|
|
180
|
+
const normalizedTag = tag.toLowerCase();
|
|
181
|
+
tagCounts[normalizedTag] = (tagCounts[normalizedTag] || 0) + 1;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return Object.entries(tagCounts)
|
|
187
|
+
.sort((a, b) => b[1] - a[1])
|
|
188
|
+
.map(([tag, count]) => ({ tag, count }));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
sortBy(field, direction = 'asc') {
|
|
192
|
+
const sorted = [...this.templates].sort((a, b) => {
|
|
193
|
+
let valueA = a[field];
|
|
194
|
+
let valueB = b[field];
|
|
195
|
+
|
|
196
|
+
if (typeof valueA === 'string' && typeof valueB === 'string') {
|
|
197
|
+
valueA = valueA.toLowerCase();
|
|
198
|
+
valueB = valueB.toLowerCase();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (valueA < valueB) {
|
|
202
|
+
return direction === 'asc' ? -1 : 1;
|
|
203
|
+
}
|
|
204
|
+
if (valueA > valueB) {
|
|
205
|
+
return direction === 'asc' ? 1 : -1;
|
|
206
|
+
}
|
|
207
|
+
return 0;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return new TemplateFilter(sorted);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
limit(count) {
|
|
214
|
+
return new TemplateFilter(this.templates.slice(0, count));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
offset(count) {
|
|
218
|
+
return new TemplateFilter(this.templates.slice(count));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
paginate(page, pageSize) {
|
|
222
|
+
const start = (page - 1) * pageSize;
|
|
223
|
+
const end = start + pageSize;
|
|
224
|
+
return new TemplateFilter(this.templates.slice(start, end));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
isEmpty() {
|
|
228
|
+
return this.templates.length === 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
first() {
|
|
232
|
+
return this.templates.length > 0 ? this.templates[0] : null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
last() {
|
|
236
|
+
return this.templates.length > 0 ? this.templates[this.templates.length - 1] : null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
find(predicate) {
|
|
240
|
+
return this.templates.find(predicate);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
some(predicate) {
|
|
244
|
+
return this.templates.some(predicate);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
every(predicate) {
|
|
248
|
+
return this.templates.every(predicate);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
static createFromCatalog(catalog) {
|
|
252
|
+
const templates = catalog?.agents || [];
|
|
253
|
+
return new TemplateFilter(templates);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = TemplateFilter;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const TemplateCatalog = require('./catalog');
|
|
2
|
+
const MetadataExtractor = require('./metadata-extractor');
|
|
3
|
+
const TemplateFilter = require('./filter');
|
|
4
|
+
const TemplateSearch = require('./search');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
TemplateCatalog,
|
|
8
|
+
MetadataExtractor,
|
|
9
|
+
TemplateFilter,
|
|
10
|
+
TemplateSearch,
|
|
11
|
+
|
|
12
|
+
async createCatalog() {
|
|
13
|
+
return TemplateCatalog.createInstance();
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
async refreshCatalog() {
|
|
17
|
+
const catalog = TemplateCatalog.createInstance();
|
|
18
|
+
return await catalog.refreshCatalog();
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async getCatalogStats() {
|
|
22
|
+
const catalog = TemplateCatalog.createInstance();
|
|
23
|
+
return await catalog.getStats();
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
createFilter(templates) {
|
|
27
|
+
return new TemplateFilter(templates);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
createSearch(templates) {
|
|
31
|
+
return new TemplateSearch(templates);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async searchTemplates(query, options = {}) {
|
|
35
|
+
const catalog = TemplateCatalog.createInstance();
|
|
36
|
+
const templates = await catalog.load();
|
|
37
|
+
return new TemplateSearch(templates.agents || []).search(query, options);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async filterTemplates(filters = {}) {
|
|
41
|
+
const catalog = TemplateCatalog.createInstance();
|
|
42
|
+
const templates = await catalog.load();
|
|
43
|
+
return new TemplateFilter(templates.agents || []).combine(filters);
|
|
44
|
+
}
|
|
45
|
+
};
|