fraim-framework 2.0.177 → 2.0.179
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/dist/src/ai-hub/server.js +50 -1
- package/dist/src/cli/commands/add-provider.js +74 -61
- package/dist/src/cli/commands/add-surface.js +128 -0
- package/dist/src/cli/commands/login.js +5 -69
- package/dist/src/cli/commands/setup.js +27 -347
- package/dist/src/cli/distribution/marketplace-bundles.js +576 -0
- package/dist/src/cli/fraim.js +2 -0
- package/dist/src/cli/mcp/ide-formats.js +5 -3
- package/dist/src/cli/mcp/mcp-server-registry.js +10 -3
- package/dist/src/cli/providers/local-provider-registry.js +2 -3
- package/dist/src/cli/setup/auto-mcp-setup.js +9 -32
- package/dist/src/cli/setup/ide-detector.js +34 -14
- package/dist/src/config/persona-capability-bundles.js +17 -13
- package/dist/src/first-run/session-service.js +2 -2
- package/dist/src/local-mcp-server/stdio-server.js +28 -4
- package/dist/src/local-mcp-server/usage-collector.js +24 -0
- package/package.json +3 -2
- package/public/ai-hub/index.html +14 -2
- package/public/ai-hub/script.js +340 -66
- package/public/ai-hub/styles.css +83 -0
|
@@ -44,17 +44,7 @@ const prompts_1 = __importDefault(require("prompts"));
|
|
|
44
44
|
const ide_detector_1 = require("./ide-detector");
|
|
45
45
|
const mcp_config_generator_1 = require("./mcp-config-generator");
|
|
46
46
|
const codex_local_config_1 = require("./codex-local-config");
|
|
47
|
-
const
|
|
48
|
-
const normalizePlatformTokens = (tokenInput) => {
|
|
49
|
-
if (!tokenInput)
|
|
50
|
-
return {};
|
|
51
|
-
if (typeof tokenInput === 'string') {
|
|
52
|
-
return tokenInput ? { github: tokenInput } : {};
|
|
53
|
-
}
|
|
54
|
-
return tokenInput;
|
|
55
|
-
};
|
|
56
|
-
const promptForIDESelection = async (detectedIDEs, tokenInput) => {
|
|
57
|
-
const tokens = normalizePlatformTokens(tokenInput);
|
|
47
|
+
const promptForIDESelection = async (detectedIDEs) => {
|
|
58
48
|
if (process.env.FRAIM_NON_INTERACTIVE) {
|
|
59
49
|
console.log(chalk_1.default.yellow(`\nℹ️ Non-interactive mode: configuring all detected IDEs (${detectedIDEs.length})`));
|
|
60
50
|
return detectedIDEs;
|
|
@@ -70,17 +60,8 @@ const promptForIDESelection = async (detectedIDEs, tokenInput) => {
|
|
|
70
60
|
console.log(chalk_1.default.blue('FRAIM will add these MCP servers to selected IDEs:'));
|
|
71
61
|
console.log(chalk_1.default.gray(' • fraim (required for FRAIM jobs)'));
|
|
72
62
|
console.log(chalk_1.default.gray(' • git (version control integration)'));
|
|
73
|
-
// Show configured provider MCP servers dynamically
|
|
74
|
-
const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
|
|
75
|
-
for (const providerId of allProviderIds) {
|
|
76
|
-
if (tokens[providerId]) {
|
|
77
|
-
const provider = await (0, provider_registry_1.getProvider)(providerId);
|
|
78
|
-
if (provider) {
|
|
79
|
-
console.log(chalk_1.default.gray(` - ${providerId} (${provider.displayName} API access)`));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
63
|
console.log(chalk_1.default.gray(' • playwright (browser automation)'));
|
|
64
|
+
console.log(chalk_1.default.blue('\n💡 Run "fraim add-provider <provider>" after setup to connect GitHub, GitLab, Jira, or other tools.'));
|
|
84
65
|
console.log(chalk_1.default.yellow('\n💡 Existing MCP servers will be preserved - only missing servers will be added.'));
|
|
85
66
|
const response = await (0, prompts_1.default)({
|
|
86
67
|
type: 'text',
|
|
@@ -153,8 +134,7 @@ const validateSetupResults = async (configuredIDEs) => {
|
|
|
153
134
|
}
|
|
154
135
|
};
|
|
155
136
|
exports.validateSetupResults = validateSetupResults;
|
|
156
|
-
const configureIDEMCP = async (ide, fraimKey
|
|
157
|
-
const tokens = normalizePlatformTokens(tokenInput);
|
|
137
|
+
const configureIDEMCP = async (ide, fraimKey) => {
|
|
158
138
|
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
159
139
|
console.log(chalk_1.default.blue(`🔧 Configuring ${ide.name}...`));
|
|
160
140
|
// Create backup
|
|
@@ -185,11 +165,9 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
|
|
|
185
165
|
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
186
166
|
console.log(chalk_1.default.gray(` 📋 Found existing TOML config`));
|
|
187
167
|
}
|
|
188
|
-
const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
168
|
+
const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, {});
|
|
189
169
|
const { getAllMCPServerIds } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
|
|
190
|
-
const
|
|
191
|
-
const providerServerIds = Object.keys(tokens).filter(id => tokens[id]);
|
|
192
|
-
const serversToAdd = [...baseServerIds, ...providerServerIds];
|
|
170
|
+
const serversToAdd = getAllMCPServerIds();
|
|
193
171
|
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
|
|
194
172
|
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
195
173
|
mergeResult.addedServers.forEach(server => {
|
|
@@ -204,7 +182,7 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
|
|
|
204
182
|
}
|
|
205
183
|
else {
|
|
206
184
|
// For JSON configs - intelligent merging
|
|
207
|
-
const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
185
|
+
const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, {});
|
|
208
186
|
const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
|
|
209
187
|
// Merge MCP servers intelligently
|
|
210
188
|
const mergedMCPServers = { ...existingMCPServers };
|
|
@@ -251,8 +229,7 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
|
|
|
251
229
|
console.log(chalk_1.default.green(` ✅ ${status} local Codex config: ${localResult.path}`));
|
|
252
230
|
}
|
|
253
231
|
};
|
|
254
|
-
const autoConfigureMCP = async (fraimKey,
|
|
255
|
-
const tokens = normalizePlatformTokens(tokenInput);
|
|
232
|
+
const autoConfigureMCP = async (fraimKey, selectedIDEs) => {
|
|
256
233
|
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
257
234
|
if (detectedIDEs.length === 0 && (!selectedIDEs || selectedIDEs.length === 0)) {
|
|
258
235
|
console.log(chalk_1.default.yellow('⚠️ No supported IDEs detected.'));
|
|
@@ -294,7 +271,7 @@ const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConf
|
|
|
294
271
|
}
|
|
295
272
|
else {
|
|
296
273
|
// Interactive selection
|
|
297
|
-
idesToConfigure = await (0, exports.promptForIDESelection)(detectedIDEs
|
|
274
|
+
idesToConfigure = await (0, exports.promptForIDESelection)(detectedIDEs);
|
|
298
275
|
}
|
|
299
276
|
if (idesToConfigure.length === 0) {
|
|
300
277
|
console.log(chalk_1.default.yellow('⚠️ No IDEs selected for configuration.'));
|
|
@@ -308,7 +285,7 @@ const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConf
|
|
|
308
285
|
};
|
|
309
286
|
for (const ide of idesToConfigure) {
|
|
310
287
|
try {
|
|
311
|
-
await configureIDEMCP(ide, fraimKey
|
|
288
|
+
await configureIDEMCP(ide, fraimKey);
|
|
312
289
|
results.successful.push(ide.name);
|
|
313
290
|
}
|
|
314
291
|
catch (error) {
|
|
@@ -24,15 +24,25 @@ const checkMultiplePaths = (paths) => {
|
|
|
24
24
|
return paths.some(p => fs_1.default.existsSync(expandPath(p)));
|
|
25
25
|
};
|
|
26
26
|
// Issue #646 (Bug 2): a config directory left behind after uninstall (e.g. ~/.cursor,
|
|
27
|
-
// ~/Library/Application Support/Cursor) made config-surface detection report
|
|
28
|
-
// as "Installed" when it was not.
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
const macAppBundlePaths = (appName) => [
|
|
27
|
+
// ~/Library/Application Support/Cursor) made config-surface detection report Cursor
|
|
28
|
+
// as "Installed" when it was not. App evidence is the reliable signal for Cursor;
|
|
29
|
+
// other GUI surfaces keep their existing config-surface checks for compatibility
|
|
30
|
+
// with the #640 first-run detection contract.
|
|
31
|
+
const appBundlePaths = (appName) => [
|
|
33
32
|
`/Applications/${appName}.app`,
|
|
34
33
|
`~/Applications/${appName}.app`,
|
|
35
34
|
];
|
|
35
|
+
const windowsUserAppPaths = (appName) => {
|
|
36
|
+
switch (appName) {
|
|
37
|
+
case 'Cursor':
|
|
38
|
+
return [
|
|
39
|
+
'~/AppData/Local/Programs/Cursor/Cursor.exe',
|
|
40
|
+
'~/AppData/Local/Cursor/Cursor.exe',
|
|
41
|
+
];
|
|
42
|
+
default:
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
};
|
|
36
46
|
// Issue #646 follow-up: a user may run a GUI app from a non-standard location
|
|
37
47
|
// (e.g. straight from a mounted DMG: /Volumes/.../Cursor.app) that the
|
|
38
48
|
// /Applications checks miss. A running process of the app is definitive proof it
|
|
@@ -48,10 +58,19 @@ const isMacAppRunning = (appName) => {
|
|
|
48
58
|
const result = (0, child_process_1.spawnSync)('pgrep', ['-f', `${appName}.app/Contents`], { encoding: 'utf8', timeout: 1500 });
|
|
49
59
|
return result.status === 0 && Boolean((result.stdout || '').trim());
|
|
50
60
|
};
|
|
51
|
-
const
|
|
61
|
+
const hasAppEvidence = (appName) => {
|
|
62
|
+
const evidencePaths = process.platform === 'win32'
|
|
63
|
+
? [...appBundlePaths(appName), ...windowsUserAppPaths(appName)]
|
|
64
|
+
: appBundlePaths(appName);
|
|
65
|
+
return checkMultiplePaths(evidencePaths) || isMacAppRunning(appName);
|
|
66
|
+
};
|
|
67
|
+
const guiAppDetect = (configSurfaceCheck, appName, options = {}) => {
|
|
52
68
|
return () => {
|
|
69
|
+
if (options.requireAppEvidence) {
|
|
70
|
+
return hasAppEvidence(appName);
|
|
71
|
+
}
|
|
53
72
|
if (process.platform === 'darwin') {
|
|
54
|
-
return
|
|
73
|
+
return hasAppEvidence(appName);
|
|
55
74
|
}
|
|
56
75
|
return configSurfaceCheck();
|
|
57
76
|
};
|
|
@@ -81,12 +100,12 @@ const detectVSCode = () => {
|
|
|
81
100
|
];
|
|
82
101
|
return checkMultiplePaths(paths);
|
|
83
102
|
};
|
|
84
|
-
const
|
|
103
|
+
const detectCursorMcpConfig = () => {
|
|
85
104
|
const paths = [
|
|
86
|
-
'~/.cursor',
|
|
87
|
-
'~/Library/Application Support/Cursor',
|
|
88
|
-
'~/AppData/Roaming/Cursor',
|
|
89
|
-
'~/.config/Cursor'
|
|
105
|
+
'~/.cursor/mcp.json',
|
|
106
|
+
'~/Library/Application Support/Cursor/mcp.json',
|
|
107
|
+
'~/AppData/Roaming/Cursor/mcp.json',
|
|
108
|
+
'~/.config/Cursor/mcp.json'
|
|
90
109
|
];
|
|
91
110
|
return checkMultiplePaths(paths);
|
|
92
111
|
};
|
|
@@ -104,6 +123,7 @@ const detectGeminiCli = () => {
|
|
|
104
123
|
};
|
|
105
124
|
const detectGeminiSurface = () => {
|
|
106
125
|
const paths = [
|
|
126
|
+
'~/.gemini',
|
|
107
127
|
'~/.gemini/settings.json',
|
|
108
128
|
'~/AppData/Roaming/gemini/settings.json',
|
|
109
129
|
'~/.config/gemini/settings.json'
|
|
@@ -196,7 +216,7 @@ exports.IDE_CONFIGS = [
|
|
|
196
216
|
configType: 'kiro',
|
|
197
217
|
adapterConfigType: 'cursor',
|
|
198
218
|
invocationProfile: 'cursor-mention',
|
|
199
|
-
detectMethod:
|
|
219
|
+
detectMethod: () => detectCursorMcpConfig() || hasAppEvidence('Cursor'),
|
|
200
220
|
alternativePaths: [
|
|
201
221
|
'~/Library/Application Support/Cursor/mcp.json',
|
|
202
222
|
'~/AppData/Roaming/Cursor/mcp.json',
|
|
@@ -40,7 +40,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
40
40
|
personaKey: 'beza',
|
|
41
41
|
bundleId: 'persona-beza-core',
|
|
42
42
|
catalogMetadata: buildCatalogMetadata('beza', ['competitive-analysis', 'review-business-strategy', 'business-plan-creation']),
|
|
43
|
-
protectedJobs: ['competitive-analysis', 'review-business-strategy', 'business-plan-creation', 'problem-statement-crystallization', 'business-idea-validation-and-scoping', 'founder-market-fit-analysis', '
|
|
43
|
+
protectedJobs: ['competitive-analysis', 'review-business-strategy', 'business-plan-creation', 'problem-statement-crystallization', 'business-idea-validation-and-scoping', 'founder-market-fit-analysis', 'community-funding-preparation', 'fundraising-prospect-discovery', 'investor-pitch-preparation', 'review-funding-preparation', 'advisory-board-development', 'advisor-interview', 'advisory-board-selection', 'blue-sky-brainstorming', 'domain-registration-research'],
|
|
44
44
|
protectedAliases: ['business-strategy', 'company-strategy'],
|
|
45
45
|
defaultHireMode: 'job',
|
|
46
46
|
lockCopy: 'Hire BeZa to unlock business strategy work for this request.'
|
|
@@ -49,7 +49,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
49
49
|
personaKey: 'pam',
|
|
50
50
|
bundleId: 'persona-pam-core',
|
|
51
51
|
catalogMetadata: buildCatalogMetadata('pam', ['feature-specification', 'technical-design', 'issue-preparation']),
|
|
52
|
-
protectedJobs: ['feature-specification', 'technical-design', 'issue-preparation', 'implementation-design-review', 'issue-retrospective', 'work-completion', 'scrum-sprint-planning', 'mvp-validation-plan'],
|
|
52
|
+
protectedJobs: ['feature-specification', 'technical-design', 'issue-preparation', 'implementation-design-review', 'issue-retrospective', 'work-completion', 'scrum-sprint-planning', 'mvp-validation-plan', 'sprint-planning', 'customer-prospect-discovery', 'interview-preparation', 'participant-recruitment', 'process-interview-notes', 'review-customer-development', 'triage-customer-needs'],
|
|
53
53
|
protectedAliases: ['product-management', 'product-spec'],
|
|
54
54
|
defaultHireMode: 'job',
|
|
55
55
|
lockCopy: 'Hire PaM to unlock product-management work for this request.'
|
|
@@ -58,7 +58,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
58
58
|
personaKey: 'swen',
|
|
59
59
|
bundleId: 'persona-swen-core',
|
|
60
60
|
catalogMetadata: buildCatalogMetadata('swen', ['feature-implementation', 'code-refactoring', 'pr-iteration']),
|
|
61
|
-
protectedJobs: ['feature-implementation', 'code-refactoring', 'pr-iteration', 'mobile-app-development', 'mcp-server-creation', 'cloud-application-deployment', 'cloud-cost-optimization', 'cloud-performance-diagnosis', 'route-llm-spend-to-cloud-credits', 'set-up-cloud-cost-alerts', 'gitlabs-to-github', 'system-migration', 'cross-cloud-migration', 'data-pipeline-design', 'data-quality-monitoring', 'data-platform-architecture', 'write-dev-docs', 'database-schema-design'],
|
|
61
|
+
protectedJobs: ['feature-implementation', 'code-refactoring', 'pr-iteration', 'mobile-app-development', 'mcp-server-creation', 'cloud-application-deployment', 'cloud-cost-optimization', 'cloud-performance-diagnosis', 'route-llm-spend-to-cloud-credits', 'set-up-cloud-cost-alerts', 'gitlabs-to-github', 'system-migration', 'cross-cloud-migration', 'data-pipeline-design', 'data-quality-monitoring', 'data-platform-architecture', 'write-dev-docs', 'database-schema-design', 'create-architecture', 'project-scaffolding', 'codebase-analysis-and-ideation', 'github-org-setup', 'google-workspace-setup', 'mobile-app-rejection-response', 'mobile-app-submission', 'publish-mcp-app', 'application-replication-workflow'],
|
|
62
62
|
protectedAliases: ['software-engineering', 'implementation'],
|
|
63
63
|
defaultHireMode: 'job',
|
|
64
64
|
lockCopy: 'Hire SWEn to unlock software-engineering delivery for this request.'
|
|
@@ -76,7 +76,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
76
76
|
personaKey: 'huxley',
|
|
77
77
|
bundleId: 'persona-huxley-core',
|
|
78
78
|
catalogMetadata: buildCatalogMetadata('huxley', ['design-system-creation', 'user-facing-prototyping', 'ux-research-synthesis']),
|
|
79
|
-
protectedJobs: ['design-system-creation', 'user-facing-prototyping', 'website-creation', '
|
|
79
|
+
protectedJobs: ['design-system-creation', 'user-facing-prototyping', 'website-creation', 'brand-creation', 'branding-quality-audit', 'ux-research-synthesis', 'user-journey-mapping'],
|
|
80
80
|
protectedAliases: ['ux-design', 'brand-design'],
|
|
81
81
|
defaultHireMode: 'job',
|
|
82
82
|
lockCopy: 'Hire hUXley to unlock design-system, UX research, and brand design work for this request.'
|
|
@@ -85,7 +85,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
85
85
|
personaKey: 'gautam',
|
|
86
86
|
bundleId: 'persona-gautam-core',
|
|
87
87
|
catalogMetadata: buildCatalogMetadata('gautam', ['analyze-revenue-system', 'build-gtm-motion', 'ppc-campaign-management']),
|
|
88
|
-
protectedJobs: ['pricing-strategy-definition', 'marketing-strategy-definition', 'product-launch-management', '
|
|
88
|
+
protectedJobs: ['pricing-strategy-definition', 'marketing-strategy-definition', 'product-launch-management', 'evangelist-content-development', 'funnel-analysis', 'growth-loop-design', 'analyze-revenue-system', 'design-gtm-system', 'build-gtm-motion', 'build-gtm-stack', 'plan-gtm-team', 'marketing-content-creation', 'ppc-campaign-management', 'paid-social-strategy', 'ad-performance-analysis', 'tracking-and-attribution-setup', 'developer-advocacy', 'seo-strategy', 'marketing-analytics-review', 'linkedin-company-page-setup', 'x-account-setup', 'social-engagement-campaign', 'thought-leadership-engagement', 'promo-video-creation', 'linkedin-carousel-from-deck'],
|
|
89
89
|
protectedAliases: ['gtm', 'marketing'],
|
|
90
90
|
defaultHireMode: 'job',
|
|
91
91
|
lockCopy: 'Hire GauTaM to unlock go-to-market and paid media work for this request.'
|
|
@@ -94,7 +94,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
94
94
|
personaKey: 'cela',
|
|
95
95
|
bundleId: 'persona-cela-core',
|
|
96
96
|
catalogMetadata: buildCatalogMetadata('cela', ['contract-review-analysis', 'nda-creation', 'saas-contract-package-creation']),
|
|
97
|
-
protectedJobs: ['contract-review-analysis', 'nda-creation', 'saas-contract-package-creation', 'trademark-registration-management', 'provisional-patent-application-creation', 'opensign-cloud-esign-dispatch', 'w9-creation', '
|
|
97
|
+
protectedJobs: ['contract-review-analysis', 'nda-creation', 'saas-contract-package-creation', 'trademark-registration-management', 'provisional-patent-application-creation', 'opensign-cloud-esign-dispatch', 'w9-creation', 'entity-type-selection', 'state-incorporation-filing', 'business-tax-registration', 'ein-application', 'cap-table-construction', 'founder-and-equity-agreements', 'ip-assignment-agreement-creation', 'employment-structure-decision'],
|
|
98
98
|
protectedAliases: ['legal', 'counsel'],
|
|
99
99
|
defaultHireMode: 'job',
|
|
100
100
|
lockCopy: 'Hire CELiA to unlock legal workflow support for this request.'
|
|
@@ -103,7 +103,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
103
103
|
personaKey: 'sekhar',
|
|
104
104
|
bundleId: 'persona-sekhar-core',
|
|
105
105
|
catalogMetadata: buildCatalogMetadata('sekhar', ['security-review', 'ai-native-security-setup', 'security-findings-command-center']),
|
|
106
|
-
protectedJobs: ['security-review', 'ai-native-security-setup', 'security-findings-command-center', 'vulnerability-triage-and-remediation', '
|
|
106
|
+
protectedJobs: ['security-review', 'ai-native-security-setup', 'security-findings-command-center', 'vulnerability-triage-and-remediation', 'compliance-review', 'compliance-continuous-monitoring', 'compliance-policy-authoring', 'compliance-questionnaire-response', 'compliance-requirements-detection', 'generate-audit-evidence', 'regulation-evidence-management', 'soc2-evidence-management'],
|
|
107
107
|
protectedAliases: ['security', 'appsec'],
|
|
108
108
|
defaultHireMode: 'job',
|
|
109
109
|
lockCopy: 'Hire SEChar to unlock security setup, review, and findings-command work for this request.'
|
|
@@ -112,7 +112,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
112
112
|
personaKey: 'ashley',
|
|
113
113
|
bundleId: 'persona-ashley-core',
|
|
114
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'
|
|
115
|
+
protectedJobs: ['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.'
|
|
@@ -121,7 +121,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
121
121
|
personaKey: 'mandy',
|
|
122
122
|
bundleId: 'persona-mandy-core',
|
|
123
123
|
catalogMetadata: buildCatalogMetadata('mandy', ['fully-delegate', 'project-plan-creation', 'stakeholder-status-reporting']),
|
|
124
|
-
protectedJobs: ['fully-delegate', 'delivery-governance-review', 'project-plan-creation', 'stakeholder-status-reporting', 'experiment-tracking', 'cross-functional-dependency-management'],
|
|
124
|
+
protectedJobs: ['fully-delegate', 'delivery-governance-review', 'project-plan-creation', 'stakeholder-status-reporting', 'experiment-tracking', 'cross-functional-dependency-management', 'portfolio-impact-report'],
|
|
125
125
|
protectedAliases: ['manager', 'team-lead', 'orchestrator'],
|
|
126
126
|
defaultHireMode: 'job',
|
|
127
127
|
lockCopy: 'Hire MANdy to unlock autonomous multi-role orchestration — MANdy plans the job sequence, runs sub-agents in parallel, coaches them through verification loops, and hands back a synthesized DRAFT for your approval.'
|
|
@@ -175,7 +175,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
175
175
|
personaKey: 'casey',
|
|
176
176
|
bundleId: 'persona-casey-core',
|
|
177
177
|
catalogMetadata: buildCatalogMetadata('casey', ['support-case-resolution', 'support-sop-operationalization', 'support-system-operationalization', 'support-playbook-evaluation']),
|
|
178
|
-
protectedJobs: ['crm-account-health-review', 'crm-case-resolution', 'customer-health-review', 'loyalty-program-management', 'survey-campaign-management', 'support-queue-management', 'support-case-resolution', 'support-sop-operationalization', 'support-system-operationalization', 'support-playbook-evaluation'],
|
|
178
|
+
protectedJobs: ['crm-account-health-review', 'crm-case-resolution', 'customer-health-review', 'loyalty-program-management', 'survey-campaign-management', 'support-queue-management', 'support-case-resolution', 'support-sop-operationalization', 'support-system-operationalization', 'support-playbook-evaluation', 'user-survey-management'],
|
|
179
179
|
protectedAliases: ['customer-success', 'customer-support', 'csm', 'support'],
|
|
180
180
|
defaultHireMode: 'job',
|
|
181
181
|
lockCopy: 'Hire CaSey to unlock customer success and support work for this request.'
|
|
@@ -205,7 +205,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
205
205
|
personaKey: 'mona',
|
|
206
206
|
bundleId: 'persona-mona-core',
|
|
207
207
|
catalogMetadata: buildCatalogMetadata('mona', ['financial-analysis', 'fpa-and-forecasting', 'monthly-close-review']),
|
|
208
|
-
protectedJobs: ['financial-analysis', 'fpa-and-forecasting', 'monthly-close-review', 'tax-strategy-planning'],
|
|
208
|
+
protectedJobs: ['financial-analysis', 'fpa-and-forecasting', 'monthly-close-review', 'tax-strategy-planning', 'aws-activate-credits-application', 'google-cloud-credits-application', 'microsoft-azure-credits-application', 'invoice-generation', 'business-banking-setup'],
|
|
209
209
|
protectedAliases: ['finance', 'financial-modeling', 'fpa', 'bookkeeping'],
|
|
210
210
|
defaultHireMode: 'job',
|
|
211
211
|
lockCopy: 'Hire MONa to unlock financial modeling, FP&A, and tax strategy work for this request.'
|
|
@@ -214,7 +214,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
214
214
|
personaKey: 'sreya',
|
|
215
215
|
bundleId: 'persona-sreya-core',
|
|
216
216
|
catalogMetadata: buildCatalogMetadata('sreya', ['slo-design-and-implementation', 'incident-response', 'reliability-review']),
|
|
217
|
-
protectedJobs: ['slo-design-and-implementation', 'incident-response', 'reliability-review'],
|
|
217
|
+
protectedJobs: ['slo-design-and-implementation', 'incident-response', 'reliability-review', 'production-readiness-review'],
|
|
218
218
|
protectedAliases: ['sre', 'reliability', 'incident-response', 'on-call'],
|
|
219
219
|
defaultHireMode: 'job',
|
|
220
220
|
lockCopy: 'Hire SREya to unlock SLO governance, incident response, and production reliability work for this request.'
|
|
@@ -231,7 +231,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
231
231
|
'procurement-negotiation-prep',
|
|
232
232
|
'purchase-approval-and-po-package',
|
|
233
233
|
'equipment-acceptance-and-supplier-retrospective',
|
|
234
|
-
'supplier-performance-review'
|
|
234
|
+
'supplier-performance-review',
|
|
235
|
+
'rfp-response-preparation'
|
|
235
236
|
],
|
|
236
237
|
protectedAliases: ['procurement', 'sourcing', 'purchasing', 'supplier-management'],
|
|
237
238
|
defaultHireMode: 'job',
|
|
@@ -266,6 +267,9 @@ function getPersonaCapabilityBundle(personaKey) {
|
|
|
266
267
|
return exports.PERSONA_CAPABILITY_BUNDLES[personaKey];
|
|
267
268
|
}
|
|
268
269
|
function getProtectedPersonaForJob(jobName) {
|
|
270
|
+
if (isFreeJob(jobName)) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
269
273
|
return PROTECTED_JOB_TO_PERSONA.get(jobName) || null;
|
|
270
274
|
}
|
|
271
275
|
function listPersonaCapabilityBundles() {
|
|
@@ -607,12 +607,12 @@ class FirstRunSessionService {
|
|
|
607
607
|
persistShellPath();
|
|
608
608
|
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
609
609
|
if (detectedIDEs.length > 0) {
|
|
610
|
-
await (0, auto_mcp_setup_1.autoConfigureMCP)(this.key,
|
|
610
|
+
await (0, auto_mcp_setup_1.autoConfigureMCP)(this.key, detectedIDEs.map((ide) => ide.name));
|
|
611
611
|
}
|
|
612
612
|
// Write config.json after autoConfigureMCP so detectRowsOnLoad can use its
|
|
613
613
|
// existence as a signal that IDE setup completed. Writing it before would cause
|
|
614
614
|
// the wizard to mark this row 'ok' on retry before IDEs are actually configured.
|
|
615
|
-
(0, setup_1.saveGlobalConfig)(this.key, 'conversational'
|
|
615
|
+
(0, setup_1.saveGlobalConfig)(this.key, 'conversational');
|
|
616
616
|
const { syncUserLevelArtifacts } = await Promise.resolve().then(() => __importStar(require('../cli/setup/user-level-sync')));
|
|
617
617
|
await syncUserLevelArtifacts();
|
|
618
618
|
const { installSlashCommands, installGlobalRules } = await Promise.resolve().then(() => __importStar(require('../cli/setup/ide-global-integration')));
|
|
@@ -413,6 +413,7 @@ class FraimLocalMCPServer {
|
|
|
413
413
|
this.otlpServer = null;
|
|
414
414
|
this.isShutdown = false;
|
|
415
415
|
this.mentoringResponseCache = null;
|
|
416
|
+
this.jobStartCache = new Map();
|
|
416
417
|
this.connectSyncInFlight = null;
|
|
417
418
|
this.latestConnectSyncWarning = null;
|
|
418
419
|
this.orgCacheRefreshInFlight = false;
|
|
@@ -1996,7 +1997,7 @@ class FraimLocalMCPServer {
|
|
|
1996
1997
|
if (!this.mentoringResponseCache) {
|
|
1997
1998
|
this.mentoringResponseCache = new Map();
|
|
1998
1999
|
}
|
|
1999
|
-
this.mentoringResponseCache.set(
|
|
2000
|
+
this.mentoringResponseCache.set(request.id, {
|
|
2000
2001
|
nextPhase: tutoringResponse.nextPhase,
|
|
2001
2002
|
jobId: args.jobId || requestSessionId // Use jobId from args, fallback to sessionId
|
|
2002
2003
|
});
|
|
@@ -2063,10 +2064,13 @@ class FraimLocalMCPServer {
|
|
|
2063
2064
|
const overview = await mentor.getJobOverview(name);
|
|
2064
2065
|
if (overview) {
|
|
2065
2066
|
this.log(`✅ Local override found for get_fraim_job: ${name}`);
|
|
2067
|
+
const jobId = (0, crypto_1.randomUUID)();
|
|
2068
|
+
this.jobStartCache.set(request.id, { jobId, jobName: name });
|
|
2069
|
+
this.log(`📊 Generated jobId=${jobId} for job=${name}`);
|
|
2066
2070
|
let responseText = overview.overview;
|
|
2067
2071
|
if (!overview.isSimple) {
|
|
2068
2072
|
responseText = `${mentor.getCompactPhaseAuthority()}\n\n${responseText}`;
|
|
2069
|
-
responseText += `\n\n---\n\n**This job has phases.** Use \`seekMentoring\` to get phase-specific instructions.`;
|
|
2073
|
+
responseText += `\n\n---\n\n**Job ID:** \`${jobId}\`\n\n**This job has phases.** Use \`seekMentoring\` with the jobId above to get phase-specific instructions.`;
|
|
2070
2074
|
}
|
|
2071
2075
|
// Inject local learning context for job requests (RFC 177).
|
|
2072
2076
|
const userEmail = this.ensureEngine().getUserEmail();
|
|
@@ -2356,7 +2360,7 @@ class FraimLocalMCPServer {
|
|
|
2356
2360
|
if (!this.repoInfo) {
|
|
2357
2361
|
this.detectRepoInfo();
|
|
2358
2362
|
}
|
|
2359
|
-
//
|
|
2363
|
+
// Enrich seekMentoring args with response data (nextPhase, jobId from cache)
|
|
2360
2364
|
if (toolName === 'seekMentoring' && this.mentoringResponseCache) {
|
|
2361
2365
|
const requestId = request.id;
|
|
2362
2366
|
const cachedResponse = this.mentoringResponseCache.get(requestId);
|
|
@@ -2366,10 +2370,20 @@ class FraimLocalMCPServer {
|
|
|
2366
2370
|
nextPhase: cachedResponse.nextPhase,
|
|
2367
2371
|
jobId: cachedResponse.jobId
|
|
2368
2372
|
};
|
|
2369
|
-
this.mentoringResponseCache.delete(requestId);
|
|
2373
|
+
this.mentoringResponseCache.delete(requestId);
|
|
2370
2374
|
this.log(`📊 Enriched seekMentoring args with nextPhase=${cachedResponse.nextPhase}, jobId=${cachedResponse.jobId}`);
|
|
2371
2375
|
}
|
|
2372
2376
|
}
|
|
2377
|
+
// Enrich get_fraim_job args with the UUID jobId and action:start from jobStartCache
|
|
2378
|
+
if (toolName === 'get_fraim_job') {
|
|
2379
|
+
const requestId = request.id;
|
|
2380
|
+
const cached = this.jobStartCache.get(requestId);
|
|
2381
|
+
if (cached) {
|
|
2382
|
+
args = { ...args, jobId: cached.jobId, action: 'start' };
|
|
2383
|
+
this.jobStartCache.delete(requestId);
|
|
2384
|
+
this.log(`📊 Enriched get_fraim_job args with jobId=${cached.jobId}, action=start`);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2373
2387
|
// Capture the current queue size before collection
|
|
2374
2388
|
const beforeCount = this.usageCollector.getEventCount();
|
|
2375
2389
|
try {
|
|
@@ -2410,6 +2424,16 @@ class FraimLocalMCPServer {
|
|
|
2410
2424
|
this.log(`📊 🔍 Debug: get_fraim_file path="${args.path}"`);
|
|
2411
2425
|
}
|
|
2412
2426
|
}
|
|
2427
|
+
// Emit job-complete event when seekMentoring reaches the final phase
|
|
2428
|
+
if (toolName === 'seekMentoring' && args.nextPhase === null && args.jobName && args.jobId) {
|
|
2429
|
+
try {
|
|
2430
|
+
this.usageCollector.collectJobComplete(args.jobName, args.jobId, requestSessionId);
|
|
2431
|
+
this.log(`📊 ✅ Job complete event emitted: ${args.jobName} (jobId: ${args.jobId})`);
|
|
2432
|
+
}
|
|
2433
|
+
catch (err) {
|
|
2434
|
+
this.log(`📊 ⚠️ Job complete event failed (non-blocking): ${err.message}`);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2413
2437
|
}
|
|
2414
2438
|
catch (error) {
|
|
2415
2439
|
this.log(`📊 ❌ Usage collection error: ${error.message}`);
|
|
@@ -142,6 +142,8 @@ class UsageCollector {
|
|
|
142
142
|
case 'get_fraim_job':
|
|
143
143
|
if (args.job)
|
|
144
144
|
analyticsArgs.job = args.job;
|
|
145
|
+
if (args.action)
|
|
146
|
+
analyticsArgs.action = args.action;
|
|
145
147
|
break;
|
|
146
148
|
case 'fraim_connect':
|
|
147
149
|
if (args.agent?.name)
|
|
@@ -179,6 +181,28 @@ class UsageCollector {
|
|
|
179
181
|
return null;
|
|
180
182
|
}
|
|
181
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Emit a job-completion event when seekMentoring returns nextPhase === null.
|
|
186
|
+
* Called by the local proxy after the final mentoring event is queued.
|
|
187
|
+
*/
|
|
188
|
+
collectJobComplete(jobName, jobId, sessionId) {
|
|
189
|
+
const event = {
|
|
190
|
+
type: 'job',
|
|
191
|
+
name: jobName,
|
|
192
|
+
userId: this.userId || 'unknown',
|
|
193
|
+
sessionId,
|
|
194
|
+
success: true,
|
|
195
|
+
jobId,
|
|
196
|
+
repoIdentifier: this.repoIdentifier || undefined,
|
|
197
|
+
agentName: this.agentName || undefined,
|
|
198
|
+
agentModel: this.agentModel || undefined,
|
|
199
|
+
args: { action: 'complete', jobId },
|
|
200
|
+
};
|
|
201
|
+
this.events.push(event);
|
|
202
|
+
const msg = `[UsageCollector] ✅ Job complete event: ${jobName} (jobId: ${jobId}, queue: ${this.events.length})`;
|
|
203
|
+
console.error(msg);
|
|
204
|
+
process.stderr.write(msg + '\n');
|
|
205
|
+
}
|
|
182
206
|
/**
|
|
183
207
|
* Get collected events for upload and clear the queue
|
|
184
208
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.179",
|
|
4
4
|
"description": "FRAIM: AI Workforce Infrastructure — the organizational capability that turns AI agents into an accountable workforce, their operators into capable AI managers, and executives into leaders with clear optics on AI proficiency.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "tsx --watch src/fraim-mcp-server.ts > server.log 2>&1",
|
|
11
11
|
"dev:prod": "npm run build && node dist/src/fraim-mcp-server.js > server.log 2>&1",
|
|
12
|
-
"build": "tsx scripts/build-fraim-config-schema-template.ts && npm run typecheck:scripts && tsc && npm run build:stubs && npm run build:fraim-brain && node scripts/copy-registry.js && npm run validate:registry && npm run validate:fraim-pro-assets && npm run validate:employee-catalog && npm run validate:learning-format-contract && tsx scripts/validate-purity.ts",
|
|
12
|
+
"build": "tsx scripts/build-fraim-config-schema-template.ts && npm run typecheck:scripts && tsc && npm run build:stubs && npm run build:fraim-brain && node scripts/copy-registry.js && npm run validate:registry && npm run validate:marketplaces && npm run validate:fraim-pro-assets && npm run validate:employee-catalog && npm run validate:learning-format-contract && tsx scripts/validate-purity.ts",
|
|
13
13
|
"validate:learning-format-contract": "tsx scripts/validate-learning-format-contract.ts",
|
|
14
14
|
"build:stubs": "tsx scripts/build-stub-registry.ts",
|
|
15
15
|
"build:fraim-brain": "node scripts/generate-fraim-brain.js",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"publish-fraim-only": "node scripts/publish-fraim.js",
|
|
51
51
|
"publish-both-manual": "node scripts/publish-both.js",
|
|
52
52
|
"validate:registry": "tsx scripts/verify-registry-paths.ts && npm run validate:jobs && npm run validate:skills && npm run validate:registry-references && npm run validate:platform-agnostic && npm run validate:template-namespaces && npm run validate:config-fallbacks && npm run validate:bootstrap-config-coverage && npm run validate:provider-action-mappings && npm run validate:fidelity && npm run validate:config-tokens && npm run validate:brain-mapping && npm run validate:template-syntax",
|
|
53
|
+
"validate:marketplaces": "tsx scripts/validate-marketplace-bundles.ts",
|
|
53
54
|
"typecheck:scripts": "tsc -p tsconfig.scripts.json --pretty false",
|
|
54
55
|
"validate:registry-references": "tsx scripts/validate-registry-references.ts",
|
|
55
56
|
"validate:brain-mapping": "tsx scripts/validate-brain-mapping.ts",
|
package/public/ai-hub/index.html
CHANGED
|
@@ -144,8 +144,20 @@
|
|
|
144
144
|
<!-- Overview sub-view: dashboard project cards -->
|
|
145
145
|
<div id="proj-overview">
|
|
146
146
|
<div class="hub-area-page">
|
|
147
|
-
|
|
148
|
-
<div class="
|
|
147
|
+
<!-- Issue #671: Cards / Kanban view toggle -->
|
|
148
|
+
<div class="kb-view-toggle" id="kb-view-toggle">
|
|
149
|
+
<button class="kb-vtab on" data-view="cards" type="button">Cards</button>
|
|
150
|
+
<button class="kb-vtab" data-view="kanban" type="button">Kanban</button>
|
|
151
|
+
</div>
|
|
152
|
+
<div id="proj-cards-view">
|
|
153
|
+
<div class="sec-label" id="proj-cards-label">All projects</div>
|
|
154
|
+
<div class="proj-grid" id="proj-grid"></div>
|
|
155
|
+
<div id="kb-runs-grid" hidden></div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
<!-- Issue #671: Kanban board — rendered by tfRenderKanban() -->
|
|
159
|
+
<div id="proj-kanban" hidden>
|
|
160
|
+
<div id="kb-stats" class="kb-stats"></div>
|
|
149
161
|
</div>
|
|
150
162
|
</div>
|
|
151
163
|
|