@forwardimpact/pathway 0.16.1 → 0.17.1

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.
@@ -103,31 +103,62 @@
103
103
  color: var(--color-primary);
104
104
  }
105
105
 
106
- /* Code display - unified component for code/markdown with copy buttons */
107
- .code-display-container {
108
- display: flex;
109
- flex-direction: column;
110
- gap: var(--space-sm);
106
+ /* Code display - collapsible component for code/markdown with copy buttons */
107
+ .code-display-pane {
108
+ border: 1px solid var(--color-border);
109
+ border-radius: var(--radius-md);
110
+ background: var(--color-bg);
111
+ overflow: hidden;
111
112
  }
112
113
 
113
- .code-display-header {
114
+ .code-display-summary {
114
115
  display: flex;
115
- justify-content: space-between;
116
116
  align-items: center;
117
- flex-wrap: wrap;
118
- gap: var(--space-md);
117
+ gap: var(--space-sm);
118
+ padding: var(--space-sm) var(--space-md);
119
+ cursor: pointer;
120
+ list-style: none;
121
+ background: var(--color-surface);
122
+ border-bottom: 1px solid transparent;
119
123
  }
120
124
 
121
- .code-display-info {
122
- display: flex;
123
- flex-direction: column;
124
- gap: var(--space-xs);
125
+ .code-display-summary::-webkit-details-marker {
126
+ display: none;
127
+ }
128
+
129
+ .code-display-summary::before {
130
+ content: "▸";
131
+ font-size: var(--font-size-xs);
132
+ color: var(--color-text-muted);
133
+ flex-shrink: 0;
134
+ }
135
+
136
+ .code-display-pane[open] > .code-display-summary {
137
+ border-bottom-color: var(--color-border);
138
+ }
139
+
140
+ .code-display-pane[open] > .code-display-summary::before {
141
+ content: "▾";
142
+ }
143
+
144
+ .code-display-summary .code-display-filename {
125
145
  flex: 1;
126
- min-width: 200px;
146
+ min-width: 0;
147
+ overflow: hidden;
148
+ text-overflow: ellipsis;
149
+ white-space: nowrap;
127
150
  }
128
151
 
129
- .code-display-info .text-muted {
130
- margin: 0;
152
+ .code-display-summary .button-group {
153
+ flex-shrink: 0;
154
+ }
155
+
156
+ .code-display-body {
157
+ padding: var(--space-sm) var(--space-md) var(--space-md);
158
+ }
159
+
160
+ .code-display-body .text-muted {
161
+ margin: 0 0 var(--space-sm);
131
162
  }
132
163
 
133
164
  .code-display-filename {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Skill File Viewer
3
+ *
4
+ * Vertically stacked collapsible panes for viewing skill files
5
+ * (SKILL.md, install.sh, REFERENCE.md).
6
+ * Uses code-display-pane component with sfv-pane modifier.
7
+ */
8
+
9
+ @layer components {
10
+ .sfv {
11
+ display: flex;
12
+ flex-direction: column;
13
+ }
14
+
15
+ .sfv-pane + .sfv-pane {
16
+ margin-top: var(--space-sm);
17
+ }
18
+ }
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Agent Builder Page Styles
3
3
  *
4
- * Agent builder form, preview, and card components.
4
+ * Agent builder form, preview, and card grid layout.
5
+ * Card surfaces are in components/file-card.css.
5
6
  */
6
7
 
7
8
  @layer pages {
@@ -32,11 +33,9 @@
32
33
  gap: var(--space-xl);
33
34
  }
34
35
 
36
+ /* Agent section — spacing container, no surface */
35
37
  .agent-section {
36
- background: var(--color-surface);
37
- border-radius: var(--radius-lg);
38
- padding: var(--space-lg);
39
- box-shadow: var(--shadow-md);
38
+ margin-bottom: var(--space-lg);
40
39
  }
41
40
 
42
41
  .agent-section .section-header {
@@ -50,13 +49,6 @@
50
49
  margin: 0;
51
50
  }
52
51
 
53
- .agent-section .filename {
54
- font-family: var(--font-family-mono);
55
- font-size: var(--font-size-xs);
56
- color: var(--color-text-muted);
57
- margin-bottom: var(--space-sm);
58
- }
59
-
60
52
  /* Agent cards grid */
61
53
  .agent-cards-grid {
62
54
  display: grid;
@@ -69,51 +61,6 @@
69
61
  max-width: 800px;
70
62
  }
71
63
 
72
- .agent-card {
73
- border: 1px solid var(--color-border);
74
- border-radius: var(--radius-lg);
75
- background: var(--color-bg);
76
- overflow: hidden;
77
- }
78
-
79
- .agent-card-header {
80
- display: flex;
81
- justify-content: space-between;
82
- align-items: flex-start;
83
- padding: var(--space-md);
84
- background: var(--color-surface);
85
- border-bottom: 1px solid var(--color-border);
86
- gap: var(--space-sm);
87
- }
88
-
89
- .agent-card-title {
90
- display: flex;
91
- align-items: center;
92
- gap: var(--space-sm);
93
- flex-wrap: wrap;
94
- }
95
-
96
- .agent-card-title h3 {
97
- margin: 0;
98
- font-size: var(--font-size-md);
99
- }
100
-
101
- .agent-card-emoji {
102
- font-size: 1.5rem;
103
- }
104
-
105
- .agent-card-preview {
106
- padding: var(--space-md);
107
- }
108
-
109
- .agent-card-preview .code-display-container {
110
- margin: 0;
111
- }
112
-
113
- .agent-card-preview .code-display {
114
- max-height: 400px;
115
- }
116
-
117
64
  /* Skill cards grid */
118
65
  .skill-cards-grid {
119
66
  display: grid;
@@ -121,39 +68,6 @@
121
68
  gap: var(--space-md);
122
69
  }
123
70
 
124
- .skill-card {
125
- border: 1px solid var(--color-border);
126
- border-radius: var(--radius-md);
127
- background: var(--color-bg);
128
- overflow: hidden;
129
- }
130
-
131
- .skill-card-header {
132
- display: flex;
133
- justify-content: space-between;
134
- align-items: center;
135
- padding: var(--space-sm) var(--space-md);
136
- background: var(--color-surface);
137
- border-bottom: 1px solid var(--color-border);
138
- }
139
-
140
- .skill-card-name {
141
- font-weight: 600;
142
- font-size: var(--font-size-sm);
143
- }
144
-
145
- .skill-card-preview {
146
- padding: var(--space-sm) var(--space-md) var(--space-md);
147
- }
148
-
149
- .skill-card-preview .code-display-container {
150
- margin: 0;
151
- }
152
-
153
- .skill-card-preview .code-display {
154
- max-height: 300px;
155
- }
156
-
157
71
  .skills-list {
158
72
  display: flex;
159
73
  flex-direction: column;
@@ -167,59 +81,6 @@
167
81
  gap: var(--space-md);
168
82
  }
169
83
 
170
- .role-agent-card {
171
- border: 1px solid var(--color-border);
172
- border-radius: var(--radius-md);
173
- overflow: hidden;
174
- }
175
-
176
- .role-agent-card > summary {
177
- cursor: pointer;
178
- padding: var(--space-md);
179
- background: var(--color-bg);
180
- border-bottom: 1px solid transparent;
181
- list-style: none;
182
- }
183
-
184
- .role-agent-card > summary::-webkit-details-marker {
185
- display: none;
186
- }
187
-
188
- .role-agent-card[open] > summary {
189
- border-bottom-color: var(--color-border);
190
- }
191
-
192
- .role-agent-header {
193
- display: flex;
194
- justify-content: space-between;
195
- align-items: center;
196
- }
197
-
198
- .role-name {
199
- font-weight: var(--font-weight-semibold);
200
- color: var(--color-text);
201
- }
202
-
203
- .role-filename {
204
- font-family: var(--font-family-mono);
205
- font-size: var(--font-size-xs);
206
- color: var(--color-text-muted);
207
- }
208
-
209
- .role-agent-content {
210
- padding: var(--space-md);
211
- }
212
-
213
- .role-description {
214
- margin-bottom: var(--space-md);
215
- }
216
-
217
- .role-agent-actions {
218
- display: flex;
219
- gap: var(--space-sm);
220
- margin-bottom: var(--space-sm);
221
- }
222
-
223
84
  /* Stage agent preview */
224
85
  .stage-agent-preview .stage-header {
225
86
  display: flex;
@@ -5,17 +5,7 @@
5
5
  * Includes copy and download functionality.
6
6
  */
7
7
 
8
- import {
9
- div,
10
- h2,
11
- h3,
12
- p,
13
- span,
14
- button,
15
- section,
16
- details,
17
- summary,
18
- } from "../../lib/render.js";
8
+ import { div, h2, h3, p, span, button, section } from "../../lib/render.js";
19
9
  import { createCodeDisplay } from "../../components/code-display.js";
20
10
  import { formatAgentProfile } from "./profile.js";
21
11
  import { formatAgentSkill } from "./skill.js";
@@ -163,33 +153,13 @@ function createSkillCard(skill) {
163
153
  */
164
154
  function createRoleAgentCard(agent) {
165
155
  const content = formatAgentProfile(agent);
166
- const roleName = agent.frontmatter.name.split("-").pop(); // Extract role suffix (plan, review)
167
156
 
168
- return details(
169
- { className: "role-agent-card" },
170
- summary(
171
- {},
172
- div(
173
- { className: "role-agent-header" },
174
- span(
175
- { className: "role-name" },
176
- `${roleName.charAt(0).toUpperCase() + roleName.slice(1)} Agent`,
177
- ),
178
- span({ className: "role-filename" }, agent.filename),
179
- ),
180
- ),
181
- div(
182
- { className: "role-agent-content" },
183
- p(
184
- { className: "text-muted role-description" },
185
- agent.frontmatter.description,
186
- ),
187
- createCodeDisplay({
188
- content,
189
- maxHeight: 400,
190
- }),
191
- ),
192
- );
157
+ return createCodeDisplay({
158
+ content,
159
+ filename: agent.filename,
160
+ description: agent.frontmatter.description,
161
+ maxHeight: 400,
162
+ });
193
163
  }
194
164
 
195
165
  /**
@@ -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 file content
5
- * following the Agent Skills Standard specification.
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.reference - Reference content (markdown)
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
- reference,
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 trimmedReference = trimValue(reference) || "";
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
- reference: trimmedReference,
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, reference
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.reference - Reference content (markdown)
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
- { frontmatter, title, stages, reference, toolReferences },
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
- reference,
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 createDetailSection({
237
- title: "Job Description",
238
- content: createCodeDisplay({
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 { createCodeDisplay } from "../../components/code-display.js";
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
- // Implementation Reference
226
- showToolsAndPatterns && view.implementationReference
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" }, "Implementation Patterns"),
230
- createCodeDisplay({
231
- content: view.implementationReference,
232
- description:
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
  }