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.
Files changed (120) hide show
  1. package/README.md +467 -0
  2. package/bin/fraim-hub.js +17 -0
  3. package/dist/src/ai-hub/brand-store.js +17 -0
  4. package/dist/src/ai-hub/catalog.js +412 -0
  5. package/dist/src/ai-hub/cert-store.js +70 -0
  6. package/dist/src/ai-hub/cli.js +212 -0
  7. package/dist/src/ai-hub/configured-agents.js +283 -0
  8. package/dist/src/ai-hub/conversation-store.js +428 -0
  9. package/dist/src/ai-hub/desktop-main.js +449 -0
  10. package/dist/src/ai-hub/hosts.js +1669 -0
  11. package/dist/src/ai-hub/hub-latest-version.js +52 -0
  12. package/dist/src/ai-hub/hub-launch-decision.js +57 -0
  13. package/dist/src/ai-hub/hub-runtime-file.js +43 -0
  14. package/dist/src/ai-hub/managed-browser.js +269 -0
  15. package/dist/src/ai-hub/manager-turns.js +64 -0
  16. package/dist/src/ai-hub/office-sideload.js +156 -0
  17. package/dist/src/ai-hub/openclaw-bridge.js +250 -0
  18. package/dist/src/ai-hub/preferences.js +201 -0
  19. package/dist/src/ai-hub/remote-hub-gateway.js +88 -0
  20. package/dist/src/ai-hub/server.js +4013 -0
  21. package/dist/src/ai-hub/types.js +2 -0
  22. package/dist/src/ai-hub/url-safety.js +19 -0
  23. package/dist/src/api/admin/payments.js +33 -0
  24. package/dist/src/api/admin/sales-leads.js +21 -0
  25. package/dist/src/api/payment/create-session.js +338 -0
  26. package/dist/src/api/payment/dashboard-link.js +149 -0
  27. package/dist/src/api/payment/session-details.js +31 -0
  28. package/dist/src/api/payment/webhook.js +587 -0
  29. package/dist/src/api/sales/contact.js +44 -0
  30. package/dist/src/cli/api/get-provider-client.js +41 -0
  31. package/dist/src/cli/api/provider-client.js +107 -0
  32. package/dist/src/cli/commands/add-ide.js +462 -0
  33. package/dist/src/cli/fraim-hub.js +50 -0
  34. package/dist/src/cli/internal/device-flow-service.js +83 -0
  35. package/dist/src/cli/mcp/command-resolution.js +81 -0
  36. package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +136 -0
  37. package/dist/src/cli/mcp/ide-formats.js +317 -0
  38. package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
  39. package/dist/src/cli/mcp/mcp-server-registry.js +173 -0
  40. package/dist/src/cli/providers/local-provider-registry.js +165 -0
  41. package/dist/src/cli/providers/provider-registry.js +230 -0
  42. package/dist/src/cli/setup/claude-code-telemetry.js +59 -0
  43. package/dist/src/cli/setup/codex-local-config.js +37 -0
  44. package/dist/src/cli/setup/ide-detector.js +383 -0
  45. package/dist/src/cli/setup/ide-global-integration.js +100 -0
  46. package/dist/src/cli/setup/ide-invocation-surfaces.js +160 -0
  47. package/dist/src/cli/setup/mcp-config-generator.js +225 -0
  48. package/dist/src/cli/setup/provider-prompts.js +339 -0
  49. package/dist/src/cli/utils/managed-agent-paths.js +118 -0
  50. package/dist/src/cli/utils/script-sync-utils.js +221 -0
  51. package/dist/src/cli/utils/user-config.js +100 -0
  52. package/dist/src/cli/utils/version-utils.js +35 -0
  53. package/dist/src/config/ai-manager-hiring.js +121 -0
  54. package/dist/src/config/feature-flags.js +25 -0
  55. package/dist/src/config/persona-capability-bundles.js +277 -0
  56. package/dist/src/config/persona-hiring.js +270 -0
  57. package/dist/src/config/portfolio-slug-overrides.js +17 -0
  58. package/dist/src/config/pricing.js +37 -0
  59. package/dist/src/config/stripe.js +43 -0
  60. package/dist/src/core/brand-store.js +232 -0
  61. package/dist/src/core/quality-evidence.js +331 -0
  62. package/dist/src/core/utils/git-utils.js +194 -0
  63. package/dist/src/core/utils/ports.js +32 -0
  64. package/dist/src/core/utils/project-fraim-paths.js +220 -0
  65. package/dist/src/db/payment-repository.js +61 -0
  66. package/dist/src/first-run/types.js +93 -0
  67. package/dist/src/fraim/db-service.js +2409 -0
  68. package/dist/src/local-mcp-server/agent-token-prices.js +167 -0
  69. package/dist/src/local-mcp-server/learning-context-builder.js +975 -0
  70. package/dist/src/middleware/rate-limit.js +110 -0
  71. package/dist/src/models/payment.js +2 -0
  72. package/dist/src/routes/payment-routes.js +186 -0
  73. package/dist/src/routes/persona-catalog-routes.js +84 -0
  74. package/dist/src/services/dashboard-access.js +27 -0
  75. package/dist/src/services/email-service.js +951 -0
  76. package/dist/src/services/persona-entitlement-service.js +352 -0
  77. package/dist/src/services/workspace-identity.js +21 -0
  78. package/dist/src/types/analytics.js +2 -0
  79. package/dist/src/utils/payment-calculator.js +52 -0
  80. package/extensions/office-word/favicon.ico +0 -0
  81. package/extensions/office-word/icon-64.png +0 -0
  82. package/extensions/office-word/manifest.xml +33 -0
  83. package/extensions/office-word/taskpane.html +242 -0
  84. package/index.js +85 -0
  85. package/package.json +106 -0
  86. package/public/ai-hub/index.html +910 -0
  87. package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
  88. package/public/ai-hub/powerpoint-taskpane/index.html +236 -0
  89. package/public/ai-hub/powerpoint-taskpane/manifest.xml +30 -0
  90. package/public/ai-hub/review.css +444 -0
  91. package/public/ai-hub/script.js +11737 -0
  92. package/public/ai-hub/styles.css +5102 -0
  93. package/public/first-run/error-frame.js +100 -0
  94. package/public/first-run/index.html +35 -0
  95. package/public/first-run/script.js +739 -0
  96. package/public/first-run/styles.css +929 -0
  97. package/public/portfolio/ashley.html +523 -0
  98. package/public/portfolio/auditya.html +83 -0
  99. package/public/portfolio/banke.html +83 -0
  100. package/public/portfolio/beza.html +659 -0
  101. package/public/portfolio/careena.html +632 -0
  102. package/public/portfolio/casey.html +568 -0
  103. package/public/portfolio/celia.html +490 -0
  104. package/public/portfolio/deidre.html +642 -0
  105. package/public/portfolio/gautam.html +597 -0
  106. package/public/portfolio/hari.html +469 -0
  107. package/public/portfolio/huxley.html +1354 -0
  108. package/public/portfolio/index.html +741 -0
  109. package/public/portfolio/maestro.html +518 -0
  110. package/public/portfolio/mandy.html +590 -0
  111. package/public/portfolio/mona.html +597 -0
  112. package/public/portfolio/pam.html +887 -0
  113. package/public/portfolio/procella.html +107 -0
  114. package/public/portfolio/qasm.html +569 -0
  115. package/public/portfolio/ricardo.html +489 -0
  116. package/public/portfolio/sade.html +560 -0
  117. package/public/portfolio/sam.html +654 -0
  118. package/public/portfolio/sechar.html +580 -0
  119. package/public/portfolio/sreya.html +599 -0
  120. 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
+ }