fraim 2.0.203 → 2.0.204

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 (79) hide show
  1. package/README.md +3 -3
  2. package/bin/fraim.js +8 -18
  3. package/dist/src/cli/commands/first-run.js +2 -37
  4. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +1 -1
  5. package/dist/src/cli/fraim.js +0 -2
  6. package/dist/src/cli/setup/ide-invocation-surfaces.js +6 -6
  7. package/dist/src/cli/utils/managed-agent-paths.js +76 -6
  8. package/dist/src/cli/utils/version-utils.js +2 -2
  9. package/dist/src/config/persona-capability-bundles.js +3 -3
  10. package/dist/src/core/job-phases.js +168 -0
  11. package/dist/src/core/utils/ports.js +32 -0
  12. package/dist/src/core/utils/stub-generator.js +24 -2
  13. package/dist/src/first-run/session-service.js +106 -24
  14. package/dist/src/local-mcp-server/learning-context-builder.js +1 -1
  15. package/dist/src/local-mcp-server/stdio-server.js +41 -9
  16. package/dist/src/routes/analytics.js +2 -2
  17. package/dist/src/services/registry-service.js +2 -2
  18. package/index.js +1 -1
  19. package/package.json +61 -181
  20. package/dist/src/ai-hub/catalog.js +0 -406
  21. package/dist/src/ai-hub/cert-store.js +0 -70
  22. package/dist/src/ai-hub/conversation-store.js +0 -359
  23. package/dist/src/ai-hub/desktop-main.js +0 -449
  24. package/dist/src/ai-hub/hosts.js +0 -1664
  25. package/dist/src/ai-hub/hub-latest-version.js +0 -52
  26. package/dist/src/ai-hub/hub-launch-decision.js +0 -57
  27. package/dist/src/ai-hub/hub-runtime-file.js +0 -43
  28. package/dist/src/ai-hub/managed-browser.js +0 -269
  29. package/dist/src/ai-hub/manager-turns.js +0 -64
  30. package/dist/src/ai-hub/office-sideload.js +0 -156
  31. package/dist/src/ai-hub/openclaw-bridge.js +0 -250
  32. package/dist/src/ai-hub/preferences.js +0 -201
  33. package/dist/src/ai-hub/remote-hub-gateway.js +0 -88
  34. package/dist/src/ai-hub/server.js +0 -3827
  35. package/dist/src/ai-hub/types.js +0 -2
  36. package/dist/src/ai-hub/word-sideload.js +0 -95
  37. package/dist/src/cli/commands/hub.js +0 -238
  38. package/dist/src/cli/commands/test-mcp.js +0 -171
  39. package/dist/src/cli/setup/first-run.js +0 -242
  40. package/dist/src/core/config-writer.js +0 -75
  41. package/dist/src/core/utils/job-aliases.js +0 -47
  42. package/dist/src/core/utils/workflow-parser.js +0 -174
  43. package/dist/src/services/email-service-clean.js +0 -782
  44. package/extensions/office-word/favicon.ico +0 -0
  45. package/extensions/office-word/icon-64.png +0 -0
  46. package/extensions/office-word/manifest.xml +0 -33
  47. package/extensions/office-word/taskpane.html +0 -242
  48. package/public/ai-hub/index.html +0 -894
  49. package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
  50. package/public/ai-hub/powerpoint-taskpane/index.html +0 -236
  51. package/public/ai-hub/powerpoint-taskpane/manifest.xml +0 -30
  52. package/public/ai-hub/review.css +0 -381
  53. package/public/ai-hub/script.js +0 -11015
  54. package/public/ai-hub/styles.css +0 -4873
  55. package/public/portfolio/ashley.html +0 -523
  56. package/public/portfolio/auditya.html +0 -83
  57. package/public/portfolio/banke.html +0 -83
  58. package/public/portfolio/beza.html +0 -659
  59. package/public/portfolio/careena.html +0 -632
  60. package/public/portfolio/casey.html +0 -568
  61. package/public/portfolio/celia.html +0 -490
  62. package/public/portfolio/deidre.html +0 -642
  63. package/public/portfolio/gautam.html +0 -597
  64. package/public/portfolio/hari.html +0 -469
  65. package/public/portfolio/huxley.html +0 -1354
  66. package/public/portfolio/index.html +0 -741
  67. package/public/portfolio/maestro.html +0 -518
  68. package/public/portfolio/mandy.html +0 -590
  69. package/public/portfolio/mona.html +0 -597
  70. package/public/portfolio/pam.html +0 -887
  71. package/public/portfolio/procella.html +0 -107
  72. package/public/portfolio/qasm.html +0 -569
  73. package/public/portfolio/ricardo.html +0 -489
  74. package/public/portfolio/sade.html +0 -560
  75. package/public/portfolio/sam.html +0 -654
  76. package/public/portfolio/sechar.html +0 -580
  77. package/public/portfolio/sreya.html +0 -599
  78. package/public/portfolio/swen.html +0 -601
  79. /package/dist/src/{ai-hub → core}/brand-store.js +0 -0
package/README.md CHANGED
@@ -234,13 +234,13 @@ R - Retrospectives: Continuous learning from experience
234
234
  npx fraim@latest setup --key=<your-fraim-key>
235
235
 
236
236
  # Optional: Create alias for convenience
237
- echo 'alias fraim="npx fraim-framework"' >> ~/.bashrc
237
+ echo 'alias fraim="npx fraim"' >> ~/.bashrc
238
238
  source ~/.bashrc
239
239
  ```
240
240
 
241
241
  **Alternative: Global install**
242
242
  ```bash
243
- npm install -g fraim-framework
243
+ npm install -g fraim
244
244
  fraim setup --key=<your-fraim-key>
245
245
  ```
246
246
 
@@ -460,7 +460,7 @@ Stop fighting with AI agents. Start building your AI organization.
460
460
 
461
461
  **🚀 Ready to become an AI manager? Start with FRAIM today.**
462
462
 
463
- [![npm version](https://img.shields.io/npm/v/fraim-framework.svg)](https://www.npmjs.com/package/fraim-framework)
463
+ [![npm version](https://img.shields.io/npm/v/fraim.svg)](https://www.npmjs.com/package/fraim)
464
464
  [![GitHub stars](https://img.shields.io/github/stars/mathursrus/FRAIM.svg)](https://github.com/mathursrus/FRAIM/stargazers)
465
465
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
466
466
 
package/bin/fraim.js CHANGED
@@ -1,23 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /**
4
- * FRAIM Framework CLI Entry Point
5
- *
6
- * This file delegates to the compiled TypeScript implementation.
7
- */
8
-
9
3
  try {
10
- // In production/installed package, code is in dist/
11
- require('../dist/src/cli/fraim.js');
4
+ require('../dist/src/cli/fraim.js');
12
5
  } catch (error) {
13
- if (error.code === 'MODULE_NOT_FOUND') {
14
- // In development (local clone), we might use tsx if dist is missing
15
- // But typically we should just tell user to build.
16
- console.error('❌ Could not find FRAIM CLI implementation.');
17
- console.error(' If you are running from source, please run "npm run build" first.');
18
- console.error(` Error details: ${error.message}`);
19
- process.exit(1);
20
- } else {
21
- throw error;
22
- }
6
+ if (error && error.code === 'MODULE_NOT_FOUND') {
7
+ console.error('Could not find FRAIM CLI implementation.');
8
+ console.error('If you are running from source, run "npm run build" first.');
9
+ console.error(`Error details: ${error.message}`);
10
+ process.exit(1);
11
+ }
12
+ throw error;
23
13
  }
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
@@ -42,6 +9,7 @@ const chalk_1 = __importDefault(require("chalk"));
42
9
  const child_process_1 = require("child_process");
43
10
  const server_1 = require("../../first-run/server");
44
11
  const session_service_1 = require("../../first-run/session-service");
12
+ const ports_1 = require("../../core/utils/ports");
45
13
  function openBrowser(url) {
46
14
  try {
47
15
  if (process.platform === 'win32') {
@@ -75,10 +43,7 @@ const runFirstRun = async (options) => {
75
43
  projectRoot: options.projectRoot,
76
44
  });
77
45
  const server = new server_1.FirstRunServer({ sessionService });
78
- // Lazy import: ai-hub/server pulls server-only code not shipped in the client
79
- // package; keep it out of the CLI startup graph so the packed client loads (#422).
80
- const { findAvailablePort } = await Promise.resolve().then(() => __importStar(require('../../ai-hub/server')));
81
- const port = await findAvailablePort(43120);
46
+ const port = await (0, ports_1.findAvailablePort)(43120);
82
47
  const url = `http://127.0.0.1:${port}/first-run/`;
83
48
  await server.start(port);
84
49
  console.log(chalk_1.default.blue('Starting FRAIM first-run...'));
@@ -124,7 +124,7 @@ function diagnoseFraimMcpLaunchPlan(fraimServer, platform = process.platform, np
124
124
  const command = String(fraimServer?.command || '');
125
125
  const args = Array.isArray(fraimServer?.args) ? fraimServer.args.map(String) : [];
126
126
  const usesDirectLatest = command.toLowerCase().includes('npx')
127
- && args.some((arg) => arg === 'fraim-framework@latest' || arg === 'fraim@latest');
127
+ && args.some((arg) => arg === 'fraim@latest');
128
128
  if (usesDirectLatest) {
129
129
  const isKnownBrokenWindowsNpm = platform === 'win32' && npmMajorVersion !== null && npmMajorVersion >= 11;
130
130
  return {
@@ -51,7 +51,6 @@ const list_overridable_1 = require("./commands/list-overridable");
51
51
  const login_1 = require("./commands/login");
52
52
  const mcp_1 = require("./commands/mcp");
53
53
  const migrate_project_fraim_1 = require("./commands/migrate-project-fraim");
54
- const hub_1 = require("./commands/hub");
55
54
  const first_run_1 = require("./commands/first-run");
56
55
  const workspace_config_1 = require("./commands/workspace-config");
57
56
  const org_1 = require("./commands/org");
@@ -97,7 +96,6 @@ program.addCommand(list_overridable_1.listOverridableCommand);
97
96
  program.addCommand(login_1.loginCommand);
98
97
  program.addCommand(mcp_1.mcpCommand);
99
98
  program.addCommand(migrate_project_fraim_1.migrateProjectFraimCommand);
100
- program.addCommand(hub_1.hubCommand);
101
99
  program.addCommand(first_run_1.firstRunCommand);
102
100
  program.addCommand(workspace_config_1.workspaceConfigCommand);
103
101
  program.addCommand(org_1.orgCommand);
@@ -96,18 +96,18 @@ ${buildFraimInvocationBody('generic-tool-discovery')}
96
96
  `;
97
97
  }
98
98
  function buildCodexSkillContent() {
99
- return `# FRAIM
100
-
99
+ return `# FRAIM
100
+
101
101
  ${buildFraimInvocationBody('codex-tool-search')}`;
102
102
  }
103
103
  function buildGrokSkillContent() {
104
- return `# FRAIM
105
-
104
+ return `# FRAIM
105
+
106
106
  ${buildFraimInvocationBody('generic-tool-discovery')}`;
107
107
  }
108
108
  function buildWindsurfCommandContent() {
109
- return `# FRAIM
110
-
109
+ return `# FRAIM
110
+
111
111
  ${buildFraimInvocationBody('generic-tool-discovery')}`;
112
112
  }
113
113
  function buildKiroCommandContent() {
@@ -6,10 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getManagedNodeRoot = getManagedNodeRoot;
7
7
  exports.getPortableNodeBinPath = getPortableNodeBinPath;
8
8
  exports.getManagedAgentBinDirs = getManagedAgentBinDirs;
9
+ exports.stripManagedAgentBinDirsFromPath = stripManagedAgentBinDirsFromPath;
10
+ exports.appendBinDirsToPath = appendBinDirsToPath;
11
+ exports.getNpmGlobalBinDirsFromPrefix = getNpmGlobalBinDirsFromPrefix;
12
+ exports.resolveNpmGlobalBinDirs = resolveNpmGlobalBinDirs;
9
13
  exports.buildPathWithManagedAgentBins = buildPathWithManagedAgentBins;
10
- exports.prependManagedAgentBinDirsToProcessPath = prependManagedAgentBinDirsToProcessPath;
14
+ exports.appendManagedAgentBinDirsToProcessPath = appendManagedAgentBinDirsToProcessPath;
11
15
  const fs_1 = __importDefault(require("fs"));
12
16
  const path_1 = __importDefault(require("path"));
17
+ const child_process_1 = require("child_process");
13
18
  const script_sync_utils_1 = require("./script-sync-utils");
14
19
  function getManagedNodeRoot() {
15
20
  return path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'node');
@@ -37,12 +42,77 @@ function getManagedAgentBinDirs() {
37
42
  : [nodeRoot, path_1.default.join(nodeRoot, 'bin'), portableNodeBin];
38
43
  return [...new Set(candidates.filter(Boolean))];
39
44
  }
45
+ function normalizePathEntry(entry) {
46
+ const resolved = path_1.default.resolve(entry.trim());
47
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
48
+ }
49
+ function uniquePathEntries(entries) {
50
+ const seen = new Set();
51
+ const result = [];
52
+ for (const entry of entries.filter(Boolean)) {
53
+ const normalized = normalizePathEntry(entry);
54
+ if (seen.has(normalized))
55
+ continue;
56
+ seen.add(normalized);
57
+ result.push(entry);
58
+ }
59
+ return result;
60
+ }
61
+ function stripManagedAgentBinDirsFromPath(basePath) {
62
+ const managed = new Set(getManagedAgentBinDirs().map(normalizePathEntry));
63
+ return (basePath ?? process.env.PATH ?? '')
64
+ .split(path_1.default.delimiter)
65
+ .filter(Boolean)
66
+ .filter((entry) => !managed.has(normalizePathEntry(entry)))
67
+ .join(path_1.default.delimiter);
68
+ }
69
+ function appendBinDirsToPath(basePath, binDirs) {
70
+ const existing = (basePath ?? '').split(path_1.default.delimiter).filter(Boolean);
71
+ return uniquePathEntries([...existing, ...binDirs]).join(path_1.default.delimiter);
72
+ }
73
+ function getNpmGlobalBinDirsFromPrefix(prefix) {
74
+ const trimmed = prefix.trim();
75
+ if (!trimmed)
76
+ return [];
77
+ const resolved = path_1.default.resolve(trimmed);
78
+ const candidates = process.platform === 'win32'
79
+ ? [resolved, path_1.default.join(resolved, 'bin')]
80
+ : [path_1.default.join(resolved, 'bin')];
81
+ return uniquePathEntries(candidates);
82
+ }
83
+ function buildChildEnv(env) {
84
+ const childEnv = { ...process.env };
85
+ for (const [key, value] of Object.entries(env || {})) {
86
+ if (value === undefined)
87
+ delete childEnv[key];
88
+ else
89
+ childEnv[key] = value;
90
+ }
91
+ return childEnv;
92
+ }
93
+ function resolveNpmGlobalBinDirs(basePath, env) {
94
+ const childEnv = buildChildEnv({
95
+ ...env,
96
+ ...(basePath === undefined ? {} : { PATH: basePath }),
97
+ });
98
+ const executable = process.platform === 'win32' ? 'cmd.exe' : 'npm';
99
+ const args = process.platform === 'win32'
100
+ ? ['/d', '/s', '/c', 'npm', 'prefix', '-g']
101
+ : ['prefix', '-g'];
102
+ const result = (0, child_process_1.spawnSync)(executable, args, {
103
+ encoding: 'utf8',
104
+ timeout: 5000,
105
+ env: childEnv,
106
+ });
107
+ if (result.status !== 0 || result.error)
108
+ return [];
109
+ const output = (result.stdout || '').trim() || (result.stderr || '').trim();
110
+ const prefix = output.split(/\r?\n/)[0]?.trim() || '';
111
+ return getNpmGlobalBinDirsFromPrefix(prefix);
112
+ }
40
113
  function buildPathWithManagedAgentBins(basePath) {
41
- const current = basePath ?? process.env.PATH ?? '';
42
- const existing = current.split(path_1.default.delimiter).filter(Boolean);
43
- const merged = [...getManagedAgentBinDirs(), ...existing];
44
- return [...new Set(merged)].join(path_1.default.delimiter);
114
+ return appendBinDirsToPath(stripManagedAgentBinDirsFromPath(basePath), getManagedAgentBinDirs());
45
115
  }
46
- function prependManagedAgentBinDirsToProcessPath() {
116
+ function appendManagedAgentBinDirsToProcessPath() {
47
117
  process.env.PATH = buildPathWithManagedAgentBins(process.env.PATH);
48
118
  }
@@ -10,7 +10,7 @@ function getFraimVersion() {
10
10
  // Try reliable paths to find package.json relative to this file
11
11
  // locally: src/cli/utils/version-utils.ts -> package.json is ../../../package.json
12
12
  // dist: dist/src/cli/utils/version-utils.js -> package.json is ../../../../package.json
13
- // Traverse up from __dirname until we find fraim-framework's package.json.
13
+ // Traverse up from __dirname until we find a FRAIM package.json.
14
14
  // Fixed relative paths break when npx cache layouts differ across OS/npm versions.
15
15
  let dir = __dirname;
16
16
  for (let i = 0; i < 10; i++) {
@@ -18,7 +18,7 @@ function getFraimVersion() {
18
18
  if (fs_1.default.existsSync(pkgPath)) {
19
19
  try {
20
20
  const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
21
- if (pkg.name === 'fraim-framework' || pkg.name === 'fraim') {
21
+ if (pkg.name === 'fraim' || pkg.name === 'fraim-hub' || pkg.name === 'fraim-workspace') {
22
22
  return pkg.version;
23
23
  }
24
24
  }
@@ -111,8 +111,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
111
111
  ashley: {
112
112
  personaKey: 'ashley',
113
113
  bundleId: 'persona-ashley-core',
114
- catalogMetadata: buildCatalogMetadata('ashley', ['executive-assistant', 'weekly-operating-review', 'send-thank-you-notes']),
115
- protectedJobs: ['executive-assistant', 'weekly-operating-review', 'send-thank-you-notes', 'send-newsletter', 'analyze-transcript', 'send-stakeholder-update'],
114
+ catalogMetadata: buildCatalogMetadata('ashley', ['chief-of-staff-briefing', 'executive-assistant', 'weekly-operating-review']),
115
+ protectedJobs: ['chief-of-staff-briefing', 'calendar-triage', 'meeting-preparation', 'executive-assistant', 'weekly-operating-review', 'send-thank-you-notes', 'send-newsletter', 'analyze-transcript', 'send-stakeholder-update'],
116
116
  protectedAliases: ['executive-assistant', 'operations-assistant'],
117
117
  defaultHireMode: 'job',
118
118
  lockCopy: 'Hire AshLey to unlock executive-assistant work for this request.'
@@ -147,7 +147,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
147
147
  careena: {
148
148
  personaKey: 'careena',
149
149
  bundleId: 'persona-careena-core',
150
- catalogMetadata: buildCatalogMetadata('careena', ['job-application-execution', 'pitch-and-interview-prep', 'career-networking']),
150
+ catalogMetadata: buildCatalogMetadata('careena', ['career-spec', 'resume-creation', 'job-application-execution']),
151
151
  protectedJobs: ['career-spec', 'job-application-execution', 'resume-creation', 'public-profile-updates', 'career-trajectory-planning', 'pitch-and-interview-prep', 'career-networking', 'offer-negotiation', 'resilience-planning'],
152
152
  protectedAliases: ['career', 'career-coach', 'job-search'],
153
153
  defaultHireMode: 'job',
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadJobPhases = loadJobPhases;
7
+ exports.loadAllJobPhaseIds = loadAllJobPhaseIds;
8
+ exports.labelForPhaseId = labelForPhaseId;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const project_fraim_paths_1 = require("./utils/project-fraim-paths");
12
+ const EMPLOYEE_JOB_LAYERS = [
13
+ { segments: ['ai-employee', 'jobs'] },
14
+ { segments: ['personalized-employee', 'jobs'] },
15
+ ];
16
+ const MANAGER_JOB_LAYERS = [
17
+ { segments: ['ai-manager', 'jobs'] },
18
+ { segments: ['personalized-employee', 'manager-jobs'] },
19
+ ];
20
+ const KNOWN_PHASE_PREFIXES = [
21
+ 'implement-',
22
+ 'address-',
23
+ 'spec-',
24
+ 'design-',
25
+ 'context-',
26
+ ];
27
+ function readSubdirectoryNames(dirPath) {
28
+ if (!fs_1.default.existsSync(dirPath))
29
+ return [];
30
+ return fs_1.default
31
+ .readdirSync(dirPath, { withFileTypes: true })
32
+ .filter((entry) => entry.isDirectory())
33
+ .map((entry) => entry.name)
34
+ .sort((a, b) => a.localeCompare(b));
35
+ }
36
+ function friendlyPhaseLabel(phaseId, override) {
37
+ if (override)
38
+ return override;
39
+ let body = phaseId;
40
+ for (const prefix of KNOWN_PHASE_PREFIXES) {
41
+ if (body.startsWith(prefix) && body.length > prefix.length) {
42
+ body = body.slice(prefix.length);
43
+ break;
44
+ }
45
+ }
46
+ const spaced = body.replace(/-/g, ' ').trim();
47
+ if (spaced.length === 0)
48
+ return phaseId;
49
+ return spaced[0].toUpperCase() + spaced.slice(1).toLowerCase();
50
+ }
51
+ function readJobFrontmatter(filePath) {
52
+ const raw = fs_1.default.readFileSync(filePath, 'utf8');
53
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
54
+ if (!match)
55
+ return null;
56
+ const block = match[1].trim();
57
+ if (!block.startsWith('{'))
58
+ return null;
59
+ try {
60
+ return JSON.parse(block);
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ function discoverPhaseLayers(projectPath, layers) {
67
+ const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectPath);
68
+ const result = [];
69
+ for (const layer of layers) {
70
+ const layerRoot = path_1.default.join(fraimDir, ...layer.segments);
71
+ for (const categoryName of readSubdirectoryNames(layerRoot)) {
72
+ result.push(path_1.default.join(layerRoot, categoryName));
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+ function findJobStubPath(projectPath, jobId) {
78
+ const layers = discoverPhaseLayers(projectPath, [
79
+ ...EMPLOYEE_JOB_LAYERS,
80
+ ...MANAGER_JOB_LAYERS,
81
+ ]);
82
+ let resolved = null;
83
+ for (const categoryDir of layers) {
84
+ const candidate = path_1.default.join(categoryDir, `${jobId}.md`);
85
+ if (fs_1.default.existsSync(candidate)) {
86
+ resolved = candidate;
87
+ }
88
+ }
89
+ if (resolved)
90
+ return resolved;
91
+ const builtInStubsDir = path_1.default.resolve(__dirname, '..', 'registry', 'stubs', 'jobs');
92
+ for (const hostType of ['ai-employee', 'ai-manager']) {
93
+ const hostDir = path_1.default.join(builtInStubsDir, hostType);
94
+ for (const category of readSubdirectoryNames(hostDir)) {
95
+ const candidate = path_1.default.join(hostDir, category, `${jobId}.md`);
96
+ if (fs_1.default.existsSync(candidate))
97
+ return candidate;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ function nextPhase(edge, discriminant) {
103
+ if (edge == null)
104
+ return null;
105
+ if (typeof edge === 'string')
106
+ return edge;
107
+ if (typeof edge === 'object') {
108
+ if (typeof edge[discriminant] === 'string')
109
+ return edge[discriminant];
110
+ if (typeof edge.default === 'string')
111
+ return edge.default;
112
+ }
113
+ return null;
114
+ }
115
+ function loadJobPhasesFromSteps(filePath) {
116
+ const raw = fs_1.default.readFileSync(filePath, 'utf8');
117
+ const stepsMatch = raw.match(/## Steps\r?\n([\s\S]*?)(?:\r?\n## |\r?\n---|$)/);
118
+ if (!stepsMatch)
119
+ return [];
120
+ const phases = [];
121
+ for (const line of stepsMatch[1].split(/\r?\n/)) {
122
+ const m = line.match(/`([a-z][a-z0-9-]*)`/);
123
+ if (m)
124
+ phases.push({ id: m[1], label: friendlyPhaseLabel(m[1]) });
125
+ }
126
+ return phases;
127
+ }
128
+ function loadJobPhases(jobId, projectPath, discriminant = 'feature') {
129
+ const stubPath = findJobStubPath(projectPath, jobId);
130
+ if (!stubPath)
131
+ return [];
132
+ const fm = readJobFrontmatter(stubPath);
133
+ if (!fm || !fm.initialPhase || !fm.phases)
134
+ return loadJobPhasesFromSteps(stubPath);
135
+ const visited = new Set();
136
+ const ordered = [];
137
+ let cursor = fm.initialPhase;
138
+ while (cursor && !visited.has(cursor)) {
139
+ visited.add(cursor);
140
+ ordered.push(cursor);
141
+ const phaseDef = fm.phases[cursor];
142
+ if (!phaseDef)
143
+ break;
144
+ cursor = nextPhase(phaseDef.onSuccess, discriminant);
145
+ }
146
+ const labels = fm.phaseLabels || {};
147
+ return ordered.map((id) => ({ id, label: friendlyPhaseLabel(id, labels[id]) }));
148
+ }
149
+ function loadAllJobPhaseIds(jobId, projectPath) {
150
+ const stubPath = findJobStubPath(projectPath, jobId);
151
+ if (!stubPath)
152
+ return new Set();
153
+ const fm = readJobFrontmatter(stubPath);
154
+ if (!fm || !fm.phases) {
155
+ const phases = loadJobPhasesFromSteps(stubPath);
156
+ return new Set(phases.map((p) => p.id));
157
+ }
158
+ return new Set(Object.keys(fm.phases));
159
+ }
160
+ function labelForPhaseId(phaseId, jobId, projectPath) {
161
+ const stubPath = findJobStubPath(projectPath, jobId);
162
+ if (stubPath) {
163
+ const fm = readJobFrontmatter(stubPath);
164
+ if (fm?.phaseLabels?.[phaseId])
165
+ return fm.phaseLabels[phaseId];
166
+ }
167
+ return friendlyPhaseLabel(phaseId);
168
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findAvailablePort = findAvailablePort;
7
+ exports.findAvailablePortExcluding = findAvailablePortExcluding;
8
+ const net_1 = __importDefault(require("net"));
9
+ async function findAvailablePort(preferredPort) {
10
+ return findAvailablePortExcluding(preferredPort, new Set());
11
+ }
12
+ async function findAvailablePortExcluding(preferredPort, excludedPorts) {
13
+ let port = preferredPort;
14
+ while (port < preferredPort + 20) {
15
+ if (excludedPorts.has(port)) {
16
+ port += 1;
17
+ continue;
18
+ }
19
+ const available = await new Promise((resolve) => {
20
+ const server = net_1.default.createServer();
21
+ server.once('error', () => resolve(false));
22
+ server.once('listening', () => {
23
+ server.close(() => resolve(true));
24
+ });
25
+ server.listen(port, '127.0.0.1');
26
+ });
27
+ if (available)
28
+ return port;
29
+ port += 1;
30
+ }
31
+ throw new Error(`No free loopback port found near ${preferredPort}.`);
32
+ }
@@ -46,8 +46,12 @@ function extractLeadParagraph(content) {
46
46
  /**
47
47
  * Coaching stubs are discoverability artifacts and should be resolved with get_fraim_file.
48
48
  */
49
- function generateJobStub(jobName, _jobPath, intent, outcome, steps) {
49
+ function generateJobStub(jobName, _jobPath, intent, outcome, steps, displayName) {
50
+ const metadata = displayName
51
+ ? `---\n${JSON.stringify({ name: jobName, displayName }, null, 2)}\n---\n`
52
+ : '';
50
53
  return `${STUB_MARKER}
54
+ ${metadata}
51
55
  # FRAIM Job: ${jobName}
52
56
 
53
57
  ## Intent
@@ -116,6 +120,23 @@ ${intent}
116
120
  /**
117
121
  * Parses a job file from the registry to extract its intent, outcome, and steps for the stub.
118
122
  */
123
+ function parseDisplayName(content) {
124
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
125
+ if (!frontmatterMatch?.[1])
126
+ return undefined;
127
+ const block = frontmatterMatch[1].trim();
128
+ if (!block.startsWith('{'))
129
+ return undefined;
130
+ try {
131
+ const parsed = JSON.parse(block);
132
+ return typeof parsed.displayName === 'string' && parsed.displayName.trim().length > 0
133
+ ? parsed.displayName.trim()
134
+ : undefined;
135
+ }
136
+ catch {
137
+ return undefined;
138
+ }
139
+ }
119
140
  function parseRegistryJob(content) {
120
141
  const intentMatch = content.match(/##\s*intent\s+([\s\S]*?)(?=\n##|$)/i);
121
142
  const outcomeMatch = content.match(/##\s*outcome\s+([\s\S]*?)(?=\n##|$)/i);
@@ -123,7 +144,8 @@ function parseRegistryJob(content) {
123
144
  const intent = intentMatch ? intentMatch[1].trim() : 'No intent defined.';
124
145
  const outcome = outcomeMatch ? outcomeMatch[1].trim() : 'No outcome defined.';
125
146
  const steps = stepsMatch ? stepsMatch[1].trim() : 'Use get_fraim_job to retrieve the full phase-by-phase execution steps.';
126
- return { intent, outcome, steps };
147
+ const displayName = parseDisplayName(content);
148
+ return { intent, outcome, steps, displayName };
127
149
  }
128
150
  /**
129
151
  * Parses a skill file from the registry to extract intent and expected outcome for stubs.