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.
- package/README.md +3 -3
- package/bin/fraim.js +8 -18
- package/dist/src/cli/commands/first-run.js +2 -37
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +1 -1
- package/dist/src/cli/fraim.js +0 -2
- package/dist/src/cli/setup/ide-invocation-surfaces.js +6 -6
- package/dist/src/cli/utils/managed-agent-paths.js +76 -6
- package/dist/src/cli/utils/version-utils.js +2 -2
- package/dist/src/config/persona-capability-bundles.js +3 -3
- package/dist/src/core/job-phases.js +168 -0
- package/dist/src/core/utils/ports.js +32 -0
- package/dist/src/core/utils/stub-generator.js +24 -2
- package/dist/src/first-run/session-service.js +106 -24
- package/dist/src/local-mcp-server/learning-context-builder.js +1 -1
- package/dist/src/local-mcp-server/stdio-server.js +41 -9
- package/dist/src/routes/analytics.js +2 -2
- package/dist/src/services/registry-service.js +2 -2
- package/index.js +1 -1
- package/package.json +61 -181
- package/dist/src/ai-hub/catalog.js +0 -406
- package/dist/src/ai-hub/cert-store.js +0 -70
- package/dist/src/ai-hub/conversation-store.js +0 -359
- package/dist/src/ai-hub/desktop-main.js +0 -449
- package/dist/src/ai-hub/hosts.js +0 -1664
- package/dist/src/ai-hub/hub-latest-version.js +0 -52
- package/dist/src/ai-hub/hub-launch-decision.js +0 -57
- package/dist/src/ai-hub/hub-runtime-file.js +0 -43
- package/dist/src/ai-hub/managed-browser.js +0 -269
- package/dist/src/ai-hub/manager-turns.js +0 -64
- package/dist/src/ai-hub/office-sideload.js +0 -156
- package/dist/src/ai-hub/openclaw-bridge.js +0 -250
- package/dist/src/ai-hub/preferences.js +0 -201
- package/dist/src/ai-hub/remote-hub-gateway.js +0 -88
- package/dist/src/ai-hub/server.js +0 -3827
- package/dist/src/ai-hub/types.js +0 -2
- package/dist/src/ai-hub/word-sideload.js +0 -95
- package/dist/src/cli/commands/hub.js +0 -238
- package/dist/src/cli/commands/test-mcp.js +0 -171
- package/dist/src/cli/setup/first-run.js +0 -242
- package/dist/src/core/config-writer.js +0 -75
- package/dist/src/core/utils/job-aliases.js +0 -47
- package/dist/src/core/utils/workflow-parser.js +0 -174
- package/dist/src/services/email-service-clean.js +0 -782
- package/extensions/office-word/favicon.ico +0 -0
- package/extensions/office-word/icon-64.png +0 -0
- package/extensions/office-word/manifest.xml +0 -33
- package/extensions/office-word/taskpane.html +0 -242
- package/public/ai-hub/index.html +0 -894
- package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
- package/public/ai-hub/powerpoint-taskpane/index.html +0 -236
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +0 -30
- package/public/ai-hub/review.css +0 -381
- package/public/ai-hub/script.js +0 -11015
- package/public/ai-hub/styles.css +0 -4873
- package/public/portfolio/ashley.html +0 -523
- package/public/portfolio/auditya.html +0 -83
- package/public/portfolio/banke.html +0 -83
- package/public/portfolio/beza.html +0 -659
- package/public/portfolio/careena.html +0 -632
- package/public/portfolio/casey.html +0 -568
- package/public/portfolio/celia.html +0 -490
- package/public/portfolio/deidre.html +0 -642
- package/public/portfolio/gautam.html +0 -597
- package/public/portfolio/hari.html +0 -469
- package/public/portfolio/huxley.html +0 -1354
- package/public/portfolio/index.html +0 -741
- package/public/portfolio/maestro.html +0 -518
- package/public/portfolio/mandy.html +0 -590
- package/public/portfolio/mona.html +0 -597
- package/public/portfolio/pam.html +0 -887
- package/public/portfolio/procella.html +0 -107
- package/public/portfolio/qasm.html +0 -569
- package/public/portfolio/ricardo.html +0 -489
- package/public/portfolio/sade.html +0 -560
- package/public/portfolio/sam.html +0 -654
- package/public/portfolio/sechar.html +0 -580
- package/public/portfolio/sreya.html +0 -599
- package/public/portfolio/swen.html +0 -601
- /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
|
|
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
|
|
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
|
-
[](https://www.npmjs.com/package/fraim)
|
|
464
464
|
[](https://github.com/mathursrus/FRAIM/stargazers)
|
|
465
465
|
[](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
|
-
|
|
11
|
-
require('../dist/src/cli/fraim.js');
|
|
4
|
+
require('../dist/src/cli/fraim.js');
|
|
12
5
|
} catch (error) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
127
|
+
&& args.some((arg) => arg === 'fraim@latest');
|
|
128
128
|
if (usesDirectLatest) {
|
|
129
129
|
const isKnownBrokenWindowsNpm = platform === 'win32' && npmMajorVersion !== null && npmMajorVersion >= 11;
|
|
130
130
|
return {
|
package/dist/src/cli/fraim.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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-
|
|
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'
|
|
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', ['
|
|
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
|
-
|
|
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.
|