crewly 1.20.133 → 1.20.134
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/dist/backend/backend/src/constants.d.ts +82 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +87 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts +44 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.js +122 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts +25 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.js +33 -0
- package/dist/backend/backend/src/controllers/bundle/bundle.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +14 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts +62 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js +173 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.js +888 -0
- package/dist/backend/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.js +248 -0
- package/dist/backend/backend/src/services/bundle/bundle-catalog.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts +43 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.js +57 -0
- package/dist/backend/backend/src/services/bundle/bundle-connectors.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts +37 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.js +60 -0
- package/dist/backend/backend/src/services/bundle/bundle-deps.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.js +532 -0
- package/dist/backend/backend/src/services/bundle/bundle-manifest.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.js +183 -0
- package/dist/backend/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.js +38 -0
- package/dist/backend/backend/src/services/bundle/bundle-runtime.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js +144 -0
- package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.js +90 -0
- package/dist/backend/backend/src/services/bundle/bundle-state.store.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.js +172 -0
- package/dist/backend/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.d.ts +59 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.js +98 -0
- package/dist/backend/backend/src/services/bundle/bundle-time.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +5 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/teams-backup.service.js +4 -2
- package/dist/backend/backend/src/services/core/teams-backup.service.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.d.ts.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js +2 -0
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +85 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/template/template.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/template/template.service.js +5 -2
- package/dist/backend/backend/src/services/template/template.service.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.js +4 -2
- package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
- package/dist/backend/backend/src/types/solution-bundle.types.d.ts +367 -0
- package/dist/backend/backend/src/types/solution-bundle.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/solution-bundle.types.js +17 -0
- package/dist/backend/backend/src/types/solution-bundle.types.js.map +1 -0
- package/dist/backend/backend/src/types/team-template.types.d.ts +8 -0
- package/dist/backend/backend/src/types/team-template.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/team-template.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +82 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +87 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.js +888 -0
- package/dist/cli/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.js +248 -0
- package/dist/cli/backend/src/services/bundle/bundle-catalog.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts +37 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.js +60 -0
- package/dist/cli/backend/src/services/bundle/bundle-deps.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.js +532 -0
- package/dist/cli/backend/src/services/bundle/bundle-manifest.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.js +183 -0
- package/dist/cli/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.js +38 -0
- package/dist/cli/backend/src/services/bundle/bundle-runtime.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js +144 -0
- package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.js +90 -0
- package/dist/cli/backend/src/services/bundle/bundle-state.store.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.js +172 -0
- package/dist/cli/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.d.ts +59 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.js +98 -0
- package/dist/cli/backend/src/services/bundle/bundle-time.js.map +1 -0
- package/dist/cli/backend/src/services/core/storage.service.js +1 -1
- package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/cli/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/teams-backup.service.js +4 -2
- package/dist/cli/backend/src/services/core/teams-backup.service.js.map +1 -1
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts +72 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js +389 -0
- package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/index.d.ts +15 -0
- package/dist/cli/backend/src/services/messaging/index.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/index.js +20 -0
- package/dist/cli/backend/src/services/messaging/index.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts +224 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js +969 -0
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.d.ts +110 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.js +268 -0
- package/dist/cli/backend/src/services/messaging/response-router.service.js.map +1 -0
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
- package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts +133 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js +232 -0
- package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js.map +1 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts +277 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.js +708 -0
- package/dist/cli/backend/src/services/skill/skill-catalog.service.js.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts +243 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js +439 -0
- package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +85 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/template/template.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/template/template.service.js +5 -2
- package/dist/cli/backend/src/services/template/template.service.js.map +1 -1
- package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts +316 -0
- package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/workflow/cron-task.service.js +1043 -0
- package/dist/cli/backend/src/services/workflow/cron-task.service.js.map +1 -0
- package/dist/cli/backend/src/types/cron-task.types.d.ts +95 -0
- package/dist/cli/backend/src/types/cron-task.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/cron-task.types.js +10 -0
- package/dist/cli/backend/src/types/cron-task.types.js.map +1 -0
- package/dist/cli/backend/src/types/solution-bundle.types.d.ts +367 -0
- package/dist/cli/backend/src/types/solution-bundle.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/solution-bundle.types.js +17 -0
- package/dist/cli/backend/src/types/solution-bundle.types.js.map +1 -0
- package/dist/cli/backend/src/types/team-template.types.d.ts +8 -0
- package/dist/cli/backend/src/types/team-template.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/team-template.types.js.map +1 -1
- package/dist/cli/cli/src/commands/deploy-bundle.d.ts +160 -0
- package/dist/cli/cli/src/commands/deploy-bundle.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/deploy-bundle.js +386 -0
- package/dist/cli/cli/src/commands/deploy-bundle.js.map +1 -0
- package/dist/cli/cli/src/commands/onboard.d.ts +32 -1
- package/dist/cli/cli/src/commands/onboard.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/onboard.js +80 -3
- package/dist/cli/cli/src/commands/onboard.js.map +1 -1
- package/dist/cli/cli/src/constants.d.ts +5 -0
- package/dist/cli/cli/src/constants.d.ts.map +1 -1
- package/dist/cli/cli/src/constants.js +5 -0
- package/dist/cli/cli/src/constants.js.map +1 -1
- package/dist/cli/cli/src/index.js +15 -0
- package/dist/cli/cli/src/index.js.map +1 -1
- package/frontend/dist/assets/{index-e6c403b6.js → index-97bb17d4.js} +274 -274
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Catalog Service
|
|
3
|
+
*
|
|
4
|
+
* Scans skill directories (orchestrator and agent), reads SKILL.md
|
|
5
|
+
* (YAML frontmatter + markdown body) from each, and generates formatted
|
|
6
|
+
* catalog Markdown files at ~/.crewly/skills/.
|
|
7
|
+
*
|
|
8
|
+
* Generates two catalogs:
|
|
9
|
+
* - SKILLS_CATALOG.md for orchestrator skills
|
|
10
|
+
* - AGENT_SKILLS_CATALOG.md for agent skills
|
|
11
|
+
*
|
|
12
|
+
* Each catalog provides a human-readable and LLM-readable reference of all
|
|
13
|
+
* available skills grouped by category, including usage examples and parameter
|
|
14
|
+
* documentation extracted from each skill's SKILL.md body.
|
|
15
|
+
*
|
|
16
|
+
* @module services/skill/skill-catalog.service
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'fs/promises';
|
|
19
|
+
import * as path from 'path';
|
|
20
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
21
|
+
import * as os from 'os';
|
|
22
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
23
|
+
import { CREWLY_CONSTANTS } from '../../constants.js';
|
|
24
|
+
import { parseSkillMd } from '../../utils/skill-md-parser.js';
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Constants
|
|
27
|
+
// =============================================================================
|
|
28
|
+
/** Name of the generated orchestrator catalog file */
|
|
29
|
+
const CATALOG_FILENAME = 'SKILLS_CATALOG.md';
|
|
30
|
+
/** Name of the generated agent catalog file */
|
|
31
|
+
const AGENT_CATALOG_FILENAME = 'AGENT_SKILLS_CATALOG.md';
|
|
32
|
+
/** Subdirectory under ~/.crewly where the catalog is written */
|
|
33
|
+
const CATALOG_SUBDIR = 'skills';
|
|
34
|
+
/** Relative path from project root to orchestrator skills */
|
|
35
|
+
const ORCHESTRATOR_SKILLS_RELATIVE_PATH = 'config/skills/orchestrator';
|
|
36
|
+
/** Relative path from project root to agent skills */
|
|
37
|
+
const AGENT_SKILLS_RELATIVE_PATH = 'config/skills/agent';
|
|
38
|
+
/** Directory names to skip when scanning for skills */
|
|
39
|
+
const SKIP_DIRECTORIES = ['_common'];
|
|
40
|
+
/** Skill definition file name expected in each skill directory */
|
|
41
|
+
const SKILL_DEFINITION_FILE = 'SKILL.md';
|
|
42
|
+
/** Legacy skill definition file name (for backward compatibility) */
|
|
43
|
+
const LEGACY_SKILL_DEFINITION_FILE = 'skill.json';
|
|
44
|
+
/** Markdown heading pattern for detecting section boundaries */
|
|
45
|
+
const HEADING_PATTERN = /^#{1,2}\s/;
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// Service
|
|
48
|
+
// =============================================================================
|
|
49
|
+
/**
|
|
50
|
+
* Service for generating a Markdown skills catalog from orchestrator skill directories.
|
|
51
|
+
*
|
|
52
|
+
* Scans config/skills/orchestrator/ for subdirectories (skipping _common),
|
|
53
|
+
* reads skill.json and instructions.md from each, groups skills by category,
|
|
54
|
+
* and writes a formatted SKILLS_CATALOG.md to ~/.crewly/skills/.
|
|
55
|
+
*
|
|
56
|
+
* Uses a singleton pattern consistent with other Crewly services.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const catalogService = SkillCatalogService.getInstance('/path/to/project');
|
|
61
|
+
* const result = await catalogService.generateCatalog();
|
|
62
|
+
* console.log(`Generated catalog at ${result.catalogPath} with ${result.skillCount} skills`);
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export class SkillCatalogService {
|
|
66
|
+
static instance = null;
|
|
67
|
+
logger;
|
|
68
|
+
projectRoot;
|
|
69
|
+
catalogDir;
|
|
70
|
+
/**
|
|
71
|
+
* Create a new SkillCatalogService instance.
|
|
72
|
+
*
|
|
73
|
+
* @param projectRoot - Absolute path to the project root directory.
|
|
74
|
+
* Defaults to the current working directory if not provided.
|
|
75
|
+
*/
|
|
76
|
+
constructor(projectRoot) {
|
|
77
|
+
this.logger = LoggerService.getInstance().createComponentLogger('SkillCatalogService');
|
|
78
|
+
// #222: Resolve install dir reliably instead of defaulting to CWD
|
|
79
|
+
this.projectRoot = projectRoot || this.resolveInstallDir();
|
|
80
|
+
this.catalogDir = path.join(os.homedir(), CREWLY_CONSTANTS.PATHS.CREWLY_HOME, CATALOG_SUBDIR);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* #222: Resolve the Crewly install directory from __dirname or CWD.
|
|
84
|
+
*
|
|
85
|
+
* @returns Absolute path to the Crewly install directory
|
|
86
|
+
*/
|
|
87
|
+
resolveInstallDir() {
|
|
88
|
+
const candidates = [
|
|
89
|
+
path.resolve(__dirname, '..', '..', '..'), // from src/services/skill/
|
|
90
|
+
path.resolve(__dirname, '..', '..', '..', '..'), // from dist/src/services/skill/
|
|
91
|
+
];
|
|
92
|
+
for (const candidate of candidates) {
|
|
93
|
+
if (existsSync(path.join(candidate, 'config', 'skills'))) {
|
|
94
|
+
return candidate;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return process.cwd();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the singleton SkillCatalogService instance.
|
|
101
|
+
*
|
|
102
|
+
* Creates a new instance on first call with the provided project root,
|
|
103
|
+
* or returns the existing instance on subsequent calls.
|
|
104
|
+
*
|
|
105
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
106
|
+
* @returns The shared SkillCatalogService instance
|
|
107
|
+
*/
|
|
108
|
+
static getInstance(projectRoot) {
|
|
109
|
+
if (!SkillCatalogService.instance) {
|
|
110
|
+
SkillCatalogService.instance = new SkillCatalogService(projectRoot);
|
|
111
|
+
}
|
|
112
|
+
return SkillCatalogService.instance;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Reset the singleton instance.
|
|
116
|
+
*
|
|
117
|
+
* Used primarily for testing to ensure a clean state between test runs.
|
|
118
|
+
*/
|
|
119
|
+
static clearInstance() {
|
|
120
|
+
SkillCatalogService.instance = null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Generate the skills catalog by scanning skill directories and writing
|
|
124
|
+
* the formatted Markdown file.
|
|
125
|
+
*
|
|
126
|
+
* This method performs the following steps:
|
|
127
|
+
* 1. Ensures the output directory (~/.crewly/skills/) exists
|
|
128
|
+
* 2. Scans config/skills/orchestrator/ for skill subdirectories
|
|
129
|
+
* 3. Reads skill.json and instructions.md from each valid directory
|
|
130
|
+
* 4. Groups loaded skills by their category field
|
|
131
|
+
* 5. Renders a Markdown catalog document
|
|
132
|
+
* 6. Writes the catalog to ~/.crewly/skills/SKILLS_CATALOG.md
|
|
133
|
+
*
|
|
134
|
+
* @returns A result object with success status, path, and counts
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const result = await catalogService.generateCatalog();
|
|
139
|
+
* if (result.success) {
|
|
140
|
+
* console.log(`Catalog written to ${result.catalogPath}`);
|
|
141
|
+
* } else {
|
|
142
|
+
* console.error(`Catalog generation failed: ${result.error}`);
|
|
143
|
+
* }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
async generateCatalog() {
|
|
147
|
+
const catalogPath = this.getCatalogPath();
|
|
148
|
+
try {
|
|
149
|
+
this.logger.info('Starting skills catalog generation', {
|
|
150
|
+
projectRoot: this.projectRoot,
|
|
151
|
+
catalogPath,
|
|
152
|
+
});
|
|
153
|
+
// Ensure the output directory exists
|
|
154
|
+
this.ensureCatalogDirectory();
|
|
155
|
+
// Scan and load all skill directories
|
|
156
|
+
const skills = await this.scanSkillDirectories();
|
|
157
|
+
if (skills.length === 0) {
|
|
158
|
+
this.logger.warn('No skills found during catalog generation');
|
|
159
|
+
const emptyMarkdown = this.renderCatalog(new Map());
|
|
160
|
+
await fs.writeFile(catalogPath, emptyMarkdown, 'utf-8');
|
|
161
|
+
return {
|
|
162
|
+
success: true,
|
|
163
|
+
catalogPath,
|
|
164
|
+
skillCount: 0,
|
|
165
|
+
categoryCount: 0,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// Group skills by category
|
|
169
|
+
const grouped = this.groupByCategory(skills);
|
|
170
|
+
// Render the Markdown catalog
|
|
171
|
+
const markdown = this.renderCatalog(grouped);
|
|
172
|
+
// Write the catalog file
|
|
173
|
+
await fs.writeFile(catalogPath, markdown, 'utf-8');
|
|
174
|
+
const categoryCount = grouped.size;
|
|
175
|
+
this.logger.info('Skills catalog generated successfully', {
|
|
176
|
+
skillCount: skills.length,
|
|
177
|
+
categoryCount,
|
|
178
|
+
catalogPath,
|
|
179
|
+
});
|
|
180
|
+
return {
|
|
181
|
+
success: true,
|
|
182
|
+
catalogPath,
|
|
183
|
+
skillCount: skills.length,
|
|
184
|
+
categoryCount,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
189
|
+
this.logger.error('Failed to generate skills catalog', {
|
|
190
|
+
error: errorMessage,
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
catalogPath,
|
|
195
|
+
skillCount: 0,
|
|
196
|
+
categoryCount: 0,
|
|
197
|
+
error: errorMessage,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Generate the agent skills catalog by scanning agent skill directories
|
|
203
|
+
* and writing a formatted Markdown file.
|
|
204
|
+
*
|
|
205
|
+
* This method mirrors generateCatalog() but targets config/skills/agent/
|
|
206
|
+
* and writes to ~/.crewly/skills/AGENT_SKILLS_CATALOG.md.
|
|
207
|
+
*
|
|
208
|
+
* @returns A result object with success status, path, and counts
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```typescript
|
|
212
|
+
* const result = await catalogService.generateAgentCatalog();
|
|
213
|
+
* if (result.success) {
|
|
214
|
+
* console.log(`Agent catalog written to ${result.catalogPath}`);
|
|
215
|
+
* }
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
async generateAgentCatalog() {
|
|
219
|
+
const catalogPath = this.getAgentCatalogPath();
|
|
220
|
+
try {
|
|
221
|
+
this.logger.info('Starting agent skills catalog generation', {
|
|
222
|
+
projectRoot: this.projectRoot,
|
|
223
|
+
catalogPath,
|
|
224
|
+
});
|
|
225
|
+
this.ensureCatalogDirectory();
|
|
226
|
+
// Scan bundled agent skills
|
|
227
|
+
const bundledSkills = await this.scanSkillDirectoriesAt(AGENT_SKILLS_RELATIVE_PATH);
|
|
228
|
+
// Use absolute path for agent skills because agents run in their target
|
|
229
|
+
// project directory (e.g. ~/projects/business_os), NOT the Crewly root.
|
|
230
|
+
// Relative paths like config/skills/agent/ won't resolve from there.
|
|
231
|
+
const absoluteSkillsPath = path.join(this.projectRoot, AGENT_SKILLS_RELATIVE_PATH);
|
|
232
|
+
// Also scan marketplace-installed skills
|
|
233
|
+
const marketplacePath = path.join(os.homedir(), '.crewly', 'marketplace', 'skills');
|
|
234
|
+
const marketplaceSkills = await this.scanSkillDirectoriesAt(marketplacePath, { absolute: true, setBasePath: true });
|
|
235
|
+
// Scan addon-installed skills at ~/.crewly/skills/agent/ (e.g. from crewly-pro)
|
|
236
|
+
const addonSkillsPath = path.join(os.homedir(), '.crewly', 'skills', 'agent');
|
|
237
|
+
const addonSkills = await this.scanSkillDirectoriesAt(addonSkillsPath, { absolute: true, setBasePath: true });
|
|
238
|
+
// Merge: addon skills take precedence over bundled on name conflict
|
|
239
|
+
const skillsByName = new Map();
|
|
240
|
+
for (const s of bundledSkills) {
|
|
241
|
+
skillsByName.set(s.definition.name, s);
|
|
242
|
+
}
|
|
243
|
+
for (const s of marketplaceSkills) {
|
|
244
|
+
skillsByName.set(s.definition.name, s);
|
|
245
|
+
}
|
|
246
|
+
for (const s of addonSkills) {
|
|
247
|
+
skillsByName.set(s.definition.name, s);
|
|
248
|
+
}
|
|
249
|
+
const skills = Array.from(skillsByName.values());
|
|
250
|
+
if (skills.length === 0) {
|
|
251
|
+
this.logger.warn('No agent skills found during catalog generation');
|
|
252
|
+
const emptyMarkdown = this.renderCatalogWithConfig(new Map(), {
|
|
253
|
+
title: 'Agent Skills Catalog',
|
|
254
|
+
skillsRelativePath: absoluteSkillsPath,
|
|
255
|
+
});
|
|
256
|
+
await fs.writeFile(catalogPath, emptyMarkdown, 'utf-8');
|
|
257
|
+
return {
|
|
258
|
+
success: true,
|
|
259
|
+
catalogPath,
|
|
260
|
+
skillCount: 0,
|
|
261
|
+
categoryCount: 0,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const grouped = this.groupByCategory(skills);
|
|
265
|
+
const markdown = this.renderCatalogWithConfig(grouped, {
|
|
266
|
+
title: 'Agent Skills Catalog',
|
|
267
|
+
skillsRelativePath: absoluteSkillsPath,
|
|
268
|
+
});
|
|
269
|
+
await fs.writeFile(catalogPath, markdown, 'utf-8');
|
|
270
|
+
const categoryCount = grouped.size;
|
|
271
|
+
this.logger.info('Agent skills catalog generated successfully', {
|
|
272
|
+
skillCount: skills.length,
|
|
273
|
+
categoryCount,
|
|
274
|
+
catalogPath,
|
|
275
|
+
marketplaceSkillCount: marketplaceSkills.length,
|
|
276
|
+
addonSkillCount: addonSkills.length,
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
catalogPath,
|
|
281
|
+
skillCount: skills.length,
|
|
282
|
+
categoryCount,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
287
|
+
this.logger.error('Failed to generate agent skills catalog', {
|
|
288
|
+
error: errorMessage,
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
success: false,
|
|
292
|
+
catalogPath,
|
|
293
|
+
skillCount: 0,
|
|
294
|
+
categoryCount: 0,
|
|
295
|
+
error: errorMessage,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Get the absolute path where the orchestrator catalog file will be written.
|
|
301
|
+
*
|
|
302
|
+
* @returns Absolute path to ~/.crewly/skills/SKILLS_CATALOG.md
|
|
303
|
+
*/
|
|
304
|
+
getCatalogPath() {
|
|
305
|
+
return path.join(this.catalogDir, CATALOG_FILENAME);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get the absolute path where the agent catalog file will be written.
|
|
309
|
+
*
|
|
310
|
+
* @returns Absolute path to ~/.crewly/skills/AGENT_SKILLS_CATALOG.md
|
|
311
|
+
*/
|
|
312
|
+
getAgentCatalogPath() {
|
|
313
|
+
return path.join(this.catalogDir, AGENT_CATALOG_FILENAME);
|
|
314
|
+
}
|
|
315
|
+
// ==========================================================================
|
|
316
|
+
// Private methods
|
|
317
|
+
// ==========================================================================
|
|
318
|
+
/**
|
|
319
|
+
* Ensure the catalog output directory exists, creating it recursively
|
|
320
|
+
* if necessary.
|
|
321
|
+
*
|
|
322
|
+
* Uses synchronous mkdirSync since this is a one-time setup operation
|
|
323
|
+
* that must complete before writing the catalog.
|
|
324
|
+
*/
|
|
325
|
+
ensureCatalogDirectory() {
|
|
326
|
+
if (!existsSync(this.catalogDir)) {
|
|
327
|
+
mkdirSync(this.catalogDir, { recursive: true });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Scan the orchestrator skills directory for valid skill subdirectories,
|
|
332
|
+
* plus agent skills whose `assignableRoles` includes `orchestrator`.
|
|
333
|
+
*
|
|
334
|
+
* Pipeline-#4 fix (spec 2026-05-05-request-decompose-pipeline-gap.md, Patch C
|
|
335
|
+
* zero-LOC alternative): some materialisation skills (e.g.
|
|
336
|
+
* `agent/core/break-down-request`) are role-shared between agents and the
|
|
337
|
+
* orchestrator and live under `config/skills/agent/core/`. Without this
|
|
338
|
+
* cross-scan, the orchestrator's catalog would not surface them and the orc
|
|
339
|
+
* would have no discoverable path to materialise WorkItems for a Request,
|
|
340
|
+
* even though the prompt at prompt-builder.service.ts:374 instructs it to.
|
|
341
|
+
*
|
|
342
|
+
* Skills already present under `config/skills/orchestrator/` win on
|
|
343
|
+
* collisions (deduped by name), so adding an explicit orchestrator-side
|
|
344
|
+
* skill later transparently shadows the agent-side one.
|
|
345
|
+
*
|
|
346
|
+
* @returns Array of loaded skills with their definitions and instructions
|
|
347
|
+
*/
|
|
348
|
+
async scanSkillDirectories() {
|
|
349
|
+
const orchestratorSkills = await this.scanSkillDirectoriesAt(ORCHESTRATOR_SKILLS_RELATIVE_PATH);
|
|
350
|
+
// setBasePath: cross-scanned agent skills live under config/skills/agent/core/,
|
|
351
|
+
// so their catalog usage line must point there — without it the entry was
|
|
352
|
+
// rendered under the orchestrator path and the orc ran
|
|
353
|
+
// `orchestrator/<skill>/execute.sh` which does not exist (finding 12).
|
|
354
|
+
const agentCoreSkills = await this.scanSkillDirectoriesAt(`${AGENT_SKILLS_RELATIVE_PATH}/core`, {
|
|
355
|
+
setBasePath: true,
|
|
356
|
+
});
|
|
357
|
+
const orchestratorTagged = agentCoreSkills.filter((skill) => (skill.definition.assignableRoles ?? []).includes('orchestrator'));
|
|
358
|
+
// Dedup by skill name, with explicit orchestrator-side winning on collision.
|
|
359
|
+
const seenNames = new Set(orchestratorSkills.map((s) => s.definition.name));
|
|
360
|
+
const merged = [...orchestratorSkills];
|
|
361
|
+
for (const skill of orchestratorTagged) {
|
|
362
|
+
if (!seenNames.has(skill.definition.name)) {
|
|
363
|
+
merged.push(skill);
|
|
364
|
+
seenNames.add(skill.definition.name);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
merged.sort((a, b) => a.definition.name.localeCompare(b.definition.name));
|
|
368
|
+
return merged;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Scan a skills directory for valid skill subdirectories.
|
|
372
|
+
*
|
|
373
|
+
* Reads each subdirectory (skipping entries in SKIP_DIRECTORIES),
|
|
374
|
+
* loads skill.json and instructions.md from each, and returns an array
|
|
375
|
+
* of successfully loaded skills. Directories that fail to load are
|
|
376
|
+
* logged as warnings but do not cause the entire scan to fail.
|
|
377
|
+
*
|
|
378
|
+
* When a subdirectory has no skill.json, it is treated as a category
|
|
379
|
+
* directory (e.g. core/, marketplace/) and its children are scanned.
|
|
380
|
+
*
|
|
381
|
+
* @param pathOrRelative - Path to the skills directory. Joined with
|
|
382
|
+
* projectRoot by default; used directly when `options.absolute` is true.
|
|
383
|
+
* @param options - Optional flags to control path resolution and basePath tagging
|
|
384
|
+
* @returns Array of loaded skills with their definitions and instructions
|
|
385
|
+
*/
|
|
386
|
+
async scanSkillDirectoriesAt(pathOrRelative, options) {
|
|
387
|
+
const skillsRootDir = options?.absolute
|
|
388
|
+
? pathOrRelative
|
|
389
|
+
: path.join(this.projectRoot, pathOrRelative);
|
|
390
|
+
if (!existsSync(skillsRootDir)) {
|
|
391
|
+
if (!options?.absolute) {
|
|
392
|
+
this.logger.warn('Skills directory not found', { path: skillsRootDir });
|
|
393
|
+
}
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
const entries = await fs.readdir(skillsRootDir, { withFileTypes: true });
|
|
397
|
+
const skills = [];
|
|
398
|
+
for (const entry of entries) {
|
|
399
|
+
if (!entry.isDirectory()) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (SKIP_DIRECTORIES.includes(entry.name)) {
|
|
403
|
+
this.logger.debug('Skipping excluded directory', { dirName: entry.name });
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const skillDir = path.join(skillsRootDir, entry.name);
|
|
407
|
+
const skillMdPath = path.join(skillDir, SKILL_DEFINITION_FILE);
|
|
408
|
+
const legacyJsonPath = path.join(skillDir, LEGACY_SKILL_DEFINITION_FILE);
|
|
409
|
+
if (existsSync(skillMdPath) || existsSync(legacyJsonPath)) {
|
|
410
|
+
// Direct skill directory
|
|
411
|
+
const skill = await this.loadSkillFromDirectory(skillsRootDir, entry.name);
|
|
412
|
+
if (skill) {
|
|
413
|
+
if (options?.setBasePath) {
|
|
414
|
+
skill.basePath = skillsRootDir;
|
|
415
|
+
}
|
|
416
|
+
skills.push(skill);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// Category subdirectory (e.g., core/, marketplace/) — recurse
|
|
421
|
+
const nestedEntries = await fs.readdir(skillDir, { withFileTypes: true });
|
|
422
|
+
for (const nested of nestedEntries) {
|
|
423
|
+
if (!nested.isDirectory())
|
|
424
|
+
continue;
|
|
425
|
+
const skill = await this.loadSkillFromDirectory(skillDir, nested.name);
|
|
426
|
+
if (skill) {
|
|
427
|
+
skill.basePath = skillDir;
|
|
428
|
+
skills.push(skill);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
skills.sort((a, b) => a.definition.name.localeCompare(b.definition.name));
|
|
434
|
+
return skills;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Load a single skill from a directory by reading its SKILL.md file
|
|
438
|
+
* (YAML frontmatter + markdown body). Falls back to legacy skill.json
|
|
439
|
+
* + instructions.md format for backward compatibility.
|
|
440
|
+
*
|
|
441
|
+
* @param parentDir - Absolute path to the parent skills directory
|
|
442
|
+
* @param dirName - Name of the skill subdirectory
|
|
443
|
+
* @returns A LoadedSkill object, or null if loading fails
|
|
444
|
+
*/
|
|
445
|
+
async loadSkillFromDirectory(parentDir, dirName) {
|
|
446
|
+
const skillDir = path.join(parentDir, dirName);
|
|
447
|
+
const skillMdPath = path.join(skillDir, SKILL_DEFINITION_FILE);
|
|
448
|
+
const legacyJsonPath = path.join(skillDir, LEGACY_SKILL_DEFINITION_FILE);
|
|
449
|
+
// Try SKILL.md first, then fall back to legacy skill.json
|
|
450
|
+
if (existsSync(skillMdPath)) {
|
|
451
|
+
return this.loadSkillFromSkillMd(skillDir, skillMdPath, dirName);
|
|
452
|
+
}
|
|
453
|
+
else if (existsSync(legacyJsonPath)) {
|
|
454
|
+
return this.loadSkillFromLegacyJson(skillDir, legacyJsonPath, dirName);
|
|
455
|
+
}
|
|
456
|
+
this.logger.debug('No SKILL.md or skill.json found, skipping directory', {
|
|
457
|
+
dirName,
|
|
458
|
+
});
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Load a skill from SKILL.md format (YAML frontmatter + markdown body).
|
|
463
|
+
*
|
|
464
|
+
* @param skillDir - Absolute path to the skill directory
|
|
465
|
+
* @param skillMdPath - Path to the SKILL.md file
|
|
466
|
+
* @param dirName - Name of the skill subdirectory
|
|
467
|
+
* @returns A LoadedSkill object, or null if loading fails
|
|
468
|
+
*/
|
|
469
|
+
async loadSkillFromSkillMd(skillDir, skillMdPath, dirName) {
|
|
470
|
+
try {
|
|
471
|
+
const content = await fs.readFile(skillMdPath, 'utf-8');
|
|
472
|
+
const { frontmatter, body } = parseSkillMd(content);
|
|
473
|
+
const definition = {
|
|
474
|
+
id: frontmatter.id || `skill-${dirName}`,
|
|
475
|
+
name: frontmatter.name,
|
|
476
|
+
description: frontmatter.description,
|
|
477
|
+
category: frontmatter.category,
|
|
478
|
+
execution: frontmatter.execution,
|
|
479
|
+
tags: frontmatter.tags,
|
|
480
|
+
version: frontmatter.version,
|
|
481
|
+
assignableRoles: frontmatter.assignableRoles,
|
|
482
|
+
};
|
|
483
|
+
if (!definition.name || !definition.description || !definition.category) {
|
|
484
|
+
this.logger.warn('SKILL.md missing required fields', {
|
|
485
|
+
dirName,
|
|
486
|
+
path: skillMdPath,
|
|
487
|
+
});
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
definition,
|
|
492
|
+
instructions: body,
|
|
493
|
+
dirName,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
498
|
+
this.logger.warn('Failed to load skill from SKILL.md', {
|
|
499
|
+
dirName,
|
|
500
|
+
error: errorMessage,
|
|
501
|
+
});
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Load a skill from legacy skill.json + instructions.md format.
|
|
507
|
+
*
|
|
508
|
+
* @param skillDir - Absolute path to the skill directory
|
|
509
|
+
* @param skillJsonPath - Path to the skill.json file
|
|
510
|
+
* @param dirName - Name of the skill subdirectory
|
|
511
|
+
* @returns A LoadedSkill object, or null if loading fails
|
|
512
|
+
*/
|
|
513
|
+
async loadSkillFromLegacyJson(skillDir, skillJsonPath, dirName) {
|
|
514
|
+
try {
|
|
515
|
+
const skillJsonContent = await fs.readFile(skillJsonPath, 'utf-8');
|
|
516
|
+
const definition = JSON.parse(skillJsonContent);
|
|
517
|
+
if (!definition.id || !definition.name || !definition.description || !definition.category) {
|
|
518
|
+
this.logger.warn('Skill definition missing required fields', {
|
|
519
|
+
dirName,
|
|
520
|
+
id: definition.id,
|
|
521
|
+
});
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
let instructions = '';
|
|
525
|
+
const instructionsPath = path.join(skillDir, 'instructions.md');
|
|
526
|
+
if (existsSync(instructionsPath)) {
|
|
527
|
+
instructions = await fs.readFile(instructionsPath, 'utf-8');
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
definition,
|
|
531
|
+
instructions,
|
|
532
|
+
dirName,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
537
|
+
this.logger.warn('Failed to load skill from skill.json', {
|
|
538
|
+
dirName,
|
|
539
|
+
error: errorMessage,
|
|
540
|
+
});
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Group loaded skills by their category field.
|
|
546
|
+
*
|
|
547
|
+
* Categories are title-cased for display (e.g., "management" becomes "Management").
|
|
548
|
+
* Skills within each category are maintained in their current sorted order.
|
|
549
|
+
*
|
|
550
|
+
* @param skills - Array of loaded skills to group
|
|
551
|
+
* @returns Map of category display name to array of skills in that category
|
|
552
|
+
*/
|
|
553
|
+
groupByCategory(skills) {
|
|
554
|
+
const grouped = new Map();
|
|
555
|
+
for (const skill of skills) {
|
|
556
|
+
const categoryKey = this.formatCategoryName(skill.definition.category);
|
|
557
|
+
const existing = grouped.get(categoryKey) ?? [];
|
|
558
|
+
existing.push(skill);
|
|
559
|
+
grouped.set(categoryKey, existing);
|
|
560
|
+
}
|
|
561
|
+
// Sort categories alphabetically
|
|
562
|
+
const sorted = new Map(Array.from(grouped.entries()).sort(([a], [b]) => a.localeCompare(b)));
|
|
563
|
+
return sorted;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Format a raw category string into a display-friendly title.
|
|
567
|
+
*
|
|
568
|
+
* Capitalizes the first letter of each word and replaces hyphens/underscores
|
|
569
|
+
* with spaces (e.g., "task-management" becomes "Task Management").
|
|
570
|
+
*
|
|
571
|
+
* @param category - Raw category string from skill.json
|
|
572
|
+
* @returns Formatted category display name
|
|
573
|
+
*/
|
|
574
|
+
formatCategoryName(category) {
|
|
575
|
+
return category
|
|
576
|
+
.split(/[-_\s]+/)
|
|
577
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
578
|
+
.join(' ');
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Render the complete Markdown catalog document from grouped skills.
|
|
582
|
+
*
|
|
583
|
+
* Delegates to renderCatalogWithConfig() with orchestrator defaults.
|
|
584
|
+
*
|
|
585
|
+
* @param groupedSkills - Map of category name to skills in that category
|
|
586
|
+
* @returns Complete Markdown document as a string
|
|
587
|
+
*/
|
|
588
|
+
renderCatalog(groupedSkills) {
|
|
589
|
+
// Use absolute path so skills work regardless of the orchestrator's cwd
|
|
590
|
+
const absoluteSkillsPath = path.join(this.projectRoot, ORCHESTRATOR_SKILLS_RELATIVE_PATH);
|
|
591
|
+
return this.renderCatalogWithConfig(groupedSkills, {
|
|
592
|
+
title: 'Orchestrator Skills Catalog',
|
|
593
|
+
skillsRelativePath: absoluteSkillsPath,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Render the complete Markdown catalog document from grouped skills
|
|
598
|
+
* with configurable title and path prefix.
|
|
599
|
+
*
|
|
600
|
+
* Produces the full catalog including header, usage instructions,
|
|
601
|
+
* category sections, and individual skill entries with descriptions
|
|
602
|
+
* and parameter documentation extracted from instructions.md.
|
|
603
|
+
*
|
|
604
|
+
* @param groupedSkills - Map of category name to skills in that category
|
|
605
|
+
* @param config - Catalog rendering configuration
|
|
606
|
+
* @param config.title - Catalog title for the Markdown heading
|
|
607
|
+
* @param config.skillsRelativePath - Relative path prefix for skill usage commands
|
|
608
|
+
* @returns Complete Markdown document as a string
|
|
609
|
+
*/
|
|
610
|
+
renderCatalogWithConfig(groupedSkills, config) {
|
|
611
|
+
const timestamp = new Date().toISOString();
|
|
612
|
+
const lines = [];
|
|
613
|
+
// Header
|
|
614
|
+
lines.push(`# ${config.title}`);
|
|
615
|
+
lines.push(`> Auto-generated on ${timestamp}. Execute skills via bash scripts.`);
|
|
616
|
+
lines.push('');
|
|
617
|
+
// How to Use section
|
|
618
|
+
lines.push('## How to Use');
|
|
619
|
+
lines.push('');
|
|
620
|
+
lines.push('All skills follow the pattern:');
|
|
621
|
+
lines.push('```bash');
|
|
622
|
+
lines.push(`bash ${config.skillsRelativePath}/{skill-name}/execute.sh '{"param":"value"}'`);
|
|
623
|
+
lines.push('```');
|
|
624
|
+
lines.push('All scripts output JSON to stdout. Errors go to stderr.');
|
|
625
|
+
lines.push('');
|
|
626
|
+
// Category sections
|
|
627
|
+
for (const [categoryName, skills] of groupedSkills) {
|
|
628
|
+
lines.push(`## ${categoryName}`);
|
|
629
|
+
lines.push('');
|
|
630
|
+
for (const skill of skills) {
|
|
631
|
+
lines.push(...this.renderSkillEntry(skill, config.skillsRelativePath));
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return lines.join('\n');
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Render a single skill entry as Markdown lines.
|
|
638
|
+
*
|
|
639
|
+
* Includes the skill name, description, usage command, and any
|
|
640
|
+
* Parameters section extracted from the skill's instructions.md.
|
|
641
|
+
*
|
|
642
|
+
* @param skill - The loaded skill to render
|
|
643
|
+
* @param skillsRelativePath - Relative path prefix for usage command
|
|
644
|
+
* @returns Array of Markdown lines for this skill entry
|
|
645
|
+
*/
|
|
646
|
+
renderSkillEntry(skill, skillsRelativePath = ORCHESTRATOR_SKILLS_RELATIVE_PATH) {
|
|
647
|
+
const lines = [];
|
|
648
|
+
// Skill heading and description
|
|
649
|
+
lines.push(`### ${skill.definition.name}`);
|
|
650
|
+
lines.push(skill.definition.description);
|
|
651
|
+
lines.push('');
|
|
652
|
+
// Usage line — use the skill's basePath if it has one (marketplace skills),
|
|
653
|
+
// otherwise fall back to the catalog-level skillsRelativePath (bundled skills)
|
|
654
|
+
const effectivePath = skill.basePath || skillsRelativePath;
|
|
655
|
+
lines.push(`**Usage:** \`bash ${effectivePath}/${skill.dirName}/execute.sh '{}'\``);
|
|
656
|
+
lines.push('');
|
|
657
|
+
// Extract and include Parameters section from instructions.md if available
|
|
658
|
+
const parametersSection = this.extractParametersSection(skill.instructions);
|
|
659
|
+
if (parametersSection) {
|
|
660
|
+
lines.push(parametersSection);
|
|
661
|
+
lines.push('');
|
|
662
|
+
}
|
|
663
|
+
// Separator
|
|
664
|
+
lines.push('---');
|
|
665
|
+
lines.push('');
|
|
666
|
+
return lines;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Extract the Parameters section from an instructions.md file's content.
|
|
670
|
+
*
|
|
671
|
+
* Uses a line-based approach to find the "## Parameters" heading and capture
|
|
672
|
+
* all content until the next heading of the same or higher level (# or ##)
|
|
673
|
+
* or the end of the file. Returns null if no Parameters section is found.
|
|
674
|
+
*
|
|
675
|
+
* @param instructionsContent - Full content of the instructions.md file
|
|
676
|
+
* @returns The Parameters section content (without the heading), or null
|
|
677
|
+
*/
|
|
678
|
+
extractParametersSection(instructionsContent) {
|
|
679
|
+
if (!instructionsContent) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
const lines = instructionsContent.split('\n');
|
|
683
|
+
let capturing = false;
|
|
684
|
+
const captured = [];
|
|
685
|
+
for (const line of lines) {
|
|
686
|
+
if (capturing) {
|
|
687
|
+
// Stop capturing when we hit another heading (# or ##)
|
|
688
|
+
if (HEADING_PATTERN.test(line)) {
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
captured.push(line);
|
|
692
|
+
}
|
|
693
|
+
else if (line.match(/^## Parameters\s*$/)) {
|
|
694
|
+
// Start capturing after the Parameters heading
|
|
695
|
+
capturing = true;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (!capturing || captured.length === 0) {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
const content = captured.join('\n').trim();
|
|
702
|
+
if (content.length === 0) {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
return content;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
//# sourceMappingURL=skill-catalog.service.js.map
|