@forwardimpact/pathway 0.25.15 → 0.25.20

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.
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Download button components for agent builder page
3
+ */
4
+
5
+ import { formatAgentProfile } from "../formatters/agent/profile.js";
6
+ import {
7
+ formatAgentSkill,
8
+ formatInstallScript,
9
+ formatReference,
10
+ } from "../formatters/agent/skill.js";
11
+
12
+ /**
13
+ * Dynamically import JSZip from CDN
14
+ * @returns {Promise<typeof JSZip>}
15
+ */
16
+ async function importJSZip() {
17
+ const module = await import("https://cdn.jsdelivr.net/npm/jszip@3.10.1/+esm");
18
+ return module.default;
19
+ }
20
+
21
+ /**
22
+ * Add skill files to a zip archive
23
+ * @param {Object} zip - JSZip instance
24
+ * @param {Array} skillFiles - Skill file objects
25
+ * @param {Object} templates - Templates object
26
+ */
27
+ function addSkillsToZip(zip, skillFiles, templates) {
28
+ for (const skill of skillFiles) {
29
+ const content = formatAgentSkill(skill, templates.skill);
30
+ zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, content);
31
+
32
+ if (skill.installScript) {
33
+ const installContent = formatInstallScript(skill, templates.install);
34
+ zip.file(
35
+ `.claude/skills/${skill.dirname}/scripts/install.sh`,
36
+ installContent,
37
+ { unixPermissions: "755" },
38
+ );
39
+ }
40
+
41
+ if (skill.implementationReference) {
42
+ const refContent = formatReference(skill, templates.reference);
43
+ zip.file(
44
+ `.claude/skills/${skill.dirname}/references/REFERENCE.md`,
45
+ refContent,
46
+ );
47
+ }
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Add Claude Code settings to a zip archive
53
+ * @param {Object} zip - JSZip instance
54
+ * @param {Object} claudeCodeSettings
55
+ */
56
+ function addSettingsToZip(zip, claudeCodeSettings) {
57
+ if (Object.keys(claudeCodeSettings).length > 0) {
58
+ zip.file(
59
+ ".claude/settings.json",
60
+ JSON.stringify(claudeCodeSettings, null, 2) + "\n",
61
+ );
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Trigger a browser download of a blob
67
+ * @param {Blob} blob
68
+ * @param {string} filename
69
+ */
70
+ function downloadBlob(blob, filename) {
71
+ const url = URL.createObjectURL(blob);
72
+ const link = document.createElement("a");
73
+ link.href = url;
74
+ link.download = filename;
75
+ document.body.appendChild(link);
76
+ link.click();
77
+ document.body.removeChild(link);
78
+ URL.revokeObjectURL(url);
79
+ }
80
+
81
+ /**
82
+ * Create download all button for all stages
83
+ * @param {Array} stageAgents - Array of {stage, derived, profile}
84
+ * @param {Array} skillFiles - Array of skill file objects
85
+ * @param {Object} claudeCodeSettings - Claude Code settings
86
+ * @param {Object} context - Context with discipline/track info and templates
87
+ * @returns {HTMLElement}
88
+ */
89
+ export function createDownloadAllButton(
90
+ stageAgents,
91
+ skillFiles,
92
+ claudeCodeSettings,
93
+ context,
94
+ ) {
95
+ const { humanDiscipline, humanTrack, templates } = context;
96
+ const agentName = `${humanDiscipline.id}-${humanTrack.id}`.replace(/_/g, "-");
97
+
98
+ const btn = document.createElement("button");
99
+ btn.className = "btn btn-primary download-all-btn";
100
+ btn.textContent = "📦 Download All (.zip)";
101
+
102
+ btn.addEventListener("click", async () => {
103
+ btn.disabled = true;
104
+ btn.textContent = "Generating...";
105
+
106
+ try {
107
+ const JSZip = await importJSZip();
108
+ const zip = new JSZip();
109
+
110
+ for (const { profile } of stageAgents) {
111
+ const content = formatAgentProfile(profile, templates.agent);
112
+ zip.file(`.claude/agents/${profile.filename}`, content);
113
+ }
114
+
115
+ addSkillsToZip(zip, skillFiles, templates);
116
+ addSettingsToZip(zip, claudeCodeSettings);
117
+
118
+ const blob = await zip.generateAsync({ type: "blob" });
119
+ downloadBlob(blob, `${agentName}-agents.zip`);
120
+ } finally {
121
+ btn.disabled = false;
122
+ btn.textContent = "📦 Download All (.zip)";
123
+ }
124
+ });
125
+
126
+ return btn;
127
+ }
128
+
129
+ /**
130
+ * Create download button for single stage
131
+ * @param {Object} profile - Agent profile
132
+ * @param {Array} skillFiles - Skill files
133
+ * @param {Object} claudeCodeSettings - Claude Code settings
134
+ * @param {{agent: string, skill: string}} templates - Mustache templates
135
+ * @returns {HTMLElement}
136
+ */
137
+ export function createDownloadSingleButton(
138
+ profile,
139
+ skillFiles,
140
+ claudeCodeSettings,
141
+ templates,
142
+ ) {
143
+ const btn = document.createElement("button");
144
+ btn.className = "btn btn-primary download-all-btn";
145
+ btn.textContent = "📥 Download Agent (.zip)";
146
+
147
+ btn.addEventListener("click", async () => {
148
+ btn.disabled = true;
149
+ btn.textContent = "Generating...";
150
+
151
+ try {
152
+ const JSZip = await importJSZip();
153
+ const zip = new JSZip();
154
+
155
+ const content = formatAgentProfile(profile, templates.agent);
156
+ zip.file(`.claude/agents/${profile.filename}`, content);
157
+
158
+ addSkillsToZip(zip, skillFiles, templates);
159
+ addSettingsToZip(zip, claudeCodeSettings);
160
+
161
+ const blob = await zip.generateAsync({ type: "blob" });
162
+ downloadBlob(blob, `${profile.frontmatter.name}-agent.zip`);
163
+ } finally {
164
+ btn.disabled = false;
165
+ btn.textContent = "📥 Download Agent (.zip)";
166
+ }
167
+ });
168
+
169
+ return btn;
170
+ }
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Agent builder preview components
3
+ *
4
+ * Preview panels for all-stages and single-stage agent generation.
5
+ */
6
+
7
+ import { div, h2, h3, p, span, section } from "../lib/render.js";
8
+ import {
9
+ generateStageAgentProfile,
10
+ deriveStageAgent,
11
+ generateSkillMarkdown,
12
+ deriveAgentSkills,
13
+ deriveToolkit,
14
+ } from "@forwardimpact/libskill";
15
+ import { getStageEmoji } from "../formatters/stage/shared.js";
16
+ import { formatAgentProfile } from "../formatters/agent/profile.js";
17
+ import {
18
+ formatAgentSkill,
19
+ formatInstallScript,
20
+ formatReference,
21
+ } from "../formatters/agent/skill.js";
22
+ import { createFileCard } from "../components/file-card.js";
23
+ import { createToolkitTable } from "../formatters/toolkit/dom.js";
24
+ import { createDetailSection } from "../components/detail.js";
25
+ import {
26
+ createDownloadAllButton,
27
+ createDownloadSingleButton,
28
+ } from "./agent-builder-download.js";
29
+
30
+ /**
31
+ * Build a file card for a skill with 1-3 file panes (accordion).
32
+ * @param {Object} skill
33
+ * @param {Object} templates
34
+ * @returns {HTMLElement}
35
+ */
36
+ function buildSkillFileCard(skill, templates) {
37
+ const content = formatAgentSkill(skill, templates.skill);
38
+
39
+ const files = [
40
+ {
41
+ filename: `${skill.dirname}/SKILL.md`,
42
+ content,
43
+ language: "markdown",
44
+ },
45
+ ];
46
+
47
+ if (skill.installScript) {
48
+ files.push({
49
+ filename: `${skill.dirname}/scripts/install.sh`,
50
+ content: formatInstallScript(skill, templates.install),
51
+ language: "bash",
52
+ });
53
+ }
54
+
55
+ if (skill.implementationReference) {
56
+ files.push({
57
+ filename: `${skill.dirname}/references/REFERENCE.md`,
58
+ content: formatReference(skill, templates.reference),
59
+ language: "markdown",
60
+ });
61
+ }
62
+
63
+ const headerChildren = [
64
+ span({ className: "file-card-name" }, skill.frontmatter.name),
65
+ ];
66
+ if (files.length > 1) {
67
+ headerChildren.push(
68
+ span({ className: "file-card-badge" }, `${files.length} files`),
69
+ );
70
+ }
71
+
72
+ return createFileCard({
73
+ header: headerChildren,
74
+ files,
75
+ maxHeight: 300,
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Create a skills section for the preview
81
+ * @param {Array} skillFiles
82
+ * @param {Object} templates
83
+ * @returns {HTMLElement}
84
+ */
85
+ function createSkillsSection(skillFiles, templates) {
86
+ return section(
87
+ { className: "agent-section" },
88
+ h2({}, `Skills (${skillFiles.length})`),
89
+ skillFiles.length > 0
90
+ ? div(
91
+ { className: "skill-cards-grid" },
92
+ ...skillFiles.map((skill) => buildSkillFileCard(skill, templates)),
93
+ )
94
+ : p(
95
+ { className: "text-muted" },
96
+ "No skills with agent sections found for this discipline.",
97
+ ),
98
+ );
99
+ }
100
+
101
+ /**
102
+ * Derive skill files and toolkit from context
103
+ * @param {Object} context
104
+ * @returns {{derivedSkills: Array, skillFiles: Array, toolkit: Array}}
105
+ */
106
+ function deriveSkillData(context) {
107
+ const { humanDiscipline, humanTrack, level, skills, stages } = context;
108
+ const derivedSkills = deriveAgentSkills({
109
+ discipline: humanDiscipline,
110
+ track: humanTrack,
111
+ level,
112
+ skills,
113
+ });
114
+
115
+ const skillFiles = derivedSkills
116
+ .map((d) => skills.find((s) => s.id === d.skillId))
117
+ .filter((skill) => skill?.agent)
118
+ .map((skill) => generateSkillMarkdown(skill, stages));
119
+
120
+ const toolkit = deriveToolkit({
121
+ skillMatrix: derivedSkills,
122
+ skills,
123
+ });
124
+
125
+ return { derivedSkills, skillFiles, toolkit };
126
+ }
127
+
128
+ /**
129
+ * Create preview for all stages
130
+ * @param {Object} context
131
+ * @returns {HTMLElement}
132
+ */
133
+ export function createAllStagesPreview(context) {
134
+ const {
135
+ humanDiscipline,
136
+ humanTrack,
137
+ agentDiscipline,
138
+ agentTrack,
139
+ level,
140
+ stages,
141
+ skills,
142
+ behaviours,
143
+ agentBehaviours,
144
+ claudeCodeSettings,
145
+ templates,
146
+ } = context;
147
+
148
+ const stageAgents = stages.map((stage) => {
149
+ const derived = deriveStageAgent({
150
+ discipline: humanDiscipline,
151
+ track: humanTrack,
152
+ stage,
153
+ level,
154
+ skills,
155
+ behaviours,
156
+ agentBehaviours,
157
+ agentDiscipline,
158
+ agentTrack,
159
+ });
160
+
161
+ const profile = generateStageAgentProfile({
162
+ discipline: humanDiscipline,
163
+ track: humanTrack,
164
+ stage,
165
+ level,
166
+ skills,
167
+ behaviours,
168
+ agentBehaviours,
169
+ agentDiscipline,
170
+ agentTrack,
171
+ stages,
172
+ });
173
+
174
+ return { stage, derived, profile };
175
+ });
176
+
177
+ const { skillFiles, toolkit } = deriveSkillData(context);
178
+
179
+ return div(
180
+ { className: "agent-deployment" },
181
+ createDownloadAllButton(
182
+ stageAgents,
183
+ skillFiles,
184
+ claudeCodeSettings,
185
+ context,
186
+ ),
187
+ section(
188
+ { className: "agent-section" },
189
+ h2({}, `Agents (${stageAgents.length})`),
190
+ p(
191
+ { className: "text-muted" },
192
+ "Stage-specific agents with skills, constraints, and stage transitions.",
193
+ ),
194
+ div(
195
+ { className: "agent-cards-grid" },
196
+ ...stageAgents.map(({ stage, profile }) => {
197
+ const content = formatAgentProfile(profile, templates.agent);
198
+ const stageEmoji = getStageEmoji(stages, stage.id);
199
+ return createFileCard({
200
+ header: [
201
+ span({ className: "file-card-emoji" }, stageEmoji),
202
+ h3({}, `${stage.name} Agent`),
203
+ ],
204
+ files: [
205
+ { filename: profile.filename, content, language: "markdown" },
206
+ ],
207
+ maxHeight: 400,
208
+ });
209
+ }),
210
+ ),
211
+ ),
212
+ createSkillsSection(skillFiles, templates),
213
+ toolkit.length > 0
214
+ ? createDetailSection({
215
+ title: `Tool Kit (${toolkit.length})`,
216
+ content: createToolkitTable(toolkit),
217
+ })
218
+ : null,
219
+ );
220
+ }
221
+
222
+ /**
223
+ * Create preview for a single stage
224
+ * @param {Object} context
225
+ * @param {Object} stage
226
+ * @returns {HTMLElement}
227
+ */
228
+ export function createSingleStagePreview(context, stage) {
229
+ const {
230
+ humanDiscipline,
231
+ humanTrack,
232
+ agentDiscipline,
233
+ agentTrack,
234
+ level,
235
+ skills,
236
+ behaviours,
237
+ agentBehaviours,
238
+ claudeCodeSettings,
239
+ stages,
240
+ templates,
241
+ } = context;
242
+
243
+ const profile = generateStageAgentProfile({
244
+ discipline: humanDiscipline,
245
+ track: humanTrack,
246
+ stage,
247
+ level,
248
+ skills,
249
+ behaviours,
250
+ agentBehaviours,
251
+ agentDiscipline,
252
+ agentTrack,
253
+ stages,
254
+ });
255
+
256
+ const { skillFiles, toolkit } = deriveSkillData(context);
257
+
258
+ return div(
259
+ { className: "agent-deployment" },
260
+ createDownloadSingleButton(
261
+ profile,
262
+ skillFiles,
263
+ claudeCodeSettings,
264
+ templates,
265
+ ),
266
+ section(
267
+ { className: "agent-section" },
268
+ h2({}, "Agent"),
269
+ div(
270
+ { className: "agent-cards-grid single" },
271
+ (() => {
272
+ const content = formatAgentProfile(profile, templates.agent);
273
+ const stageEmoji = getStageEmoji(stages, stage.id);
274
+ return createFileCard({
275
+ header: [
276
+ span({ className: "file-card-emoji" }, stageEmoji),
277
+ h3({}, `${stage.name} Agent`),
278
+ ],
279
+ files: [
280
+ { filename: profile.filename, content, language: "markdown" },
281
+ ],
282
+ maxHeight: 400,
283
+ });
284
+ })(),
285
+ ),
286
+ ),
287
+ createSkillsSection(skillFiles, templates),
288
+ toolkit.length > 0
289
+ ? createDetailSection({
290
+ title: `Tool Kit (${toolkit.length})`,
291
+ content: createToolkitTable(toolkit),
292
+ })
293
+ : null,
294
+ );
295
+ }
296
+
297
+ /**
298
+ * Create help section explaining how agent builder works
299
+ * @returns {HTMLElement}
300
+ */
301
+ export function createHelpSection() {
302
+ return section(
303
+ { className: "section section-detail" },
304
+ h2({ className: "section-title" }, "How It Works"),
305
+ div(
306
+ { className: "auto-grid-md" },
307
+ div(
308
+ { className: "detail-item" },
309
+ div({ className: "detail-item-label" }, "Stages"),
310
+ p(
311
+ {},
312
+ "Agents are generated for each stage: Plan (research), Code (implement), and Review (verify). " +
313
+ "Each stage has specific skills, constraints, and stage transitions.",
314
+ ),
315
+ ),
316
+ div(
317
+ { className: "detail-item" },
318
+ div({ className: "detail-item-label" }, "Agent Profiles"),
319
+ p(
320
+ {},
321
+ "The .md files contain the agent's identity, skills, and constraints. " +
322
+ "Place them in .claude/agents/ for Claude Code to discover.",
323
+ ),
324
+ ),
325
+ div(
326
+ { className: "detail-item" },
327
+ div({ className: "detail-item-label" }, "Skills"),
328
+ p(
329
+ {},
330
+ "SKILL.md files provide specialized knowledge that agents can use. " +
331
+ "Place them in .claude/skills/{skill-name}/ directories.",
332
+ ),
333
+ ),
334
+ div(
335
+ { className: "detail-item" },
336
+ div({ className: "detail-item-label" }, "All Stages"),
337
+ p(
338
+ {},
339
+ "Select 'All Stages' to download a complete agent deployment with all stage agents and skills in one zip file.",
340
+ ),
341
+ ),
342
+ ),
343
+ );
344
+ }