fraim-framework 2.0.124 → 2.0.127
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/bin/fraim.js +1 -1
- package/dist/src/ai-hub/catalog.js +280 -44
- package/dist/src/ai-hub/desktop-main.js +2 -2
- package/dist/src/ai-hub/hosts.js +384 -10
- package/dist/src/ai-hub/server.js +255 -9
- package/dist/src/cli/commands/add-ide.js +4 -3
- package/dist/src/cli/commands/first-run.js +61 -0
- package/dist/src/cli/commands/hub.js +4 -4
- package/dist/src/cli/commands/init-project.js +4 -4
- package/dist/src/cli/commands/setup.js +4 -3
- package/dist/src/cli/commands/sync.js +21 -2
- package/dist/src/cli/doctor/checks/ide-config-checks.js +20 -2
- package/dist/src/cli/fraim.js +2 -0
- package/dist/src/cli/mcp/ide-formats.js +29 -1
- package/dist/src/cli/mcp/mcp-server-registry.js +1 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +14 -8
- package/dist/src/cli/setup/ide-detector.js +32 -1
- package/dist/src/cli/setup/ide-global-integration.js +5 -1
- package/dist/src/cli/setup/ide-invocation-surfaces.js +70 -17
- package/dist/src/cli/setup/mcp-config-generator.js +12 -1
- package/dist/src/cli/utils/agent-adapters.js +12 -2
- package/dist/src/cli/utils/project-bootstrap.js +4 -3
- package/dist/src/core/quality-evidence.js +81 -8
- package/dist/src/core/utils/git-utils.js +32 -7
- package/dist/src/core/utils/job-aliases.js +47 -0
- package/dist/src/core/utils/workflow-parser.js +3 -5
- package/dist/src/first-run/install-state.js +68 -0
- package/dist/src/first-run/server.js +153 -0
- package/dist/src/first-run/session-service.js +302 -0
- package/dist/src/first-run/types.js +40 -0
- package/dist/src/local-mcp-server/agent-token-prices.js +114 -0
- package/dist/src/local-mcp-server/codex-token-adapter.js +232 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +21 -8
- package/dist/src/local-mcp-server/otlp-metrics-receiver.js +7 -1
- package/dist/src/local-mcp-server/stdio-server.js +70 -17
- package/dist/src/local-mcp-server/token-adapter-registry.js +64 -0
- package/dist/src/local-mcp-server/usage-collector.js +25 -0
- package/index.js +83 -83
- package/package.json +7 -1
- package/public/ai-hub/index.html +149 -102
- package/public/ai-hub/script.js +1154 -271
- package/public/ai-hub/styles.css +753 -450
- package/public/first-run/index.html +221 -0
- package/public/first-run/script.js +361 -0
- package/dist/src/cli/services/device-flow-service.js +0 -83
- package/dist/src/local-mcp-server/prometheus-scraper.js +0 -152
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// IDE Format Adapters - transform logical server structure to IDE-specific formats
|
|
3
3
|
// Uses the centralized registry to determine server types
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.IDE_FORMATS = exports.CodexFormat = exports.WindsurfFormat = exports.ClaudeCodeFormat = exports.ClaudeFormat = exports.VSCodeFormat = exports.KiroFormat = exports.StandardFormat = void 0;
|
|
5
|
+
exports.IDE_FORMATS = exports.CodexFormat = exports.WindsurfFormat = exports.ClaudeCodeFormat = exports.ClaudeFormat = exports.GeminiCliFormat = exports.VSCodeFormat = exports.KiroFormat = exports.StandardFormat = void 0;
|
|
6
6
|
exports.getIDEFormat = getIDEFormat;
|
|
7
7
|
const mcp_server_registry_1 = require("./mcp-server-registry");
|
|
8
8
|
const provider_registry_1 = require("../providers/provider-registry");
|
|
@@ -93,6 +93,33 @@ class VSCodeFormat {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
exports.VSCodeFormat = VSCodeFormat;
|
|
96
|
+
class GeminiCliFormat {
|
|
97
|
+
constructor() {
|
|
98
|
+
this.name = 'gemini-cli';
|
|
99
|
+
}
|
|
100
|
+
transform(servers) {
|
|
101
|
+
const mcpServers = {};
|
|
102
|
+
for (const [key, server] of servers) {
|
|
103
|
+
if (server.url) {
|
|
104
|
+
mcpServers[key] = {
|
|
105
|
+
...(server.type === 'sse'
|
|
106
|
+
? { url: server.url }
|
|
107
|
+
: { httpUrl: server.httpUrl || server.url }),
|
|
108
|
+
...(server.headers && { headers: server.headers })
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
mcpServers[key] = {
|
|
113
|
+
command: server.command,
|
|
114
|
+
...(server.args && { args: server.args }),
|
|
115
|
+
...(server.env && { env: server.env })
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { mcpServers };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.GeminiCliFormat = GeminiCliFormat;
|
|
96
123
|
// Claude Desktop format (excludes provider servers - Issue #132)
|
|
97
124
|
class ClaudeFormat {
|
|
98
125
|
constructor() {
|
|
@@ -230,6 +257,7 @@ exports.IDE_FORMATS = {
|
|
|
230
257
|
standard: new StandardFormat(),
|
|
231
258
|
kiro: new KiroFormat(),
|
|
232
259
|
vscode: new VSCodeFormat(),
|
|
260
|
+
'gemini-cli': new GeminiCliFormat(),
|
|
233
261
|
claude: new ClaudeFormat(),
|
|
234
262
|
'claude-code': new ClaudeCodeFormat(),
|
|
235
263
|
windsurf: new WindsurfFormat(),
|
|
@@ -252,9 +252,9 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
|
|
|
252
252
|
const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConfigs) => {
|
|
253
253
|
const tokens = normalizePlatformTokens(tokenInput);
|
|
254
254
|
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
255
|
-
if (detectedIDEs.length === 0) {
|
|
255
|
+
if (detectedIDEs.length === 0 && (!selectedIDEs || selectedIDEs.length === 0)) {
|
|
256
256
|
console.log(chalk_1.default.yellow('⚠️ No supported IDEs detected.'));
|
|
257
|
-
console.log(chalk_1.default.gray('Supported IDEs: Claude, Antigravity, Kiro, Cursor, VSCode, Codex, Windsurf'));
|
|
257
|
+
console.log(chalk_1.default.gray('Supported IDEs: Claude, Claude Code, Antigravity, Gemini CLI, Kiro, Cursor, VSCode, Codex, Windsurf'));
|
|
258
258
|
console.log(chalk_1.default.blue('\n💡 You can install an IDE and run setup again later.'));
|
|
259
259
|
console.log(chalk_1.default.gray(' Or continue with manual MCP configuration.'));
|
|
260
260
|
if (process.env.FRAIM_NON_INTERACTIVE) {
|
|
@@ -275,13 +275,19 @@ const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConf
|
|
|
275
275
|
}
|
|
276
276
|
let idesToConfigure;
|
|
277
277
|
if (selectedIDEs && selectedIDEs.length > 0) {
|
|
278
|
-
|
|
279
|
-
idesToConfigure = detectedIDEs.filter(ide => selectedIDEs.some(selected => ide.name.toLowerCase().includes(selected.toLowerCase())));
|
|
278
|
+
idesToConfigure = detectedIDEs.filter((ide) => selectedIDEs.some((selected) => ide.name.toLowerCase().includes(selected.toLowerCase())));
|
|
280
279
|
if (idesToConfigure.length === 0) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
280
|
+
idesToConfigure = (0, ide_detector_1.getAllSupportedIDEs)().filter((ide) => selectedIDEs.some((selected) => {
|
|
281
|
+
const normalized = selected.toLowerCase();
|
|
282
|
+
return ide.name.toLowerCase().includes(normalized)
|
|
283
|
+
|| ide.aliases?.some((alias) => alias.includes(normalized));
|
|
284
|
+
}));
|
|
285
|
+
if (idesToConfigure.length === 0) {
|
|
286
|
+
console.log(chalk_1.default.yellow(`⚠️ No IDEs found matching: ${selectedIDEs.join(', ')}`));
|
|
287
|
+
console.log(chalk_1.default.gray('Available IDEs:'));
|
|
288
|
+
detectedIDEs.forEach((ide) => console.log(chalk_1.default.gray(` • ${ide.name}`)));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
285
291
|
}
|
|
286
292
|
}
|
|
287
293
|
else {
|
|
@@ -7,6 +7,7 @@ exports.expandPath = exports.findIDEByName = exports.getAllSupportedIDEs = expor
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
10
11
|
const expandPath = (filePath) => {
|
|
11
12
|
if (filePath.startsWith('~/')) {
|
|
12
13
|
return path_1.default.join(os_1.default.homedir(), filePath.slice(2));
|
|
@@ -17,6 +18,12 @@ exports.expandPath = expandPath;
|
|
|
17
18
|
const checkMultiplePaths = (paths) => {
|
|
18
19
|
return paths.some(p => fs_1.default.existsSync(expandPath(p)));
|
|
19
20
|
};
|
|
21
|
+
const availableByVersionProbe = (command) => {
|
|
22
|
+
const result = process.platform === 'win32'
|
|
23
|
+
? (0, child_process_1.spawnSync)('cmd.exe', ['/d', '/s', '/c', `${command} --version`], { encoding: 'utf8', timeout: 1500 })
|
|
24
|
+
: (0, child_process_1.spawnSync)(command, ['--version'], { encoding: 'utf8', timeout: 1500 });
|
|
25
|
+
return result.status === 0;
|
|
26
|
+
};
|
|
20
27
|
const detectClaude = () => {
|
|
21
28
|
const paths = [
|
|
22
29
|
'~/.claude.json',
|
|
@@ -54,6 +61,15 @@ const detectWindsurf = () => {
|
|
|
54
61
|
];
|
|
55
62
|
return checkMultiplePaths(paths);
|
|
56
63
|
};
|
|
64
|
+
const detectGeminiCli = () => {
|
|
65
|
+
const paths = [
|
|
66
|
+
'~/.gemini/settings.json',
|
|
67
|
+
'~/.gemini',
|
|
68
|
+
'~/AppData/Roaming/gemini/settings.json',
|
|
69
|
+
'~/.config/gemini/settings.json'
|
|
70
|
+
];
|
|
71
|
+
return checkMultiplePaths(paths) || availableByVersionProbe('gemini');
|
|
72
|
+
};
|
|
57
73
|
exports.IDE_CONFIGS = [
|
|
58
74
|
{
|
|
59
75
|
name: 'Claude Code',
|
|
@@ -90,6 +106,21 @@ exports.IDE_CONFIGS = [
|
|
|
90
106
|
detectMethod: () => fs_1.default.existsSync(expandPath('~/.gemini/antigravity')),
|
|
91
107
|
description: 'Google Gemini Antigravity IDE'
|
|
92
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: 'Gemini CLI',
|
|
111
|
+
configPath: '~/.gemini/settings.json',
|
|
112
|
+
configFormat: 'json',
|
|
113
|
+
configType: 'gemini-cli',
|
|
114
|
+
invocationProfile: 'gemini-command',
|
|
115
|
+
detectMethod: detectGeminiCli,
|
|
116
|
+
supportsConfigBootstrap: true,
|
|
117
|
+
aliases: ['gemini', 'gemini-cli', 'gemini cli'],
|
|
118
|
+
alternativePaths: [
|
|
119
|
+
'~/AppData/Roaming/gemini/settings.json',
|
|
120
|
+
'~/.config/gemini/settings.json'
|
|
121
|
+
],
|
|
122
|
+
description: 'Google Gemini CLI local settings'
|
|
123
|
+
},
|
|
93
124
|
{
|
|
94
125
|
name: 'Kiro',
|
|
95
126
|
configPath: '~/.kiro/settings/mcp.json',
|
|
@@ -186,6 +217,6 @@ const findIDEByName = (name) => {
|
|
|
186
217
|
const normalized = name.toLowerCase();
|
|
187
218
|
return exports.IDE_CONFIGS.find(ide => ide.name.toLowerCase().includes(normalized) ||
|
|
188
219
|
normalized.includes(ide.name.toLowerCase()) ||
|
|
189
|
-
ide.aliases?.some(alias => alias.includes(normalized) || normalized.includes(alias)));
|
|
220
|
+
ide.aliases?.some(alias => alias.toLowerCase().includes(normalized) || normalized.includes(alias.toLowerCase())));
|
|
190
221
|
};
|
|
191
222
|
exports.findIDEByName = findIDEByName;
|
|
@@ -36,7 +36,7 @@ async function installSlashCommands(homeDir) {
|
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* Install FRAIM invocation artifacts for non-Claude IDEs.
|
|
39
|
-
* Supports: Cursor, Codex, Windsurf, Kiro
|
|
39
|
+
* Supports: Cursor, Codex, Gemini CLI, Windsurf, Kiro
|
|
40
40
|
* Does not overwrite existing files.
|
|
41
41
|
*/
|
|
42
42
|
async function installGlobalRules(homeDir) {
|
|
@@ -49,6 +49,10 @@ async function installGlobalRules(homeDir) {
|
|
|
49
49
|
if (fs_1.default.existsSync(codexDir)) {
|
|
50
50
|
installFileIfMissing(path_1.default.join(codexDir, 'skills', 'fraim', 'SKILL.md'), (0, ide_invocation_surfaces_1.buildCodexSkillContent)(), 'Codex FRAIM skill (~/.codex/skills/fraim/SKILL.md)');
|
|
51
51
|
}
|
|
52
|
+
const geminiDir = path_1.default.join(home, '.gemini');
|
|
53
|
+
if (fs_1.default.existsSync(geminiDir)) {
|
|
54
|
+
installFileIfMissing(path_1.default.join(geminiDir, 'commands', 'fraim.toml'), (0, ide_invocation_surfaces_1.buildGeminiCommandContent)(), 'Gemini CLI FRAIM command (~/.gemini/commands/fraim.toml)');
|
|
55
|
+
}
|
|
52
56
|
const windsurfDir = path_1.default.join(home, '.codeium', 'windsurf');
|
|
53
57
|
if (fs_1.default.existsSync(windsurfDir)) {
|
|
54
58
|
installFileIfMissing(path_1.default.join(windsurfDir, 'commands', 'fraim.md'), (0, ide_invocation_surfaces_1.buildWindsurfCommandContent)(), 'Windsurf FRAIM command (~/.codeium/windsurf/commands/fraim.md)');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FRAIM_INVOCATION_BODY = exports.CURSOR_MDC_FRONTMATTER = exports.FRAIM_LAUNCH_PHRASE = void 0;
|
|
3
|
+
exports.FRAIM_INVOCATION_BODY = exports.FRAIM_DEFERRED_TOOL_PRELOAD = exports.CURSOR_MDC_FRONTMATTER = exports.FRAIM_LAUNCH_PHRASE = void 0;
|
|
4
|
+
exports.buildFraimInvocationBody = buildFraimInvocationBody;
|
|
4
5
|
exports.buildClaudeSkillContent = buildClaudeSkillContent;
|
|
5
6
|
exports.buildClaudeCommandShimContent = buildClaudeCommandShimContent;
|
|
6
7
|
exports.buildClaudeSlashCommandContent = buildClaudeSlashCommandContent;
|
|
@@ -8,15 +9,52 @@ exports.buildCursorMentionRuleContent = buildCursorMentionRuleContent;
|
|
|
8
9
|
exports.buildCodexSkillContent = buildCodexSkillContent;
|
|
9
10
|
exports.buildWindsurfCommandContent = buildWindsurfCommandContent;
|
|
10
11
|
exports.buildKiroCommandContent = buildKiroCommandContent;
|
|
12
|
+
exports.buildGeminiCommandContent = buildGeminiCommandContent;
|
|
11
13
|
exports.describeInvocationSurface = describeInvocationSurface;
|
|
12
14
|
exports.FRAIM_LAUNCH_PHRASE = 'Use FRAIM for <job or task>';
|
|
13
15
|
exports.CURSOR_MDC_FRONTMATTER = `---
|
|
14
16
|
description: FRAIM discovery and execution contract
|
|
15
17
|
alwaysApply: true
|
|
16
18
|
---`;
|
|
17
|
-
exports.
|
|
19
|
+
exports.FRAIM_DEFERRED_TOOL_PRELOAD = [
|
|
20
|
+
'fraim_connect',
|
|
21
|
+
'list_fraim_jobs',
|
|
22
|
+
'get_fraim_job',
|
|
23
|
+
'get_fraim_file',
|
|
24
|
+
'seekMentoring'
|
|
25
|
+
];
|
|
26
|
+
function buildDeferredToolBootstrapSection(profile) {
|
|
27
|
+
if (profile === 'none') {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
const preloadList = exports.FRAIM_DEFERRED_TOOL_PRELOAD.map((toolName) => `\`${toolName}\``).join(', ');
|
|
31
|
+
switch (profile) {
|
|
32
|
+
case 'claude-toolsearch':
|
|
33
|
+
return `0. **Preload deferred FRAIM tools when needed**:
|
|
34
|
+
- If FRAIM MCP tools are unavailable because this host lazily loads deferred tool schemas, call \`ToolSearch\` once to load ${preloadList}.
|
|
35
|
+
- Do the preload as one batched discovery step, not one search per tool.
|
|
36
|
+
|
|
37
|
+
`;
|
|
38
|
+
case 'codex-tool-search':
|
|
39
|
+
return `0. **Preload deferred FRAIM tools when needed**:
|
|
40
|
+
- If FRAIM MCP tools are unavailable because this host lazily loads deferred tool schemas, call \`tool_search\` once to load ${preloadList}.
|
|
41
|
+
- Do the preload as one batched discovery step, not one search per tool.
|
|
42
|
+
|
|
43
|
+
`;
|
|
44
|
+
case 'generic-tool-discovery':
|
|
45
|
+
return `0. **Preload deferred FRAIM tools when needed**:
|
|
46
|
+
- If FRAIM MCP tools are unavailable because this host lazily loads deferred tool schemas, use the host's tool discovery surface once to load ${preloadList}.
|
|
47
|
+
- Do the preload as one batched discovery step, not one search per tool.
|
|
48
|
+
|
|
49
|
+
`;
|
|
50
|
+
default:
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function buildFraimInvocationBody(profile = 'none') {
|
|
55
|
+
return `Follow this process:
|
|
18
56
|
|
|
19
|
-
1. **If the user did not specify a FRAIM job or topic**:
|
|
57
|
+
${buildDeferredToolBootstrapSection(profile)}1. **If the user did not specify a FRAIM job or topic**:
|
|
20
58
|
Call \`list_fraim_jobs()\` to discover available jobs. Present the results grouped by the categories returned by the server. For each group, list 3-5 of the most relevant jobs with a one-line description.
|
|
21
59
|
|
|
22
60
|
2. **Find the match**:
|
|
@@ -27,20 +65,22 @@ exports.FRAIM_INVOCATION_BODY = `Follow this process:
|
|
|
27
65
|
- For skills, use the content returned by \`get_fraim_file(...)\`.
|
|
28
66
|
|
|
29
67
|
4. **Execute**:
|
|
30
|
-
- For jobs, follow the phased instructions and use \`seekMentoring\` when the job requires phase transitions.
|
|
31
|
-
- For skills, apply the skill steps directly to the user's current context.
|
|
68
|
+
- For jobs, follow the phased instructions and use \`seekMentoring\` when the job requires phase transitions.
|
|
69
|
+
- For skills, apply the skill steps directly to the user's current context.
|
|
32
70
|
`;
|
|
71
|
+
}
|
|
72
|
+
exports.FRAIM_INVOCATION_BODY = buildFraimInvocationBody();
|
|
33
73
|
function buildClaudeSkillContent() {
|
|
34
|
-
return `# FRAIM
|
|
35
|
-
|
|
36
|
-
${
|
|
74
|
+
return `# FRAIM
|
|
75
|
+
|
|
76
|
+
${buildFraimInvocationBody('claude-toolsearch')}`;
|
|
37
77
|
}
|
|
38
78
|
function buildClaudeCommandShimContent() {
|
|
39
|
-
return `# FRAIM Compatibility Command
|
|
40
|
-
|
|
41
|
-
Use the FRAIM skill when Claude exposes skills directly. This compatibility command keeps \`/fraim\` working on surfaces that still discover legacy command files.
|
|
42
|
-
|
|
43
|
-
${
|
|
79
|
+
return `# FRAIM Compatibility Command
|
|
80
|
+
|
|
81
|
+
Use the FRAIM skill when Claude exposes skills directly. This compatibility command keeps \`/fraim\` working on surfaces that still discover legacy command files.
|
|
82
|
+
|
|
83
|
+
${buildFraimInvocationBody('claude-toolsearch')}`;
|
|
44
84
|
}
|
|
45
85
|
function buildClaudeSlashCommandContent() {
|
|
46
86
|
return buildClaudeCommandShimContent();
|
|
@@ -50,23 +90,34 @@ function buildCursorMentionRuleContent() {
|
|
|
50
90
|
|
|
51
91
|
# FRAIM
|
|
52
92
|
|
|
53
|
-
${
|
|
93
|
+
${buildFraimInvocationBody('generic-tool-discovery')}
|
|
54
94
|
`;
|
|
55
95
|
}
|
|
56
96
|
function buildCodexSkillContent() {
|
|
57
97
|
return `# FRAIM
|
|
58
98
|
|
|
59
|
-
${
|
|
99
|
+
${buildFraimInvocationBody('codex-tool-search')}`;
|
|
60
100
|
}
|
|
61
101
|
function buildWindsurfCommandContent() {
|
|
62
102
|
return `# FRAIM
|
|
63
103
|
|
|
64
|
-
${
|
|
104
|
+
${buildFraimInvocationBody('generic-tool-discovery')}`;
|
|
65
105
|
}
|
|
66
106
|
function buildKiroCommandContent() {
|
|
67
107
|
return `# FRAIM
|
|
68
108
|
|
|
69
|
-
${
|
|
109
|
+
${buildFraimInvocationBody('generic-tool-discovery')}`;
|
|
110
|
+
}
|
|
111
|
+
function escapeTomlMultiline(value) {
|
|
112
|
+
return value.replace(/"""/g, '\\"""');
|
|
113
|
+
}
|
|
114
|
+
function buildGeminiCommandContent() {
|
|
115
|
+
return `description = "Discover and execute FRAIM jobs and skills"
|
|
116
|
+
prompt = """
|
|
117
|
+
# FRAIM
|
|
118
|
+
|
|
119
|
+
${escapeTomlMultiline(buildFraimInvocationBody('generic-tool-discovery'))}
|
|
120
|
+
"""`;
|
|
70
121
|
}
|
|
71
122
|
function describeInvocationSurface(ideName, invocationProfile) {
|
|
72
123
|
switch (invocationProfile) {
|
|
@@ -86,6 +137,8 @@ function describeInvocationSurface(ideName, invocationProfile) {
|
|
|
86
137
|
return `${ideName}: /fraim`;
|
|
87
138
|
case 'kiro-hashtag':
|
|
88
139
|
return `${ideName}: #fraim`;
|
|
140
|
+
case 'gemini-command':
|
|
141
|
+
return `${ideName}: /fraim`;
|
|
89
142
|
case 'instructions-only':
|
|
90
143
|
default:
|
|
91
144
|
return `${ideName}: natural language`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateVSCodeMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeCodeMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = exports.mergeTomlMCPServers = exports.extractTomlMcpServerBlock = void 0;
|
|
3
|
+
exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateGeminiCliMCPServers = exports.generateVSCodeMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeCodeMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = exports.mergeTomlMCPServers = exports.extractTomlMcpServerBlock = void 0;
|
|
4
4
|
const mcp_server_builder_1 = require("../mcp/mcp-server-builder");
|
|
5
5
|
const ide_formats_1 = require("../mcp/ide-formats");
|
|
6
6
|
const normalizeTokens = (tokenInput) => {
|
|
@@ -160,6 +160,15 @@ const generateVSCodeMCPServers = async (fraimKey, tokenInput, providerConfigs) =
|
|
|
160
160
|
return format.transform(builder.getServers());
|
|
161
161
|
};
|
|
162
162
|
exports.generateVSCodeMCPServers = generateVSCodeMCPServers;
|
|
163
|
+
const generateGeminiCliMCPServers = async (fraimKey, tokenInput, providerConfigs) => {
|
|
164
|
+
const tokens = normalizeTokens(tokenInput);
|
|
165
|
+
const builder = new mcp_server_builder_1.MCPServerBuilder();
|
|
166
|
+
builder.addBaseServers(fraimKey);
|
|
167
|
+
await addProviderServers(builder, tokens, providerConfigs);
|
|
168
|
+
const format = (0, ide_formats_1.getIDEFormat)('gemini-cli');
|
|
169
|
+
return format.transform(builder.getServers());
|
|
170
|
+
};
|
|
171
|
+
exports.generateGeminiCliMCPServers = generateGeminiCliMCPServers;
|
|
163
172
|
const generateWindsurfMCPServers = async (fraimKey, tokenInput, providerConfigs) => {
|
|
164
173
|
const tokens = normalizeTokens(tokenInput);
|
|
165
174
|
const builder = new mcp_server_builder_1.MCPServerBuilder();
|
|
@@ -181,6 +190,8 @@ const generateMCPConfig = async (configType, fraimKey, tokenInput, providerConfi
|
|
|
181
190
|
return await (0, exports.generateKiroMCPServers)(fraimKey, tokenInput, providerConfigs);
|
|
182
191
|
case 'vscode':
|
|
183
192
|
return await (0, exports.generateVSCodeMCPServers)(fraimKey, tokenInput, providerConfigs);
|
|
193
|
+
case 'gemini-cli':
|
|
194
|
+
return await (0, exports.generateGeminiCliMCPServers)(fraimKey, tokenInput, providerConfigs);
|
|
184
195
|
case 'codex':
|
|
185
196
|
return await (0, exports.generateCodexMCPServers)(fraimKey, tokenInput, providerConfigs);
|
|
186
197
|
case 'windsurf':
|
|
@@ -15,6 +15,8 @@ const CLAUDE_FRAIM_COMMAND_PATH = path_1.default.join('.claude', 'commands', 'fr
|
|
|
15
15
|
const CLAUDE_FRAIM_SKILL_PATH = path_1.default.join('.claude', 'skills', 'fraim', 'SKILL.md');
|
|
16
16
|
const VSCODE_FRAIM_PROMPT_PATH = path_1.default.join('.github', 'prompts', 'fraim.prompt.md');
|
|
17
17
|
const CODEX_FRAIM_SKILL_PATH = path_1.default.join('.codex', 'skills', 'fraim', 'SKILL.md');
|
|
18
|
+
const GEMINI_FRAIM_COMMAND_PATH = path_1.default.join('.gemini', 'commands', 'fraim.toml');
|
|
19
|
+
const GEMINI_PROJECT_INSTRUCTIONS_PATH = path_1.default.join('.gemini', 'GEMINI.md');
|
|
18
20
|
const WINDSURF_FRAIM_COMMAND_PATH = path_1.default.join('.windsurf', 'commands', 'fraim.md');
|
|
19
21
|
const KIRO_FRAIM_COMMAND_PATH = path_1.default.join('.kiro', 'commands', 'fraim.md');
|
|
20
22
|
function buildManagedSection(body) {
|
|
@@ -74,7 +76,7 @@ This repository uses FRAIM.
|
|
|
74
76
|
const cursorManagedBody = buildManagedSection(`
|
|
75
77
|
# FRAIM
|
|
76
78
|
|
|
77
|
-
${ide_invocation_surfaces_1.
|
|
79
|
+
${(0, ide_invocation_surfaces_1.buildFraimInvocationBody)('generic-tool-discovery')}
|
|
78
80
|
`);
|
|
79
81
|
const copilotBody = buildManagedSection(`
|
|
80
82
|
## FRAIM
|
|
@@ -101,7 +103,11 @@ Use the stubs here to discover which FRAIM job, skill, or rule is relevant, then
|
|
|
101
103
|
`;
|
|
102
104
|
const vscodePrompt = `# FRAIM
|
|
103
105
|
|
|
104
|
-
${ide_invocation_surfaces_1.
|
|
106
|
+
${(0, ide_invocation_surfaces_1.buildFraimInvocationBody)('generic-tool-discovery')}`;
|
|
107
|
+
const geminiProjectInstructions = `# Gemini Project Instructions
|
|
108
|
+
|
|
109
|
+
@../AGENTS.md
|
|
110
|
+
`;
|
|
105
111
|
return [
|
|
106
112
|
{ path: 'AGENTS.md', content: markdownBody },
|
|
107
113
|
{ path: 'CLAUDE.md', content: markdownBody },
|
|
@@ -112,6 +118,8 @@ ${ide_invocation_surfaces_1.FRAIM_INVOCATION_BODY}`;
|
|
|
112
118
|
{ path: CLAUDE_FRAIM_SKILL_PATH, content: (0, ide_invocation_surfaces_1.buildClaudeSkillContent)() },
|
|
113
119
|
{ path: CLAUDE_FRAIM_COMMAND_PATH, content: (0, ide_invocation_surfaces_1.buildClaudeCommandShimContent)() },
|
|
114
120
|
{ path: CODEX_FRAIM_SKILL_PATH, content: (0, ide_invocation_surfaces_1.buildCodexSkillContent)() },
|
|
121
|
+
{ path: GEMINI_FRAIM_COMMAND_PATH, content: (0, ide_invocation_surfaces_1.buildGeminiCommandContent)() },
|
|
122
|
+
{ path: GEMINI_PROJECT_INSTRUCTIONS_PATH, content: geminiProjectInstructions },
|
|
115
123
|
{ path: WINDSURF_FRAIM_COMMAND_PATH, content: (0, ide_invocation_surfaces_1.buildWindsurfCommandContent)() },
|
|
116
124
|
{ path: KIRO_FRAIM_COMMAND_PATH, content: (0, ide_invocation_surfaces_1.buildKiroCommandContent)() }
|
|
117
125
|
];
|
|
@@ -132,6 +140,8 @@ function ensureAgentAdapterFiles(projectRoot) {
|
|
|
132
140
|
|| file.path === CLAUDE_FRAIM_SKILL_PATH
|
|
133
141
|
|| file.path === CLAUDE_FRAIM_COMMAND_PATH
|
|
134
142
|
|| file.path === CODEX_FRAIM_SKILL_PATH
|
|
143
|
+
|| file.path === GEMINI_FRAIM_COMMAND_PATH
|
|
144
|
+
|| file.path === GEMINI_PROJECT_INSTRUCTIONS_PATH
|
|
135
145
|
|| file.path === WINDSURF_FRAIM_COMMAND_PATH
|
|
136
146
|
|| file.path === KIRO_FRAIM_COMMAND_PATH
|
|
137
147
|
? file.content
|
|
@@ -8,6 +8,7 @@ exports.recordPathStatus = recordPathStatus;
|
|
|
8
8
|
exports.buildInitProjectSummary = buildInitProjectSummary;
|
|
9
9
|
exports.printInitProjectSummary = printInitProjectSummary;
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const ONBOARDING_VIDEO_PLAYLIST_URL = 'https://fraimworks.ai/resources.html#videos';
|
|
11
12
|
function formatModeLabel(mode) {
|
|
12
13
|
switch (mode) {
|
|
13
14
|
case 'conversational':
|
|
@@ -21,11 +22,11 @@ function formatModeLabel(mode) {
|
|
|
21
22
|
function getModeSpecificNextStep(mode) {
|
|
22
23
|
switch (mode) {
|
|
23
24
|
case 'conversational':
|
|
24
|
-
return
|
|
25
|
+
return `The agent will focus on project context, validation commands, and durable repo rules. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
|
|
25
26
|
case 'split':
|
|
26
|
-
return
|
|
27
|
+
return `The agent will confirm the code-host and issue-tracker split, then ask only for the missing project details. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
|
|
27
28
|
default:
|
|
28
|
-
return
|
|
29
|
+
return `The agent will review the detected repo setup, then ask only for the highest-value missing project details. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
function createInitProjectResult(projectName, mode) {
|
|
@@ -26,7 +26,11 @@ exports.validateQualityEvidence = validateQualityEvidence;
|
|
|
26
26
|
exports.buildQualityRejectionMessage = buildQualityRejectionMessage;
|
|
27
27
|
exports.QUALITY_REGISTRY = {
|
|
28
28
|
// Customer Development
|
|
29
|
-
|
|
29
|
+
// review-customer-development is the sole quality emitter for this stage.
|
|
30
|
+
// process-interview-notes is retained here for stage mapping only; it does
|
|
31
|
+
// not emit (enforced: false).
|
|
32
|
+
'process-interview-notes': { stage: 'customer-development', enforced: false },
|
|
33
|
+
'review-customer-development': { stage: 'customer-development', enforced: true, telemetryKind: 'score' },
|
|
30
34
|
'triage-customer-needs': { stage: 'customer-development', enforced: true, telemetryKind: 'gate' },
|
|
31
35
|
'interview-preparation': { stage: 'customer-discovery', enforced: false },
|
|
32
36
|
// Business Strategy
|
|
@@ -39,6 +43,7 @@ exports.QUALITY_REGISTRY = {
|
|
|
39
43
|
'test-quality-assessment': { stage: 'test-quality', enforced: true, telemetryKind: 'score' },
|
|
40
44
|
// Security
|
|
41
45
|
'security-review': { stage: 'security', enforced: true, telemetryKind: 'score' },
|
|
46
|
+
'production-readiness-review': { stage: 'production-readiness', enforced: true, telemetryKind: 'score' },
|
|
42
47
|
// Fundraising
|
|
43
48
|
'investor-pitch-preparation': { stage: 'fundraising', enforced: false },
|
|
44
49
|
// Go-to-Market
|
|
@@ -73,6 +78,7 @@ exports.STAGE_DISPLAY_NAMES = {
|
|
|
73
78
|
'product-quality': 'Product Quality',
|
|
74
79
|
'test-quality': 'Test Quality',
|
|
75
80
|
'security': 'Security',
|
|
81
|
+
'production-readiness': 'Production Readiness',
|
|
76
82
|
'fundraising': 'Fundraising',
|
|
77
83
|
'go-to-market': 'Go-to-Market',
|
|
78
84
|
};
|
|
@@ -86,6 +92,7 @@ exports.ALL_STAGE_CATEGORIES = [
|
|
|
86
92
|
'product-quality',
|
|
87
93
|
'test-quality',
|
|
88
94
|
'security',
|
|
95
|
+
'production-readiness',
|
|
89
96
|
'fundraising',
|
|
90
97
|
'go-to-market',
|
|
91
98
|
];
|
|
@@ -114,20 +121,85 @@ const GATE_REQUIRED_FIELDS = [
|
|
|
114
121
|
const UNIVERSAL_REQUIRED_FIELDS = [
|
|
115
122
|
{ path: 'composite', type: 'number' },
|
|
116
123
|
];
|
|
124
|
+
const SCORE_DIMENSION_REQUIRED_FIELDS = [
|
|
125
|
+
{ suffix: 'score', type: 'number' },
|
|
126
|
+
{ suffix: 'rationale', type: 'string' }
|
|
127
|
+
];
|
|
128
|
+
const QUALITY_SCORE_DIMENSIONS = {
|
|
129
|
+
'review-customer-development': [
|
|
130
|
+
'icpCoherence',
|
|
131
|
+
'interviewCoverage',
|
|
132
|
+
'evidenceQuality',
|
|
133
|
+
'patternSaturation',
|
|
134
|
+
'signalToProduct'
|
|
135
|
+
],
|
|
136
|
+
'review-business-strategy': [
|
|
137
|
+
'marketEvidence',
|
|
138
|
+
'competitiveRigor',
|
|
139
|
+
'unitEconomics',
|
|
140
|
+
'strategicCoherence'
|
|
141
|
+
],
|
|
142
|
+
'branding-quality-audit': [
|
|
143
|
+
'clarity',
|
|
144
|
+
'differentiation',
|
|
145
|
+
'coherence',
|
|
146
|
+
'proof',
|
|
147
|
+
'identityExpressiveness',
|
|
148
|
+
'governanceReadiness'
|
|
149
|
+
],
|
|
150
|
+
'code-quality-assessment': [
|
|
151
|
+
'typeSafety',
|
|
152
|
+
'errorHandling',
|
|
153
|
+
'architecture',
|
|
154
|
+
'maintainability'
|
|
155
|
+
],
|
|
156
|
+
'test-quality-assessment': [
|
|
157
|
+
'coverage',
|
|
158
|
+
'testIntegrity',
|
|
159
|
+
'testDesign',
|
|
160
|
+
'reliability'
|
|
161
|
+
],
|
|
162
|
+
'security-review': [
|
|
163
|
+
'findingSeverity',
|
|
164
|
+
'remediationReadiness',
|
|
165
|
+
'coverageCompleteness'
|
|
166
|
+
],
|
|
167
|
+
'production-readiness-review': [
|
|
168
|
+
'securityPosture',
|
|
169
|
+
'availabilityResilience',
|
|
170
|
+
'backupRestore',
|
|
171
|
+
'observabilityOps',
|
|
172
|
+
'releaseSafety',
|
|
173
|
+
'governanceRunbooks'
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
function requiredDimensionFields(jobName) {
|
|
177
|
+
const dimensions = QUALITY_SCORE_DIMENSIONS[jobName] ?? [];
|
|
178
|
+
return dimensions.flatMap((dimension) => SCORE_DIMENSION_REQUIRED_FIELDS.map(({ suffix, type }) => ({
|
|
179
|
+
path: `${dimension}.${suffix}`,
|
|
180
|
+
type
|
|
181
|
+
})));
|
|
182
|
+
}
|
|
117
183
|
/**
|
|
118
184
|
* Returns the required quality fields for a given job.
|
|
119
|
-
*
|
|
120
|
-
*
|
|
185
|
+
* Gate-only jobs use the gate schema. The strict V1 interview schema is
|
|
186
|
+
* pinned to process-interview-notes for defense-in-depth on the
|
|
187
|
+
* /api/analytics/quality-score endpoint. Scored quality review jobs require
|
|
188
|
+
* their stage-specific rubric dimensions as direct `{ score, rationale }`
|
|
189
|
+
* properties so the Quality view can render those dimensions dynamically.
|
|
121
190
|
*/
|
|
122
191
|
function getRequiredFieldsForJob(jobName) {
|
|
123
192
|
const entry = exports.QUALITY_REGISTRY[jobName];
|
|
124
193
|
if (entry?.telemetryKind === 'gate') {
|
|
125
194
|
return GATE_REQUIRED_FIELDS;
|
|
126
195
|
}
|
|
127
|
-
if (
|
|
196
|
+
if (jobName === 'process-interview-notes') {
|
|
128
197
|
return CUSTOMER_DEV_REQUIRED_FIELDS;
|
|
129
198
|
}
|
|
130
|
-
return
|
|
199
|
+
return [
|
|
200
|
+
...UNIVERSAL_REQUIRED_FIELDS,
|
|
201
|
+
...requiredDimensionFields(jobName)
|
|
202
|
+
];
|
|
131
203
|
}
|
|
132
204
|
/**
|
|
133
205
|
* @deprecated Use getRequiredFieldsForJob() for stage-aware validation.
|
|
@@ -188,6 +260,9 @@ function buildQualityRejectionMessage(jobName, currentPhase, errors) {
|
|
|
188
260
|
const entry = exports.QUALITY_REGISTRY[jobName];
|
|
189
261
|
const isGateOnly = entry?.telemetryKind === 'gate';
|
|
190
262
|
const isCustomerDev = entry?.stage === 'customer-development';
|
|
263
|
+
const dimensionExamples = (QUALITY_SCORE_DIMENSIONS[jobName] ?? ['marketEvidence', 'unitEconomics'])
|
|
264
|
+
.map((dimension) => ` ${dimension}: { score: <number>, rationale: "<string>" },`)
|
|
265
|
+
.join('\n');
|
|
191
266
|
const schemaExample = isGateOnly
|
|
192
267
|
? [
|
|
193
268
|
'```javascript',
|
|
@@ -226,9 +301,7 @@ function buildQualityRejectionMessage(jobName, currentPhase, errors) {
|
|
|
226
301
|
' quality: {',
|
|
227
302
|
' composite: <number 0-10>,',
|
|
228
303
|
' coaching: "<actionable recommendation>",',
|
|
229
|
-
|
|
230
|
-
' unitEconomics: { score: <number>, rationale: "<string>" },',
|
|
231
|
-
' // add other sub-scores as direct properties',
|
|
304
|
+
dimensionExamples,
|
|
232
305
|
' }',
|
|
233
306
|
'}',
|
|
234
307
|
'```'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isExplicitLocalRepoPath = isExplicitLocalRepoPath;
|
|
3
4
|
exports.getPort = getPort;
|
|
4
5
|
exports.determineDatabaseName = determineDatabaseName;
|
|
5
6
|
exports.getCurrentGitBranch = getCurrentGitBranch;
|
|
@@ -7,6 +8,30 @@ exports.determineSchema = determineSchema;
|
|
|
7
8
|
exports.getDefaultBranch = getDefaultBranch;
|
|
8
9
|
exports.sanitizeRepoIdentifier = sanitizeRepoIdentifier;
|
|
9
10
|
const child_process_1 = require("child_process");
|
|
11
|
+
function extractLocalFolderLabel(rawPath) {
|
|
12
|
+
const normalized = rawPath
|
|
13
|
+
.trim()
|
|
14
|
+
.replace(/^file:\/\//i, '')
|
|
15
|
+
.replace(/\\/g, '/')
|
|
16
|
+
.replace(/\/+$/, '');
|
|
17
|
+
if (!normalized)
|
|
18
|
+
return undefined;
|
|
19
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
20
|
+
const candidate = parts[parts.length - 1];
|
|
21
|
+
return candidate || undefined;
|
|
22
|
+
}
|
|
23
|
+
function isExplicitLocalRepoPath(repoUrl) {
|
|
24
|
+
if (!repoUrl || typeof repoUrl !== 'string') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const trimmed = repoUrl.trim();
|
|
28
|
+
return (trimmed.startsWith('/') ||
|
|
29
|
+
trimmed.startsWith('\\') ||
|
|
30
|
+
/^[a-zA-Z]:[\\/]/.test(trimmed) ||
|
|
31
|
+
trimmed.startsWith('./') ||
|
|
32
|
+
trimmed.startsWith('../') ||
|
|
33
|
+
trimmed.startsWith('file://'));
|
|
34
|
+
}
|
|
10
35
|
/**
|
|
11
36
|
* Gets a unique port based on the current git branch name (if it's an issue branch)
|
|
12
37
|
* Default to 15302 if not on an issue branch
|
|
@@ -106,13 +131,9 @@ function sanitizeRepoIdentifier(repoUrl) {
|
|
|
106
131
|
return undefined;
|
|
107
132
|
}
|
|
108
133
|
const trimmed = repoUrl.trim();
|
|
109
|
-
//
|
|
110
|
-
if (trimmed
|
|
111
|
-
trimmed
|
|
112
|
-
/^[a-zA-Z]:\\/.test(trimmed) ||
|
|
113
|
-
trimmed.startsWith('./') ||
|
|
114
|
-
trimmed.startsWith('../')) {
|
|
115
|
-
return undefined;
|
|
134
|
+
// Convert local paths to just the folder name for privacy-safe workspace labeling.
|
|
135
|
+
if (isExplicitLocalRepoPath(trimmed)) {
|
|
136
|
+
return extractLocalFolderLabel(trimmed);
|
|
116
137
|
}
|
|
117
138
|
// Normalize GitHub/GitLab SSH and HTTPS URLs
|
|
118
139
|
// Patterns:
|
|
@@ -144,5 +165,9 @@ function sanitizeRepoIdentifier(repoUrl) {
|
|
|
144
165
|
catch (e) {
|
|
145
166
|
// Not a valid URL
|
|
146
167
|
}
|
|
168
|
+
// Preserve plain folder labels that are already privacy-safe and not path-like.
|
|
169
|
+
if (!trimmed.includes('/') && !trimmed.includes('\\') && !trimmed.includes('://')) {
|
|
170
|
+
return trimmed;
|
|
171
|
+
}
|
|
147
172
|
return undefined;
|
|
148
173
|
}
|