@forwardimpact/pathway 0.25.22 → 0.25.25

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.
Files changed (40) hide show
  1. package/bin/fit-pathway.js +117 -325
  2. package/package.json +2 -2
  3. package/src/commands/agent-io.js +1 -1
  4. package/src/commands/agent-list.js +164 -0
  5. package/src/commands/agent.js +83 -184
  6. package/src/commands/behaviour.js +22 -10
  7. package/src/commands/build-packs.js +208 -34
  8. package/src/commands/build.js +2 -2
  9. package/src/commands/command-factory.js +39 -14
  10. package/src/commands/discipline.js +24 -10
  11. package/src/commands/driver.js +28 -19
  12. package/src/commands/index.js +0 -1
  13. package/src/commands/interview.js +15 -10
  14. package/src/commands/job.js +110 -62
  15. package/src/commands/level.js +23 -11
  16. package/src/commands/progress.js +12 -7
  17. package/src/commands/questions.js +32 -14
  18. package/src/commands/skill.js +36 -18
  19. package/src/commands/stage.js +37 -27
  20. package/src/commands/tool.js +29 -19
  21. package/src/commands/track.js +23 -10
  22. package/src/formatters/questions/yaml.js +1 -1
  23. package/src/index.html +1 -1
  24. package/src/lib/cli-command.js +33 -33
  25. package/src/lib/cli-output.js +9 -189
  26. package/src/pages/agent-builder-install.js +6 -5
  27. package/src/commands/init.js +0 -64
  28. package/starter/behaviours/systems_thinking.yaml +0 -32
  29. package/starter/capabilities/delivery.yaml +0 -105
  30. package/starter/capabilities/reliability.yaml +0 -72
  31. package/starter/disciplines/software_engineering.yaml +0 -46
  32. package/starter/drivers.yaml +0 -10
  33. package/starter/framework.yaml +0 -49
  34. package/starter/levels.yaml +0 -39
  35. package/starter/questions/behaviours/.gitkeep +0 -0
  36. package/starter/questions/capabilities/.gitkeep +0 -0
  37. package/starter/questions/skills/.gitkeep +0 -0
  38. package/starter/stages.yaml +0 -21
  39. package/starter/tracks/forward_deployed.yaml +0 -33
  40. package/starter/tracks/platform.yaml +0 -33
@@ -4,14 +4,14 @@
4
4
  * Generates and displays job definitions in the terminal.
5
5
  *
6
6
  * Usage:
7
- * bunx pathway job # Summary with stats
8
- * bunx pathway job --list # All valid combinations (for piping)
9
- * bunx pathway job <discipline> <level> # Detail view (trackless)
10
- * bunx pathway job <discipline> <level> --track=<track> # Detail view (with track)
11
- * bunx pathway job <d> <l> [--track=<t>] --skills # Plain list of skill IDs
12
- * bunx pathway job <d> <l> [--track=<t>] --tools # Plain list of tool names
13
- * bunx pathway job se L3 --track=platform --checklist=code # Show checklist for handoff
14
- * bunx pathway job --validate # Validation checks
7
+ * npx fit-pathway job # Summary with stats
8
+ * npx fit-pathway job --list # All valid combinations (for piping)
9
+ * npx fit-pathway job <discipline> <level> # Detail view (trackless)
10
+ * npx fit-pathway job <discipline> <level> --track=<track> # Detail view (with track)
11
+ * npx fit-pathway job <d> <l> [--track=<t>] --skills # Plain list of skill IDs
12
+ * npx fit-pathway job <d> <l> [--track=<t>] --tools # Plain list of tool names
13
+ * npx fit-pathway job se L3 --track=platform --checklist=code # Show checklist for handoff
14
+ * npx fit-pathway job --validate # Validation checks
15
15
  */
16
16
 
17
17
  import { prepareJobDetail } from "@forwardimpact/libskill/job";
@@ -20,7 +20,13 @@ import {
20
20
  generateJobTitle,
21
21
  generateAllJobs,
22
22
  } from "@forwardimpact/libskill/derivation";
23
- import { formatTable } from "../lib/cli-output.js";
23
+ import {
24
+ formatTable,
25
+ formatHeader,
26
+ formatSubheader,
27
+ formatBullet,
28
+ formatError,
29
+ } from "@forwardimpact/libcli";
24
30
  import {
25
31
  deriveChecklist,
26
32
  formatChecklistMarkdown,
@@ -66,7 +72,9 @@ function printJobList(filteredJobs) {
66
72
  */
67
73
  function printJobSummary(filteredJobs, options) {
68
74
  const trackLabel = options.track ? ` — ${options.track}` : "";
69
- console.log(`\nšŸ’¼ Jobs${trackLabel}\n`);
75
+ process.stdout.write(
76
+ "\n" + formatHeader(`\u{1F4BC} Jobs${trackLabel}`) + "\n\n",
77
+ );
70
78
 
71
79
  const byDiscipline = {};
72
80
  for (const job of filteredJobs) {
@@ -91,15 +99,24 @@ function printJobSummary(filteredJobs, options) {
91
99
  info.count,
92
100
  info.tracks.size > 0 ? [...info.tracks].join(", ") : "—",
93
101
  ]);
94
- console.log(
95
- formatTable(["ID", "Specialization", "Type", "Jobs", "Tracks"], rows),
102
+ process.stdout.write(
103
+ formatTable(["ID", "Specialization", "Type", "Jobs", "Tracks"], rows) +
104
+ "\n",
96
105
  );
97
- console.log(`\nTotal: ${filteredJobs.length} valid job combinations`);
98
- console.log(
99
- `\nRun 'bunx pathway job --list' for all combinations with titles`,
106
+ process.stdout.write(
107
+ "\n" +
108
+ formatSubheader(`Total: ${filteredJobs.length} valid job combinations`) +
109
+ "\n\n",
100
110
  );
101
- console.log(
102
- `Run 'bunx pathway job <discipline> <level> [--track=<track>]' for details\n`,
111
+ process.stdout.write(
112
+ formatBullet(
113
+ "Run 'npx fit-pathway job --list' for all combinations with titles",
114
+ ) + "\n",
115
+ );
116
+ process.stdout.write(
117
+ formatBullet(
118
+ "Run 'npx fit-pathway job <discipline> <level> [--track=<track>]' for details",
119
+ ) + "\n\n",
103
120
  );
104
121
  }
105
122
 
@@ -112,22 +129,28 @@ function handleSingleArg(arg, data) {
112
129
  const isLevel = data.levels.some((g) => g.id === arg);
113
130
  const isTrack = data.tracks.some((t) => t.id === arg);
114
131
  if (isLevel) {
115
- console.error(
116
- `Missing discipline. Usage: bunx pathway job <discipline> ${arg} [--track=<track>]`,
132
+ process.stderr.write(
133
+ formatError(
134
+ `Missing discipline. Usage: npx fit-pathway job <discipline> ${arg} [--track=<track>]`,
135
+ ) + "\n",
117
136
  );
118
- console.error(
119
- `Disciplines: ${data.disciplines.map((d) => d.id).join(", ")}`,
137
+ process.stderr.write(
138
+ `Disciplines: ${data.disciplines.map((d) => d.id).join(", ")}\n`,
120
139
  );
121
140
  } else if (isTrack) {
122
- console.error(`Track must be passed as a flag: --track=${arg}`);
123
- console.error(
124
- `Usage: bunx pathway job <discipline> <level> --track=${arg}`,
141
+ process.stderr.write(
142
+ formatError(`Track must be passed as a flag: --track=${arg}`) + "\n",
143
+ );
144
+ process.stderr.write(
145
+ `Usage: npx fit-pathway job <discipline> <level> --track=${arg}\n`,
125
146
  );
126
147
  } else {
127
- console.error(
128
- "Usage: bunx pathway job <discipline> <level> [--track=<track>]",
148
+ process.stderr.write(
149
+ formatError(
150
+ "Usage: npx fit-pathway job <discipline> <level> [--track=<track>]",
151
+ ) + "\n",
129
152
  );
130
- console.error(" bunx pathway job --list");
153
+ process.stderr.write(" npx fit-pathway job --list\n");
131
154
  }
132
155
  process.exit(1);
133
156
  }
@@ -150,14 +173,18 @@ function resolveJobEntities(data, args, options) {
150
173
  const maybeLevel = data.levels.find((g) => g.id === args[0]);
151
174
  const maybeDiscipline = data.disciplines.find((d) => d.id === args[1]);
152
175
  if (maybeLevel && maybeDiscipline) {
153
- console.error(`Arguments are in the wrong order. Try:`);
154
- console.error(
155
- ` bunx pathway job ${args[1]} ${args[0]}${options.track ? ` --track=${options.track}` : ""}`,
176
+ process.stderr.write(
177
+ formatError("Arguments are in the wrong order. Try:") + "\n",
178
+ );
179
+ process.stderr.write(
180
+ ` npx fit-pathway job ${args[1]} ${args[0]}${options.track ? ` --track=${options.track}` : ""}\n`,
156
181
  );
157
182
  } else {
158
- console.error(`Discipline not found: ${args[0]}`);
159
- console.error(
160
- `Available: ${data.disciplines.map((d) => d.id).join(", ")}`,
183
+ process.stderr.write(
184
+ formatError(`Discipline not found: ${args[0]}`) + "\n",
185
+ );
186
+ process.stderr.write(
187
+ `Available: ${data.disciplines.map((d) => d.id).join(", ")}\n`,
161
188
  );
162
189
  }
163
190
  process.exit(1);
@@ -166,21 +193,33 @@ function resolveJobEntities(data, args, options) {
166
193
  if (!level) {
167
194
  const isTrack = data.tracks.some((t) => t.id === args[1]);
168
195
  if (isTrack) {
169
- console.error(
170
- `Track must be passed as a flag, not a positional argument:`,
196
+ process.stderr.write(
197
+ formatError(
198
+ "Track must be passed as a flag, not a positional argument:",
199
+ ) + "\n",
200
+ );
201
+ process.stderr.write(
202
+ ` npx fit-pathway job ${args[0]} <level> --track=${args[1]}\n`,
203
+ );
204
+ process.stderr.write(
205
+ `Levels: ${data.levels.map((g) => g.id).join(", ")}\n`,
171
206
  );
172
- console.error(` bunx pathway job ${args[0]} <level> --track=${args[1]}`);
173
- console.error(`Levels: ${data.levels.map((g) => g.id).join(", ")}`);
174
207
  } else {
175
- console.error(`Level not found: ${args[1]}`);
176
- console.error(`Available: ${data.levels.map((g) => g.id).join(", ")}`);
208
+ process.stderr.write(formatError(`Level not found: ${args[1]}`) + "\n");
209
+ process.stderr.write(
210
+ `Available: ${data.levels.map((g) => g.id).join(", ")}\n`,
211
+ );
177
212
  }
178
213
  process.exit(1);
179
214
  }
180
215
 
181
216
  if (options.track && !track) {
182
- console.error(`Track not found: ${options.track}`);
183
- console.error(`Available: ${data.tracks.map((t) => t.id).join(", ")}`);
217
+ process.stderr.write(
218
+ formatError(`Track not found: ${options.track}`) + "\n",
219
+ );
220
+ process.stderr.write(
221
+ `Available: ${data.tracks.map((t) => t.id).join(", ")}\n`,
222
+ );
184
223
  process.exit(1);
185
224
  }
186
225
 
@@ -196,8 +235,8 @@ function resolveJobEntities(data, args, options) {
196
235
  function handleChecklist(view, data, stageId) {
197
236
  const validStages = data.stages.map((s) => s.id);
198
237
  if (!validStages.includes(stageId)) {
199
- console.error(`Invalid stage: ${stageId}`);
200
- console.error(`Available: ${validStages.join(", ")}`);
238
+ process.stderr.write(formatError(`Invalid stage: ${stageId}`) + "\n");
239
+ process.stderr.write(`Available: ${validStages.join(", ")}\n`);
201
240
  process.exit(1);
202
241
  }
203
242
 
@@ -209,21 +248,24 @@ function handleChecklist(view, data, stageId) {
209
248
  });
210
249
 
211
250
  if (readChecklist.length === 0 && confirmChecklist.length === 0) {
212
- console.log(`\nNo checklist items for ${stageId} stage\n`);
251
+ process.stdout.write(
252
+ "\n" +
253
+ formatSubheader(`No checklist items for ${stageId} stage`) +
254
+ "\n\n",
255
+ );
213
256
  return;
214
257
  }
215
258
 
259
+ // Markdown output (#/##) is intentional — this is consumed as markdown.
216
260
  const stageLabel = stageId.charAt(0).toUpperCase() + stageId.slice(1);
217
- console.log(`\n# ${view.title} — ${stageLabel} Stage Checklist\n`);
261
+ process.stdout.write(`\n# ${view.title} — ${stageLabel} Stage Checklist\n\n`);
218
262
  if (readChecklist.length > 0) {
219
- console.log("## Read-Then-Do\n");
220
- console.log(formatChecklistMarkdown(readChecklist));
221
- console.log("");
263
+ process.stdout.write("## Read-Then-Do\n\n");
264
+ process.stdout.write(formatChecklistMarkdown(readChecklist) + "\n\n");
222
265
  }
223
266
  if (confirmChecklist.length > 0) {
224
- console.log("## Do-Then-Confirm\n");
225
- console.log(formatChecklistMarkdown(confirmChecklist));
226
- console.log("");
267
+ process.stdout.write("## Do-Then-Confirm\n\n");
268
+ process.stdout.write(formatChecklistMarkdown(confirmChecklist) + "\n\n");
227
269
  }
228
270
  }
229
271
 
@@ -245,16 +287,22 @@ function validateTrackFilter(filteredJobs, data, options) {
245
287
  if (!options.track || filteredJobs.length > 0) return;
246
288
  const trackExists = data.tracks.some((t) => t.id === options.track);
247
289
  if (!trackExists) {
248
- console.error(`Track not found: ${options.track}`);
249
- console.error(`Available: ${data.tracks.map((t) => t.id).join(", ")}`);
290
+ process.stderr.write(
291
+ formatError(`Track not found: ${options.track}`) + "\n",
292
+ );
293
+ process.stderr.write(
294
+ `Available: ${data.tracks.map((t) => t.id).join(", ")}\n`,
295
+ );
250
296
  } else {
251
- console.error(`No jobs found for track: ${options.track}`);
297
+ process.stderr.write(
298
+ formatError(`No jobs found for track: ${options.track}`) + "\n",
299
+ );
252
300
  const trackDisciplines = data.disciplines
253
301
  .filter((d) => d.validTracks && d.validTracks.includes(options.track))
254
302
  .map((d) => d.id);
255
303
  if (trackDisciplines.length > 0) {
256
- console.error(
257
- `Disciplines with this track: ${trackDisciplines.join(", ")}`,
304
+ process.stderr.write(
305
+ `Disciplines with this track: ${trackDisciplines.join(", ")}\n`,
258
306
  );
259
307
  }
260
308
  }
@@ -272,23 +320,23 @@ function reportInvalidCombination(discipline, level, track, data) {
272
320
  const combo = track
273
321
  ? `${discipline.id} Ɨ ${level.id} Ɨ ${track.id}`
274
322
  : `${discipline.id} Ɨ ${level.id}`;
275
- console.error(`Invalid combination: ${combo}`);
323
+ process.stderr.write(formatError(`Invalid combination: ${combo}`) + "\n");
276
324
  if (track) {
277
325
  const validTracks = discipline.validTracks?.filter((t) => t !== null) || [];
278
326
  if (validTracks.length > 0) {
279
- console.error(
280
- `Valid tracks for ${discipline.id}: ${validTracks.join(", ")}`,
327
+ process.stderr.write(
328
+ `Valid tracks for ${discipline.id}: ${validTracks.join(", ")}\n`,
281
329
  );
282
330
  } else {
283
- console.error(`${discipline.id} does not support tracks`);
331
+ process.stderr.write(`${discipline.id} does not support tracks\n`);
284
332
  }
285
333
  }
286
334
  if (discipline.minLevel) {
287
335
  const levelIndex = data.levels.findIndex((g) => g.id === level.id);
288
336
  const minIndex = data.levels.findIndex((g) => g.id === discipline.minLevel);
289
337
  if (levelIndex >= 0 && minIndex >= 0 && levelIndex < minIndex) {
290
- console.error(
291
- `${discipline.id} requires minimum level: ${discipline.minLevel}`,
338
+ process.stderr.write(
339
+ `${discipline.id} requires minimum level: ${discipline.minLevel}\n`,
292
340
  );
293
341
  }
294
342
  }
@@ -4,15 +4,20 @@
4
4
  * Handles level summary, listing, and detail display in the terminal.
5
5
  *
6
6
  * Usage:
7
- * bunx pathway level # Summary with stats
8
- * bunx pathway level --list # IDs only (for piping)
9
- * bunx pathway level <id> # Detail view
10
- * bunx pathway level --validate # Validation checks
7
+ * npx fit-pathway level # Summary with stats
8
+ * npx fit-pathway level --list # IDs only (for piping)
9
+ * npx fit-pathway level <id> # Detail view
10
+ * npx fit-pathway level --validate # Validation checks
11
11
  */
12
12
 
13
13
  import { createEntityCommand } from "./command-factory.js";
14
14
  import { levelToMarkdown } from "../formatters/level/markdown.js";
15
- import { formatTable } from "../lib/cli-output.js";
15
+ import {
16
+ formatTable,
17
+ formatHeader,
18
+ formatSubheader,
19
+ formatBullet,
20
+ } from "@forwardimpact/libcli";
16
21
  import { getConceptEmoji } from "@forwardimpact/map/levels";
17
22
  import { capitalize } from "../formatters/shared.js";
18
23
 
@@ -34,7 +39,7 @@ function formatSummary(levels, data) {
34
39
  const { framework } = data;
35
40
  const emoji = framework ? getConceptEmoji(framework, "level") : "šŸ“Š";
36
41
 
37
- console.log(`\n${emoji} Levels\n`);
42
+ process.stdout.write("\n" + formatHeader(`${emoji} Levels`) + "\n\n");
38
43
 
39
44
  const rows = levels.map((g) => [
40
45
  g.id,
@@ -44,7 +49,7 @@ function formatSummary(levels, data) {
44
49
  capitalize(g.baseSkillProficiencies?.primary || "-"),
45
50
  ]);
46
51
 
47
- console.log(
52
+ process.stdout.write(
48
53
  formatTable(
49
54
  [
50
55
  "ID",
@@ -54,11 +59,18 @@ function formatSummary(levels, data) {
54
59
  "Primary Level",
55
60
  ],
56
61
  rows,
57
- ),
62
+ ) + "\n",
63
+ );
64
+ process.stdout.write(
65
+ "\n" + formatSubheader(`Total: ${levels.length} levels`) + "\n\n",
66
+ );
67
+ process.stdout.write(
68
+ formatBullet("Run 'npx fit-pathway level --list' for IDs and titles") +
69
+ "\n",
70
+ );
71
+ process.stdout.write(
72
+ formatBullet("Run 'npx fit-pathway level <id>' for details") + "\n\n",
58
73
  );
59
- console.log(`\nTotal: ${levels.length} levels`);
60
- console.log(`\nRun 'bunx pathway level --list' for IDs and titles`);
61
- console.log(`Run 'bunx pathway level <id>' for details\n`);
62
74
  }
63
75
 
64
76
  /**
@@ -4,9 +4,9 @@
4
4
  * Shows career progression analysis in the terminal.
5
5
  *
6
6
  * Usage:
7
- * bunx pathway progress <discipline> <level> # Progress for trackless job
8
- * bunx pathway progress <discipline> <level> --track=<track> # Progress with track
9
- * bunx pathway progress <discipline> <from_level> --compare=<to_level> # Compare levels
7
+ * npx fit-pathway progress <discipline> <level> # Progress for trackless job
8
+ * npx fit-pathway progress <discipline> <level> --track=<track> # Progress with track
9
+ * npx fit-pathway progress <discipline> <from_level> --compare=<to_level> # Compare levels
10
10
  */
11
11
 
12
12
  import { createCompositeCommand } from "./command-factory.js";
@@ -15,13 +15,14 @@ import {
15
15
  getDefaultTargetLevel,
16
16
  } from "../formatters/progress/shared.js";
17
17
  import { progressToMarkdown } from "../formatters/progress/markdown.js";
18
+ import { formatError } from "@forwardimpact/libcli";
18
19
 
19
20
  /**
20
21
  * Format progress output
21
22
  * @param {Object} view - Presenter view
22
23
  */
23
24
  function formatProgress(view) {
24
- console.log(progressToMarkdown(view));
25
+ process.stdout.write(progressToMarkdown(view) + "\n");
25
26
  }
26
27
 
27
28
  export const runProgressCommand = createCompositeCommand({
@@ -38,13 +39,17 @@ export const runProgressCommand = createCompositeCommand({
38
39
  if (options.compare) {
39
40
  targetLevel = data.levels.find((g) => g.id === options.compare);
40
41
  if (!targetLevel) {
41
- console.error(`Target level not found: ${options.compare}`);
42
+ process.stderr.write(
43
+ formatError(`Target level not found: ${options.compare}`) + "\n",
44
+ );
42
45
  process.exit(1);
43
46
  }
44
47
  } else {
45
48
  targetLevel = getDefaultTargetLevel(level, data.levels);
46
49
  if (!targetLevel) {
47
- console.error("No next level available for progression.");
50
+ process.stderr.write(
51
+ formatError("No next level available for progression.") + "\n",
52
+ );
48
53
  process.exit(1);
49
54
  }
50
55
  }
@@ -80,5 +85,5 @@ export const runProgressCommand = createCompositeCommand({
80
85
  }),
81
86
  formatter: formatProgress,
82
87
  usageExample:
83
- "bunx pathway progress software_engineering L3 --track=platform --compare=L4",
88
+ "npx fit-pathway progress software_engineering L3 --track=platform --compare=L4",
84
89
  });
@@ -4,10 +4,10 @@
4
4
  * Browse and compare interview questions across skills and behaviours.
5
5
  *
6
6
  * Usage:
7
- * bunx pathway questions # Summary with stats
8
- * bunx pathway questions --list # Question IDs (for piping)
9
- * bunx pathway questions --level=practitioner # Filter by level
10
- * bunx pathway questions --stats # Detailed statistics
7
+ * npx fit-pathway questions # Summary with stats
8
+ * npx fit-pathway questions --list # Question IDs (for piping)
9
+ * npx fit-pathway questions --level=practitioner # Filter by level
10
+ * npx fit-pathway questions --stats # Detailed statistics
11
11
  */
12
12
 
13
13
  import {
@@ -17,7 +17,12 @@ import {
17
17
  import { questionsToMarkdown } from "../formatters/questions/markdown.js";
18
18
  import { questionsToYaml } from "../formatters/questions/yaml.js";
19
19
  import { questionsToJson } from "../formatters/questions/json.js";
20
- import { formatTable } from "../lib/cli-output.js";
20
+ import {
21
+ formatTable,
22
+ formatHeader,
23
+ formatSubheader,
24
+ formatBullet,
25
+ } from "@forwardimpact/libcli";
21
26
 
22
27
  /**
23
28
  * Parse questions command options
@@ -46,7 +51,7 @@ function showQuestionsSummary(data) {
46
51
  const { skills, behaviours } = data;
47
52
  const questions = data.questions;
48
53
 
49
- console.log(`\nā“ Questions\n`);
54
+ process.stdout.write("\n" + formatHeader("\u2753 Questions") + "\n\n");
50
55
 
51
56
  // Skill questions by level
52
57
  const skillProficiencies = [
@@ -70,8 +75,8 @@ function showQuestionsSummary(data) {
70
75
  return [level, count];
71
76
  });
72
77
 
73
- console.log("Skill Questions:");
74
- console.log(formatTable(["Level", "Count"], skillRows));
78
+ process.stdout.write(formatSubheader("Skill Questions") + "\n");
79
+ process.stdout.write(formatTable(["Level", "Count"], skillRows) + "\n");
75
80
 
76
81
  // Behaviour questions by maturity
77
82
  const maturities = [
@@ -94,12 +99,25 @@ function showQuestionsSummary(data) {
94
99
  return [maturity.replace(/_/g, " "), count];
95
100
  });
96
101
 
97
- console.log("\nBehaviour Questions:");
98
- console.log(formatTable(["Maturity", "Count"], behaviourRows));
99
-
100
- console.log(`\nRun 'bunx pathway questions --list' for question IDs`);
101
- console.log(`Run 'bunx pathway questions --stats' for detailed stats`);
102
- console.log(`Run 'bunx pathway questions --level=practitioner' to filter\n`);
102
+ process.stdout.write("\n" + formatSubheader("Behaviour Questions") + "\n");
103
+ process.stdout.write(
104
+ formatTable(["Maturity", "Count"], behaviourRows) + "\n",
105
+ );
106
+
107
+ process.stdout.write("\n");
108
+ process.stdout.write(
109
+ formatBullet("Run 'npx fit-pathway questions --list' for question IDs") +
110
+ "\n",
111
+ );
112
+ process.stdout.write(
113
+ formatBullet("Run 'npx fit-pathway questions --stats' for detailed stats") +
114
+ "\n",
115
+ );
116
+ process.stdout.write(
117
+ formatBullet(
118
+ "Run 'npx fit-pathway questions --level=practitioner' to filter",
119
+ ) + "\n\n",
120
+ );
103
121
  }
104
122
 
105
123
  /**
@@ -4,18 +4,24 @@
4
4
  * Handles skill summary, listing, and detail display in the terminal.
5
5
  *
6
6
  * Usage:
7
- * bunx pathway skill # Summary with stats
8
- * bunx pathway skill --list # IDs only (for piping)
9
- * bunx pathway skill <id> # Detail view
10
- * bunx pathway skill <id> --agent # Agent SKILL.md output
11
- * bunx pathway skill --validate # Validation checks
7
+ * npx fit-pathway skill # Summary with stats
8
+ * npx fit-pathway skill --list # IDs only (for piping)
9
+ * npx fit-pathway skill <id> # Detail view
10
+ * npx fit-pathway skill <id> --agent # Agent SKILL.md output
11
+ * npx fit-pathway skill --validate # Validation checks
12
12
  */
13
13
 
14
14
  import { createEntityCommand } from "./command-factory.js";
15
15
  import { skillToMarkdown } from "../formatters/skill/markdown.js";
16
16
  import { prepareSkillsList } from "../formatters/skill/shared.js";
17
17
  import { getConceptEmoji } from "@forwardimpact/map/levels";
18
- import { formatTable, formatError } from "../lib/cli-output.js";
18
+ import {
19
+ formatTable,
20
+ formatError,
21
+ formatHeader,
22
+ formatSubheader,
23
+ formatBullet,
24
+ } from "@forwardimpact/libcli";
19
25
  import { generateSkillMarkdown } from "@forwardimpact/libskill/agent";
20
26
  import { formatAgentSkill } from "../formatters/agent/skill.js";
21
27
 
@@ -29,7 +35,7 @@ function formatSummary(skills, data) {
29
35
  const { groups, groupOrder } = prepareSkillsList(skills, capabilities);
30
36
  const emoji = framework ? getConceptEmoji(framework, "skill") : "šŸ“š";
31
37
 
32
- console.log(`\n${emoji} Skills\n`);
38
+ process.stdout.write("\n" + formatHeader(`${emoji} Skills`) + "\n\n");
33
39
 
34
40
  // Summary table by capability
35
41
  const rows = groupOrder.map((capability) => {
@@ -38,10 +44,18 @@ function formatSummary(skills, data) {
38
44
  return [capability, count, withAgent];
39
45
  });
40
46
 
41
- console.log(formatTable(["Capability", "Count", "Agent"], rows));
42
- console.log(`\nTotal: ${skills.length} skills`);
43
- console.log(`\nRun 'bunx pathway skill --list' for IDs`);
44
- console.log(`Run 'bunx pathway skill <id>' for details\n`);
47
+ process.stdout.write(
48
+ formatTable(["Capability", "Count", "Agent"], rows) + "\n",
49
+ );
50
+ process.stdout.write(
51
+ "\n" + formatSubheader(`Total: ${skills.length} skills`) + "\n\n",
52
+ );
53
+ process.stdout.write(
54
+ formatBullet("Run 'npx fit-pathway skill --list' for IDs") + "\n",
55
+ );
56
+ process.stdout.write(
57
+ formatBullet("Run 'npx fit-pathway skill <id>' for details") + "\n\n",
58
+ );
45
59
  }
46
60
 
47
61
  /**
@@ -70,10 +84,12 @@ function formatDetail(viewAndContext, framework) {
70
84
  */
71
85
  async function formatAgentDetail(skill, stages, templateLoader, dataDir) {
72
86
  if (!skill.agent) {
73
- console.error(formatError(`Skill '${skill.id}' has no agent section`));
74
- console.error(`\nSkills with agent support:`);
75
- console.error(
76
- ` bunx pathway skill --list | xargs -I{} sh -c 'bunx pathway skill {} --json | jq -e .skill.agent > /dev/null && echo {}'`,
87
+ process.stderr.write(
88
+ formatError(`Skill '${skill.id}' has no agent section`) + "\n",
89
+ );
90
+ process.stderr.write("\nSkills with agent support:\n");
91
+ process.stderr.write(
92
+ ` npx fit-pathway skill --list | xargs -I{} sh -c 'npx fit-pathway skill {} --json | jq -e .skill.agent > /dev/null && echo {}'\n`,
77
93
  );
78
94
  process.exit(1);
79
95
  }
@@ -81,7 +97,7 @@ async function formatAgentDetail(skill, stages, templateLoader, dataDir) {
81
97
  const template = templateLoader.load("skill.template.md", dataDir);
82
98
  const skillMd = generateSkillMarkdown({ skillData: skill, stages });
83
99
  const output = formatAgentSkill(skillMd, template);
84
- console.log(output);
100
+ process.stdout.write(output + "\n");
85
101
  }
86
102
 
87
103
  /**
@@ -131,8 +147,10 @@ export async function runSkillCommand({
131
147
  const skill = data.skills.find((s) => s.id === id);
132
148
 
133
149
  if (!skill) {
134
- console.error(formatError(`Skill not found: ${id}`));
135
- console.error(`Available: ${data.skills.map((s) => s.id).join(", ")}`);
150
+ process.stderr.write(formatError(`Skill not found: ${id}`) + "\n");
151
+ process.stderr.write(
152
+ `Available: ${data.skills.map((s) => s.id).join(", ")}\n`,
153
+ );
136
154
  process.exit(1);
137
155
  }
138
156