convoke-agents 3.1.0 → 3.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/CHANGELOG.md +31 -0
- package/README.md +1 -1
- package/_bmad/bme/_artifacts/config.yaml +15 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-01-scope.md +138 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-02-dryrun.md +199 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-03-resolve.md +174 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/steps/step-04-execute.md +213 -0
- package/_bmad/bme/_artifacts/workflows/bmad-migrate-artifacts/workflow.md +85 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/SKILL.md +6 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-01-scan.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-02-explore.md +131 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/steps/step-03-recommend.md +149 -0
- package/_bmad/bme/_artifacts/workflows/bmad-portfolio-status/workflow.md +78 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-export-skill/workflow.md +74 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-generate-catalog/workflow.md +42 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-seed-catalog/workflow.md +61 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/SKILL.md +6 -0
- package/_bmad/bme/_portability/skills/bmad-validate-exports/workflow.md +43 -0
- package/_bmad/bme/_team-factory/agents/team-factory.md +128 -0
- package/_bmad/bme/_team-factory/config.yaml +13 -0
- package/_bmad/bme/_team-factory/lib/cascade-logic.js +184 -0
- package/_bmad/bme/_team-factory/lib/collision-detector.js +228 -0
- package/_bmad/bme/_team-factory/lib/manifest-tracker.js +214 -0
- package/_bmad/bme/_team-factory/lib/spec-differ.js +176 -0
- package/_bmad/bme/_team-factory/lib/spec-parser.js +201 -0
- package/_bmad/bme/_team-factory/lib/spec-writer.js +128 -0
- package/_bmad/bme/_team-factory/lib/types/factory-types.js +193 -0
- package/_bmad/bme/_team-factory/lib/utils/csv-utils.js +62 -0
- package/_bmad/bme/_team-factory/lib/utils/naming-utils.js +45 -0
- package/_bmad/bme/_team-factory/lib/validators/end-to-end-validator.js +898 -0
- package/_bmad/bme/_team-factory/lib/writers/activation-validator.js +175 -0
- package/_bmad/bme/_team-factory/lib/writers/config-appender.js +192 -0
- package/_bmad/bme/_team-factory/lib/writers/config-creator.js +215 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-appender.js +118 -0
- package/_bmad/bme/_team-factory/lib/writers/csv-creator.js +190 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-appender.js +372 -0
- package/_bmad/bme/_team-factory/lib/writers/registry-writer.js +409 -0
- package/_bmad/bme/_team-factory/module-help.csv +3 -0
- package/_bmad/bme/_team-factory/schemas/schema-independent.json +147 -0
- package/_bmad/bme/_team-factory/schemas/schema-sequential.json +242 -0
- package/_bmad/bme/_team-factory/templates/team-spec-template.yaml +86 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-01-scope.md +105 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-02-connect.md +110 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-03-review.md +116 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-04-generate.md +160 -0
- package/_bmad/bme/_team-factory/workflows/add-team/step-05-validate.md +146 -0
- package/_bmad/bme/_team-factory/workflows/step-00-route.md +76 -0
- package/_bmad/bme/_vortex/config.yaml +4 -4
- package/package.json +12 -7
- package/scripts/convoke-doctor.js +172 -1
- package/scripts/install-gyre-agents.js +0 -0
- package/scripts/lib/artifact-utils.js +521 -13
- package/scripts/lib/portfolio/portfolio-engine.js +301 -34
- package/scripts/lib/portfolio/rules/artifact-chain-rule.js +33 -3
- package/scripts/lib/portfolio/rules/conflict-resolver.js +22 -0
- package/scripts/migrate-artifacts.js +69 -10
- package/scripts/portability/catalog-generator.js +353 -0
- package/scripts/portability/classify-skills.js +646 -0
- package/scripts/portability/convoke-export.js +522 -0
- package/scripts/portability/export-engine.js +1133 -0
- package/scripts/portability/generate-adapters.js +79 -0
- package/scripts/portability/manifest-csv.js +147 -0
- package/scripts/portability/seed-catalog-repo.js +427 -0
- package/scripts/portability/templates/canonical-example.md +102 -0
- package/scripts/portability/templates/canonical-format.md +218 -0
- package/scripts/portability/templates/readme-template.md +72 -0
- package/scripts/portability/test-constants.js +42 -0
- package/scripts/portability/validate-classification.js +529 -0
- package/scripts/portability/validate-exports.js +348 -0
- package/scripts/update/lib/agent-registry.js +35 -0
- package/scripts/update/lib/config-merger.js +140 -10
- package/scripts/update/lib/refresh-installation.js +293 -8
- package/scripts/update/lib/utils.js +27 -1
- package/scripts/update/lib/validator.js +114 -4
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* catalog-generator.js — Story sp-3-1
|
|
4
|
+
*
|
|
5
|
+
* Generates a decision-tree catalog README from skill-manifest.csv and
|
|
6
|
+
* agent-manifest.csv. The catalog is organized by user intent ("I need to...")
|
|
7
|
+
* so consultants new to agentic tools can find the right skill quickly.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/portability/catalog-generator.js # stdout
|
|
11
|
+
* node scripts/portability/catalog-generator.js --output <path>
|
|
12
|
+
* node scripts/portability/catalog-generator.js --help
|
|
13
|
+
*
|
|
14
|
+
* Read-only on the source tree — reads two CSV manifests, writes one file
|
|
15
|
+
* (or stdout).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const { findProjectRoot } = require('../update/lib/utils');
|
|
23
|
+
const { readManifest } = require('./manifest-csv');
|
|
24
|
+
const { resolvePersonaSummary, loadAgentManifest } = require('./export-engine');
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// CONSTANTS
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
const INTENT_TO_HEADING = {
|
|
31
|
+
'think-through-problem': 'I need to think through a problem',
|
|
32
|
+
'define-what-to-build': 'I need to define what to build',
|
|
33
|
+
'review-something': 'I need to review something',
|
|
34
|
+
'write-documentation': 'I need to write documentation',
|
|
35
|
+
'plan-your-work': 'I need to plan my work',
|
|
36
|
+
'test-your-code': 'I need to test my code',
|
|
37
|
+
'discover-product-fit': 'I need to discover product-market fit',
|
|
38
|
+
'assess-readiness': 'I need to assess production readiness',
|
|
39
|
+
'meta-platform': null, // excluded from catalog
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const TIER_BADGES = {
|
|
43
|
+
standalone: '**✅ Ready to use**',
|
|
44
|
+
'light-deps': '**📦 Needs setup**',
|
|
45
|
+
pipeline: '**🔒 Framework only** (Requires full Convoke installation)',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Intent display order: standalone-heavy intents first, pipeline-only last
|
|
49
|
+
const INTENT_ORDER = [
|
|
50
|
+
'think-through-problem',
|
|
51
|
+
'define-what-to-build',
|
|
52
|
+
'review-something',
|
|
53
|
+
'write-documentation',
|
|
54
|
+
'plan-your-work',
|
|
55
|
+
'test-your-code',
|
|
56
|
+
'discover-product-fit',
|
|
57
|
+
'assess-readiness',
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// HELPERS
|
|
62
|
+
// =============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Truncate description to first sentence (period + space) or 120 chars.
|
|
66
|
+
*/
|
|
67
|
+
function truncateDescription(desc) {
|
|
68
|
+
if (!desc) return '';
|
|
69
|
+
const periodIdx = desc.indexOf('. ');
|
|
70
|
+
if (periodIdx > 0 && periodIdx < 120) {
|
|
71
|
+
return desc.slice(0, periodIdx + 1);
|
|
72
|
+
}
|
|
73
|
+
if (desc.length <= 120) return desc;
|
|
74
|
+
return desc.slice(0, 120) + '...';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Render a single skill entry line.
|
|
79
|
+
*/
|
|
80
|
+
function renderSkillEntry(skill) {
|
|
81
|
+
const icon = skill.icon || '🔧';
|
|
82
|
+
const badge = TIER_BADGES[skill.tier] || skill.tier;
|
|
83
|
+
const desc = truncateDescription(skill.description);
|
|
84
|
+
const link = `[→ instructions](./${skill.name}/)`;
|
|
85
|
+
return `- ${icon} **${skill.personaName}** — ${desc} ${badge} ${link}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// =============================================================================
|
|
89
|
+
// CATALOG GENERATION
|
|
90
|
+
// =============================================================================
|
|
91
|
+
|
|
92
|
+
function generateCatalog(projectRoot) {
|
|
93
|
+
// Load manifests
|
|
94
|
+
const skillManifestPath = path.join(projectRoot, '_bmad', '_config', 'skill-manifest.csv');
|
|
95
|
+
const { header, rows } = readManifest(skillManifestPath);
|
|
96
|
+
const agents = loadAgentManifest(projectRoot);
|
|
97
|
+
|
|
98
|
+
const nameIdx = header.indexOf('name');
|
|
99
|
+
const tierIdx = header.indexOf('tier');
|
|
100
|
+
const intentIdx = header.indexOf('intent');
|
|
101
|
+
const descIdx = header.indexOf('description');
|
|
102
|
+
|
|
103
|
+
// Dedupe by skill name (first row wins)
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
const skills = [];
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
const name = row[nameIdx];
|
|
108
|
+
if (seen.has(name)) continue;
|
|
109
|
+
seen.add(name);
|
|
110
|
+
|
|
111
|
+
const intent = row[intentIdx] || 'unknown';
|
|
112
|
+
const tier = row[tierIdx] || 'unknown';
|
|
113
|
+
|
|
114
|
+
// Skip meta-platform
|
|
115
|
+
if (intent === 'meta-platform') continue;
|
|
116
|
+
|
|
117
|
+
const persona = resolvePersonaSummary(name, agents);
|
|
118
|
+
skills.push({
|
|
119
|
+
name,
|
|
120
|
+
tier,
|
|
121
|
+
intent,
|
|
122
|
+
description: row[descIdx] || '',
|
|
123
|
+
personaName: persona.name,
|
|
124
|
+
icon: persona.icon,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Count by tier
|
|
129
|
+
const standaloneCount = skills.filter((s) => s.tier === 'standalone').length;
|
|
130
|
+
const lightDepsCount = skills.filter((s) => s.tier === 'light-deps').length;
|
|
131
|
+
const pipelineCount = skills.filter((s) => s.tier === 'pipeline').length;
|
|
132
|
+
const mainBodyCount = standaloneCount + lightDepsCount;
|
|
133
|
+
|
|
134
|
+
// Group by intent
|
|
135
|
+
const byIntent = {};
|
|
136
|
+
for (const skill of skills) {
|
|
137
|
+
if (!byIntent[skill.intent]) byIntent[skill.intent] = [];
|
|
138
|
+
byIntent[skill.intent].push(skill);
|
|
139
|
+
}
|
|
140
|
+
// Sort skills alphabetically within each intent
|
|
141
|
+
for (const intent of Object.keys(byIntent)) {
|
|
142
|
+
byIntent[intent].sort((a, b) => a.name.localeCompare(b.name));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Warn on unknown intents
|
|
146
|
+
for (const intent of Object.keys(byIntent)) {
|
|
147
|
+
if (!(intent in INTENT_TO_HEADING)) {
|
|
148
|
+
process.stderr.write(`Warning: unknown intent "${intent}" — using fallback heading\n`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// === Render ===
|
|
153
|
+
const lines = [];
|
|
154
|
+
|
|
155
|
+
// Header
|
|
156
|
+
lines.push('# Convoke Skills Catalog');
|
|
157
|
+
lines.push('');
|
|
158
|
+
lines.push('> Find the right AI skill for your task. Browse by what you\'re trying to do.');
|
|
159
|
+
lines.push('');
|
|
160
|
+
lines.push('## How to use a skill');
|
|
161
|
+
lines.push('');
|
|
162
|
+
lines.push('1. Find your intent below — what are you trying to do?');
|
|
163
|
+
lines.push('2. Pick a skill marked **✅ Ready to use**');
|
|
164
|
+
lines.push('3. Copy the skill folder into your project\'s `.claude/skills/` directory');
|
|
165
|
+
lines.push('');
|
|
166
|
+
lines.push('### Tier legend');
|
|
167
|
+
lines.push('');
|
|
168
|
+
lines.push('- **✅ Ready to use** — standalone skill, just copy and go');
|
|
169
|
+
lines.push('- **📦 Needs setup** — includes templates/config that need minor configuration');
|
|
170
|
+
lines.push('- **🔒 Framework only** — requires full Convoke installation');
|
|
171
|
+
lines.push('');
|
|
172
|
+
lines.push(
|
|
173
|
+
`**${mainBodyCount} skills in this catalog** (${standaloneCount} ready to use, ${lightDepsCount} need setup) | ${pipelineCount} framework-only skills listed below`
|
|
174
|
+
);
|
|
175
|
+
lines.push('');
|
|
176
|
+
lines.push('---');
|
|
177
|
+
lines.push('');
|
|
178
|
+
|
|
179
|
+
// Main body: Tier 1 + Tier 2 skills grouped by intent
|
|
180
|
+
for (const intent of INTENT_ORDER) {
|
|
181
|
+
const heading = INTENT_TO_HEADING[intent];
|
|
182
|
+
if (!heading) continue; // meta-platform excluded
|
|
183
|
+
|
|
184
|
+
const intentSkills = (byIntent[intent] || []).filter(
|
|
185
|
+
(s) => s.tier === 'standalone' || s.tier === 'light-deps'
|
|
186
|
+
);
|
|
187
|
+
if (intentSkills.length === 0) continue;
|
|
188
|
+
|
|
189
|
+
lines.push(`## ${heading}`);
|
|
190
|
+
lines.push('');
|
|
191
|
+
for (const skill of intentSkills) {
|
|
192
|
+
lines.push(renderSkillEntry(skill));
|
|
193
|
+
}
|
|
194
|
+
lines.push('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Render any unknown-intent skills with fallback heading (spec AC #2)
|
|
198
|
+
for (const intent of Object.keys(byIntent)) {
|
|
199
|
+
if (intent in INTENT_TO_HEADING) continue; // already rendered above
|
|
200
|
+
const intentSkills = byIntent[intent].filter(
|
|
201
|
+
(s) => s.tier === 'standalone' || s.tier === 'light-deps'
|
|
202
|
+
);
|
|
203
|
+
if (intentSkills.length === 0) continue;
|
|
204
|
+
lines.push(`## I need to: ${intent}`);
|
|
205
|
+
lines.push('');
|
|
206
|
+
for (const skill of intentSkills) {
|
|
207
|
+
lines.push(renderSkillEntry(skill));
|
|
208
|
+
}
|
|
209
|
+
lines.push('');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Collapsed section: Tier 3 pipeline skills
|
|
213
|
+
const pipelineSkills = skills.filter((s) => s.tier === 'pipeline');
|
|
214
|
+
if (pipelineSkills.length > 0) {
|
|
215
|
+
lines.push('---');
|
|
216
|
+
lines.push('');
|
|
217
|
+
lines.push('<details>');
|
|
218
|
+
lines.push('<summary><strong>Framework-only skills (requires Convoke installation)</strong></summary>');
|
|
219
|
+
lines.push('');
|
|
220
|
+
|
|
221
|
+
// Group pipeline skills by intent too
|
|
222
|
+
const pipelineByIntent = {};
|
|
223
|
+
for (const skill of pipelineSkills) {
|
|
224
|
+
if (!pipelineByIntent[skill.intent]) pipelineByIntent[skill.intent] = [];
|
|
225
|
+
pipelineByIntent[skill.intent].push(skill);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const intent of INTENT_ORDER) {
|
|
229
|
+
const heading = INTENT_TO_HEADING[intent];
|
|
230
|
+
if (!heading) continue;
|
|
231
|
+
const intentSkills = pipelineByIntent[intent] || [];
|
|
232
|
+
if (intentSkills.length === 0) continue;
|
|
233
|
+
|
|
234
|
+
lines.push(`### ${heading}`);
|
|
235
|
+
lines.push('');
|
|
236
|
+
for (const skill of intentSkills) {
|
|
237
|
+
lines.push(renderSkillEntry(skill));
|
|
238
|
+
}
|
|
239
|
+
lines.push('');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Also render unknown-intent pipeline skills
|
|
243
|
+
for (const intent of Object.keys(pipelineByIntent)) {
|
|
244
|
+
if (intent in INTENT_TO_HEADING) continue;
|
|
245
|
+
const unknownSkills = pipelineByIntent[intent] || [];
|
|
246
|
+
if (unknownSkills.length === 0) continue;
|
|
247
|
+
lines.push(`### I need to: ${intent}`);
|
|
248
|
+
lines.push('');
|
|
249
|
+
for (const skill of unknownSkills) {
|
|
250
|
+
lines.push(renderSkillEntry(skill));
|
|
251
|
+
}
|
|
252
|
+
lines.push('');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
lines.push('</details>');
|
|
256
|
+
lines.push('');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Footer
|
|
260
|
+
lines.push('---');
|
|
261
|
+
lines.push('');
|
|
262
|
+
lines.push(`*Generated by \`convoke-export\` — do not edit manually.*`);
|
|
263
|
+
lines.push(`*Generated on ${new Date().toISOString().slice(0, 10)}.*`);
|
|
264
|
+
lines.push('');
|
|
265
|
+
lines.push('For the full Convoke framework, see the [Convoke Agents repository](https://github.com/amalik/convoke-agents).');
|
|
266
|
+
lines.push('');
|
|
267
|
+
|
|
268
|
+
return lines.join('\n');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// =============================================================================
|
|
272
|
+
// CLI
|
|
273
|
+
// =============================================================================
|
|
274
|
+
|
|
275
|
+
function printHelp() {
|
|
276
|
+
process.stdout.write(
|
|
277
|
+
[
|
|
278
|
+
'Usage: catalog-generator [options]',
|
|
279
|
+
'',
|
|
280
|
+
'Generate a decision-tree skill catalog README from manifest data.',
|
|
281
|
+
'',
|
|
282
|
+
'Options:',
|
|
283
|
+
' --output <path> Write catalog to file (default: stdout)',
|
|
284
|
+
' --help, -h Show this help message',
|
|
285
|
+
'',
|
|
286
|
+
'Exit codes:',
|
|
287
|
+
' 0 Success',
|
|
288
|
+
' 1 Usage error',
|
|
289
|
+
' 2 Manifest read failure',
|
|
290
|
+
'',
|
|
291
|
+
].join('\n')
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function main() {
|
|
296
|
+
const argv = process.argv.slice(2);
|
|
297
|
+
|
|
298
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
299
|
+
printHelp();
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let outputPath = null;
|
|
304
|
+
for (let i = 0; i < argv.length; i++) {
|
|
305
|
+
if (argv[i] === '--output') {
|
|
306
|
+
outputPath = argv[++i];
|
|
307
|
+
if (!outputPath) {
|
|
308
|
+
process.stderr.write('Error: --output requires a path argument\n');
|
|
309
|
+
return 1;
|
|
310
|
+
}
|
|
311
|
+
} else if (argv[i].startsWith('--')) {
|
|
312
|
+
process.stderr.write(`Unknown flag: ${argv[i]}. Run --help for usage.\n`);
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let projectRoot;
|
|
318
|
+
try {
|
|
319
|
+
projectRoot = findProjectRoot();
|
|
320
|
+
} catch (e) {
|
|
321
|
+
process.stderr.write(`Error: could not find project root — ${e.message}\n`);
|
|
322
|
+
return 2;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
let catalog;
|
|
326
|
+
try {
|
|
327
|
+
catalog = generateCatalog(projectRoot);
|
|
328
|
+
} catch (e) {
|
|
329
|
+
process.stderr.write(`Error generating catalog: ${e.message}\n`);
|
|
330
|
+
return 2;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (outputPath) {
|
|
334
|
+
try {
|
|
335
|
+
const dir = path.dirname(outputPath);
|
|
336
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
337
|
+
fs.writeFileSync(outputPath, catalog);
|
|
338
|
+
} catch (e) {
|
|
339
|
+
process.stderr.write(`Error writing catalog: ${e.message}\n`);
|
|
340
|
+
return 2;
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
process.stdout.write(catalog);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (require.main === module) {
|
|
350
|
+
process.exit(main());
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
module.exports = { generateCatalog, main };
|