@forwardimpact/pathway 0.25.12 → 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.
@@ -235,6 +235,62 @@ Options:
235
235
  --format=FORMAT Output format: table, yaml, json
236
236
  `;
237
237
 
238
+ /** Boolean flags: exact match sets the field to true */
239
+ const BOOLEAN_FLAGS = {
240
+ "--version": "version",
241
+ "-v": "version",
242
+ "--help": "help",
243
+ "-h": "help",
244
+ "--list": "list",
245
+ "-l": "list",
246
+ "--json": "json",
247
+ "--stats": "stats",
248
+ "--all-roles": "all-roles",
249
+ "--all-stages": "all-stages",
250
+ "--agent": "agent",
251
+ "--skills": "skills",
252
+ "--tools": "tools",
253
+ };
254
+
255
+ /** Negation flags: exact match sets the field to false */
256
+ const NEGATION_FLAGS = { "--no-clean": "clean" };
257
+
258
+ /** Value flags: --key=val sets result[field] = val */
259
+ const VALUE_FLAGS = {
260
+ "--type": "type",
261
+ "--compare": "compare",
262
+ "--data": "data",
263
+ "--track": "track",
264
+ "--output": "output",
265
+ "--level": "level",
266
+ "--maturity": "maturity",
267
+ "--skill": "skill",
268
+ "--behaviour": "behaviour",
269
+ "--capability": "capability",
270
+ "--format": "format",
271
+ "--role": "role",
272
+ "--stage": "stage",
273
+ "--checklist": "checklist",
274
+ "--path": "path",
275
+ "--url": "url",
276
+ };
277
+
278
+ /**
279
+ * Try to parse a --key=value argument using the VALUE_FLAGS table
280
+ * @param {string} arg
281
+ * @param {Object} result
282
+ * @returns {boolean} true if the arg was handled
283
+ */
284
+ function parseValueFlag(arg, result) {
285
+ const eqIndex = arg.indexOf("=");
286
+ if (eqIndex === -1) return false;
287
+ const key = arg.slice(0, eqIndex);
288
+ const field = VALUE_FLAGS[key];
289
+ if (!field) return false;
290
+ result[field] = arg.slice(eqIndex + 1);
291
+ return true;
292
+ }
293
+
238
294
  /**
239
295
  * Parse command line arguments
240
296
  * @param {string[]} args
@@ -273,62 +329,14 @@ function parseArgs(args) {
273
329
  };
274
330
 
275
331
  for (const arg of args) {
276
- if (arg === "--version" || arg === "-v") {
277
- result.version = true;
278
- } else if (arg === "--help" || arg === "-h") {
279
- result.help = true;
280
- } else if (arg === "--list" || arg === "-l") {
281
- result.list = true;
282
- } else if (arg === "--json") {
283
- result.json = true;
284
- } else if (arg.startsWith("--type=")) {
285
- result.type = arg.slice(7);
286
- } else if (arg.startsWith("--compare=")) {
287
- result.compare = arg.slice(10);
288
- } else if (arg.startsWith("--data=")) {
289
- result.data = arg.slice(7);
290
- } else if (arg.startsWith("--track=")) {
291
- result.track = arg.slice(8);
292
- } else if (arg.startsWith("--output=")) {
293
- result.output = arg.slice(9);
294
- } else if (arg.startsWith("--level=")) {
295
- result.level = arg.slice(8);
296
- } else if (arg.startsWith("--maturity=")) {
297
- result.maturity = arg.slice(11);
298
- } else if (arg.startsWith("--skill=")) {
299
- result.skill = arg.slice(8);
300
- } else if (arg.startsWith("--behaviour=")) {
301
- result.behaviour = arg.slice(12);
302
- } else if (arg.startsWith("--capability=")) {
303
- result.capability = arg.slice(14);
304
- } else if (arg.startsWith("--format=")) {
305
- result.format = arg.slice(9);
306
- } else if (arg === "--stats") {
307
- result.stats = true;
308
- } else if (arg.startsWith("--role=")) {
309
- result.role = arg.slice(7);
310
- } else if (arg === "--all-roles") {
311
- result["all-roles"] = true;
312
- } else if (arg.startsWith("--stage=")) {
313
- result.stage = arg.slice(8);
314
- } else if (arg === "--all-stages") {
315
- result["all-stages"] = true;
316
- } else if (arg === "--agent") {
317
- result.agent = true;
318
- } else if (arg.startsWith("--checklist=")) {
319
- result.checklist = arg.slice(12);
320
- } else if (arg === "--skills") {
321
- result.skills = true;
322
- } else if (arg === "--tools") {
323
- result.tools = true;
332
+ if (BOOLEAN_FLAGS[arg]) {
333
+ result[BOOLEAN_FLAGS[arg]] = true;
334
+ } else if (NEGATION_FLAGS[arg]) {
335
+ result[NEGATION_FLAGS[arg]] = false;
324
336
  } else if (arg.startsWith("--port=")) {
325
337
  result.port = parseInt(arg.slice(7), 10);
326
- } else if (arg.startsWith("--path=")) {
327
- result.path = arg.slice(7);
328
- } else if (arg === "--no-clean") {
329
- result.clean = false;
330
- } else if (arg.startsWith("--url=")) {
331
- result.url = arg.slice(6);
338
+ } else if (parseValueFlag(arg, result)) {
339
+ // handled
332
340
  } else if (!arg.startsWith("-")) {
333
341
  if (!result.command) {
334
342
  result.command = arg;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/pathway",
3
- "version": "0.25.12",
3
+ "version": "0.25.20",
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": {
@@ -20,7 +20,6 @@
20
20
  "engineering"
21
21
  ],
22
22
  "type": "module",
23
- "main": "src/index.js",
24
23
  "bin": {
25
24
  "fit-pathway": "./bin/fit-pathway.js"
26
25
  },
@@ -36,7 +35,6 @@
36
35
  "templates/"
37
36
  ],
38
37
  "exports": {
39
- ".": "./src/index.js",
40
38
  "./formatters": "./src/formatters/index.js",
41
39
  "./commands": "./src/commands/index.js"
42
40
  },
@@ -50,7 +48,8 @@
50
48
  "yaml": "^2.8.3"
51
49
  },
52
50
  "engines": {
53
- "bun": ">=1.2.0"
51
+ "bun": ">=1.2.0",
52
+ "node": ">=18.0.0"
54
53
  },
55
54
  "publishConfig": {
56
55
  "access": "public"
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Agent file I/O helpers
3
+ *
4
+ * Handles writing agent profiles, skills, settings, and team instructions
5
+ * to disk. Extracted from agent.js to keep command logic focused.
6
+ */
7
+
8
+ import { writeFile, mkdir, readFile } from "fs/promises";
9
+ import { join, dirname } from "path";
10
+ import { existsSync } from "fs";
11
+ import { formatAgentProfile } from "../formatters/agent/profile.js";
12
+ import {
13
+ formatAgentSkill,
14
+ formatInstallScript,
15
+ formatReference,
16
+ } from "../formatters/agent/skill.js";
17
+ import { formatSuccess } from "../lib/cli-output.js";
18
+
19
+ /**
20
+ * Ensure directory exists for a file path
21
+ * @param {string} filePath - Full file path
22
+ */
23
+ async function ensureDir(filePath) {
24
+ await mkdir(dirname(filePath), { recursive: true });
25
+ }
26
+
27
+ /**
28
+ * Generate Claude Code settings file
29
+ * Merges with existing settings if file exists
30
+ * @param {string} baseDir - Base output directory
31
+ * @param {Object} claudeCodeSettings - Settings loaded from data
32
+ */
33
+ export async function generateClaudeCodeSettings(baseDir, claudeCodeSettings) {
34
+ const settingsPath = join(baseDir, ".claude", "settings.json");
35
+
36
+ let settings = {};
37
+ if (existsSync(settingsPath)) {
38
+ const content = await readFile(settingsPath, "utf-8");
39
+ settings = JSON.parse(content);
40
+ }
41
+
42
+ const merged = { ...settings, ...claudeCodeSettings };
43
+
44
+ await ensureDir(settingsPath);
45
+ await writeFile(
46
+ settingsPath,
47
+ JSON.stringify(merged, null, 2) + "\n",
48
+ "utf-8",
49
+ );
50
+ console.log(formatSuccess(`Updated: ${settingsPath}`));
51
+ }
52
+
53
+ /**
54
+ * Write agent profile to file
55
+ * @param {Object} profile - Generated profile
56
+ * @param {string} baseDir - Base output directory
57
+ * @param {string} template - Mustache template for agent profile
58
+ */
59
+ export async function writeProfile(profile, baseDir, template) {
60
+ const profilePath = join(baseDir, ".claude", "agents", profile.filename);
61
+ const profileContent = formatAgentProfile(profile, template);
62
+ await ensureDir(profilePath);
63
+ await writeFile(profilePath, profileContent, "utf-8");
64
+ console.log(formatSuccess(`Created: ${profilePath}`));
65
+ return profilePath;
66
+ }
67
+
68
+ /**
69
+ * Write team instructions to CLAUDE.md
70
+ * @param {string|null} teamInstructions - Interpolated team instructions content
71
+ * @param {string} baseDir - Base output directory
72
+ * @returns {string|null} Path written, or null if skipped
73
+ */
74
+ export async function writeTeamInstructions(teamInstructions, baseDir) {
75
+ if (!teamInstructions) return null;
76
+ const filePath = join(baseDir, ".claude", "CLAUDE.md");
77
+ await ensureDir(filePath);
78
+ await writeFile(filePath, teamInstructions.trim() + "\n", "utf-8");
79
+ console.log(formatSuccess(`Created: ${filePath}`));
80
+ return filePath;
81
+ }
82
+
83
+ /**
84
+ * Write skill files (SKILL.md, scripts/install.sh, references/REFERENCE.md)
85
+ * @param {Array} skills - Generated skills
86
+ * @param {string} baseDir - Base output directory
87
+ * @param {Object} templates - Templates object with skill, install, reference
88
+ */
89
+ export async function writeSkills(skills, baseDir, templates) {
90
+ let fileCount = 0;
91
+ for (const skill of skills) {
92
+ const skillDir = join(baseDir, ".claude", "skills", skill.dirname);
93
+
94
+ const skillPath = join(skillDir, "SKILL.md");
95
+ const skillContent = formatAgentSkill(skill, templates.skill);
96
+ await ensureDir(skillPath);
97
+ await writeFile(skillPath, skillContent, "utf-8");
98
+ console.log(formatSuccess(`Created: ${skillPath}`));
99
+ fileCount++;
100
+
101
+ if (skill.installScript) {
102
+ const installPath = join(skillDir, "scripts", "install.sh");
103
+ const installContent = formatInstallScript(skill, templates.install);
104
+ await ensureDir(installPath);
105
+ await writeFile(installPath, installContent, { mode: 0o755 });
106
+ console.log(formatSuccess(`Created: ${installPath}`));
107
+ fileCount++;
108
+ }
109
+
110
+ if (skill.implementationReference) {
111
+ const refPath = join(skillDir, "references", "REFERENCE.md");
112
+ const refContent = formatReference(skill, templates.reference);
113
+ await ensureDir(refPath);
114
+ await writeFile(refPath, refContent, "utf-8");
115
+ console.log(formatSuccess(`Created: ${refPath}`));
116
+ fileCount++;
117
+ }
118
+ }
119
+ return fileCount;
120
+ }