fraim 2.0.176 → 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/first-run.js +3 -5
- 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 +65 -12
- package/dist/src/config/persona-capability-bundles.js +17 -13
- package/dist/src/first-run/install-state.js +1 -0
- package/dist/src/first-run/server.js +13 -0
- package/dist/src/first-run/session-service.js +22 -3
- 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 +5 -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
- package/public/first-run/script.js +64 -0
- package/public/first-run/styles.css +14 -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) {
|
|
@@ -23,6 +23,58 @@ exports.expandPath = expandPath;
|
|
|
23
23
|
const checkMultiplePaths = (paths) => {
|
|
24
24
|
return paths.some(p => fs_1.default.existsSync(expandPath(p)));
|
|
25
25
|
};
|
|
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 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) => [
|
|
32
|
+
`/Applications/${appName}.app`,
|
|
33
|
+
`~/Applications/${appName}.app`,
|
|
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
|
+
};
|
|
46
|
+
// Issue #646 follow-up: a user may run a GUI app from a non-standard location
|
|
47
|
+
// (e.g. straight from a mounted DMG: /Volumes/.../Cursor.app) that the
|
|
48
|
+
// /Applications checks miss. A running process of the app is definitive proof it
|
|
49
|
+
// is installed and in use, and a stale config dir alone has no such process — so
|
|
50
|
+
// this catches real installs without re-introducing the stale-dir false positive.
|
|
51
|
+
const isMacAppRunning = (appName) => {
|
|
52
|
+
if (process.platform !== 'darwin')
|
|
53
|
+
return false;
|
|
54
|
+
// Test seam: `pgrep` is system-global (not HOME-scoped), so tests that simulate
|
|
55
|
+
// "app not installed" via a temp HOME set this to keep results deterministic.
|
|
56
|
+
if (process.env.FRAIM_DETECT_DISABLE_PROCESS_CHECK === '1')
|
|
57
|
+
return false;
|
|
58
|
+
const result = (0, child_process_1.spawnSync)('pgrep', ['-f', `${appName}.app/Contents`], { encoding: 'utf8', timeout: 1500 });
|
|
59
|
+
return result.status === 0 && Boolean((result.stdout || '').trim());
|
|
60
|
+
};
|
|
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 = {}) => {
|
|
68
|
+
return () => {
|
|
69
|
+
if (options.requireAppEvidence) {
|
|
70
|
+
return hasAppEvidence(appName);
|
|
71
|
+
}
|
|
72
|
+
if (process.platform === 'darwin') {
|
|
73
|
+
return hasAppEvidence(appName);
|
|
74
|
+
}
|
|
75
|
+
return configSurfaceCheck();
|
|
76
|
+
};
|
|
77
|
+
};
|
|
26
78
|
const availableByVersionProbe = (command) => {
|
|
27
79
|
const result = process.platform === 'win32'
|
|
28
80
|
? (0, child_process_1.spawnSync)('cmd.exe', ['/d', '/s', '/c', `${command} --version`], { encoding: 'utf8', timeout: 1500 })
|
|
@@ -48,12 +100,12 @@ const detectVSCode = () => {
|
|
|
48
100
|
];
|
|
49
101
|
return checkMultiplePaths(paths);
|
|
50
102
|
};
|
|
51
|
-
const
|
|
103
|
+
const detectCursorMcpConfig = () => {
|
|
52
104
|
const paths = [
|
|
53
|
-
'~/.cursor',
|
|
54
|
-
'~/Library/Application Support/Cursor',
|
|
55
|
-
'~/AppData/Roaming/Cursor',
|
|
56
|
-
'~/.config/Cursor'
|
|
105
|
+
'~/.cursor/mcp.json',
|
|
106
|
+
'~/Library/Application Support/Cursor/mcp.json',
|
|
107
|
+
'~/AppData/Roaming/Cursor/mcp.json',
|
|
108
|
+
'~/.config/Cursor/mcp.json'
|
|
57
109
|
];
|
|
58
110
|
return checkMultiplePaths(paths);
|
|
59
111
|
};
|
|
@@ -71,6 +123,7 @@ const detectGeminiCli = () => {
|
|
|
71
123
|
};
|
|
72
124
|
const detectGeminiSurface = () => {
|
|
73
125
|
const paths = [
|
|
126
|
+
'~/.gemini',
|
|
74
127
|
'~/.gemini/settings.json',
|
|
75
128
|
'~/AppData/Roaming/gemini/settings.json',
|
|
76
129
|
'~/.config/gemini/settings.json'
|
|
@@ -112,7 +165,7 @@ exports.IDE_CONFIGS = [
|
|
|
112
165
|
configFormat: 'json',
|
|
113
166
|
configType: 'claude',
|
|
114
167
|
invocationProfile: 'launch-phrase',
|
|
115
|
-
detectMethod: detectClaude,
|
|
168
|
+
detectMethod: guiAppDetect(detectClaude, 'Claude'),
|
|
116
169
|
aliases: ['claude', 'claude-desktop', 'claude desktop', 'claude-cowork', 'cowork', 'claude cowork'],
|
|
117
170
|
alternativePaths: [
|
|
118
171
|
'~/Library/Application Support/Claude/claude_desktop_config.json'
|
|
@@ -126,9 +179,9 @@ exports.IDE_CONFIGS = [
|
|
|
126
179
|
configFormat: 'json',
|
|
127
180
|
configType: 'standard',
|
|
128
181
|
invocationProfile: 'cursor-mention',
|
|
129
|
-
detectMethod: () => fs_1.default.existsSync(expandPath('~/.gemini/antigravity')),
|
|
182
|
+
detectMethod: guiAppDetect(() => fs_1.default.existsSync(expandPath('~/.gemini/antigravity')), 'Antigravity'),
|
|
130
183
|
description: 'Google Gemini Antigravity IDE',
|
|
131
|
-
downloadUrl: 'https://
|
|
184
|
+
downloadUrl: 'https://antigravity.google/',
|
|
132
185
|
},
|
|
133
186
|
{
|
|
134
187
|
name: 'Gemini CLI',
|
|
@@ -152,7 +205,7 @@ exports.IDE_CONFIGS = [
|
|
|
152
205
|
configFormat: 'json',
|
|
153
206
|
configType: 'kiro',
|
|
154
207
|
invocationProfile: 'kiro-hashtag',
|
|
155
|
-
detectMethod: () => fs_1.default.existsSync(expandPath('~/.kiro')),
|
|
208
|
+
detectMethod: guiAppDetect(() => fs_1.default.existsSync(expandPath('~/.kiro')), 'Kiro'),
|
|
156
209
|
description: 'Kiro AI-powered IDE',
|
|
157
210
|
downloadUrl: 'https://kiro.dev/',
|
|
158
211
|
},
|
|
@@ -163,7 +216,7 @@ exports.IDE_CONFIGS = [
|
|
|
163
216
|
configType: 'kiro',
|
|
164
217
|
adapterConfigType: 'cursor',
|
|
165
218
|
invocationProfile: 'cursor-mention',
|
|
166
|
-
detectMethod:
|
|
219
|
+
detectMethod: () => detectCursorMcpConfig() || hasAppEvidence('Cursor'),
|
|
167
220
|
alternativePaths: [
|
|
168
221
|
'~/Library/Application Support/Cursor/mcp.json',
|
|
169
222
|
'~/AppData/Roaming/Cursor/mcp.json',
|
|
@@ -182,7 +235,7 @@ exports.IDE_CONFIGS = [
|
|
|
182
235
|
configFormat: 'json',
|
|
183
236
|
configType: 'vscode',
|
|
184
237
|
invocationProfile: 'vscode-prompt',
|
|
185
|
-
detectMethod: detectVSCode,
|
|
238
|
+
detectMethod: guiAppDetect(detectVSCode, 'Visual Studio Code'),
|
|
186
239
|
alternativePaths: [
|
|
187
240
|
'~/Library/Application Support/Code/User/mcp.json',
|
|
188
241
|
'~/AppData/Roaming/Code/User/mcp.json',
|
|
@@ -219,7 +272,7 @@ exports.IDE_CONFIGS = [
|
|
|
219
272
|
configFormat: 'json',
|
|
220
273
|
configType: 'windsurf',
|
|
221
274
|
invocationProfile: 'windsurf-command',
|
|
222
|
-
detectMethod: detectWindsurf,
|
|
275
|
+
detectMethod: guiAppDetect(detectWindsurf, 'Windsurf'),
|
|
223
276
|
alternativePaths: [
|
|
224
277
|
'~/Library/Application Support/Windsurf/mcp_config.json',
|
|
225
278
|
'~/AppData/Roaming/Windsurf/mcp_config.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() {
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.maskInstallKey = maskInstallKey;
|
|
6
7
|
exports.createInitialFirstRunState = createInitialFirstRunState;
|
|
7
8
|
exports.loadFirstRunState = loadFirstRunState;
|
|
8
9
|
exports.saveFirstRunState = saveFirstRunState;
|
|
@@ -183,6 +183,19 @@ class FirstRunServer {
|
|
|
183
183
|
return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not run row.' });
|
|
184
184
|
}
|
|
185
185
|
});
|
|
186
|
+
// Issue #646: accept a user-pasted key when first-run was launched without one
|
|
187
|
+
// (the no-terminal macOS installer path).
|
|
188
|
+
this.app.post('/api/first-run/set-key', (req, res) => {
|
|
189
|
+
const { key } = req.body || {};
|
|
190
|
+
if (!key || typeof key !== 'string') {
|
|
191
|
+
return res.status(400).json({ ok: false, error: 'key is required.' });
|
|
192
|
+
}
|
|
193
|
+
const result = this.sessionService.setKey(key);
|
|
194
|
+
if (!result.ok) {
|
|
195
|
+
return res.status(400).json({ ok: false, error: result.message });
|
|
196
|
+
}
|
|
197
|
+
return res.json({ ok: true, session: this.sessionService.getSession() });
|
|
198
|
+
});
|
|
186
199
|
this.app.post('/api/first-run/agent/change', (req, res) => {
|
|
187
200
|
try {
|
|
188
201
|
return res.json(this.sessionService.changeAgent(req.body || {}));
|
|
@@ -200,7 +200,10 @@ function surfaceForAgent(option) {
|
|
|
200
200
|
}
|
|
201
201
|
class FirstRunSessionService {
|
|
202
202
|
constructor(options) {
|
|
203
|
-
|
|
203
|
+
// Issue #646: the key may be absent when first-run is launched by the macOS
|
|
204
|
+
// installer (.pkg) instead of `fraim first-run --key=…`. In that case the
|
|
205
|
+
// wizard prompts the user to paste their key (see setKey / needsKey).
|
|
206
|
+
this.key = options.key || '';
|
|
204
207
|
this.headless = options.headless === true;
|
|
205
208
|
this.fakeMode = getFakeStateMode();
|
|
206
209
|
this.fakeStderr =
|
|
@@ -449,8 +452,24 @@ class FirstRunSessionService {
|
|
|
449
452
|
agentOptions: types_1.FIRST_RUN_AGENT_OPTIONS,
|
|
450
453
|
currentAgentId: this.state.agentId,
|
|
451
454
|
supportedAgents,
|
|
455
|
+
needsKey: !this.key,
|
|
452
456
|
};
|
|
453
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Issue #646: accept a user-pasted FRAIM key when first-run was launched
|
|
460
|
+
* without one (the no-terminal macOS installer path). Returns ok=false with a
|
|
461
|
+
* message on an invalid key so the wizard can show inline guidance.
|
|
462
|
+
*/
|
|
463
|
+
setKey(rawKey) {
|
|
464
|
+
const key = (rawKey || '').trim();
|
|
465
|
+
if (!/^fraim_[A-Za-z0-9]+$/.test(key)) {
|
|
466
|
+
return { ok: false, message: 'That doesn\'t look like a FRAIM key. It should start with "fraim_" — copy it from your account page.' };
|
|
467
|
+
}
|
|
468
|
+
this.key = key;
|
|
469
|
+
this.state.installKeyRef = (0, install_state_1.maskInstallKey)(key);
|
|
470
|
+
this.persist();
|
|
471
|
+
return { ok: true };
|
|
472
|
+
}
|
|
454
473
|
respond(message, ok) {
|
|
455
474
|
return {
|
|
456
475
|
ok,
|
|
@@ -588,12 +607,12 @@ class FirstRunSessionService {
|
|
|
588
607
|
persistShellPath();
|
|
589
608
|
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
590
609
|
if (detectedIDEs.length > 0) {
|
|
591
|
-
await (0, auto_mcp_setup_1.autoConfigureMCP)(this.key,
|
|
610
|
+
await (0, auto_mcp_setup_1.autoConfigureMCP)(this.key, detectedIDEs.map((ide) => ide.name));
|
|
592
611
|
}
|
|
593
612
|
// Write config.json after autoConfigureMCP so detectRowsOnLoad can use its
|
|
594
613
|
// existence as a signal that IDE setup completed. Writing it before would cause
|
|
595
614
|
// the wizard to mark this row 'ok' on retry before IDEs are actually configured.
|
|
596
|
-
(0, setup_1.saveGlobalConfig)(this.key, 'conversational'
|
|
615
|
+
(0, setup_1.saveGlobalConfig)(this.key, 'conversational');
|
|
597
616
|
const { syncUserLevelArtifacts } = await Promise.resolve().then(() => __importStar(require('../cli/setup/user-level-sync')));
|
|
598
617
|
await syncUserLevelArtifacts();
|
|
599
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",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.179",
|
|
4
4
|
"description": "FRAIM CLI - Framework for Rigor-based AI Management (alias for fraim-framework)",
|
|
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",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"hub:desktop": "npm run build && electron dist/src/ai-hub/desktop-main.js",
|
|
29
29
|
"hub:dev": "tsx scripts/start-hub-dev.ts",
|
|
30
30
|
"firstrun:dev": "tsx scripts/start-firstrun-dev.ts",
|
|
31
|
+
"sign:mac": "bash scripts/sign-macos-installer.sh",
|
|
32
|
+
"build:mac-installer": "bash scripts/build-macos-installer.sh",
|
|
31
33
|
"start:fraim": "tsx src/fraim-mcp-server.ts",
|
|
32
34
|
"dev:fraim": "tsx --watch src/fraim-mcp-server.ts",
|
|
33
35
|
"serve:website": "node fraim-pro/serve.js",
|
|
@@ -48,6 +50,7 @@
|
|
|
48
50
|
"publish-fraim-only": "node scripts/publish-fraim.js",
|
|
49
51
|
"publish-both-manual": "node scripts/publish-both.js",
|
|
50
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",
|
|
51
54
|
"typecheck:scripts": "tsc -p tsconfig.scripts.json --pretty false",
|
|
52
55
|
"validate:registry-references": "tsx scripts/validate-registry-references.ts",
|
|
53
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
|
|