@forwardimpact/pathway 0.16.0 → 0.17.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/package.json +3 -3
- package/src/commands/agent.js +47 -15
- package/src/components/code-display.js +108 -48
- package/src/components/skill-file-viewer.js +61 -0
- package/src/css/bundles/app.css +1 -0
- package/src/css/components/forms.css +47 -16
- package/src/css/components/skill-file-viewer.css +18 -0
- package/src/css/pages/agent-builder.css +13 -55
- package/src/formatters/agent/dom.js +7 -37
- package/src/formatters/agent/profile.js +0 -29
- package/src/formatters/agent/skill.js +69 -11
- package/src/formatters/job/dom.js +6 -4
- package/src/formatters/skill/dom.js +47 -9
- package/src/formatters/skill/shared.js +2 -0
- package/src/lib/template-loader.js +18 -0
- package/src/lib/yaml-loader.js +5 -0
- package/src/pages/agent-builder.js +94 -31
- package/src/pages/skill.js +27 -1
- package/templates/agent.template.md +18 -48
- package/templates/skill-install.template.sh +4 -0
- package/templates/skill-reference.template.md +3 -0
- package/templates/skill.template.md +37 -34
- package/src/components/markdown-textarea.js +0 -153
|
@@ -22,13 +22,6 @@ import {
|
|
|
22
22
|
* @property {string} content - Working style content (markdown)
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* @typedef {Object} ChecklistEntry
|
|
27
|
-
* @property {{id: string, name: string}} skill - Skill info
|
|
28
|
-
* @property {{id: string, name: string, emojiIcon: string}} capability - Capability info
|
|
29
|
-
* @property {string[]} items - Checklist items
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
25
|
/**
|
|
33
26
|
* Prepare agent profile data for template rendering
|
|
34
27
|
* Normalizes string values by trimming trailing newlines for consistent template output.
|
|
@@ -49,8 +42,6 @@ import {
|
|
|
49
42
|
* @param {Array<{name: string, dirname: string, useWhen: string}>} params.bodyData.skillIndex - Skill index entries
|
|
50
43
|
* @param {string} params.bodyData.roleContext - Role context text
|
|
51
44
|
* @param {WorkingStyleEntry[]} params.bodyData.workingStyles - Working style entries
|
|
52
|
-
* @param {ChecklistEntry[]} [params.bodyData.readChecklist] - Read-Then-Do Checklist entries
|
|
53
|
-
* @param {ChecklistEntry[]} [params.bodyData.confirmChecklist] - Do-Then-Confirm Checklist entries
|
|
54
45
|
* @param {string[]} params.bodyData.constraints - List of constraints
|
|
55
46
|
* @param {Array<{id: string, name: string, description: string}>} [params.bodyData.agentIndex] - List of all available agents
|
|
56
47
|
* @param {boolean} [params.bodyData.hasAgentIndex] - Whether agent index is available
|
|
@@ -81,20 +72,6 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
|
|
|
81
72
|
content: "required",
|
|
82
73
|
});
|
|
83
74
|
|
|
84
|
-
// Process readChecklist: trim items in each entry
|
|
85
|
-
const readChecklist = (bodyData.readChecklist || []).map((entry) => ({
|
|
86
|
-
skill: entry.skill,
|
|
87
|
-
capability: entry.capability,
|
|
88
|
-
items: (entry.items || []).map((item) => trimRequired(item)),
|
|
89
|
-
}));
|
|
90
|
-
|
|
91
|
-
// Process confirmChecklist: trim items in each entry
|
|
92
|
-
const confirmChecklist = (bodyData.confirmChecklist || []).map((entry) => ({
|
|
93
|
-
skill: entry.skill,
|
|
94
|
-
capability: entry.capability,
|
|
95
|
-
items: (entry.items || []).map((item) => trimRequired(item)),
|
|
96
|
-
}));
|
|
97
|
-
|
|
98
75
|
return {
|
|
99
76
|
// Frontmatter - flatten description for single-line front matter
|
|
100
77
|
name: frontmatter.name,
|
|
@@ -115,10 +92,6 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
|
|
|
115
92
|
roleContext: trimValue(bodyData.roleContext),
|
|
116
93
|
workingStyles,
|
|
117
94
|
hasWorkingStyles: workingStyles.length > 0,
|
|
118
|
-
readChecklist,
|
|
119
|
-
hasReadChecklist: readChecklist.length > 0,
|
|
120
|
-
confirmChecklist,
|
|
121
|
-
hasConfirmChecklist: confirmChecklist.length > 0,
|
|
122
95
|
constraints,
|
|
123
96
|
hasConstraints: constraints.length > 0,
|
|
124
97
|
agentIndex,
|
|
@@ -143,8 +116,6 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
|
|
|
143
116
|
* @param {Array<{name: string, dirname: string, useWhen: string}>} profile.bodyData.skillIndex - Skill index entries
|
|
144
117
|
* @param {string} profile.bodyData.roleContext - Role context text
|
|
145
118
|
* @param {WorkingStyleEntry[]} profile.bodyData.workingStyles - Working style entries
|
|
146
|
-
* @param {ChecklistEntry[]} [profile.bodyData.readChecklist] - Read-Then-Do Checklist entries (optional)
|
|
147
|
-
* @param {ChecklistEntry[]} [profile.bodyData.confirmChecklist] - Do-Then-Confirm Checklist entries (optional)
|
|
148
119
|
* @param {string[]} profile.bodyData.constraints - List of constraints
|
|
149
120
|
* @param {string} template - Mustache template string
|
|
150
121
|
* @returns {string} Complete .agent.md file content
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent Skill Formatter
|
|
3
3
|
*
|
|
4
|
-
* Formats agent skill data into SKILL.md
|
|
5
|
-
* following the Agent Skills Standard
|
|
4
|
+
* Formats agent skill data into SKILL.md, scripts/install.sh, and
|
|
5
|
+
* references/REFERENCE.md file content following the Agent Skills Standard
|
|
6
|
+
* specification with progressive disclosure.
|
|
6
7
|
*
|
|
7
8
|
* Uses Mustache templates for flexible output formatting.
|
|
8
9
|
* Templates are loaded from data/ directory with fallback to templates/ directory.
|
|
@@ -13,6 +14,13 @@ import Mustache from "mustache";
|
|
|
13
14
|
import { trimValue, splitLines, trimFields } from "../shared.js";
|
|
14
15
|
import { flattenToLine } from "../template-preprocess.js";
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Lowercase the first character of a string
|
|
19
|
+
* @param {string} s
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
const lcFirst = (s) => (s ? s[0].toLowerCase() + s.slice(1) : s);
|
|
23
|
+
|
|
16
24
|
/**
|
|
17
25
|
* Prepare agent skill data for template rendering
|
|
18
26
|
* Normalizes string values by trimming trailing newlines for consistent template output.
|
|
@@ -23,7 +31,9 @@ import { flattenToLine } from "../template-preprocess.js";
|
|
|
23
31
|
* @param {string} [params.frontmatter.useWhen] - When to use this skill
|
|
24
32
|
* @param {string} params.title - Human-readable skill title for heading
|
|
25
33
|
* @param {Array} params.stages - Array of stage objects with stageName, focus, readChecklist, confirmChecklist
|
|
26
|
-
* @param {string} params.
|
|
34
|
+
* @param {string} params.instructions - Workflow guidance content (markdown)
|
|
35
|
+
* @param {string} params.installScript - Shell commands for install script
|
|
36
|
+
* @param {string} params.implementationReference - Reference content (markdown)
|
|
27
37
|
* @param {Array} [params.toolReferences] - Array of tool reference objects
|
|
28
38
|
* @returns {Object} Data object ready for Mustache template
|
|
29
39
|
*/
|
|
@@ -31,7 +41,9 @@ function prepareAgentSkillData({
|
|
|
31
41
|
frontmatter,
|
|
32
42
|
title,
|
|
33
43
|
stages,
|
|
34
|
-
|
|
44
|
+
instructions,
|
|
45
|
+
installScript,
|
|
46
|
+
implementationReference,
|
|
35
47
|
toolReferences,
|
|
36
48
|
}) {
|
|
37
49
|
// Process stages - trim focus and array values
|
|
@@ -43,12 +55,14 @@ function prepareAgentSkillData({
|
|
|
43
55
|
|
|
44
56
|
// Flatten multi-line strings to single line for front matter compatibility
|
|
45
57
|
const description = flattenToLine(frontmatter.description);
|
|
46
|
-
const useWhen = flattenToLine(frontmatter.useWhen);
|
|
58
|
+
const useWhen = lcFirst(flattenToLine(frontmatter.useWhen));
|
|
47
59
|
|
|
48
60
|
// Keep line arrays for body rendering
|
|
49
61
|
const descriptionLines = splitLines(frontmatter.description);
|
|
50
62
|
|
|
51
|
-
const
|
|
63
|
+
const trimmedInstructions = trimValue(instructions) || "";
|
|
64
|
+
const trimmedInstallScript = trimValue(installScript) || "";
|
|
65
|
+
const trimmedReference = trimValue(implementationReference) || "";
|
|
52
66
|
const tools = toolReferences || [];
|
|
53
67
|
|
|
54
68
|
return {
|
|
@@ -63,7 +77,11 @@ function prepareAgentSkillData({
|
|
|
63
77
|
title,
|
|
64
78
|
stages: processedStages,
|
|
65
79
|
hasStages: processedStages.length > 0,
|
|
66
|
-
|
|
80
|
+
instructions: trimmedInstructions,
|
|
81
|
+
hasInstructions: !!trimmedInstructions,
|
|
82
|
+
installScript: trimmedInstallScript,
|
|
83
|
+
hasInstallScript: !!trimmedInstallScript,
|
|
84
|
+
implementationReference: trimmedReference,
|
|
67
85
|
hasReference: !!trimmedReference,
|
|
68
86
|
toolReferences: tools,
|
|
69
87
|
hasToolReferences: tools.length > 0,
|
|
@@ -72,27 +90,67 @@ function prepareAgentSkillData({
|
|
|
72
90
|
|
|
73
91
|
/**
|
|
74
92
|
* Format agent skill as SKILL.md file content using Mustache template
|
|
75
|
-
* @param {Object} skill - Skill with frontmatter, title, stages,
|
|
93
|
+
* @param {Object} skill - Skill with frontmatter, title, stages, instructions, installScript, implementationReference
|
|
76
94
|
* @param {Object} skill.frontmatter - YAML frontmatter data
|
|
77
95
|
* @param {string} skill.frontmatter.name - Skill name (required)
|
|
78
96
|
* @param {string} skill.frontmatter.description - Skill description (required)
|
|
79
97
|
* @param {string} skill.title - Human-readable skill title for heading
|
|
80
98
|
* @param {Array} skill.stages - Array of stage objects with stageName, focus, readChecklist, confirmChecklist
|
|
81
|
-
* @param {string} skill.
|
|
99
|
+
* @param {string} skill.instructions - Workflow guidance (markdown)
|
|
100
|
+
* @param {string} skill.installScript - Shell commands for install script
|
|
101
|
+
* @param {string} skill.implementationReference - Reference content (markdown)
|
|
82
102
|
* @param {Array} [skill.toolReferences] - Array of tool reference objects
|
|
83
103
|
* @param {string} template - Mustache template string
|
|
84
104
|
* @returns {string} Complete SKILL.md file content
|
|
85
105
|
*/
|
|
86
106
|
export function formatAgentSkill(
|
|
87
|
-
{
|
|
107
|
+
{
|
|
108
|
+
frontmatter,
|
|
109
|
+
title,
|
|
110
|
+
stages,
|
|
111
|
+
instructions,
|
|
112
|
+
installScript,
|
|
113
|
+
implementationReference,
|
|
114
|
+
toolReferences,
|
|
115
|
+
},
|
|
88
116
|
template,
|
|
89
117
|
) {
|
|
90
118
|
const data = prepareAgentSkillData({
|
|
91
119
|
frontmatter,
|
|
92
120
|
title,
|
|
93
121
|
stages,
|
|
94
|
-
|
|
122
|
+
instructions,
|
|
123
|
+
installScript,
|
|
124
|
+
implementationReference,
|
|
95
125
|
toolReferences,
|
|
96
126
|
});
|
|
97
127
|
return Mustache.render(template, data);
|
|
98
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Format install script file content using Mustache template
|
|
132
|
+
* @param {Object} skill - Skill data with installScript and frontmatter
|
|
133
|
+
* @param {string} template - Mustache template string for install script
|
|
134
|
+
* @returns {string} Complete install.sh file content
|
|
135
|
+
*/
|
|
136
|
+
export function formatInstallScript(skill, template) {
|
|
137
|
+
const data = {
|
|
138
|
+
name: skill.frontmatter.name,
|
|
139
|
+
installScript: trimValue(skill.installScript) || "",
|
|
140
|
+
};
|
|
141
|
+
return Mustache.render(template, data);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Format reference file content using Mustache template
|
|
146
|
+
* @param {Object} skill - Skill data with implementationReference and title
|
|
147
|
+
* @param {string} template - Mustache template string for reference
|
|
148
|
+
* @returns {string} Complete REFERENCE.md file content
|
|
149
|
+
*/
|
|
150
|
+
export function formatReference(skill, template) {
|
|
151
|
+
const data = {
|
|
152
|
+
title: skill.title,
|
|
153
|
+
implementationReference: trimValue(skill.implementationReference) || "",
|
|
154
|
+
};
|
|
155
|
+
return Mustache.render(template, data);
|
|
156
|
+
}
|
|
@@ -233,16 +233,18 @@ export function createJobDescriptionSection({
|
|
|
233
233
|
template,
|
|
234
234
|
);
|
|
235
235
|
|
|
236
|
-
return
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
return section(
|
|
237
|
+
{ className: "section section-detail" },
|
|
238
|
+
h2({ className: "section-title" }, "Job Description"),
|
|
239
|
+
createCodeDisplay({
|
|
239
240
|
content: markdown,
|
|
241
|
+
filename: "job-description.md",
|
|
240
242
|
description:
|
|
241
243
|
"Copy this markdown-formatted job description for use in job postings, documentation, or sharing.",
|
|
242
244
|
toHtml: markdownToHtml,
|
|
243
245
|
minHeight: 450,
|
|
244
246
|
}),
|
|
245
|
-
|
|
247
|
+
);
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
/**
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "../../lib/render.js";
|
|
19
19
|
import { createBackLink } from "../../components/nav.js";
|
|
20
20
|
import { createLevelCell } from "../../components/detail.js";
|
|
21
|
-
import {
|
|
21
|
+
import { createSkillFileViewer } from "../../components/skill-file-viewer.js";
|
|
22
22
|
import { createToolIcon } from "../../lib/card-mappers.js";
|
|
23
23
|
import { SKILL_LEVEL_ORDER } from "@forwardimpact/schema/levels";
|
|
24
24
|
import { prepareSkillDetail } from "./shared.js";
|
|
@@ -34,6 +34,7 @@ import { createJsonLdScript, skillToJsonLd } from "../json-ld.js";
|
|
|
34
34
|
* @param {Array} context.capabilities - Capability entities
|
|
35
35
|
* @param {boolean} [context.showBackLink=true] - Whether to show back navigation link
|
|
36
36
|
* @param {boolean} [context.showToolsAndPatterns=true] - Whether to show required tools and implementation patterns
|
|
37
|
+
* @param {string} [context.agentSkillContent] - Pre-generated SKILL.md content for agent file viewer
|
|
37
38
|
* @returns {HTMLElement}
|
|
38
39
|
*/
|
|
39
40
|
export function skillToDOM(
|
|
@@ -45,6 +46,7 @@ export function skillToDOM(
|
|
|
45
46
|
capabilities,
|
|
46
47
|
showBackLink = true,
|
|
47
48
|
showToolsAndPatterns = true,
|
|
49
|
+
agentSkillContent,
|
|
48
50
|
} = {},
|
|
49
51
|
) {
|
|
50
52
|
const view = prepareSkillDetail(skill, {
|
|
@@ -222,18 +224,54 @@ export function skillToDOM(
|
|
|
222
224
|
)
|
|
223
225
|
: null,
|
|
224
226
|
|
|
225
|
-
//
|
|
226
|
-
showToolsAndPatterns &&
|
|
227
|
+
// Agent Skill Files
|
|
228
|
+
showToolsAndPatterns &&
|
|
229
|
+
(agentSkillContent || view.implementationReference || view.installScript)
|
|
227
230
|
? div(
|
|
228
231
|
{ className: "detail-section" },
|
|
229
|
-
heading2({ className: "section-title" }, "
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"Project-specific implementation guidance for this skill.",
|
|
234
|
-
minHeight: 450,
|
|
232
|
+
heading2({ className: "section-title" }, "Agent Skill Files"),
|
|
233
|
+
createSkillFileViewer({
|
|
234
|
+
files: buildSkillFiles(view, agentSkillContent),
|
|
235
|
+
maxHeight: 450,
|
|
235
236
|
}),
|
|
236
237
|
)
|
|
237
238
|
: null,
|
|
238
239
|
);
|
|
239
240
|
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Build file descriptors for the skill file viewer
|
|
244
|
+
* @param {import('./shared.js').SkillDetailView} view
|
|
245
|
+
* @param {string} [agentSkillContent] - Pre-generated SKILL.md content
|
|
246
|
+
* @returns {import('../../components/skill-file-viewer.js').SkillFile[]}
|
|
247
|
+
*/
|
|
248
|
+
function buildSkillFiles(view, agentSkillContent) {
|
|
249
|
+
/** @type {import('../../components/skill-file-viewer.js').SkillFile[]} */
|
|
250
|
+
const files = [];
|
|
251
|
+
|
|
252
|
+
if (agentSkillContent) {
|
|
253
|
+
files.push({
|
|
254
|
+
filename: "SKILL.md",
|
|
255
|
+
content: agentSkillContent,
|
|
256
|
+
language: "markdown",
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (view.installScript) {
|
|
261
|
+
files.push({
|
|
262
|
+
filename: "scripts/install.sh",
|
|
263
|
+
content: view.installScript,
|
|
264
|
+
language: "bash",
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (view.implementationReference) {
|
|
269
|
+
files.push({
|
|
270
|
+
filename: "references/REFERENCE.md",
|
|
271
|
+
content: view.implementationReference,
|
|
272
|
+
language: "markdown",
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return files;
|
|
277
|
+
}
|
|
@@ -74,6 +74,7 @@ export function prepareSkillsList(
|
|
|
74
74
|
* @property {Array<{id: string, name: string, modifier: number}>} relatedTracks
|
|
75
75
|
* @property {Array<{id: string, name: string}>} relatedDrivers
|
|
76
76
|
* @property {Array<{name: string, url?: string, description: string, useWhen: string}>} toolReferences
|
|
77
|
+
* @property {string|null} installScript
|
|
77
78
|
* @property {string|null} implementationReference
|
|
78
79
|
*/
|
|
79
80
|
|
|
@@ -130,6 +131,7 @@ export function prepareSkillDetail(
|
|
|
130
131
|
toolReferences: (skill.toolReferences || [])
|
|
131
132
|
.slice()
|
|
132
133
|
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
134
|
+
installScript: skill.installScript || null,
|
|
133
135
|
implementationReference: skill.implementationReference || null,
|
|
134
136
|
};
|
|
135
137
|
}
|
|
@@ -65,6 +65,24 @@ export async function loadSkillTemplate(dataDir) {
|
|
|
65
65
|
return loadTemplate("skill.template.md", dataDir);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Load skill install script template
|
|
70
|
+
* @param {string} dataDir - Path to data directory
|
|
71
|
+
* @returns {Promise<string>} Install script template content
|
|
72
|
+
*/
|
|
73
|
+
export async function loadSkillInstallTemplate(dataDir) {
|
|
74
|
+
return loadTemplate("skill-install.template.sh", dataDir);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load skill reference template
|
|
79
|
+
* @param {string} dataDir - Path to data directory
|
|
80
|
+
* @returns {Promise<string>} Reference template content
|
|
81
|
+
*/
|
|
82
|
+
export async function loadSkillReferenceTemplate(dataDir) {
|
|
83
|
+
return loadTemplate("skill-reference.template.md", dataDir);
|
|
84
|
+
}
|
|
85
|
+
|
|
68
86
|
/**
|
|
69
87
|
* Load job description template
|
|
70
88
|
* @param {string} dataDir - Path to data directory
|
package/src/lib/yaml-loader.js
CHANGED
|
@@ -76,6 +76,8 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
|
|
|
76
76
|
isHumanOnly,
|
|
77
77
|
human,
|
|
78
78
|
agent,
|
|
79
|
+
instructions,
|
|
80
|
+
installScript,
|
|
79
81
|
implementationReference,
|
|
80
82
|
toolReferences,
|
|
81
83
|
} = skill;
|
|
@@ -88,6 +90,9 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
|
|
|
88
90
|
// Include isHumanOnly flag for agent filtering (defaults to false)
|
|
89
91
|
...(isHumanOnly && { isHumanOnly }),
|
|
90
92
|
...(agent && { agent }),
|
|
93
|
+
// Include agent skill content fields
|
|
94
|
+
...(instructions && { instructions }),
|
|
95
|
+
...(installScript && { installScript }),
|
|
91
96
|
// Include implementation reference and tool references (shared by human and agent)
|
|
92
97
|
...(implementationReference && { implementationReference }),
|
|
93
98
|
...(toolReferences && { toolReferences }),
|
|
@@ -37,8 +37,13 @@ import {
|
|
|
37
37
|
import { createReactive } from "../lib/reactive.js";
|
|
38
38
|
import { getStageEmoji } from "../formatters/stage/shared.js";
|
|
39
39
|
import { formatAgentProfile } from "../formatters/agent/profile.js";
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
formatAgentSkill,
|
|
42
|
+
formatInstallScript,
|
|
43
|
+
formatReference,
|
|
44
|
+
} from "../formatters/agent/skill.js";
|
|
41
45
|
import { createCodeDisplay } from "../components/code-display.js";
|
|
46
|
+
import { createSkillFileViewer } from "../components/skill-file-viewer.js";
|
|
42
47
|
import { createToolkitTable } from "../formatters/toolkit/dom.js";
|
|
43
48
|
import { createDetailSection } from "../components/detail.js";
|
|
44
49
|
|
|
@@ -65,17 +70,21 @@ async function getAgentData(dataDir = "./data") {
|
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
72
|
* Load templates with caching
|
|
68
|
-
* @returns {Promise<{agent: string, skill: string}>}
|
|
73
|
+
* @returns {Promise<{agent: string, skill: string, install: string, reference: string}>}
|
|
69
74
|
*/
|
|
70
75
|
async function getTemplates() {
|
|
71
76
|
if (!templateCache) {
|
|
72
|
-
const [agentRes, skillRes] = await Promise.all([
|
|
77
|
+
const [agentRes, skillRes, installRes, referenceRes] = await Promise.all([
|
|
73
78
|
fetch("./templates/agent.template.md"),
|
|
74
79
|
fetch("./templates/skill.template.md"),
|
|
80
|
+
fetch("./templates/skill-install.template.sh"),
|
|
81
|
+
fetch("./templates/skill-reference.template.md"),
|
|
75
82
|
]);
|
|
76
83
|
templateCache = {
|
|
77
84
|
agent: await agentRes.text(),
|
|
78
85
|
skill: await skillRes.text(),
|
|
86
|
+
install: await installRes.text(),
|
|
87
|
+
reference: await referenceRes.text(),
|
|
79
88
|
};
|
|
80
89
|
}
|
|
81
90
|
return templateCache;
|
|
@@ -271,7 +280,6 @@ export async function renderAgentBuilder() {
|
|
|
271
280
|
skills: data.skills,
|
|
272
281
|
behaviours: data.behaviours,
|
|
273
282
|
agentBehaviours: agentData.behaviours,
|
|
274
|
-
capabilities: data.capabilities,
|
|
275
283
|
vscodeSettings: agentData.vscodeSettings,
|
|
276
284
|
devcontainer: agentData.devcontainer,
|
|
277
285
|
templates,
|
|
@@ -449,7 +457,6 @@ function createAllStagesPreview(context) {
|
|
|
449
457
|
skills,
|
|
450
458
|
behaviours,
|
|
451
459
|
agentBehaviours,
|
|
452
|
-
capabilities,
|
|
453
460
|
vscodeSettings,
|
|
454
461
|
devcontainer,
|
|
455
462
|
templates,
|
|
@@ -468,7 +475,6 @@ function createAllStagesPreview(context) {
|
|
|
468
475
|
agentBehaviours,
|
|
469
476
|
agentDiscipline,
|
|
470
477
|
agentTrack,
|
|
471
|
-
capabilities,
|
|
472
478
|
stages,
|
|
473
479
|
});
|
|
474
480
|
|
|
@@ -482,7 +488,6 @@ function createAllStagesPreview(context) {
|
|
|
482
488
|
agentBehaviours,
|
|
483
489
|
agentDiscipline,
|
|
484
490
|
agentTrack,
|
|
485
|
-
capabilities,
|
|
486
491
|
stages,
|
|
487
492
|
agentIndex,
|
|
488
493
|
});
|
|
@@ -545,9 +550,7 @@ function createAllStagesPreview(context) {
|
|
|
545
550
|
skillFiles.length > 0
|
|
546
551
|
? div(
|
|
547
552
|
{ className: "skill-cards-grid" },
|
|
548
|
-
...skillFiles.map((skill) =>
|
|
549
|
-
createSkillCard(skill, templates.skill),
|
|
550
|
-
),
|
|
553
|
+
...skillFiles.map((skill) => createSkillCard(skill, templates)),
|
|
551
554
|
)
|
|
552
555
|
: p(
|
|
553
556
|
{ className: "text-muted" },
|
|
@@ -581,7 +584,6 @@ function createSingleStagePreview(context, stage) {
|
|
|
581
584
|
skills,
|
|
582
585
|
behaviours,
|
|
583
586
|
agentBehaviours,
|
|
584
|
-
capabilities,
|
|
585
587
|
vscodeSettings,
|
|
586
588
|
devcontainer,
|
|
587
589
|
stages,
|
|
@@ -600,7 +602,6 @@ function createSingleStagePreview(context, stage) {
|
|
|
600
602
|
agentBehaviours,
|
|
601
603
|
agentDiscipline,
|
|
602
604
|
agentTrack,
|
|
603
|
-
capabilities,
|
|
604
605
|
stages,
|
|
605
606
|
});
|
|
606
607
|
|
|
@@ -614,7 +615,6 @@ function createSingleStagePreview(context, stage) {
|
|
|
614
615
|
agentBehaviours,
|
|
615
616
|
agentDiscipline,
|
|
616
617
|
agentTrack,
|
|
617
|
-
capabilities,
|
|
618
618
|
stages,
|
|
619
619
|
agentIndex,
|
|
620
620
|
});
|
|
@@ -667,9 +667,7 @@ function createSingleStagePreview(context, stage) {
|
|
|
667
667
|
skillFiles.length > 0
|
|
668
668
|
? div(
|
|
669
669
|
{ className: "skill-cards-grid" },
|
|
670
|
-
...skillFiles.map((skill) =>
|
|
671
|
-
createSkillCard(skill, templates.skill),
|
|
672
|
-
),
|
|
670
|
+
...skillFiles.map((skill) => createSkillCard(skill, templates)),
|
|
673
671
|
)
|
|
674
672
|
: p(
|
|
675
673
|
{ className: "text-muted" },
|
|
@@ -716,6 +714,7 @@ function createAgentCard(stage, profile, stages, agentTemplate, _derived) {
|
|
|
716
714
|
content,
|
|
717
715
|
filename: profile.filename,
|
|
718
716
|
maxHeight: 400,
|
|
717
|
+
open: true,
|
|
719
718
|
}),
|
|
720
719
|
),
|
|
721
720
|
);
|
|
@@ -724,28 +723,58 @@ function createAgentCard(stage, profile, stages, agentTemplate, _derived) {
|
|
|
724
723
|
}
|
|
725
724
|
|
|
726
725
|
/**
|
|
727
|
-
* Create a skill card
|
|
726
|
+
* Create a skill card with tabbed file viewer
|
|
728
727
|
* @param {Object} skill - Skill with frontmatter and body
|
|
729
|
-
* @param {string}
|
|
728
|
+
* @param {{skill: string, install: string, reference: string}} templates - Mustache templates
|
|
730
729
|
* @returns {HTMLElement}
|
|
731
730
|
*/
|
|
732
|
-
function createSkillCard(skill,
|
|
733
|
-
const content = formatAgentSkill(skill,
|
|
731
|
+
function createSkillCard(skill, templates) {
|
|
732
|
+
const content = formatAgentSkill(skill, templates.skill);
|
|
734
733
|
const filename = `${skill.dirname}/SKILL.md`;
|
|
735
734
|
|
|
735
|
+
// Build files array for the tabbed viewer
|
|
736
|
+
/** @type {import('../components/skill-file-viewer.js').SkillFile[]} */
|
|
737
|
+
const files = [
|
|
738
|
+
{
|
|
739
|
+
filename,
|
|
740
|
+
content,
|
|
741
|
+
language: "markdown",
|
|
742
|
+
},
|
|
743
|
+
];
|
|
744
|
+
|
|
745
|
+
if (skill.installScript) {
|
|
746
|
+
files.push({
|
|
747
|
+
filename: `${skill.dirname}/scripts/install.sh`,
|
|
748
|
+
content: formatInstallScript(skill, templates.install),
|
|
749
|
+
language: "bash",
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (skill.implementationReference) {
|
|
754
|
+
files.push({
|
|
755
|
+
filename: `${skill.dirname}/references/REFERENCE.md`,
|
|
756
|
+
content: formatReference(skill, templates.reference),
|
|
757
|
+
language: "markdown",
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Count total files for badge
|
|
762
|
+
const fileCount = files.length;
|
|
763
|
+
const headerChildren = [
|
|
764
|
+
span({ className: "skill-card-name" }, skill.frontmatter.name),
|
|
765
|
+
];
|
|
766
|
+
if (fileCount > 1) {
|
|
767
|
+
headerChildren.push(
|
|
768
|
+
span({ className: "skill-card-badge" }, `${fileCount} files`),
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
|
|
736
772
|
return div(
|
|
737
773
|
{ className: "skill-card" },
|
|
738
|
-
div(
|
|
739
|
-
{ className: "skill-card-header" },
|
|
740
|
-
span({ className: "skill-card-name" }, skill.frontmatter.name),
|
|
741
|
-
),
|
|
774
|
+
div({ className: "skill-card-header" }, ...headerChildren),
|
|
742
775
|
div(
|
|
743
776
|
{ className: "skill-card-preview" },
|
|
744
|
-
|
|
745
|
-
content,
|
|
746
|
-
filename,
|
|
747
|
-
maxHeight: 300,
|
|
748
|
-
}),
|
|
777
|
+
createSkillFileViewer({ files, maxHeight: 300 }),
|
|
749
778
|
),
|
|
750
779
|
);
|
|
751
780
|
}
|
|
@@ -787,10 +816,27 @@ function createDownloadAllButton(
|
|
|
787
816
|
zip.file(`.github/agents/${profile.filename}`, content);
|
|
788
817
|
}
|
|
789
818
|
|
|
790
|
-
// Add skills
|
|
819
|
+
// Add skills (SKILL.md + optional install script + optional reference)
|
|
791
820
|
for (const skill of skillFiles) {
|
|
792
821
|
const content = formatAgentSkill(skill, templates.skill);
|
|
793
822
|
zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, content);
|
|
823
|
+
|
|
824
|
+
if (skill.installScript) {
|
|
825
|
+
const installContent = formatInstallScript(skill, templates.install);
|
|
826
|
+
zip.file(
|
|
827
|
+
`.claude/skills/${skill.dirname}/scripts/install.sh`,
|
|
828
|
+
installContent,
|
|
829
|
+
{ unixPermissions: "755" },
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (skill.implementationReference) {
|
|
834
|
+
const refContent = formatReference(skill, templates.reference);
|
|
835
|
+
zip.file(
|
|
836
|
+
`.claude/skills/${skill.dirname}/references/REFERENCE.md`,
|
|
837
|
+
refContent,
|
|
838
|
+
);
|
|
839
|
+
}
|
|
794
840
|
}
|
|
795
841
|
|
|
796
842
|
// Add VS Code settings
|
|
@@ -870,10 +916,27 @@ function createDownloadSingleButton(
|
|
|
870
916
|
const content = formatAgentProfile(profile, templates.agent);
|
|
871
917
|
zip.file(`.github/agents/${profile.filename}`, content);
|
|
872
918
|
|
|
873
|
-
// Add skills
|
|
919
|
+
// Add skills (SKILL.md + optional install script + optional reference)
|
|
874
920
|
for (const skill of skillFiles) {
|
|
875
921
|
const skillContent = formatAgentSkill(skill, templates.skill);
|
|
876
922
|
zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, skillContent);
|
|
923
|
+
|
|
924
|
+
if (skill.installScript) {
|
|
925
|
+
const installContent = formatInstallScript(skill, templates.install);
|
|
926
|
+
zip.file(
|
|
927
|
+
`.claude/skills/${skill.dirname}/scripts/install.sh`,
|
|
928
|
+
installContent,
|
|
929
|
+
{ unixPermissions: "755" },
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (skill.implementationReference) {
|
|
934
|
+
const refContent = formatReference(skill, templates.reference);
|
|
935
|
+
zip.file(
|
|
936
|
+
`.claude/skills/${skill.dirname}/references/REFERENCE.md`,
|
|
937
|
+
refContent,
|
|
938
|
+
);
|
|
939
|
+
}
|
|
877
940
|
}
|
|
878
941
|
|
|
879
942
|
// Add VS Code settings
|
package/src/pages/skill.js
CHANGED
|
@@ -14,6 +14,23 @@ import {
|
|
|
14
14
|
getCapabilityEmoji,
|
|
15
15
|
getConceptEmoji,
|
|
16
16
|
} from "@forwardimpact/schema/levels";
|
|
17
|
+
import { generateSkillMarkdown } from "@forwardimpact/model";
|
|
18
|
+
import { formatAgentSkill } from "../formatters/agent/skill.js";
|
|
19
|
+
|
|
20
|
+
/** @type {string|null} Cached skill template */
|
|
21
|
+
let skillTemplateCache = null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Load skill Mustache template with caching
|
|
25
|
+
* @returns {Promise<string>}
|
|
26
|
+
*/
|
|
27
|
+
async function getSkillTemplate() {
|
|
28
|
+
if (!skillTemplateCache) {
|
|
29
|
+
const res = await fetch("./templates/skill.template.md");
|
|
30
|
+
skillTemplateCache = await res.text();
|
|
31
|
+
}
|
|
32
|
+
return skillTemplateCache;
|
|
33
|
+
}
|
|
17
34
|
|
|
18
35
|
/**
|
|
19
36
|
* Render skills list page
|
|
@@ -69,7 +86,7 @@ export function renderSkillsList() {
|
|
|
69
86
|
* Render skill detail page
|
|
70
87
|
* @param {Object} params - Route params
|
|
71
88
|
*/
|
|
72
|
-
export function renderSkillDetail(params) {
|
|
89
|
+
export async function renderSkillDetail(params) {
|
|
73
90
|
const { data } = getState();
|
|
74
91
|
const skill = data.skills.find((s) => s.id === params.id);
|
|
75
92
|
|
|
@@ -83,6 +100,14 @@ export function renderSkillDetail(params) {
|
|
|
83
100
|
return;
|
|
84
101
|
}
|
|
85
102
|
|
|
103
|
+
// Generate SKILL.md content if skill has an agent section
|
|
104
|
+
let agentSkillContent;
|
|
105
|
+
if (skill.agent) {
|
|
106
|
+
const template = await getSkillTemplate();
|
|
107
|
+
const skillData = generateSkillMarkdown(skill, data.stages);
|
|
108
|
+
agentSkillContent = formatAgentSkill(skillData, template);
|
|
109
|
+
}
|
|
110
|
+
|
|
86
111
|
// Use DOM formatter - it handles transformation internally
|
|
87
112
|
render(
|
|
88
113
|
skillToDOM(skill, {
|
|
@@ -90,6 +115,7 @@ export function renderSkillDetail(params) {
|
|
|
90
115
|
tracks: data.tracks,
|
|
91
116
|
drivers: data.drivers,
|
|
92
117
|
capabilities: data.capabilities,
|
|
118
|
+
agentSkillContent,
|
|
93
119
|
}),
|
|
94
120
|
);
|
|
95
121
|
}
|