@forwardimpact/pathway 0.25.21 → 0.25.24
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/bin/fit-pathway.js +117 -325
- package/package.json +2 -2
- package/src/commands/agent-io.js +1 -1
- package/src/commands/agent.js +25 -23
- package/src/commands/behaviour.js +7 -7
- package/src/commands/build-bundle.js +88 -0
- package/src/commands/build-packs.js +566 -0
- package/src/commands/build.js +27 -84
- package/src/commands/command-factory.js +2 -2
- package/src/commands/discipline.js +7 -7
- package/src/commands/driver.js +8 -8
- package/src/commands/index.js +0 -1
- package/src/commands/interview.js +4 -4
- package/src/commands/job.js +24 -18
- package/src/commands/level.js +7 -7
- package/src/commands/progress.js +4 -4
- package/src/commands/questions.js +10 -8
- package/src/commands/skill.js +10 -10
- package/src/commands/stage.js +7 -7
- package/src/commands/tool.js +6 -6
- package/src/commands/track.js +7 -7
- package/src/css/pages/agent-builder.css +48 -0
- package/src/formatters/interview/shared.js +6 -4
- package/src/formatters/progress/shared.js +9 -20
- package/src/formatters/questions/yaml.js +1 -1
- package/src/formatters/skill/shared.js +9 -2
- package/src/formatters/track/shared.js +4 -1
- package/src/index.html +1 -1
- package/src/lib/cli-command.js +33 -33
- package/src/lib/cli-output.js +9 -189
- package/src/pages/agent-builder-install.js +118 -0
- package/src/pages/agent-builder-preview.js +3 -3
- package/src/pages/agent-builder.js +23 -1
- package/src/pages/progress.js +3 -3
- package/src/pages/skill.js +5 -2
- package/src/commands/init.js +0 -64
- package/src/lib/job-cache.js +0 -89
- package/starter/behaviours/systems_thinking.yaml +0 -32
- package/starter/capabilities/delivery.yaml +0 -105
- package/starter/capabilities/reliability.yaml +0 -72
- package/starter/disciplines/software_engineering.yaml +0 -46
- package/starter/drivers.yaml +0 -10
- package/starter/framework.yaml +0 -49
- package/starter/levels.yaml +0 -39
- package/starter/questions/behaviours/.gitkeep +0 -0
- package/starter/questions/capabilities/.gitkeep +0 -0
- package/starter/questions/skills/.gitkeep +0 -0
- package/starter/stages.yaml +0 -21
- package/starter/tracks/forward_deployed.yaml +0 -33
- package/starter/tracks/platform.yaml +0 -33
|
@@ -95,11 +95,18 @@ export function prepareSkillDetail(
|
|
|
95
95
|
if (!skill) return null;
|
|
96
96
|
|
|
97
97
|
const relatedDisciplines = disciplines
|
|
98
|
-
.filter(
|
|
98
|
+
.filter(
|
|
99
|
+
(d) =>
|
|
100
|
+
getSkillTypeForDiscipline({ discipline: d, skillId: skill.id }) !==
|
|
101
|
+
null,
|
|
102
|
+
)
|
|
99
103
|
.map((d) => ({
|
|
100
104
|
id: d.id,
|
|
101
105
|
name: d.specialization || d.name,
|
|
102
|
-
skillType: getSkillTypeForDiscipline(
|
|
106
|
+
skillType: getSkillTypeForDiscipline({
|
|
107
|
+
discipline: d,
|
|
108
|
+
skillId: skill.id,
|
|
109
|
+
}),
|
|
103
110
|
}));
|
|
104
111
|
|
|
105
112
|
const relatedTracks = tracks
|
|
@@ -88,7 +88,10 @@ export function prepareTrackDetail(track, { skills, behaviours }) {
|
|
|
88
88
|
const skillModifiers = track.skillModifiers
|
|
89
89
|
? Object.entries(track.skillModifiers).map(([key, modifier]) => {
|
|
90
90
|
if (isCapability(key)) {
|
|
91
|
-
const capabilitySkills = getSkillsByCapability(
|
|
91
|
+
const capabilitySkills = getSkillsByCapability({
|
|
92
|
+
skills,
|
|
93
|
+
capability: key,
|
|
94
|
+
});
|
|
92
95
|
return {
|
|
93
96
|
id: key,
|
|
94
97
|
name: key.charAt(0).toUpperCase() + key.slice(1),
|
package/src/index.html
CHANGED
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
<div class="top-bar__command">
|
|
84
84
|
<span class="top-bar__prompt">$</span>
|
|
85
85
|
<span class="top-bar__command-text" id="cli-command"
|
|
86
|
-
>
|
|
86
|
+
>npx fit-pathway</span
|
|
87
87
|
>
|
|
88
88
|
<button class="top-bar__copy" id="cli-copy" aria-label="Copy command">
|
|
89
89
|
<svg viewBox="0 0 24 24">
|
package/src/lib/cli-command.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI Command Mapping
|
|
3
3
|
*
|
|
4
|
-
* Maps hash routes to their equivalent `
|
|
4
|
+
* Maps hash routes to their equivalent `npx fit-pathway` CLI commands.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -11,122 +11,122 @@
|
|
|
11
11
|
*/
|
|
12
12
|
const ROUTE_COMMANDS = [
|
|
13
13
|
// Landing
|
|
14
|
-
{ pattern: /^\/$/, toCommand: () => "
|
|
14
|
+
{ pattern: /^\/$/, toCommand: () => "npx fit-pathway" },
|
|
15
15
|
|
|
16
16
|
// Entity lists
|
|
17
|
-
{ pattern: /^\/skill$/, toCommand: () => "
|
|
18
|
-
{ pattern: /^\/behaviour$/, toCommand: () => "
|
|
17
|
+
{ pattern: /^\/skill$/, toCommand: () => "npx fit-pathway skill" },
|
|
18
|
+
{ pattern: /^\/behaviour$/, toCommand: () => "npx fit-pathway behaviour" },
|
|
19
19
|
{
|
|
20
20
|
pattern: /^\/discipline$/,
|
|
21
|
-
toCommand: () => "
|
|
21
|
+
toCommand: () => "npx fit-pathway discipline",
|
|
22
22
|
},
|
|
23
|
-
{ pattern: /^\/track$/, toCommand: () => "
|
|
24
|
-
{ pattern: /^\/level$/, toCommand: () => "
|
|
25
|
-
{ pattern: /^\/driver$/, toCommand: () => "
|
|
26
|
-
{ pattern: /^\/stage$/, toCommand: () => "
|
|
27
|
-
{ pattern: /^\/tool$/, toCommand: () => "
|
|
23
|
+
{ pattern: /^\/track$/, toCommand: () => "npx fit-pathway track" },
|
|
24
|
+
{ pattern: /^\/level$/, toCommand: () => "npx fit-pathway level" },
|
|
25
|
+
{ pattern: /^\/driver$/, toCommand: () => "npx fit-pathway driver" },
|
|
26
|
+
{ pattern: /^\/stage$/, toCommand: () => "npx fit-pathway stage" },
|
|
27
|
+
{ pattern: /^\/tool$/, toCommand: () => "npx fit-pathway tool" },
|
|
28
28
|
|
|
29
29
|
// Entity details
|
|
30
30
|
{
|
|
31
31
|
pattern: /^\/skill\/(.+)$/,
|
|
32
|
-
toCommand: (m) => `
|
|
32
|
+
toCommand: (m) => `npx fit-pathway skill ${m[1]}`,
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
pattern: /^\/behaviour\/(.+)$/,
|
|
36
|
-
toCommand: (m) => `
|
|
36
|
+
toCommand: (m) => `npx fit-pathway behaviour ${m[1]}`,
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
pattern: /^\/discipline\/(.+)$/,
|
|
40
|
-
toCommand: (m) => `
|
|
40
|
+
toCommand: (m) => `npx fit-pathway discipline ${m[1]}`,
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
pattern: /^\/track\/(.+)$/,
|
|
44
|
-
toCommand: (m) => `
|
|
44
|
+
toCommand: (m) => `npx fit-pathway track ${m[1]}`,
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
pattern: /^\/level\/(.+)$/,
|
|
48
|
-
toCommand: (m) => `
|
|
48
|
+
toCommand: (m) => `npx fit-pathway level ${m[1]}`,
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
pattern: /^\/driver\/(.+)$/,
|
|
52
|
-
toCommand: (m) => `
|
|
52
|
+
toCommand: (m) => `npx fit-pathway driver ${m[1]}`,
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
pattern: /^\/stage\/(.+)$/,
|
|
56
|
-
toCommand: (m) => `
|
|
56
|
+
toCommand: (m) => `npx fit-pathway stage ${m[1]}`,
|
|
57
57
|
},
|
|
58
58
|
|
|
59
59
|
// Job builder + detail
|
|
60
60
|
{
|
|
61
61
|
pattern: /^\/job-builder$/,
|
|
62
|
-
toCommand: () => "
|
|
62
|
+
toCommand: () => "npx fit-pathway job --list",
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
pattern: /^\/job\/([^/]+)\/([^/]+)\/([^/]+)$/,
|
|
66
|
-
toCommand: (m) => `
|
|
66
|
+
toCommand: (m) => `npx fit-pathway job ${m[1]} ${m[2]} --track=${m[3]}`,
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
pattern: /^\/job\/([^/]+)\/([^/]+)$/,
|
|
70
|
-
toCommand: (m) => `
|
|
70
|
+
toCommand: (m) => `npx fit-pathway job ${m[1]} ${m[2]}`,
|
|
71
71
|
},
|
|
72
72
|
|
|
73
73
|
// Interview builder + detail
|
|
74
74
|
{
|
|
75
75
|
pattern: /^\/interview-prep$/,
|
|
76
|
-
toCommand: () => "
|
|
76
|
+
toCommand: () => "npx fit-pathway interview --list",
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
pattern: /^\/interview\/([^/]+)\/([^/]+)\/([^/]+)$/,
|
|
80
80
|
toCommand: (m) =>
|
|
81
|
-
`
|
|
81
|
+
`npx fit-pathway interview ${m[1]} ${m[2]} --track=${m[3]}`,
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
pattern: /^\/interview\/([^/]+)\/([^/]+)$/,
|
|
85
|
-
toCommand: (m) => `
|
|
85
|
+
toCommand: (m) => `npx fit-pathway interview ${m[1]} ${m[2]}`,
|
|
86
86
|
},
|
|
87
87
|
|
|
88
88
|
// Career progress builder + detail
|
|
89
89
|
{
|
|
90
90
|
pattern: /^\/career-progress$/,
|
|
91
|
-
toCommand: () => "
|
|
91
|
+
toCommand: () => "npx fit-pathway progress --list",
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
pattern: /^\/progress\/([^/]+)\/([^/]+)\/([^/]+)$/,
|
|
95
95
|
toCommand: (m) =>
|
|
96
|
-
`
|
|
96
|
+
`npx fit-pathway progress ${m[1]} ${m[2]} --track=${m[3]}`,
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
pattern: /^\/progress\/([^/]+)\/([^/]+)$/,
|
|
100
|
-
toCommand: (m) => `
|
|
100
|
+
toCommand: (m) => `npx fit-pathway progress ${m[1]} ${m[2]}`,
|
|
101
101
|
},
|
|
102
102
|
|
|
103
103
|
// Self-assessment
|
|
104
104
|
{
|
|
105
105
|
pattern: /^\/self-assessment$/,
|
|
106
|
-
toCommand: () => "
|
|
106
|
+
toCommand: () => "npx fit-pathway self-assessment",
|
|
107
107
|
},
|
|
108
108
|
{
|
|
109
109
|
pattern: /^\/self-assessment\/results$/,
|
|
110
|
-
toCommand: () => "
|
|
110
|
+
toCommand: () => "npx fit-pathway self-assessment",
|
|
111
111
|
},
|
|
112
112
|
|
|
113
113
|
// Agent builder + detail
|
|
114
114
|
{
|
|
115
115
|
pattern: /^\/agent-builder$/,
|
|
116
|
-
toCommand: () => "
|
|
116
|
+
toCommand: () => "npx fit-pathway agent --list",
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
119
|
pattern: /^\/agent\/([^/]+)\/([^/]+)\/([^/]+)$/,
|
|
120
120
|
toCommand: (m) =>
|
|
121
|
-
`
|
|
121
|
+
`npx fit-pathway agent ${m[1]} --track=${m[2]} --stage=${m[3]}`,
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
124
|
pattern: /^\/agent\/([^/]+)\/([^/]+)$/,
|
|
125
|
-
toCommand: (m) => `
|
|
125
|
+
toCommand: (m) => `npx fit-pathway agent ${m[1]} --track=${m[2]}`,
|
|
126
126
|
},
|
|
127
127
|
{
|
|
128
128
|
pattern: /^\/agent\/([^/]+)$/,
|
|
129
|
-
toCommand: (m) => `
|
|
129
|
+
toCommand: (m) => `npx fit-pathway agent ${m[1]}`,
|
|
130
130
|
},
|
|
131
131
|
];
|
|
132
132
|
|
|
@@ -141,5 +141,5 @@ export function getCliCommand(hashPath) {
|
|
|
141
141
|
const match = path.match(pattern);
|
|
142
142
|
if (match) return toCommand(match);
|
|
143
143
|
}
|
|
144
|
-
return "
|
|
144
|
+
return "npx fit-pathway";
|
|
145
145
|
}
|
package/src/lib/cli-output.js
CHANGED
|
@@ -1,129 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI Output
|
|
2
|
+
* Domain-specific CLI Output Formatters
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Pathway-specific formatting for skill proficiencies, behaviour maturities,
|
|
5
|
+
* modifiers, percentages, and change indicators. Generic formatters (colors,
|
|
6
|
+
* tables, headers, etc.) live in @forwardimpact/libcli.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
export const colors = {
|
|
10
|
-
reset: "\x1b[0m",
|
|
11
|
-
bold: "\x1b[1m",
|
|
12
|
-
dim: "\x1b[2m",
|
|
13
|
-
italic: "\x1b[3m",
|
|
14
|
-
underline: "\x1b[4m",
|
|
15
|
-
red: "\x1b[31m",
|
|
16
|
-
green: "\x1b[32m",
|
|
17
|
-
yellow: "\x1b[33m",
|
|
18
|
-
blue: "\x1b[34m",
|
|
19
|
-
magenta: "\x1b[35m",
|
|
20
|
-
cyan: "\x1b[36m",
|
|
21
|
-
white: "\x1b[37m",
|
|
22
|
-
gray: "\x1b[90m",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Check if stdout supports colors
|
|
27
|
-
* @returns {boolean}
|
|
28
|
-
*/
|
|
29
|
-
export function supportsColor() {
|
|
30
|
-
if (process.env.NO_COLOR) return false;
|
|
31
|
-
if (process.env.FORCE_COLOR) return true;
|
|
32
|
-
return process.stdout.isTTY;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Wrap text with color if supported
|
|
37
|
-
* @param {string} text
|
|
38
|
-
* @param {string} color
|
|
39
|
-
* @returns {string}
|
|
40
|
-
*/
|
|
41
|
-
function colorize(text, color) {
|
|
42
|
-
if (!supportsColor()) return text;
|
|
43
|
-
return `${color}${text}${colors.reset}`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Format a header
|
|
48
|
-
* @param {string} text
|
|
49
|
-
* @returns {string}
|
|
50
|
-
*/
|
|
51
|
-
export function formatHeader(text) {
|
|
52
|
-
return colorize(text, colors.bold + colors.cyan);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Format a subheader
|
|
57
|
-
* @param {string} text
|
|
58
|
-
* @returns {string}
|
|
59
|
-
*/
|
|
60
|
-
export function formatSubheader(text) {
|
|
61
|
-
return colorize(text, colors.bold);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Format a list item
|
|
66
|
-
* @param {string} label
|
|
67
|
-
* @param {string} value
|
|
68
|
-
* @param {number} [indent=0]
|
|
69
|
-
* @returns {string}
|
|
70
|
-
*/
|
|
71
|
-
export function formatListItem(label, value, indent = 0) {
|
|
72
|
-
const padding = " ".repeat(indent);
|
|
73
|
-
const bullet = colorize("•", colors.dim);
|
|
74
|
-
return `${padding}${bullet} ${label}: ${value}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Format a bullet item (no label)
|
|
79
|
-
* @param {string} text
|
|
80
|
-
* @param {number} [indent=0]
|
|
81
|
-
* @returns {string}
|
|
82
|
-
*/
|
|
83
|
-
export function formatBullet(text, indent = 0) {
|
|
84
|
-
const padding = " ".repeat(indent);
|
|
85
|
-
const bullet = colorize("•", colors.dim);
|
|
86
|
-
return `${padding}${bullet} ${text}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Format a table
|
|
91
|
-
* @param {string[]} headers
|
|
92
|
-
* @param {Array<Array<string|number>>} rows
|
|
93
|
-
* @param {Object} [options]
|
|
94
|
-
* @param {boolean} [options.compact=false]
|
|
95
|
-
* @returns {string}
|
|
96
|
-
*/
|
|
97
|
-
export function formatTable(headers, rows, options = {}) {
|
|
98
|
-
const { compact = false } = options;
|
|
99
|
-
|
|
100
|
-
// Calculate column widths
|
|
101
|
-
const widths = headers.map((h, i) =>
|
|
102
|
-
Math.max(String(h).length, ...rows.map((r) => String(r[i] || "").length)),
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const lines = [];
|
|
106
|
-
|
|
107
|
-
// Header
|
|
108
|
-
const headerLine = headers
|
|
109
|
-
.map((h, i) => String(h).padEnd(widths[i]))
|
|
110
|
-
.join(" ");
|
|
111
|
-
lines.push(colorize(headerLine, colors.bold));
|
|
112
|
-
|
|
113
|
-
// Separator
|
|
114
|
-
if (!compact) {
|
|
115
|
-
lines.push(widths.map((w) => "─".repeat(w)).join("──"));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Rows
|
|
119
|
-
for (const row of rows) {
|
|
120
|
-
lines.push(
|
|
121
|
-
row.map((cell, i) => String(cell || "").padEnd(widths[i])).join(" "),
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return lines.join("\n");
|
|
126
|
-
}
|
|
9
|
+
import { colorize, colors } from "@forwardimpact/libcli";
|
|
127
10
|
|
|
128
11
|
/**
|
|
129
12
|
* Format skill proficiency with color
|
|
@@ -193,78 +76,15 @@ export function formatPercent(value) {
|
|
|
193
76
|
}
|
|
194
77
|
|
|
195
78
|
/**
|
|
196
|
-
* Format a change indicator
|
|
79
|
+
* Format a change indicator
|
|
197
80
|
* @param {number} change
|
|
198
81
|
* @returns {string}
|
|
199
82
|
*/
|
|
200
83
|
export function formatChange(change) {
|
|
201
84
|
if (change > 0) {
|
|
202
|
-
return colorize(
|
|
85
|
+
return colorize(`\u2191${change}`, colors.green);
|
|
203
86
|
} else if (change < 0) {
|
|
204
|
-
return colorize(
|
|
87
|
+
return colorize(`\u2193${Math.abs(change)}`, colors.red);
|
|
205
88
|
}
|
|
206
|
-
return colorize("
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Format an error message
|
|
211
|
-
* @param {string} message
|
|
212
|
-
* @returns {string}
|
|
213
|
-
*/
|
|
214
|
-
export function formatError(message) {
|
|
215
|
-
return colorize(`Error: ${message}`, colors.red);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Format a success message
|
|
220
|
-
* @param {string} message
|
|
221
|
-
* @returns {string}
|
|
222
|
-
*/
|
|
223
|
-
export function formatSuccess(message) {
|
|
224
|
-
return colorize(message, colors.green);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Format a warning message
|
|
229
|
-
* @param {string} message
|
|
230
|
-
/**
|
|
231
|
-
* Format a warning message
|
|
232
|
-
* @param {string} message
|
|
233
|
-
* @returns {string}
|
|
234
|
-
*/
|
|
235
|
-
export function formatWarning(message) {
|
|
236
|
-
return colorize(`Warning: ${message}`, colors.yellow);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Create a horizontal rule
|
|
241
|
-
* @param {number} [width=60]
|
|
242
|
-
* @returns {string}
|
|
243
|
-
*/
|
|
244
|
-
export function horizontalRule(width = 60) {
|
|
245
|
-
return colorize("─".repeat(width), colors.dim);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Format a section with title and content
|
|
250
|
-
* @param {string} title
|
|
251
|
-
* @param {string} content
|
|
252
|
-
* @returns {string}
|
|
253
|
-
*/
|
|
254
|
-
export function formatSection(title, content) {
|
|
255
|
-
return `${formatHeader(title)}\n\n${content}`;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Indent all lines of text
|
|
260
|
-
* @param {string} text
|
|
261
|
-
* @param {number} [spaces=2]
|
|
262
|
-
* @returns {string}
|
|
263
|
-
*/
|
|
264
|
-
export function indent(text, spaces = 2) {
|
|
265
|
-
const padding = " ".repeat(spaces);
|
|
266
|
-
return text
|
|
267
|
-
.split("\n")
|
|
268
|
-
.map((line) => padding + line)
|
|
269
|
-
.join("\n");
|
|
89
|
+
return colorize("\u2192", colors.dim);
|
|
270
90
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent builder install section
|
|
3
|
+
*
|
|
4
|
+
* Surfaces the ecosystem-tool install commands (Microsoft APM and
|
|
5
|
+
* `npx skills`) for the currently selected discipline/track pack. The packs
|
|
6
|
+
* themselves are emitted by `fit-pathway build` when
|
|
7
|
+
* `framework.distribution.siteUrl` is configured — see spec 320 and
|
|
8
|
+
* `products/pathway/src/commands/build-packs.js`. The pack name derivation
|
|
9
|
+
* here must stay in sync with that generator so the command points at an
|
|
10
|
+
* archive that actually exists on the deployed site.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { code, div, h2, p, section } from "../lib/render.js";
|
|
14
|
+
import {
|
|
15
|
+
getDisciplineAbbreviation,
|
|
16
|
+
toKebabCase,
|
|
17
|
+
} from "@forwardimpact/libskill/agent";
|
|
18
|
+
import { createCommandPrompt } from "../components/command-prompt.js";
|
|
19
|
+
|
|
20
|
+
/** Stable id for the install section heading (for aria-labelledby). */
|
|
21
|
+
const INSTALL_HEADING_ID = "agent-builder-install-heading";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Derive the pack archive name for a discipline/track combination.
|
|
25
|
+
* Must match `build-packs.js` → `${abbrev}-${toKebabCase(track.id)}`.
|
|
26
|
+
* @param {{id: string}} discipline
|
|
27
|
+
* @param {{id: string}} track
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
export function getPackName(discipline, track) {
|
|
31
|
+
return `${getDisciplineAbbreviation(discipline.id)}-${toKebabCase(track.id)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Normalize a site URL by stripping a trailing slash. Matches the
|
|
36
|
+
* normalization applied by `generatePacks` so the displayed URL lines up
|
|
37
|
+
* with the manifest entries.
|
|
38
|
+
* @param {string} siteUrl
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
function normalizeSiteUrl(siteUrl) {
|
|
42
|
+
return siteUrl.replace(/\/$/, "");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build the `apm install` command for a specific pack archive. Targets the
|
|
47
|
+
* direct archive URL (rather than a registry-style shorthand) because it is
|
|
48
|
+
* the most durable path through APM's evolving resolution logic and matches
|
|
49
|
+
* the URL listed in the generated `apm.yml` manifest.
|
|
50
|
+
* @param {string} siteUrl
|
|
51
|
+
* @param {string} packName
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
export function getApmInstallCommand(siteUrl, packName) {
|
|
55
|
+
return `apm install ${normalizeSiteUrl(siteUrl)}/packs/${packName}.tar.gz`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build the `npx skills add` command that discovers the per-pack skill
|
|
60
|
+
* repository at `<siteUrl>/packs/<packName>/.well-known/skills/index.json`.
|
|
61
|
+
* @param {string} siteUrl
|
|
62
|
+
* @param {string} packName
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
export function getSkillsAddCommand(siteUrl, packName) {
|
|
66
|
+
return `npx skills add ${normalizeSiteUrl(siteUrl)}/packs/${packName}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Render the install section for the selected agent combination. Returns
|
|
71
|
+
* `null` when no site URL is configured (no packs have been published, so
|
|
72
|
+
* there is nothing meaningful to install) so the caller can skip rendering.
|
|
73
|
+
* @param {Object} params
|
|
74
|
+
* @param {{id: string}} params.discipline - Selected human discipline
|
|
75
|
+
* @param {{id: string}} params.track - Selected human track
|
|
76
|
+
* @param {string|undefined} params.siteUrl - Framework distribution site URL
|
|
77
|
+
* @returns {HTMLElement|null}
|
|
78
|
+
*/
|
|
79
|
+
export function createInstallSection({ discipline, track, siteUrl }) {
|
|
80
|
+
if (!siteUrl) return null;
|
|
81
|
+
|
|
82
|
+
const packName = getPackName(discipline, track);
|
|
83
|
+
const apmCommand = getApmInstallCommand(siteUrl, packName);
|
|
84
|
+
const skillsCommand = getSkillsAddCommand(siteUrl, packName);
|
|
85
|
+
|
|
86
|
+
return section(
|
|
87
|
+
{
|
|
88
|
+
className: "agent-install-section",
|
|
89
|
+
"aria-labelledby": INSTALL_HEADING_ID,
|
|
90
|
+
},
|
|
91
|
+
div(
|
|
92
|
+
{ className: "agent-install-header" },
|
|
93
|
+
h2({ id: INSTALL_HEADING_ID }, "📦 Install This Agent Team"),
|
|
94
|
+
p(
|
|
95
|
+
{ className: "text-muted agent-install-description" },
|
|
96
|
+
"Install the pre-built pack for this discipline × track combination " +
|
|
97
|
+
"directly through an ecosystem package manager. The pack contains " +
|
|
98
|
+
"the same stage agents, skills, team instructions, and Claude Code " +
|
|
99
|
+
"settings shown below — installed into your project's ",
|
|
100
|
+
code({}, ".claude/"),
|
|
101
|
+
" directory.",
|
|
102
|
+
),
|
|
103
|
+
),
|
|
104
|
+
div(
|
|
105
|
+
{ className: "agent-install-commands" },
|
|
106
|
+
div(
|
|
107
|
+
{ className: "agent-install-command" },
|
|
108
|
+
p({ className: "agent-install-command-label" }, "Microsoft APM"),
|
|
109
|
+
createCommandPrompt(apmCommand),
|
|
110
|
+
),
|
|
111
|
+
div(
|
|
112
|
+
{ className: "agent-install-command" },
|
|
113
|
+
p({ className: "agent-install-command-label" }, "npx skills"),
|
|
114
|
+
createCommandPrompt(skillsCommand),
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
deriveStageAgent,
|
|
11
11
|
generateSkillMarkdown,
|
|
12
12
|
deriveAgentSkills,
|
|
13
|
-
|
|
14
|
-
} from "@forwardimpact/libskill";
|
|
13
|
+
} from "@forwardimpact/libskill/agent";
|
|
14
|
+
import { deriveToolkit } from "@forwardimpact/libskill/toolkit";
|
|
15
15
|
import { getStageEmoji } from "../formatters/stage/shared.js";
|
|
16
16
|
import { formatAgentProfile } from "../formatters/agent/profile.js";
|
|
17
17
|
import {
|
|
@@ -115,7 +115,7 @@ function deriveSkillData(context) {
|
|
|
115
115
|
const skillFiles = derivedSkills
|
|
116
116
|
.map((d) => skills.find((s) => s.id === d.skillId))
|
|
117
117
|
.filter((skill) => skill?.agent)
|
|
118
|
-
.map((skill) => generateSkillMarkdown(skill, stages));
|
|
118
|
+
.map((skill) => generateSkillMarkdown({ skillData: skill, stages }));
|
|
119
119
|
|
|
120
120
|
const toolkit = deriveToolkit({
|
|
121
121
|
skillMatrix: derivedSkills,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "../lib/render.js";
|
|
19
19
|
import { getState } from "../lib/state.js";
|
|
20
20
|
import { loadAgentDataBrowser } from "../lib/yaml-loader.js";
|
|
21
|
-
import { deriveReferenceLevel } from "@forwardimpact/libskill";
|
|
21
|
+
import { deriveReferenceLevel } from "@forwardimpact/libskill/agent";
|
|
22
22
|
import {
|
|
23
23
|
createSelectWithValue,
|
|
24
24
|
createDisciplineSelect,
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
createSingleStagePreview,
|
|
31
31
|
createHelpSection,
|
|
32
32
|
} from "./agent-builder-preview.js";
|
|
33
|
+
import { createInstallSection } from "./agent-builder-install.js";
|
|
33
34
|
|
|
34
35
|
/** All stages option value */
|
|
35
36
|
const ALL_STAGES_VALUE = "all";
|
|
@@ -79,6 +80,7 @@ async function getTemplates() {
|
|
|
79
80
|
*/
|
|
80
81
|
export async function renderAgentBuilder() {
|
|
81
82
|
const { data } = getState();
|
|
83
|
+
const siteUrl = data.framework?.distribution?.siteUrl;
|
|
82
84
|
|
|
83
85
|
// Show loading state
|
|
84
86
|
render(
|
|
@@ -260,8 +262,27 @@ export async function renderAgentBuilder() {
|
|
|
260
262
|
templates,
|
|
261
263
|
};
|
|
262
264
|
|
|
265
|
+
// Install section (ecosystem-tool install commands) — appears above the
|
|
266
|
+
// preview cards so the install action is visible before users scroll
|
|
267
|
+
// through the generated files. Only rendered when the framework has a
|
|
268
|
+
// published distribution site URL, since the packs only exist at that
|
|
269
|
+
// URL after a `fit-pathway build`. Must come after the stage-validity
|
|
270
|
+
// guard below so an invalid stage id (e.g. from a stale bookmark) does
|
|
271
|
+
// not pair the install card with a "Stage not found" error.
|
|
272
|
+
function appendInstallSection() {
|
|
273
|
+
const installSection = createInstallSection({
|
|
274
|
+
discipline: humanDiscipline,
|
|
275
|
+
track: humanTrack,
|
|
276
|
+
siteUrl,
|
|
277
|
+
});
|
|
278
|
+
if (installSection) {
|
|
279
|
+
previewContainer.appendChild(installSection);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
263
283
|
// Generate preview based on stage selection
|
|
264
284
|
if (stage === ALL_STAGES_VALUE) {
|
|
285
|
+
appendInstallSection();
|
|
265
286
|
previewContainer.appendChild(createAllStagesPreview(context));
|
|
266
287
|
} else {
|
|
267
288
|
const stageObj = stages.find((s) => s.id === stage);
|
|
@@ -274,6 +295,7 @@ export async function renderAgentBuilder() {
|
|
|
274
295
|
);
|
|
275
296
|
return;
|
|
276
297
|
}
|
|
298
|
+
appendInstallSection();
|
|
277
299
|
previewContainer.appendChild(createSingleStagePreview(context, stageObj));
|
|
278
300
|
}
|
|
279
301
|
}
|
package/src/pages/progress.js
CHANGED
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
prepareCurrentJob,
|
|
17
17
|
prepareCustomProgression,
|
|
18
18
|
getDefaultTargetLevel,
|
|
19
|
-
isValidCombination,
|
|
20
19
|
} from "../formatters/progress/shared.js";
|
|
20
|
+
import { isValidJobCombination } from "@forwardimpact/libskill/derivation";
|
|
21
21
|
import { buildComparisonResult } from "./progress-comparison.js";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -188,7 +188,7 @@ function createComparisonSelectorsSection({
|
|
|
188
188
|
for (const level of data.levels) {
|
|
189
189
|
// Check trackless combination
|
|
190
190
|
if (
|
|
191
|
-
|
|
191
|
+
isValidJobCombination({ discipline: selectedDisc, level, track: null })
|
|
192
192
|
) {
|
|
193
193
|
if (!validLevels.find((g) => g.id === level.id)) {
|
|
194
194
|
validLevels.push(level);
|
|
@@ -197,7 +197,7 @@ function createComparisonSelectorsSection({
|
|
|
197
197
|
}
|
|
198
198
|
// Check each track combination
|
|
199
199
|
for (const track of data.tracks) {
|
|
200
|
-
if (
|
|
200
|
+
if (isValidJobCombination({ discipline: selectedDisc, level, track })) {
|
|
201
201
|
if (!validLevels.find((g) => g.id === level.id)) {
|
|
202
202
|
validLevels.push(level);
|
|
203
203
|
}
|
package/src/pages/skill.js
CHANGED
|
@@ -11,7 +11,7 @@ import { prepareSkillsList } from "../formatters/skill/shared.js";
|
|
|
11
11
|
import { skillToDOM } from "../formatters/skill/dom.js";
|
|
12
12
|
import { skillToCardConfig } from "../lib/card-mappers.js";
|
|
13
13
|
import { getCapabilityEmoji, getConceptEmoji } from "@forwardimpact/map/levels";
|
|
14
|
-
import { generateSkillMarkdown } from "@forwardimpact/libskill";
|
|
14
|
+
import { generateSkillMarkdown } from "@forwardimpact/libskill/agent";
|
|
15
15
|
import { formatAgentSkill } from "../formatters/agent/skill.js";
|
|
16
16
|
|
|
17
17
|
/** @type {string|null} Cached skill template */
|
|
@@ -101,7 +101,10 @@ export async function renderSkillDetail(params) {
|
|
|
101
101
|
let agentSkillContent;
|
|
102
102
|
if (skill.agent) {
|
|
103
103
|
const template = await getSkillTemplate();
|
|
104
|
-
const skillData = generateSkillMarkdown(
|
|
104
|
+
const skillData = generateSkillMarkdown({
|
|
105
|
+
skillData: skill,
|
|
106
|
+
stages: data.stages,
|
|
107
|
+
});
|
|
105
108
|
agentSkillContent = formatAgentSkill(skillData, template);
|
|
106
109
|
}
|
|
107
110
|
|