@forwardimpact/pathway 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/package.json +3 -2
  2. package/src/commands/agent.js +7 -3
  3. package/src/commands/behaviour.js +11 -1
  4. package/src/commands/build.js +11 -2
  5. package/src/commands/command-factory.js +4 -2
  6. package/src/commands/dev.js +9 -2
  7. package/src/commands/discipline.js +19 -2
  8. package/src/commands/driver.js +11 -1
  9. package/src/commands/job.js +25 -12
  10. package/src/commands/level.js +19 -3
  11. package/src/commands/skill.js +11 -1
  12. package/src/commands/stage.js +11 -1
  13. package/src/commands/tool.js +4 -3
  14. package/src/commands/track.js +11 -1
  15. package/src/components/card.js +8 -104
  16. package/src/components/comparison-radar.js +1 -1
  17. package/src/components/detail.js +16 -118
  18. package/src/components/error-page.js +8 -68
  19. package/src/components/grid.js +12 -106
  20. package/src/components/list.js +7 -116
  21. package/src/components/nav.js +7 -60
  22. package/src/css/bundles/app.css +25 -21
  23. package/src/css/bundles/handout.css +33 -33
  24. package/src/css/bundles/slides.css +25 -25
  25. package/src/formatters/interview/shared.js +3 -3
  26. package/src/formatters/job/description.js +2 -2
  27. package/src/formatters/progress/shared.js +3 -3
  28. package/src/formatters/skill/shared.js +1 -1
  29. package/src/formatters/track/shared.js +1 -1
  30. package/src/handout.html +32 -13
  31. package/src/index.html +32 -13
  32. package/src/lib/error-boundary.js +3 -66
  33. package/src/lib/errors.js +7 -45
  34. package/src/lib/job-cache.js +1 -1
  35. package/src/lib/markdown.js +2 -109
  36. package/src/lib/reactive.js +7 -73
  37. package/src/lib/render.js +49 -197
  38. package/src/lib/router-core.js +2 -156
  39. package/src/lib/router-pages.js +2 -11
  40. package/src/lib/router-slides.js +2 -197
  41. package/src/lib/state.js +14 -63
  42. package/src/lib/utils.js +3 -10
  43. package/src/lib/yaml-loader.js +13 -71
  44. package/src/pages/agent-builder.js +1 -1
  45. package/src/pages/assessment-results.js +1 -1
  46. package/src/pages/job-builder.js +1 -1
  47. package/src/pages/job.js +1 -1
  48. package/src/pages/skill.js +1 -1
  49. package/src/slide-main.js +1 -1
  50. package/src/slides/index.js +1 -1
  51. package/src/slides/job.js +1 -1
  52. package/src/slides/overview.js +1 -1
  53. package/src/slides.html +32 -13
  54. package/src/css/base.css +0 -56
  55. package/src/css/components/badges.css +0 -232
  56. package/src/css/components/buttons.css +0 -101
  57. package/src/css/components/forms.css +0 -191
  58. package/src/css/components/layout.css +0 -218
  59. package/src/css/components/nav.css +0 -206
  60. package/src/css/components/progress.css +0 -166
  61. package/src/css/components/states.css +0 -82
  62. package/src/css/components/surfaces.css +0 -347
  63. package/src/css/components/tables.css +0 -362
  64. package/src/css/components/top-bar.css +0 -180
  65. package/src/css/components/typography.css +0 -121
  66. package/src/css/components/utilities.css +0 -41
  67. package/src/css/pages/detail.css +0 -119
  68. package/src/css/reset.css +0 -50
  69. package/src/css/tokens.css +0 -162
  70. package/src/css/views/handout.css +0 -30
  71. package/src/css/views/print.css +0 -634
  72. package/src/css/views/slide-animations.css +0 -113
  73. package/src/css/views/slide-base.css +0 -331
  74. package/src/css/views/slide-sections.css +0 -597
  75. package/src/css/views/slide-tables.css +0 -275
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/pathway",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Career progression web app and CLI for exploring roles and generating agent teams",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -41,7 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@forwardimpact/map": "^0.11.0",
44
- "@forwardimpact/libpathway": "^3.0.0",
44
+ "@forwardimpact/libskill": "^3.0.0",
45
+ "@forwardimpact/libui": "^1.0.0",
45
46
  "mustache": "^4.2.0",
46
47
  "simple-icons": "^16.7.0",
47
48
  "yaml": "^2.3.4"
@@ -42,7 +42,7 @@ import {
42
42
  buildAgentIndex,
43
43
  getDisciplineAbbreviation,
44
44
  toKebabCase,
45
- } from "@forwardimpact/libpathway";
45
+ } from "@forwardimpact/libskill";
46
46
  import { formatAgentProfile } from "../formatters/agent/profile.js";
47
47
  import {
48
48
  formatAgentSkill,
@@ -199,7 +199,7 @@ function showAgentSummary(data, agentData, skillsWithAgent) {
199
199
  */
200
200
  function listAgentCombinations(data, agentData, verbose = false) {
201
201
  if (!verbose) {
202
- // Clean output for piping
202
+ // Descriptive output for piping and AI agent discovery
203
203
  for (const discipline of agentData.disciplines) {
204
204
  for (const track of agentData.tracks) {
205
205
  const humanDiscipline = data.disciplines.find(
@@ -209,7 +209,11 @@ function listAgentCombinations(data, agentData, verbose = false) {
209
209
  if (humanDiscipline && humanTrack) {
210
210
  const abbrev = getDisciplineAbbreviation(discipline.id);
211
211
  const agentName = `${abbrev}-${toKebabCase(track.id)}`;
212
- console.log(`${agentName} ${discipline.id} ${track.id}`);
212
+ const specName =
213
+ humanDiscipline.specialization || humanDiscipline.id;
214
+ console.log(
215
+ `${agentName} ${discipline.id} ${track.id}, ${specName} (${humanTrack.name})`,
216
+ );
213
217
  }
214
218
  }
215
219
  }
@@ -14,6 +14,15 @@ import { createEntityCommand } from "./command-factory.js";
14
14
  import { behaviourToMarkdown } from "../formatters/behaviour/markdown.js";
15
15
  import { formatTable } from "../lib/cli-output.js";
16
16
 
17
+ /**
18
+ * Format behaviour list item for --list output
19
+ * @param {Object} behaviour - Behaviour entity
20
+ * @returns {string} Formatted list line
21
+ */
22
+ function formatListItem(behaviour) {
23
+ return `${behaviour.id}, ${behaviour.name}`;
24
+ }
25
+
17
26
  /**
18
27
  * Format behaviour summary output
19
28
  * @param {Array} behaviours - Raw behaviour entities
@@ -34,7 +43,7 @@ function formatSummary(behaviours, data) {
34
43
 
35
44
  console.log(formatTable(["ID", "Name", "Drivers"], rows));
36
45
  console.log(`\nTotal: ${behaviours.length} behaviours`);
37
- console.log(`\nRun 'npx pathway behaviour --list' for IDs`);
46
+ console.log(`\nRun 'npx pathway behaviour --list' for IDs and names`);
38
47
  console.log(`Run 'npx pathway behaviour <id>' for details\n`);
39
48
  }
40
49
 
@@ -57,5 +66,6 @@ export const runBehaviourCommand = createEntityCommand({
57
66
  }),
58
67
  formatSummary,
59
68
  formatDetail,
69
+ formatListItem,
60
70
  emojiIcon: "🧠",
61
71
  });
@@ -42,7 +42,8 @@ function resolvePackageLib(packageName) {
42
42
  }
43
43
 
44
44
  const mapLibDir = resolvePackageLib("@forwardimpact/map");
45
- const modelLibDir = resolvePackageLib("@forwardimpact/libpathway");
45
+ const modelLibDir = resolvePackageLib("@forwardimpact/libskill");
46
+ const uiLibDir = resolvePackageLib("@forwardimpact/libui");
46
47
 
47
48
  /**
48
49
  * Files and directories to copy from app/
@@ -141,13 +142,21 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
141
142
  }
142
143
  }
143
144
 
144
- // Copy @forwardimpact/map and @forwardimpact/libpathway packages
145
+ // Copy @forwardimpact/map and @forwardimpact/libskill packages
145
146
  // These are needed by the browser's import map
146
147
  console.log("📚 Copying package dependencies...");
147
148
  await cp(mapLibDir, join(outputDir, "map/lib"), { recursive: true });
148
149
  console.log(` ✓ map/lib`);
149
150
  await cp(modelLibDir, join(outputDir, "model/lib"), { recursive: true });
150
151
  console.log(` ✓ model/lib`);
152
+ // Copy libui JS (src/) and CSS (src/css/)
153
+ await cp(uiLibDir, join(outputDir, "ui/lib"), { recursive: true });
154
+ // CSS is within uiLibDir/css/ so it's already copied as ui/lib/css/
155
+ // Create ui/css/ symlink-like copy for the CSS @import paths
156
+ await cp(join(uiLibDir, "css"), join(outputDir, "ui/css"), {
157
+ recursive: true,
158
+ });
159
+ console.log(` ✓ ui/lib + ui/css`);
151
160
 
152
161
  // Copy data directory (dereference symlinks to copy actual content)
153
162
  console.log("📁 Copying data files...");
@@ -22,6 +22,7 @@ import { capitalize } from "../formatters/shared.js";
22
22
  * @param {Function} config.presentDetail - Function to present detail: (entity, data, options) => view
23
23
  * @param {Function} config.formatSummary - Function to format summary output: (items, data) => void
24
24
  * @param {Function} config.formatDetail - Function to format detail output: (view, framework) => void
25
+ * @param {Function} [config.formatListItem] - Optional function to format list item: (item) => string (defaults to item.id)
25
26
  * @param {Function} [config.sortItems] - Optional function to sort items: (items) => sortedItems
26
27
  * @param {Function} [config.validate] - Optional validation function: (data) => {errors: [], warnings: []}
27
28
  * @param {string} [config.emojiIcon] - Optional emoji for the entity
@@ -34,6 +35,7 @@ export function createEntityCommand({
34
35
  presentDetail,
35
36
  formatSummary,
36
37
  formatDetail,
38
+ formatListItem,
37
39
  sortItems,
38
40
  validate,
39
41
  _emojiIcon = "",
@@ -48,10 +50,10 @@ export function createEntityCommand({
48
50
  return handleValidate({ data, entityName, pluralName, validate });
49
51
  }
50
52
 
51
- // --list: Output clean newline-separated IDs for piping
53
+ // --list: Output descriptive comma-separated lines for piping and AI agent discovery
52
54
  if (options.list) {
53
55
  for (const item of items) {
54
- console.log(item.id);
56
+ console.log(formatListItem ? formatListItem(item) : item.id);
55
57
  }
56
58
  return;
57
59
  }
@@ -31,7 +31,8 @@ function resolvePackageLib(packageName) {
31
31
  }
32
32
 
33
33
  const mapLibDir = resolvePackageLib("@forwardimpact/map");
34
- const modelLibDir = resolvePackageLib("@forwardimpact/libpathway");
34
+ const modelLibDir = resolvePackageLib("@forwardimpact/libskill");
35
+ const uiLibDir = resolvePackageLib("@forwardimpact/libui");
35
36
 
36
37
  const MIME_TYPES = {
37
38
  ".html": "text/html; charset=utf-8",
@@ -138,8 +139,14 @@ export async function runDevCommand({ dataDir, options }) {
138
139
  // Serve @forwardimpact/map package files (resolved via Node module resolution)
139
140
  filePath = join(mapLibDir, pathname.slice(9));
140
141
  } else if (pathname.startsWith("/model/lib/")) {
141
- // Serve @forwardimpact/libpathway package files (resolved via Node module resolution)
142
+ // Serve @forwardimpact/libskill package files (resolved via Node module resolution)
142
143
  filePath = join(modelLibDir, pathname.slice(11));
144
+ } else if (pathname.startsWith("/ui/lib/")) {
145
+ // Serve @forwardimpact/libui package JS files
146
+ filePath = join(uiLibDir, pathname.slice(8));
147
+ } else if (pathname.startsWith("/ui/css/")) {
148
+ // Serve @forwardimpact/libui package CSS files
149
+ filePath = join(uiLibDir, "css", pathname.slice(8));
143
150
  } else if (pathname === "/" || pathname === "") {
144
151
  // Serve index.html for root
145
152
  filePath = join(publicDir, "index.html");
@@ -14,6 +14,15 @@ import { createEntityCommand } from "./command-factory.js";
14
14
  import { disciplineToMarkdown } from "../formatters/discipline/markdown.js";
15
15
  import { formatTable } from "../lib/cli-output.js";
16
16
 
17
+ /**
18
+ * Format discipline list item for --list output
19
+ * @param {Object} discipline - Discipline entity
20
+ * @returns {string} Formatted list line
21
+ */
22
+ function formatListItem(discipline) {
23
+ return `${discipline.id}, ${discipline.specialization || discipline.id}, ${discipline.roleTitle || discipline.id}`;
24
+ }
25
+
17
26
  /**
18
27
  * Format discipline summary output
19
28
  * @param {Array} disciplines - Raw discipline entities
@@ -23,14 +32,21 @@ function formatSummary(disciplines) {
23
32
 
24
33
  const rows = disciplines.map((d) => [
25
34
  d.id,
35
+ d.specialization || d.id,
36
+ d.roleTitle || d.id,
26
37
  d.coreSkills?.length || 0,
27
38
  d.supportingSkills?.length || 0,
28
39
  d.broadSkills?.length || 0,
29
40
  ]);
30
41
 
31
- console.log(formatTable(["ID", "Core", "Supporting", "Broad"], rows));
42
+ console.log(
43
+ formatTable(
44
+ ["ID", "Specialization", "Role Title", "Core", "Supporting", "Broad"],
45
+ rows,
46
+ ),
47
+ );
32
48
  console.log(`\nTotal: ${disciplines.length} disciplines`);
33
- console.log(`\nRun 'npx pathway discipline --list' for IDs`);
49
+ console.log(`\nRun 'npx pathway discipline --list' for IDs and names`);
34
50
  console.log(`Run 'npx pathway discipline <id>' for details\n`);
35
51
  }
36
52
 
@@ -54,5 +70,6 @@ export const runDisciplineCommand = createEntityCommand({
54
70
  }),
55
71
  formatSummary,
56
72
  formatDetail,
73
+ formatListItem,
57
74
  emojiIcon: "📋",
58
75
  });
@@ -20,6 +20,15 @@ import {
20
20
  } from "../lib/cli-output.js";
21
21
  import { getConceptEmoji } from "@forwardimpact/map/levels";
22
22
 
23
+ /**
24
+ * Format driver list item for --list output
25
+ * @param {Object} driver - Driver entity
26
+ * @returns {string} Formatted list line
27
+ */
28
+ function formatListItem(driver) {
29
+ return `${driver.id}, ${driver.name}`;
30
+ }
31
+
23
32
  /**
24
33
  * Format driver summary output
25
34
  * @param {Array} drivers - Raw driver entities
@@ -43,7 +52,7 @@ function formatSummary(drivers, data) {
43
52
 
44
53
  console.log(formatTable(["ID", "Name", "Skills", "Behaviours"], rows));
45
54
  console.log(`\nTotal: ${drivers.length} drivers`);
46
- console.log(`\nRun 'npx pathway driver --list' for IDs`);
55
+ console.log(`\nRun 'npx pathway driver --list' for IDs and names`);
47
56
  console.log(`Run 'npx pathway driver <id>' for details\n`);
48
57
  }
49
58
 
@@ -90,5 +99,6 @@ export const runDriverCommand = createEntityCommand({
90
99
  }),
91
100
  formatSummary,
92
101
  formatDetail,
102
+ formatListItem,
93
103
  emojiIcon: "🎯",
94
104
  });
@@ -14,14 +14,14 @@
14
14
  * npx pathway job --validate # Validation checks
15
15
  */
16
16
 
17
- import { prepareJobDetail } from "@forwardimpact/libpathway/job";
17
+ import { prepareJobDetail } from "@forwardimpact/libskill/job";
18
18
  import { jobToMarkdown } from "../formatters/job/markdown.js";
19
- import { generateAllJobs } from "@forwardimpact/libpathway/derivation";
19
+ import { generateJobTitle, generateAllJobs } from "@forwardimpact/libskill/derivation";
20
20
  import { formatTable } from "../lib/cli-output.js";
21
21
  import {
22
22
  deriveChecklist,
23
23
  formatChecklistMarkdown,
24
- } from "@forwardimpact/libpathway/checklist";
24
+ } from "@forwardimpact/libskill/checklist";
25
25
  import { loadJobTemplate } from "../lib/template-loader.js";
26
26
  import { toolkitToPlainList } from "../formatters/toolkit/markdown.js";
27
27
 
@@ -54,13 +54,16 @@ export async function runJobCommand({ data, args, options, dataDir }) {
54
54
  validationRules: data.framework.validationRules,
55
55
  });
56
56
 
57
- // --list: Output clean lines for piping (discipline level track format)
57
+ // --list: Output descriptive comma-separated lines for piping and AI agent discovery
58
58
  if (options.list) {
59
59
  for (const job of jobs) {
60
+ const title = generateJobTitle(job.discipline, job.level, job.track);
60
61
  if (job.track) {
61
- console.log(`${job.discipline.id} ${job.level.id} ${job.track.id}`);
62
+ console.log(
63
+ `${job.discipline.id} ${job.level.id} ${job.track.id}, ${title}`,
64
+ );
62
65
  } else {
63
- console.log(`${job.discipline.id} ${job.level.id}`);
66
+ console.log(`${job.discipline.id} ${job.level.id}, ${title}`);
64
67
  }
65
68
  }
66
69
  return;
@@ -70,17 +73,27 @@ export async function runJobCommand({ data, args, options, dataDir }) {
70
73
  if (args.length === 0) {
71
74
  console.log(`\n💼 Jobs\n`);
72
75
 
73
- // Count by discipline
76
+ // Count by discipline with name
74
77
  const byDiscipline = {};
75
78
  for (const job of jobs) {
76
- byDiscipline[job.discipline.id] =
77
- (byDiscipline[job.discipline.id] || 0) + 1;
79
+ const key = job.discipline.id;
80
+ if (!byDiscipline[key]) {
81
+ byDiscipline[key] = {
82
+ name: job.discipline.specialization || job.discipline.id,
83
+ count: 0,
84
+ };
85
+ }
86
+ byDiscipline[key].count++;
78
87
  }
79
88
 
80
- const rows = Object.entries(byDiscipline).map(([id, count]) => [id, count]);
81
- console.log(formatTable(["Discipline", "Combinations"], rows));
89
+ const rows = Object.entries(byDiscipline).map(([id, info]) => [
90
+ id,
91
+ info.name,
92
+ info.count,
93
+ ]);
94
+ console.log(formatTable(["ID", "Specialization", "Combinations"], rows));
82
95
  console.log(`\nTotal: ${jobs.length} valid job combinations`);
83
- console.log(`\nRun 'npx pathway job --list' for all combinations`);
96
+ console.log(`\nRun 'npx pathway job --list' for all combinations with titles`);
84
97
  console.log(
85
98
  `Run 'npx pathway job <discipline> <level> [--track=<track>]' for details\n`,
86
99
  );
@@ -16,6 +16,15 @@ import { formatTable } from "../lib/cli-output.js";
16
16
  import { getConceptEmoji } from "@forwardimpact/map/levels";
17
17
  import { capitalize } from "../formatters/shared.js";
18
18
 
19
+ /**
20
+ * Format level list item for --list output
21
+ * @param {Object} level - Level entity
22
+ * @returns {string} Formatted list line
23
+ */
24
+ function formatListItem(level) {
25
+ return `${level.id}, ${level.professionalTitle || level.id}, ${level.managementTitle || level.id}`;
26
+ }
27
+
19
28
  /**
20
29
  * Format level summary output
21
30
  * @param {Array} levels - Raw level entities
@@ -29,14 +38,20 @@ function formatSummary(levels, data) {
29
38
 
30
39
  const rows = levels.map((g) => [
31
40
  g.id,
32
- g.displayName || g.id,
41
+ g.professionalTitle || g.displayName || g.id,
42
+ g.managementTitle || "-",
33
43
  g.typicalExperienceRange || "-",
34
44
  capitalize(g.baseSkillProficiencies?.primary || "-"),
35
45
  ]);
36
46
 
37
- console.log(formatTable(["ID", "Name", "Experience", "Primary Level"], rows));
47
+ console.log(
48
+ formatTable(
49
+ ["ID", "Professional Title", "Management Title", "Experience", "Primary Level"],
50
+ rows,
51
+ ),
52
+ );
38
53
  console.log(`\nTotal: ${levels.length} levels`);
39
- console.log(`\nRun 'npx pathway level --list' for IDs`);
54
+ console.log(`\nRun 'npx pathway level --list' for IDs and titles`);
40
55
  console.log(`Run 'npx pathway level <id>' for details\n`);
41
56
  }
42
57
 
@@ -56,5 +71,6 @@ export const runLevelCommand = createEntityCommand({
56
71
  presentDetail: (entity) => entity,
57
72
  formatSummary,
58
73
  formatDetail,
74
+ formatListItem,
59
75
  emojiIcon: "📊",
60
76
  });
@@ -16,7 +16,7 @@ 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
18
  import { formatTable, formatError } from "../lib/cli-output.js";
19
- import { generateSkillMarkdown } from "@forwardimpact/libpathway/agent";
19
+ import { generateSkillMarkdown } from "@forwardimpact/libskill/agent";
20
20
  import { formatAgentSkill } from "../formatters/agent/skill.js";
21
21
  import { loadSkillTemplate } from "../lib/template-loader.js";
22
22
 
@@ -85,6 +85,15 @@ async function formatAgentDetail(skill, stages, dataDir) {
85
85
  console.log(output);
86
86
  }
87
87
 
88
+ /**
89
+ * Format skill list item for --list output
90
+ * @param {Object} skill - Skill entity
91
+ * @returns {string} Formatted list line
92
+ */
93
+ function formatListItem(skill) {
94
+ return `${skill.id}, ${skill.name}, ${skill.capability || "-"}`;
95
+ }
96
+
88
97
  const baseSkillCommand = createEntityCommand({
89
98
  entityName: "skill",
90
99
  pluralName: "skills",
@@ -98,6 +107,7 @@ const baseSkillCommand = createEntityCommand({
98
107
  }),
99
108
  formatSummary,
100
109
  formatDetail,
110
+ formatListItem,
101
111
  emojiIcon: "📚",
102
112
  });
103
113
 
@@ -21,6 +21,15 @@ import {
21
21
  formatBullet,
22
22
  } from "../lib/cli-output.js";
23
23
 
24
+ /**
25
+ * Format stage list item for --list output
26
+ * @param {Object} stage - Stage entity
27
+ * @returns {string} Formatted list line
28
+ */
29
+ function formatListItem(stage) {
30
+ return `${stage.id}, ${stage.name}`;
31
+ }
32
+
24
33
  /**
25
34
  * Format stage summary output
26
35
  * @param {Array} stages - Raw stage entities
@@ -43,7 +52,7 @@ function formatSummary(stages, _data) {
43
52
 
44
53
  console.log(formatTable(["ID", "Name", "Mode", "Tools", "Handoffs"], rows));
45
54
  console.log(`\nTotal: ${stages.length} stages`);
46
- console.log(`\nRun 'npx pathway stage --list' for IDs`);
55
+ console.log(`\nRun 'npx pathway stage --list' for IDs and names`);
47
56
  console.log(`Run 'npx pathway stage <id>' for details\n`);
48
57
  }
49
58
 
@@ -115,5 +124,6 @@ export const runStageCommand = createEntityCommand({
115
124
  }),
116
125
  formatSummary,
117
126
  formatDetail,
127
+ formatListItem,
118
128
  emojiIcon: "🔄",
119
129
  });
@@ -9,6 +9,7 @@
9
9
  * npx pathway tool <name> # Detail view for specific tool
10
10
  */
11
11
 
12
+ import { truncate } from "../formatters/shared.js";
12
13
  import { prepareToolsList } from "../formatters/tool/shared.js";
13
14
  import {
14
15
  formatTable,
@@ -27,10 +28,10 @@ export async function runToolCommand({ data, args, options }) {
27
28
  const [name] = args;
28
29
  const { tools, totalCount } = prepareToolsList(data.skills);
29
30
 
30
- // --list: Output clean newline-separated tool names for piping
31
+ // --list: Output descriptive comma-separated tool lines for piping
31
32
  if (options.list) {
32
33
  for (const tool of tools) {
33
- console.log(tool.name);
34
+ console.log(`${tool.name}, ${truncate(tool.description, 60)}`);
34
35
  }
35
36
  return;
36
37
  }
@@ -87,7 +88,7 @@ function formatSummary(tools, totalCount) {
87
88
  if (sorted.length > 15) {
88
89
  console.log(`(showing top 15 by usage)`);
89
90
  }
90
- console.log(`\nRun 'npx pathway tool --list' for all tool names`);
91
+ console.log(`\nRun 'npx pathway tool --list' for all tool names and descriptions`);
91
92
  console.log(`Run 'npx pathway tool <name>' for details\n`);
92
93
  }
93
94
 
@@ -16,6 +16,15 @@ import { sortTracksByName } from "../formatters/track/shared.js";
16
16
  import { formatTable } from "../lib/cli-output.js";
17
17
  import { getConceptEmoji } from "@forwardimpact/map/levels";
18
18
 
19
+ /**
20
+ * Format track list item for --list output
21
+ * @param {Object} track - Track entity
22
+ * @returns {string} Formatted list line
23
+ */
24
+ function formatListItem(track) {
25
+ return `${track.id}, ${track.name}`;
26
+ }
27
+
19
28
  /**
20
29
  * Format track summary output
21
30
  * @param {Array} tracks - Raw track entities
@@ -34,7 +43,7 @@ function formatSummary(tracks, data) {
34
43
 
35
44
  console.log(formatTable(["ID", "Name", "Modifiers"], rows));
36
45
  console.log(`\nTotal: ${tracks.length} tracks`);
37
- console.log(`\nRun 'npx pathway track --list' for IDs`);
46
+ console.log(`\nRun 'npx pathway track --list' for IDs and names`);
38
47
  console.log(`Run 'npx pathway track <id>' for details\n`);
39
48
  }
40
49
 
@@ -63,5 +72,6 @@ export const runTrackCommand = createEntityCommand({
63
72
  sortItems: sortTracksByName,
64
73
  formatSummary,
65
74
  formatDetail,
75
+ formatListItem,
66
76
  emojiIcon: "🛤️",
67
77
  });
@@ -1,108 +1,12 @@
1
1
  /**
2
2
  * Reusable card component
3
+ *
4
+ * Re-exports from @forwardimpact/libui/components/card.
3
5
  */
4
6
 
5
- import { div, h3, p, span } from "../lib/render.js";
6
-
7
- /**
8
- * Create a card component
9
- * @param {Object} options
10
- * @param {string} options.title - Card title
11
- * @param {string} [options.description] - Card description
12
- * @param {string} [options.href] - Link destination (makes card clickable)
13
- * @param {HTMLElement[]} [options.badges] - Badges to display
14
- * @param {HTMLElement[]} [options.meta] - Meta information
15
- * @param {HTMLElement} [options.content] - Additional content
16
- * @param {HTMLElement} [options.icon] - Icon element to display
17
- * @param {string} [options.className] - Additional CSS class
18
- * @returns {HTMLElement}
19
- */
20
- export function createCard({
21
- title,
22
- description,
23
- href,
24
- badges = [],
25
- meta = [],
26
- content,
27
- icon,
28
- className = "",
29
- }) {
30
- const isClickable = !!href;
31
-
32
- const titleContent = icon
33
- ? div(
34
- { className: "card-title-with-icon" },
35
- icon,
36
- h3({ className: "card-title" }, title),
37
- )
38
- : h3({ className: "card-title" }, title);
39
-
40
- const cardHeader = div(
41
- { className: "card-header" },
42
- titleContent,
43
- badges.length > 0 ? div({ className: "card-badges" }, ...badges) : null,
44
- );
45
-
46
- const card = div(
47
- {
48
- className:
49
- `card ${isClickable ? "card-clickable" : ""} ${className}`.trim(),
50
- },
51
- cardHeader,
52
- description ? p({ className: "card-description" }, description) : null,
53
- content || null,
54
- meta.length > 0 ? div({ className: "card-meta" }, ...meta) : null,
55
- );
56
-
57
- if (isClickable) {
58
- card.addEventListener("click", () => {
59
- window.location.hash = href;
60
- });
61
- }
62
-
63
- return card;
64
- }
65
-
66
- /**
67
- * Create a stat card for the landing page
68
- * @param {Object} options
69
- * @param {number|string} options.value - The stat value
70
- * @param {string} options.label - The stat label
71
- * @param {string} [options.href] - Optional link
72
- * @returns {HTMLElement}
73
- */
74
- export function createStatCard({ value, label, href }) {
75
- const card = div(
76
- { className: "stat-card" },
77
- div({ className: "stat-value" }, String(value)),
78
- div({ className: "stat-label" }, label),
79
- );
80
-
81
- if (href) {
82
- card.style.cursor = "pointer";
83
- card.addEventListener("click", () => {
84
- window.location.hash = href;
85
- });
86
- }
87
-
88
- return card;
89
- }
90
-
91
- /**
92
- * Create a badge element
93
- * @param {string} text - Badge text
94
- * @param {string} [type] - Badge type (default, primary, secondary, broad, technical, ai, etc.)
95
- * @returns {HTMLElement}
96
- */
97
- export function createBadge(text, type = "default") {
98
- return span({ className: `badge badge-${type}` }, text);
99
- }
100
-
101
- /**
102
- * Create a tag element
103
- * @param {string} text
104
- * @returns {HTMLElement}
105
- */
106
- export function createTag(text) {
107
- return span({ className: "info-tag" }, text);
108
- }
7
+ export {
8
+ createCard,
9
+ createStatCard,
10
+ createBadge,
11
+ createTag,
12
+ } from "@forwardimpact/libui/components/card";
@@ -13,7 +13,7 @@ import {
13
13
  getBehaviourMaturityIndex,
14
14
  formatLevel,
15
15
  } from "../lib/render.js";
16
- import { compareByCapability } from "@forwardimpact/libpathway/policies";
16
+ import { compareByCapability } from "@forwardimpact/libskill/policies";
17
17
 
18
18
  /**
19
19
  * Create a comparison skill radar chart