fraim-hub 2.0.204
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +467 -0
- package/bin/fraim-hub.js +17 -0
- package/dist/src/ai-hub/brand-store.js +17 -0
- package/dist/src/ai-hub/catalog.js +412 -0
- package/dist/src/ai-hub/cert-store.js +70 -0
- package/dist/src/ai-hub/cli.js +212 -0
- package/dist/src/ai-hub/configured-agents.js +283 -0
- package/dist/src/ai-hub/conversation-store.js +428 -0
- package/dist/src/ai-hub/desktop-main.js +449 -0
- package/dist/src/ai-hub/hosts.js +1669 -0
- package/dist/src/ai-hub/hub-latest-version.js +52 -0
- package/dist/src/ai-hub/hub-launch-decision.js +57 -0
- package/dist/src/ai-hub/hub-runtime-file.js +43 -0
- package/dist/src/ai-hub/managed-browser.js +269 -0
- package/dist/src/ai-hub/manager-turns.js +64 -0
- package/dist/src/ai-hub/office-sideload.js +156 -0
- package/dist/src/ai-hub/openclaw-bridge.js +250 -0
- package/dist/src/ai-hub/preferences.js +201 -0
- package/dist/src/ai-hub/remote-hub-gateway.js +88 -0
- package/dist/src/ai-hub/server.js +4013 -0
- package/dist/src/ai-hub/types.js +2 -0
- package/dist/src/ai-hub/url-safety.js +19 -0
- package/dist/src/api/admin/payments.js +33 -0
- package/dist/src/api/admin/sales-leads.js +21 -0
- package/dist/src/api/payment/create-session.js +338 -0
- package/dist/src/api/payment/dashboard-link.js +149 -0
- package/dist/src/api/payment/session-details.js +31 -0
- package/dist/src/api/payment/webhook.js +587 -0
- package/dist/src/api/sales/contact.js +44 -0
- package/dist/src/cli/api/get-provider-client.js +41 -0
- package/dist/src/cli/api/provider-client.js +107 -0
- package/dist/src/cli/commands/add-ide.js +462 -0
- package/dist/src/cli/fraim-hub.js +50 -0
- package/dist/src/cli/internal/device-flow-service.js +83 -0
- package/dist/src/cli/mcp/command-resolution.js +81 -0
- package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +136 -0
- package/dist/src/cli/mcp/ide-formats.js +317 -0
- package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
- package/dist/src/cli/mcp/mcp-server-registry.js +173 -0
- package/dist/src/cli/providers/local-provider-registry.js +165 -0
- package/dist/src/cli/providers/provider-registry.js +230 -0
- package/dist/src/cli/setup/claude-code-telemetry.js +59 -0
- package/dist/src/cli/setup/codex-local-config.js +37 -0
- package/dist/src/cli/setup/ide-detector.js +383 -0
- package/dist/src/cli/setup/ide-global-integration.js +100 -0
- package/dist/src/cli/setup/ide-invocation-surfaces.js +160 -0
- package/dist/src/cli/setup/mcp-config-generator.js +225 -0
- package/dist/src/cli/setup/provider-prompts.js +339 -0
- package/dist/src/cli/utils/managed-agent-paths.js +118 -0
- package/dist/src/cli/utils/script-sync-utils.js +221 -0
- package/dist/src/cli/utils/user-config.js +100 -0
- package/dist/src/cli/utils/version-utils.js +35 -0
- package/dist/src/config/ai-manager-hiring.js +121 -0
- package/dist/src/config/feature-flags.js +25 -0
- package/dist/src/config/persona-capability-bundles.js +277 -0
- package/dist/src/config/persona-hiring.js +270 -0
- package/dist/src/config/portfolio-slug-overrides.js +17 -0
- package/dist/src/config/pricing.js +37 -0
- package/dist/src/config/stripe.js +43 -0
- package/dist/src/core/brand-store.js +232 -0
- package/dist/src/core/quality-evidence.js +331 -0
- package/dist/src/core/utils/git-utils.js +194 -0
- package/dist/src/core/utils/ports.js +32 -0
- package/dist/src/core/utils/project-fraim-paths.js +220 -0
- package/dist/src/db/payment-repository.js +61 -0
- package/dist/src/first-run/types.js +93 -0
- package/dist/src/fraim/db-service.js +2409 -0
- package/dist/src/local-mcp-server/agent-token-prices.js +167 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +975 -0
- package/dist/src/middleware/rate-limit.js +110 -0
- package/dist/src/models/payment.js +2 -0
- package/dist/src/routes/payment-routes.js +186 -0
- package/dist/src/routes/persona-catalog-routes.js +84 -0
- package/dist/src/services/dashboard-access.js +27 -0
- package/dist/src/services/email-service.js +951 -0
- package/dist/src/services/persona-entitlement-service.js +352 -0
- package/dist/src/services/workspace-identity.js +21 -0
- package/dist/src/types/analytics.js +2 -0
- package/dist/src/utils/payment-calculator.js +52 -0
- package/extensions/office-word/favicon.ico +0 -0
- package/extensions/office-word/icon-64.png +0 -0
- package/extensions/office-word/manifest.xml +33 -0
- package/extensions/office-word/taskpane.html +242 -0
- package/index.js +85 -0
- package/package.json +106 -0
- package/public/ai-hub/index.html +910 -0
- package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
- package/public/ai-hub/powerpoint-taskpane/index.html +236 -0
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +30 -0
- package/public/ai-hub/review.css +444 -0
- package/public/ai-hub/script.js +11737 -0
- package/public/ai-hub/styles.css +5102 -0
- package/public/first-run/error-frame.js +100 -0
- package/public/first-run/index.html +35 -0
- package/public/first-run/script.js +739 -0
- package/public/first-run/styles.css +929 -0
- package/public/portfolio/ashley.html +523 -0
- package/public/portfolio/auditya.html +83 -0
- package/public/portfolio/banke.html +83 -0
- package/public/portfolio/beza.html +659 -0
- package/public/portfolio/careena.html +632 -0
- package/public/portfolio/casey.html +568 -0
- package/public/portfolio/celia.html +490 -0
- package/public/portfolio/deidre.html +642 -0
- package/public/portfolio/gautam.html +597 -0
- package/public/portfolio/hari.html +469 -0
- package/public/portfolio/huxley.html +1354 -0
- package/public/portfolio/index.html +741 -0
- package/public/portfolio/maestro.html +518 -0
- package/public/portfolio/mandy.html +590 -0
- package/public/portfolio/mona.html +597 -0
- package/public/portfolio/pam.html +887 -0
- package/public/portfolio/procella.html +107 -0
- package/public/portfolio/qasm.html +569 -0
- package/public/portfolio/ricardo.html +489 -0
- package/public/portfolio/sade.html +560 -0
- package/public/portfolio/sam.html +654 -0
- package/public/portfolio/sechar.html +580 -0
- package/public/portfolio/sreya.html +599 -0
- package/public/portfolio/swen.html +601 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ProviderClient = void 0;
|
|
7
|
+
// CLI client for fetching provider metadata from server
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
class ProviderClient {
|
|
10
|
+
constructor(fraimKey, serverUrl) {
|
|
11
|
+
this.serverUrl = serverUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
12
|
+
this.client = axios_1.default.create({
|
|
13
|
+
baseURL: this.serverUrl,
|
|
14
|
+
headers: {
|
|
15
|
+
'x-api-key': fraimKey,
|
|
16
|
+
'Content-Type': 'application/json'
|
|
17
|
+
},
|
|
18
|
+
timeout: 10000 // 10 second timeout
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get all providers from server
|
|
23
|
+
*/
|
|
24
|
+
async getAllProviders() {
|
|
25
|
+
try {
|
|
26
|
+
const response = await this.client.get('/api/providers');
|
|
27
|
+
return response.data.providers;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.response) {
|
|
31
|
+
// Server responded with error status
|
|
32
|
+
throw new Error(`Failed to fetch providers: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
|
|
33
|
+
}
|
|
34
|
+
else if (error.request) {
|
|
35
|
+
// Request was made but no response
|
|
36
|
+
throw new Error(`Failed to fetch providers: No response from server`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Something else happened
|
|
40
|
+
throw new Error(`Failed to fetch providers: ${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get a specific provider by ID
|
|
46
|
+
*/
|
|
47
|
+
async getProvider(id) {
|
|
48
|
+
const providers = await this.getAllProviders();
|
|
49
|
+
return providers.find(p => p.id === id) || null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get provider config schema
|
|
53
|
+
*/
|
|
54
|
+
async getProviderSchema(id) {
|
|
55
|
+
try {
|
|
56
|
+
const response = await this.client.get(`/api/providers/${id}/schema`);
|
|
57
|
+
return response.data;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error.response?.status === 404) {
|
|
61
|
+
throw new Error(`Provider '${id}' not found`);
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`Failed to fetch provider schema: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validate provider config
|
|
68
|
+
*/
|
|
69
|
+
async validateProviderConfig(id, config) {
|
|
70
|
+
try {
|
|
71
|
+
const response = await this.client.post(`/api/providers/${id}/validate`, config);
|
|
72
|
+
return response.data;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
throw new Error(`Failed to validate provider config: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get all provider IDs
|
|
80
|
+
*/
|
|
81
|
+
async getAllProviderIds() {
|
|
82
|
+
const providers = await this.getAllProviders();
|
|
83
|
+
return providers.map(p => p.id);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get providers with a specific capability
|
|
87
|
+
*/
|
|
88
|
+
async getProvidersWithCapability(capability) {
|
|
89
|
+
const providers = await this.getAllProviders();
|
|
90
|
+
return providers.filter(p => p.capabilities.includes(capability));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if a provider has a specific capability
|
|
94
|
+
*/
|
|
95
|
+
async providerHasCapability(providerId, capability) {
|
|
96
|
+
const provider = await this.getProvider(providerId);
|
|
97
|
+
return provider ? provider.capabilities.includes(capability) : false;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if provider requires additional config
|
|
101
|
+
*/
|
|
102
|
+
async requiresAdditionalConfig(providerId) {
|
|
103
|
+
const provider = await this.getProvider(providerId);
|
|
104
|
+
return provider?.hasAdditionalConfig || false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.ProviderClient = ProviderClient;
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.saveProviderTokenToConfig = exports.loadGlobalConfig = exports.addIDECommand = exports.runAddIDE = void 0;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
43
|
+
const fs_1 = __importDefault(require("fs"));
|
|
44
|
+
const path_1 = __importDefault(require("path"));
|
|
45
|
+
const ide_detector_1 = require("../setup/ide-detector");
|
|
46
|
+
const mcp_config_generator_1 = require("../setup/mcp-config-generator");
|
|
47
|
+
const codex_local_config_1 = require("../setup/codex-local-config");
|
|
48
|
+
const claude_code_telemetry_1 = require("../setup/claude-code-telemetry");
|
|
49
|
+
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
50
|
+
const mcp_server_registry_1 = require("../mcp/mcp-server-registry");
|
|
51
|
+
const get_provider_client_1 = require("../api/get-provider-client");
|
|
52
|
+
const provider_prompts_1 = require("../setup/provider-prompts");
|
|
53
|
+
const provider_registry_1 = require("../providers/provider-registry");
|
|
54
|
+
const user_config_1 = require("../utils/user-config");
|
|
55
|
+
const loadGlobalConfig = async () => {
|
|
56
|
+
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
57
|
+
if (!fs_1.default.existsSync(globalConfigPath)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
62
|
+
// Support both old and new token format
|
|
63
|
+
const tokens = config.tokens || {};
|
|
64
|
+
// Backward compatibility: map old format to new
|
|
65
|
+
// Only try to fetch provider IDs if we have a valid FRAIM key
|
|
66
|
+
if (config.apiKey) {
|
|
67
|
+
try {
|
|
68
|
+
const client = (0, get_provider_client_1.getProviderClient)();
|
|
69
|
+
const providerIds = await client.getAllProviderIds();
|
|
70
|
+
for (const id of providerIds) {
|
|
71
|
+
const oldKey = `${id}Token`;
|
|
72
|
+
if (config[oldKey] && !tokens[id]) {
|
|
73
|
+
tokens[id] = config[oldKey];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
// If provider client fails (network error, invalid key, etc.),
|
|
79
|
+
// skip backward compatibility mapping and use local fallback
|
|
80
|
+
// This is fine - the config will still work with the new format
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Load all provider configs
|
|
84
|
+
const providerConfigs = {};
|
|
85
|
+
// New format: providerConfigs object
|
|
86
|
+
if (config.providerConfigs) {
|
|
87
|
+
Object.entries(config.providerConfigs).forEach(([key, value]) => {
|
|
88
|
+
const providerId = key.replace('Config', '');
|
|
89
|
+
providerConfigs[providerId] = value;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
fraimKey: config.apiKey,
|
|
94
|
+
tokens,
|
|
95
|
+
mode: config.mode,
|
|
96
|
+
providerConfigs
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
exports.loadGlobalConfig = loadGlobalConfig;
|
|
104
|
+
const promptForProviderTokenIfNeeded = async (providerId, isOptional = false) => {
|
|
105
|
+
const client = (0, get_provider_client_1.getProviderClient)();
|
|
106
|
+
const provider = await client.getProvider(providerId);
|
|
107
|
+
if (!provider)
|
|
108
|
+
return '';
|
|
109
|
+
if (isOptional) {
|
|
110
|
+
console.log(chalk_1.default.yellow(`\nš ${provider.displayName} token (optional for conversational mode)`));
|
|
111
|
+
console.log(chalk_1.default.gray(`${provider.displayName} token enables ${provider.displayName}-specific MCP features.\n`));
|
|
112
|
+
const wantsToken = await (0, prompts_1.default)({
|
|
113
|
+
type: 'confirm',
|
|
114
|
+
name: 'addToken',
|
|
115
|
+
message: `Do you want to add a ${provider.displayName} token?`,
|
|
116
|
+
initial: false
|
|
117
|
+
});
|
|
118
|
+
if (!wantsToken.addToken) {
|
|
119
|
+
console.log(chalk_1.default.blue(`ā¹ļø Skipping ${provider.displayName} token - ${provider.displayName} MCP server will not be configured`));
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.log(chalk_1.default.yellow(`\nš ${provider.displayName} token needed for MCP configuration`));
|
|
125
|
+
console.log(chalk_1.default.gray(`This is required for ${provider.displayName} MCP servers to function properly.\n`));
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
return await (0, provider_prompts_1.promptForProviderToken)(client, providerId);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (isOptional) {
|
|
132
|
+
console.log(chalk_1.default.blue(`ā¹ļø No ${provider.displayName} token provided - ${provider.displayName} MCP server will not be configured`));
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
console.log(chalk_1.default.red(`${provider.displayName} token is required. Exiting.`));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const saveProviderTokenToConfig = async (providerId, token) => {
|
|
140
|
+
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
141
|
+
if (fs_1.default.existsSync(globalConfigPath)) {
|
|
142
|
+
try {
|
|
143
|
+
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
144
|
+
// Use new tokens structure
|
|
145
|
+
if (!config.tokens) {
|
|
146
|
+
config.tokens = {};
|
|
147
|
+
}
|
|
148
|
+
config.tokens[providerId] = token;
|
|
149
|
+
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
|
|
150
|
+
const { getProvider } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
|
|
151
|
+
const provider = await getProvider(providerId);
|
|
152
|
+
console.log(chalk_1.default.green(`ā
${provider?.displayName || providerId} token saved to global config`));
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
const { getProvider } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
|
|
156
|
+
const provider = await getProvider(providerId);
|
|
157
|
+
console.log(chalk_1.default.yellow(`ā ļø Could not save ${provider?.displayName || providerId} token to config`));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
exports.saveProviderTokenToConfig = saveProviderTokenToConfig;
|
|
162
|
+
const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
|
|
163
|
+
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
164
|
+
console.log(chalk_1.default.blue(`š§ Configuring ${ide.name}...`));
|
|
165
|
+
// Create backup if config exists
|
|
166
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
167
|
+
const backupPath = `${configPath}.fraim-backup-${Date.now()}`;
|
|
168
|
+
fs_1.default.copyFileSync(configPath, backupPath);
|
|
169
|
+
console.log(chalk_1.default.gray(` š Backup created: ${backupPath}`));
|
|
170
|
+
}
|
|
171
|
+
// Ensure directory exists
|
|
172
|
+
const configDir = path_1.default.dirname(configPath);
|
|
173
|
+
if (!fs_1.default.existsSync(configDir)) {
|
|
174
|
+
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
175
|
+
console.log(chalk_1.default.gray(` š Created directory: ${configDir}`));
|
|
176
|
+
}
|
|
177
|
+
let existingConfig = {};
|
|
178
|
+
let existingMCPServers = {};
|
|
179
|
+
const serversKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
|
|
180
|
+
if (fs_1.default.existsSync(configPath) && ide.configFormat === 'json') {
|
|
181
|
+
try {
|
|
182
|
+
existingConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
183
|
+
existingMCPServers = existingConfig[serversKey] || {};
|
|
184
|
+
console.log(chalk_1.default.gray(` š Found existing config with ${Object.keys(existingMCPServers).length} MCP servers`));
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
console.log(chalk_1.default.yellow(` ā ļø Could not parse existing ${ide.name} config, creating new one`));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (ide.configFormat === 'toml') {
|
|
191
|
+
// Handle TOML format (e.g., Codex)
|
|
192
|
+
let existingTomlContent = '';
|
|
193
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
194
|
+
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
195
|
+
console.log(chalk_1.default.gray(` š Found existing TOML config`));
|
|
196
|
+
}
|
|
197
|
+
const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, providerConfigs);
|
|
198
|
+
const { getAllMCPServerIds } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
|
|
199
|
+
const baseServerIds = getAllMCPServerIds();
|
|
200
|
+
// Add provider server IDs from tokens
|
|
201
|
+
const providerServerIds = Object.keys(tokens).filter(id => tokens[id]);
|
|
202
|
+
const serversToAdd = [...baseServerIds, ...providerServerIds];
|
|
203
|
+
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
|
|
204
|
+
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
205
|
+
mergeResult.addedServers.forEach(server => {
|
|
206
|
+
console.log(chalk_1.default.green(` ā
Added ${server} MCP server`));
|
|
207
|
+
});
|
|
208
|
+
mergeResult.replacedServers.forEach(server => {
|
|
209
|
+
console.log(chalk_1.default.green(` ā
Updated ${server} MCP server`));
|
|
210
|
+
});
|
|
211
|
+
mergeResult.skippedServers.forEach(server => {
|
|
212
|
+
console.log(chalk_1.default.gray(` āļø Skipped ${server} (already configured or not requested)`));
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// For JSON configs - intelligent merging
|
|
217
|
+
const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, providerConfigs);
|
|
218
|
+
const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
|
|
219
|
+
// Merge MCP servers intelligently
|
|
220
|
+
const mergedMCPServers = { ...existingMCPServers };
|
|
221
|
+
const addedServers = [];
|
|
222
|
+
const updatedServers = [];
|
|
223
|
+
const skippedServers = [];
|
|
224
|
+
const alwaysUpdateServers = new Set(['fraim', 'github', 'gitlab', 'jira', 'ado', 'linear']);
|
|
225
|
+
for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
|
|
226
|
+
if (!existingMCPServers[serverName]) {
|
|
227
|
+
mergedMCPServers[serverName] = serverConfig;
|
|
228
|
+
addedServers.push(serverName);
|
|
229
|
+
}
|
|
230
|
+
else if (alwaysUpdateServers.has(serverName)) {
|
|
231
|
+
mergedMCPServers[serverName] = serverConfig;
|
|
232
|
+
updatedServers.push(serverName);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
skippedServers.push(serverName);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Merge with existing config
|
|
239
|
+
const mergedConfig = {
|
|
240
|
+
...existingConfig,
|
|
241
|
+
...newConfig,
|
|
242
|
+
[serversKey]: mergedMCPServers
|
|
243
|
+
};
|
|
244
|
+
// Write updated config
|
|
245
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
|
|
246
|
+
addedServers.forEach(server => {
|
|
247
|
+
console.log(chalk_1.default.green(` ā
Added ${server} MCP server`));
|
|
248
|
+
});
|
|
249
|
+
updatedServers.forEach(server => {
|
|
250
|
+
console.log(chalk_1.default.blue(` Updated ${server} MCP server`));
|
|
251
|
+
});
|
|
252
|
+
skippedServers.forEach(server => {
|
|
253
|
+
console.log(chalk_1.default.gray(` āļø Skipped ${server} (already exists)`));
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
console.log(chalk_1.default.green(`ā
Updated ${configPath}`));
|
|
257
|
+
// Handle IDE-specific local config (e.g., Codex needs project-level config)
|
|
258
|
+
if (ide.configType === 'codex') {
|
|
259
|
+
const localResult = (0, codex_local_config_1.ensureCodexLocalConfig)(process.cwd());
|
|
260
|
+
const status = localResult.created ? 'Created' : localResult.updated ? 'Updated' : 'Verified';
|
|
261
|
+
console.log(chalk_1.default.green(` ā
${status} local ${ide.name} config: ${localResult.path}`));
|
|
262
|
+
}
|
|
263
|
+
// Enable token telemetry for Claude Code via project-level settings
|
|
264
|
+
if (ide.configType === 'claude-code') {
|
|
265
|
+
(0, claude_code_telemetry_1.ensureClaudeCodeTelemetryEnv)();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const listSupportedIDEs = () => {
|
|
269
|
+
const allIDEs = (0, ide_detector_1.getAllSupportedIDEs)();
|
|
270
|
+
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
271
|
+
console.log(chalk_1.default.blue('š Supported IDEs:\n'));
|
|
272
|
+
allIDEs.forEach(ide => {
|
|
273
|
+
const isDetected = detectedIDEs.some(detected => detected.name === ide.name);
|
|
274
|
+
const statusIcon = isDetected ? 'ā
' : 'ā';
|
|
275
|
+
const statusText = isDetected ? 'detected' : 'not found';
|
|
276
|
+
console.log(chalk_1.default.white(` ${statusIcon} ${ide.name} (${statusText})`));
|
|
277
|
+
console.log(chalk_1.default.gray(` ${ide.description}`));
|
|
278
|
+
console.log(chalk_1.default.gray(` Config: ${ide.configPath}\n`));
|
|
279
|
+
});
|
|
280
|
+
console.log(chalk_1.default.yellow('š” Use "fraim add-ide --ide <name>" to configure a specific IDE'));
|
|
281
|
+
console.log(chalk_1.default.yellow(' Example: fraim add-ide --ide claude-code'));
|
|
282
|
+
console.log(chalk_1.default.yellow(' Anthropic aliases: claude, claude-code, claude-desktop, claude-cowork'));
|
|
283
|
+
console.log(chalk_1.default.yellow(' Gemini aliases: gemini, gemini-cli, gemini cli'));
|
|
284
|
+
console.log(chalk_1.default.yellow(' GitHub Copilot CLI aliases: copilot, copilot-cli, github copilot cli'));
|
|
285
|
+
};
|
|
286
|
+
const promptForIDESelection = async (availableIDEs, tokens) => {
|
|
287
|
+
console.log(chalk_1.default.green(`ā
Found ${availableIDEs.length} IDEs that can be configured:\n`));
|
|
288
|
+
availableIDEs.forEach((ide, index) => {
|
|
289
|
+
const configExists = fs_1.default.existsSync((0, ide_detector_1.expandPath)(ide.configPath));
|
|
290
|
+
const statusIcon = configExists ? 'š' : 'š';
|
|
291
|
+
const statusText = configExists ? 'has config' : 'new config';
|
|
292
|
+
console.log(chalk_1.default.white(` ${index + 1}. ${ide.name} ${statusIcon} (${statusText})`));
|
|
293
|
+
});
|
|
294
|
+
console.log(chalk_1.default.blue('\nFRAIM will add these MCP servers:'));
|
|
295
|
+
// Show base servers
|
|
296
|
+
for (const server of mcp_server_registry_1.BASE_MCP_SERVERS) {
|
|
297
|
+
console.log(chalk_1.default.gray(` ⢠${server.id} (${server.description})`));
|
|
298
|
+
}
|
|
299
|
+
// Show provider servers (only if tokens exist)
|
|
300
|
+
const allProviders = await (0, provider_registry_1.getAllProviders)();
|
|
301
|
+
for (const provider of allProviders) {
|
|
302
|
+
const hasToken = tokens?.[provider.id];
|
|
303
|
+
if (hasToken && provider.mcpServer) {
|
|
304
|
+
console.log(chalk_1.default.gray(` - ${provider.id} (${provider.description})`));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const response = await (0, prompts_1.default)({
|
|
308
|
+
type: 'text',
|
|
309
|
+
name: 'selection',
|
|
310
|
+
message: 'Configure which IDEs? (Enter \'all\' or numbers like \'1,3\')',
|
|
311
|
+
initial: 'all',
|
|
312
|
+
validate: (value) => {
|
|
313
|
+
if (value.toLowerCase() === 'all')
|
|
314
|
+
return true;
|
|
315
|
+
if (value.toLowerCase() === 'none')
|
|
316
|
+
return true;
|
|
317
|
+
const numbers = value.split(',').map(n => parseInt(n.trim()));
|
|
318
|
+
const valid = numbers.every(n => n >= 1 && n <= availableIDEs.length && !isNaN(n));
|
|
319
|
+
return valid || 'Please enter "all", "none", or valid numbers (e.g., "1,3")';
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
if (response.selection.toLowerCase() === 'all') {
|
|
323
|
+
return availableIDEs;
|
|
324
|
+
}
|
|
325
|
+
if (response.selection.toLowerCase() === 'none') {
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
const selectedIndices = response.selection.split(',').map((n) => parseInt(n.trim()) - 1);
|
|
329
|
+
return selectedIndices.map((i) => availableIDEs[i]).filter(Boolean);
|
|
330
|
+
};
|
|
331
|
+
const runAddIDE = async (options) => {
|
|
332
|
+
if (options.list) {
|
|
333
|
+
listSupportedIDEs();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
console.log(chalk_1.default.blue('š§ FRAIM IDE Configuration\n'));
|
|
337
|
+
// Load existing configuration
|
|
338
|
+
const globalConfig = await loadGlobalConfig();
|
|
339
|
+
if (!globalConfig || !globalConfig.fraimKey) {
|
|
340
|
+
console.log(chalk_1.default.red('ā No FRAIM configuration found.'));
|
|
341
|
+
console.log(chalk_1.default.yellow('š” Please run "fraim setup" first to configure your FRAIM keys.'));
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
const platformTokens = globalConfig.tokens || {};
|
|
345
|
+
const isConversationalMode = globalConfig.mode === 'conversational';
|
|
346
|
+
// Check if any provider tokens exist
|
|
347
|
+
const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
|
|
348
|
+
const hasAnyToken = allProviderIds.some(id => platformTokens[id]);
|
|
349
|
+
if (!hasAnyToken && !isConversationalMode && !options.skipTokenPrompts) {
|
|
350
|
+
console.log(chalk_1.default.yellow('ā ļø No provider tokens found in configuration.'));
|
|
351
|
+
// Prompt for first integrated provider as default
|
|
352
|
+
const integratedProviders = await (0, provider_registry_1.getProvidersWithCapability)('integrated');
|
|
353
|
+
const defaultProviderId = integratedProviders[0]?.id;
|
|
354
|
+
if (defaultProviderId) {
|
|
355
|
+
const token = await promptForProviderTokenIfNeeded(defaultProviderId, false);
|
|
356
|
+
if (token) {
|
|
357
|
+
await saveProviderTokenToConfig(defaultProviderId, token);
|
|
358
|
+
platformTokens[defaultProviderId] = token;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (isConversationalMode && !hasAnyToken) {
|
|
363
|
+
console.log(chalk_1.default.blue('ā¹ļø Conversational mode: Configuring MCP without platform integration\n'));
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
console.log(chalk_1.default.green('ā
Using existing FRAIM configuration\n'));
|
|
367
|
+
}
|
|
368
|
+
let idesToConfigure;
|
|
369
|
+
if (options.ide) {
|
|
370
|
+
// Explicit IDE requested: configure it regardless of whether it is
|
|
371
|
+
// currently detected. Use the detected path if available (may differ
|
|
372
|
+
// from the default), otherwise fall back to the default so the config
|
|
373
|
+
// can be pre-staged before the IDE is installed.
|
|
374
|
+
const requestedIDE = (0, ide_detector_1.findIDEByName)(options.ide);
|
|
375
|
+
if (!requestedIDE) {
|
|
376
|
+
console.log(chalk_1.default.red(`ā IDE "${options.ide}" not supported.`));
|
|
377
|
+
console.log(chalk_1.default.yellow('š” Use "fraim add-ide --list" to see supported IDEs.'));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
381
|
+
const detectedIDE = detectedIDEs.find(ide => ide.name === requestedIDE.name);
|
|
382
|
+
idesToConfigure = [detectedIDE || requestedIDE];
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// Auto-detect or interactive selection.
|
|
386
|
+
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
387
|
+
if (detectedIDEs.length === 0) {
|
|
388
|
+
console.log(chalk_1.default.yellow('ā ļø No supported IDEs detected on your system.'));
|
|
389
|
+
console.log(chalk_1.default.gray('Supported IDEs: Claude, Claude Code, Antigravity, Gemini CLI, GitHub Copilot CLI, Kiro, Cursor, VSCode, Codex, Grok, Windsurf'));
|
|
390
|
+
console.log(chalk_1.default.blue('\nš” Install an IDE and run this command again.'));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (options.all) {
|
|
394
|
+
// Configure all detected IDEs
|
|
395
|
+
idesToConfigure = detectedIDEs;
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
// Interactive selection
|
|
399
|
+
idesToConfigure = await promptForIDESelection(detectedIDEs, platformTokens);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (idesToConfigure.length === 0) {
|
|
403
|
+
console.log(chalk_1.default.yellow('ā ļø No IDEs selected for configuration.'));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
console.log(chalk_1.default.blue(`\nš Configuring ${idesToConfigure.length} IDE(s)...\n`));
|
|
407
|
+
const results = {
|
|
408
|
+
successful: [],
|
|
409
|
+
failed: []
|
|
410
|
+
};
|
|
411
|
+
for (const ide of idesToConfigure) {
|
|
412
|
+
try {
|
|
413
|
+
await configureIDEMCP(ide, globalConfig.fraimKey, platformTokens, globalConfig.providerConfigs);
|
|
414
|
+
results.successful.push(ide.name);
|
|
415
|
+
(0, user_config_1.addInstalledIde)((0, ide_detector_1.getAdapterConfigType)(ide));
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
419
|
+
results.failed.push({ ide: ide.name, error: errorMessage });
|
|
420
|
+
console.log(chalk_1.default.red(`ā Failed to configure ${ide.name}: ${errorMessage}`));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Summary
|
|
424
|
+
console.log(chalk_1.default.green(`\nš Configuration Complete:`));
|
|
425
|
+
if (results.successful.length > 0) {
|
|
426
|
+
console.log(chalk_1.default.green(` ā
Successfully configured: ${results.successful.length} IDE(s)`));
|
|
427
|
+
results.successful.forEach(ide => {
|
|
428
|
+
console.log(chalk_1.default.green(` ⢠${ide}`));
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (results.failed.length > 0) {
|
|
432
|
+
console.log(chalk_1.default.red(` ā Failed to configure: ${results.failed.length} IDE(s)`));
|
|
433
|
+
results.failed.forEach(({ ide, error }) => {
|
|
434
|
+
console.log(chalk_1.default.red(` ⢠${ide}: ${error}`));
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (results.successful.length > 0) {
|
|
438
|
+
const { installSlashCommands, installGlobalRules, describeConfiguredInvocationSurfaces } = await Promise.resolve().then(() => __importStar(require('../setup/ide-global-integration')));
|
|
439
|
+
try {
|
|
440
|
+
await installSlashCommands();
|
|
441
|
+
await installGlobalRules();
|
|
442
|
+
}
|
|
443
|
+
catch (e) {
|
|
444
|
+
console.log(chalk_1.default.yellow(`ā ļø IDE surface installation encountered issues: ${e.message}`));
|
|
445
|
+
}
|
|
446
|
+
const successfulIDEs = idesToConfigure.filter(ide => results.successful.includes(ide.name));
|
|
447
|
+
const invocationSummaries = describeConfiguredInvocationSurfaces(successfulIDEs);
|
|
448
|
+
console.log(chalk_1.default.blue('\nš Next steps:'));
|
|
449
|
+
console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
|
|
450
|
+
invocationSummaries.forEach((summary, index) => {
|
|
451
|
+
console.log(chalk_1.default.cyan(` ${index + 2}. ${summary}`));
|
|
452
|
+
});
|
|
453
|
+
console.log(chalk_1.default.blue('\nš” Use "fraim doctor --test-mcp" to verify the configuration.'));
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
exports.runAddIDE = runAddIDE;
|
|
457
|
+
exports.addIDECommand = new commander_1.Command('add-ide')
|
|
458
|
+
.description('Add FRAIM configuration to additional IDEs')
|
|
459
|
+
.option('--ide <name>', 'Configure specific IDE (claude, claude-code, claude-desktop, claude-cowork, antigravity, gemini, gemini-cli, copilot, copilot-cli, kiro, cursor, vscode, codex, grok, windsurf)')
|
|
460
|
+
.option('--all', 'Configure all detected IDEs')
|
|
461
|
+
.option('--list', 'List all supported IDEs and their detection status')
|
|
462
|
+
.action(exports.runAddIDE);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.createFraimHubProgram = createFraimHubProgram;
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const cli_1 = require("../ai-hub/cli");
|
|
12
|
+
function readPackageVersion() {
|
|
13
|
+
try {
|
|
14
|
+
let currentDir = __dirname;
|
|
15
|
+
for (let i = 0; i < 5; i++) {
|
|
16
|
+
const pkgPath = path_1.default.join(currentDir, 'package.json');
|
|
17
|
+
if (fs_1.default.existsSync(pkgPath)) {
|
|
18
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
|
|
19
|
+
return pkg.version || 'unknown';
|
|
20
|
+
}
|
|
21
|
+
currentDir = path_1.default.dirname(currentDir);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Fall through to unknown.
|
|
26
|
+
}
|
|
27
|
+
return 'unknown';
|
|
28
|
+
}
|
|
29
|
+
function createFraimHubProgram(action = cli_1.runHub) {
|
|
30
|
+
const program = new commander_1.Command();
|
|
31
|
+
program
|
|
32
|
+
.name('fraim-hub')
|
|
33
|
+
.description('Start the FRAIM Hub local companion')
|
|
34
|
+
.version(readPackageVersion())
|
|
35
|
+
.option('--port <port>', 'Preferred local port for the hub', (value) => Number(value), 43091)
|
|
36
|
+
.option('--project-path <path>', 'Initial project path for job discovery', process.cwd())
|
|
37
|
+
.option('--no-open', 'Do not open the hub after startup')
|
|
38
|
+
.option('--browser', 'Open in the default browser instead of the desktop shell')
|
|
39
|
+
.option('--restart', 'Replace any running Hub instance with this build (even at the same version)')
|
|
40
|
+
.option('--keep-running', 'Do not replace a running Hub; keep single-instance focus (default replaces only an older/stale instance)')
|
|
41
|
+
.allowExcessArguments(false)
|
|
42
|
+
.action(action);
|
|
43
|
+
return program;
|
|
44
|
+
}
|
|
45
|
+
if (require.main === module) {
|
|
46
|
+
createFraimHubProgram().parseAsync(process.argv).catch((error) => {
|
|
47
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
}
|