fraim-framework 2.0.176 → 2.0.179
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/ai-hub/server.js +50 -1
- package/dist/src/cli/commands/add-provider.js +74 -61
- package/dist/src/cli/commands/add-surface.js +128 -0
- package/dist/src/cli/commands/first-run.js +3 -5
- package/dist/src/cli/commands/login.js +5 -69
- package/dist/src/cli/commands/setup.js +27 -347
- package/dist/src/cli/distribution/marketplace-bundles.js +576 -0
- package/dist/src/cli/fraim.js +2 -0
- package/dist/src/cli/mcp/ide-formats.js +5 -3
- package/dist/src/cli/mcp/mcp-server-registry.js +10 -3
- package/dist/src/cli/providers/local-provider-registry.js +2 -3
- package/dist/src/cli/setup/auto-mcp-setup.js +9 -32
- package/dist/src/cli/setup/ide-detector.js +65 -12
- package/dist/src/config/persona-capability-bundles.js +17 -13
- package/dist/src/first-run/install-state.js +1 -0
- package/dist/src/first-run/server.js +13 -0
- package/dist/src/first-run/session-service.js +22 -3
- package/dist/src/local-mcp-server/stdio-server.js +28 -4
- package/dist/src/local-mcp-server/usage-collector.js +24 -0
- package/package.json +5 -2
- package/public/ai-hub/index.html +14 -2
- package/public/ai-hub/script.js +340 -66
- package/public/ai-hub/styles.css +83 -0
- package/public/first-run/script.js +64 -0
- package/public/first-run/styles.css +14 -0
|
@@ -110,6 +110,16 @@ function loadPersonaCapabilityModule() {
|
|
|
110
110
|
function getProtectedPersonaForHubJob(jobName) {
|
|
111
111
|
return loadPersonaCapabilityModule()?.getProtectedPersonaForJob(jobName) ?? null;
|
|
112
112
|
}
|
|
113
|
+
const FRAIM_INTERNAL_JOB_IDS = new Set([
|
|
114
|
+
'contribute-to-fraim',
|
|
115
|
+
'create-registry-asset',
|
|
116
|
+
'extract-ashley-learnings',
|
|
117
|
+
'file-fraim-issue',
|
|
118
|
+
'praise-fraim',
|
|
119
|
+
'run-on-remote-hub',
|
|
120
|
+
'setup-remote-hub',
|
|
121
|
+
'update-registry-override',
|
|
122
|
+
]);
|
|
113
123
|
function listHubPersonaBundles() {
|
|
114
124
|
return loadPersonaCapabilityModule()?.listPersonaCapabilityBundles() ?? [];
|
|
115
125
|
}
|
|
@@ -147,6 +157,9 @@ function createDefaultDbService() {
|
|
|
147
157
|
return undefined;
|
|
148
158
|
}
|
|
149
159
|
}
|
|
160
|
+
function supportsManagerSeatAssignments(dbService) {
|
|
161
|
+
return typeof dbService.countHubManagerAssignments === 'function';
|
|
162
|
+
}
|
|
150
163
|
class AiHubRunRegistry {
|
|
151
164
|
constructor() {
|
|
152
165
|
this.runs = new Map();
|
|
@@ -1246,6 +1259,23 @@ class AiHubServer {
|
|
|
1246
1259
|
this.app.get('/api/health', (_req, res) => {
|
|
1247
1260
|
res.json({ status: 'ok', service: 'fraim-ai-hub' });
|
|
1248
1261
|
});
|
|
1262
|
+
// Issue #659: Inbound auth middleware for remote-exposed hubs.
|
|
1263
|
+
// Activated only when FRAIM_HUB_AUTH_TOKEN env var is set. Gates all
|
|
1264
|
+
// /api/ai-hub/* routes. /health and /api/health are registered above
|
|
1265
|
+
// this block and are never gated.
|
|
1266
|
+
if (process.env.FRAIM_HUB_AUTH_TOKEN) {
|
|
1267
|
+
const expectedToken = process.env.FRAIM_HUB_AUTH_TOKEN;
|
|
1268
|
+
this.app.use('/api/ai-hub', (req, res, next) => {
|
|
1269
|
+
const token = req.headers['x-hub-auth'];
|
|
1270
|
+
if (typeof token !== 'string' ||
|
|
1271
|
+
token.length !== expectedToken.length ||
|
|
1272
|
+
!(0, crypto_1.timingSafeEqual)(Buffer.from(token), Buffer.from(expectedToken))) {
|
|
1273
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
next();
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1249
1279
|
this.registerRoutes();
|
|
1250
1280
|
}
|
|
1251
1281
|
getApp() {
|
|
@@ -1363,7 +1393,9 @@ class AiHubServer {
|
|
|
1363
1393
|
// Issue #566 (R7): jobs already carry `personalized` from catalog discovery
|
|
1364
1394
|
// (true for the fraim/personalized-employee layer). The Hub renders a plain
|
|
1365
1395
|
// "Personalized" marking from that flag — no author/attribution is tracked.
|
|
1366
|
-
const jobs = rawJobs
|
|
1396
|
+
const jobs = rawJobs
|
|
1397
|
+
.filter((job) => !FRAIM_INTERNAL_JOB_IDS.has(job.id))
|
|
1398
|
+
.map((job) => ({
|
|
1367
1399
|
...job,
|
|
1368
1400
|
requiredPersonaKey: getProtectedPersonaForHubJob(job.id),
|
|
1369
1401
|
}));
|
|
@@ -1853,6 +1885,23 @@ class AiHubServer {
|
|
|
1853
1885
|
if (!state) {
|
|
1854
1886
|
return { personas: fallbackPersonas, subscriptionActive: false, workspaceId: null, userKey: null };
|
|
1855
1887
|
}
|
|
1888
|
+
// Legacy bypass: persona gating is not active for this workspace, so all personas are accessible.
|
|
1889
|
+
// Mirrors the evaluatePersonaAccess legacy bypass in persona-entitlement-service.ts.
|
|
1890
|
+
if (!state.subscriptionActive) {
|
|
1891
|
+
const legacySeatCount = supportsManagerSeatAssignments(this.dbService) ? 0 : 1;
|
|
1892
|
+
const allPersonas = allBundles.map((bundle) => ({
|
|
1893
|
+
key: bundle.personaKey,
|
|
1894
|
+
displayName: bundle.catalogMetadata.displayName,
|
|
1895
|
+
role: bundle.catalogMetadata.role,
|
|
1896
|
+
avatarUrl: buildHubPersonaAvatarUrl(bundle.personaKey),
|
|
1897
|
+
pricingLabel: '',
|
|
1898
|
+
status: 'hired',
|
|
1899
|
+
hireUrl: buildHubPersonaHireUrl(bundle.personaKey, bundle.defaultHireMode),
|
|
1900
|
+
seatCount: legacySeatCount,
|
|
1901
|
+
seatsInUse: 0,
|
|
1902
|
+
}));
|
|
1903
|
+
return { personas: allPersonas, subscriptionActive: false, workspaceId: state.workspaceId, userKey: state.userId ?? null };
|
|
1904
|
+
}
|
|
1856
1905
|
const hiredKeys = new Set(state.entitlements
|
|
1857
1906
|
.filter((e) => e.status === 'active')
|
|
1858
1907
|
.map((e) => e.personaKey));
|
|
@@ -33,67 +33,79 @@ const saveGlobalConfig = (config) => {
|
|
|
33
33
|
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
34
34
|
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
|
|
35
35
|
};
|
|
36
|
-
const
|
|
36
|
+
const forEachInstalledIDE = async (callback) => {
|
|
37
37
|
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
38
38
|
if (detectedIDEs.length === 0) {
|
|
39
39
|
console.log(chalk_1.default.yellow('⚠️ No IDEs detected. MCP configs not updated.'));
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
console.log(chalk_1.default.blue(`\n🔧 Updating ${detectedIDEs.length} IDE configuration(s)...\n`));
|
|
43
|
-
const tokenConfig = {
|
|
44
|
-
github: config.tokens?.github,
|
|
45
|
-
gitlab: config.tokens?.gitlab,
|
|
46
|
-
ado: config.tokens?.ado,
|
|
47
|
-
jira: config.tokens?.jira
|
|
48
|
-
};
|
|
49
|
-
const providerConfigs = Object.entries(config.providerConfigs || {}).reduce((acc, [key, value]) => {
|
|
50
|
-
const providerId = key.replace(/Config$/, '');
|
|
51
|
-
acc[providerId] = value;
|
|
52
|
-
return acc;
|
|
53
|
-
}, {});
|
|
54
43
|
for (const ide of detectedIDEs) {
|
|
55
44
|
try {
|
|
56
|
-
|
|
57
|
-
if (ide.configFormat === 'json') {
|
|
58
|
-
// JSON-based config (Claude Desktop, Cursor, etc.)
|
|
59
|
-
let ideConfig = {};
|
|
60
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
61
|
-
ideConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
62
|
-
}
|
|
63
|
-
const mcpConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, config.apiKey, tokenConfig, providerConfigs);
|
|
64
|
-
const serversKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
|
|
65
|
-
if (!ideConfig[serversKey]) {
|
|
66
|
-
ideConfig[serversKey] = {};
|
|
67
|
-
}
|
|
68
|
-
// Add the new provider's MCP server
|
|
69
|
-
const providerServerKey = provider === 'ado' ? 'ado' : provider;
|
|
70
|
-
if (mcpConfig[serversKey] && mcpConfig[serversKey][providerServerKey]) {
|
|
71
|
-
ideConfig[serversKey][providerServerKey] = mcpConfig[serversKey][providerServerKey];
|
|
72
|
-
fs_1.default.writeFileSync(configPath, JSON.stringify(ideConfig, null, 2));
|
|
73
|
-
console.log(chalk_1.default.green(`✅ Updated ${ide.name} config`));
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else if (ide.configFormat === 'toml') {
|
|
77
|
-
// TOML-based config (Codex, Zed)
|
|
78
|
-
const mcpConfigToml = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, config.apiKey, tokenConfig, providerConfigs);
|
|
79
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
80
|
-
const existingContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
81
|
-
// Merge only the new provider's server
|
|
82
|
-
const serversToMerge = [provider === 'ado' ? 'ado' : provider];
|
|
83
|
-
const mergeResult = (0, mcp_config_generator_2.mergeTomlMCPServers)(existingContent, mcpConfigToml, serversToMerge);
|
|
84
|
-
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
85
|
-
console.log(chalk_1.default.green(`✅ Updated ${ide.name} config`));
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
console.log(chalk_1.default.yellow(`⚠️ ${ide.name} config not found, skipping`));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
45
|
+
await callback(ide, (0, ide_detector_1.expandPath)(ide.configPath));
|
|
91
46
|
}
|
|
92
47
|
catch (error) {
|
|
93
48
|
console.log(chalk_1.default.red(`❌ Failed to update ${ide.name}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
94
49
|
}
|
|
95
50
|
}
|
|
96
51
|
};
|
|
52
|
+
const writeOAuthProviderToIDEConfigs = async (provider, mcpUrl) => {
|
|
53
|
+
const urlOnlyEntry = { type: 'http', url: mcpUrl };
|
|
54
|
+
await forEachInstalledIDE(async (ide, configPath) => {
|
|
55
|
+
if (ide.configFormat === 'json') {
|
|
56
|
+
let ideConfig = {};
|
|
57
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
58
|
+
ideConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
59
|
+
}
|
|
60
|
+
const serversKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
|
|
61
|
+
if (!ideConfig[serversKey]) {
|
|
62
|
+
ideConfig[serversKey] = {};
|
|
63
|
+
}
|
|
64
|
+
ideConfig[serversKey][provider] = urlOnlyEntry;
|
|
65
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(ideConfig, null, 2));
|
|
66
|
+
console.log(chalk_1.default.green(`✅ Updated ${ide.name} config`));
|
|
67
|
+
}
|
|
68
|
+
else if (ide.configFormat === 'toml') {
|
|
69
|
+
console.log(chalk_1.default.yellow(`⚠️ ${ide.name} uses TOML format — update manually: add [tools.${provider}] url = "${mcpUrl}"`));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
const updateIDEConfigs = async (provider, fraimKey, token, providerConfig) => {
|
|
74
|
+
const tokenConfig = { [provider]: token };
|
|
75
|
+
const providerConfigs = providerConfig ? { [provider]: providerConfig } : {};
|
|
76
|
+
await forEachInstalledIDE(async (ide, configPath) => {
|
|
77
|
+
if (ide.configFormat === 'json') {
|
|
78
|
+
let ideConfig = {};
|
|
79
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
80
|
+
ideConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
81
|
+
}
|
|
82
|
+
const mcpConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokenConfig, providerConfigs);
|
|
83
|
+
const serversKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
|
|
84
|
+
if (!ideConfig[serversKey]) {
|
|
85
|
+
ideConfig[serversKey] = {};
|
|
86
|
+
}
|
|
87
|
+
const providerServerKey = provider === 'ado' ? 'ado' : provider;
|
|
88
|
+
if (mcpConfig[serversKey] && mcpConfig[serversKey][providerServerKey]) {
|
|
89
|
+
ideConfig[serversKey][providerServerKey] = mcpConfig[serversKey][providerServerKey];
|
|
90
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(ideConfig, null, 2));
|
|
91
|
+
console.log(chalk_1.default.green(`✅ Updated ${ide.name} config`));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (ide.configFormat === 'toml') {
|
|
95
|
+
const mcpConfigToml = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokenConfig, providerConfigs);
|
|
96
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
97
|
+
const existingContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
98
|
+
const serversToMerge = [provider === 'ado' ? 'ado' : provider];
|
|
99
|
+
const mergeResult = (0, mcp_config_generator_2.mergeTomlMCPServers)(existingContent, mcpConfigToml, serversToMerge);
|
|
100
|
+
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
101
|
+
console.log(chalk_1.default.green(`✅ Updated ${ide.name} config`));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log(chalk_1.default.yellow(`⚠️ ${ide.name} config not found, skipping`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
};
|
|
97
109
|
const offerModeSwitch = async (config) => {
|
|
98
110
|
if (config.mode === 'split') {
|
|
99
111
|
console.log(chalk_1.default.gray('\nAlready in split mode.'));
|
|
@@ -186,6 +198,19 @@ const runAddProvider = async (provider, options) => {
|
|
|
186
198
|
}
|
|
187
199
|
}
|
|
188
200
|
}
|
|
201
|
+
// Detect OAuth-first providers: HTTP MCP with no authHeaderTemplate means the IDE
|
|
202
|
+
// handles OAuth natively. Skip credential collection and write a URL-only IDE entry.
|
|
203
|
+
const providerDefOAuth = await (0, provider_registry_1.getProvider)(provider);
|
|
204
|
+
if (providerDefOAuth?.mcpServer?.type === 'http' &&
|
|
205
|
+
!providerDefOAuth.mcpServer.authHeaderTemplate) {
|
|
206
|
+
const mcpUrl = providerDefOAuth.mcpServer.url;
|
|
207
|
+
await writeOAuthProviderToIDEConfigs(provider, mcpUrl);
|
|
208
|
+
console.log(chalk_1.default.green(`\n✅ ${providerName} configured for IDE-native OAuth.`));
|
|
209
|
+
console.log(chalk_1.default.cyan('\n💡 Next steps:'));
|
|
210
|
+
console.log(chalk_1.default.gray(' 1. Restart your IDE(s) to load the new MCP server'));
|
|
211
|
+
console.log(chalk_1.default.gray(' 2. On first use, your IDE will prompt you to sign in to ' + providerName));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
189
214
|
// Get credentials using generic prompt system
|
|
190
215
|
try {
|
|
191
216
|
// Build provided tokens/configs from CLI options
|
|
@@ -205,20 +230,8 @@ const runAddProvider = async (provider, options) => {
|
|
|
205
230
|
// Get credentials using generic prompt system
|
|
206
231
|
const client = (0, get_provider_client_1.getProviderClient)();
|
|
207
232
|
const creds = await (0, provider_prompts_1.promptForProviderCredentials)(client, provider, providedTokens[provider], providedConfigs[provider]);
|
|
208
|
-
//
|
|
209
|
-
config.
|
|
210
|
-
// Save provider-specific config if present
|
|
211
|
-
if (creds.config) {
|
|
212
|
-
if (!config.providerConfigs) {
|
|
213
|
-
config.providerConfigs = {};
|
|
214
|
-
}
|
|
215
|
-
config.providerConfigs[`${provider}Config`] = creds.config;
|
|
216
|
-
}
|
|
217
|
-
// Save updated config
|
|
218
|
-
saveGlobalConfig(config);
|
|
219
|
-
console.log(chalk_1.default.green(`\n✅ ${providerName} credentials saved to global config`));
|
|
220
|
-
// Update IDE configs
|
|
221
|
-
await updateIDEConfigs(provider, config);
|
|
233
|
+
// Write provider MCP entry directly to IDE configs (token not stored in global config)
|
|
234
|
+
await updateIDEConfigs(provider, config.apiKey, creds.token, creds.config);
|
|
222
235
|
// Offer mode switch if adding a provider with issues capability
|
|
223
236
|
const providerDef = await (0, provider_registry_1.getProvider)(provider);
|
|
224
237
|
if (providerDef?.capabilities.includes('issues') && provider === 'jira') {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* fraim add-surface — Register FRAIM as an MCP server on Track B Claude surfaces.
|
|
4
|
+
*
|
|
5
|
+
* Track B surfaces connect via remote HTTPS MCP (not local stdio):
|
|
6
|
+
* - claude.ai web, Claude Design, Claude Cowork (remote mode), Claude mobile
|
|
7
|
+
* - Claude in Word, Excel, PowerPoint, Outlook (via admin manifest)
|
|
8
|
+
*
|
|
9
|
+
* Track A surfaces (local stdio) are handled by `fraim add-ide` instead.
|
|
10
|
+
*
|
|
11
|
+
* Auth model: OAuth 2.1 PKCE. claude.ai discovers endpoints via RFC 8414
|
|
12
|
+
* (/.well-known/oauth-authorization-server) and drives the full authorize/token
|
|
13
|
+
* flow automatically — no manual API key entry required.
|
|
14
|
+
* Office add-ins still use a direct gateway token via the claude-in-office CLI.
|
|
15
|
+
*/
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.addSurfaceCommand = void 0;
|
|
21
|
+
const commander_1 = require("commander");
|
|
22
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
23
|
+
const FRAIM_REMOTE_URL = 'https://fraim.wellnessatwork.me';
|
|
24
|
+
const MCP_ENDPOINT = `${FRAIM_REMOTE_URL}/mcp`;
|
|
25
|
+
// ─── surface handlers ────────────────────────────────────────────────────────
|
|
26
|
+
function printClaudeWebInstructions(surface) {
|
|
27
|
+
const label = {
|
|
28
|
+
'claude.ai': 'claude.ai web',
|
|
29
|
+
'design': 'Claude Design',
|
|
30
|
+
'cowork': 'Claude Cowork (remote mode)',
|
|
31
|
+
'mobile': 'Claude iOS / Android',
|
|
32
|
+
};
|
|
33
|
+
const name = label[surface] || surface;
|
|
34
|
+
console.log(chalk_1.default.bold(`\n ${name} — Remote MCP Connector`));
|
|
35
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────'));
|
|
36
|
+
console.log(chalk_1.default.white(' 1. Open claude.ai → profile icon → Settings → Connectors'));
|
|
37
|
+
console.log(chalk_1.default.white(' 2. Click "+ Add Custom Connector"'));
|
|
38
|
+
console.log(chalk_1.default.white(' 3. Enter this URL:'));
|
|
39
|
+
console.log(chalk_1.default.cyan(`\n ${MCP_ENDPOINT}\n`));
|
|
40
|
+
console.log(chalk_1.default.white(' 4. Click "Connect" — FRAIM uses OAuth sign-in (Google or GitHub).'));
|
|
41
|
+
console.log(chalk_1.default.white(' If you are already logged in to fraimworks.ai, the connector'));
|
|
42
|
+
console.log(chalk_1.default.white(' completes automatically with no extra steps.'));
|
|
43
|
+
console.log(chalk_1.default.white(' 5. The connector syncs to mobile and Claude Design automatically.'));
|
|
44
|
+
console.log(chalk_1.default.gray('\n Note: Settings > Connectors is the same for web, mobile, and Claude Design.'));
|
|
45
|
+
console.log(chalk_1.default.gray(` Discovery: ${FRAIM_REMOTE_URL}/.well-known/oauth-authorization-server`));
|
|
46
|
+
}
|
|
47
|
+
function printOfficeInstructions(surface) {
|
|
48
|
+
const label = {
|
|
49
|
+
'word': 'Claude in Word',
|
|
50
|
+
'excel': 'Claude in Excel',
|
|
51
|
+
'powerpoint': 'Claude in PowerPoint',
|
|
52
|
+
'outlook': 'Claude in Outlook',
|
|
53
|
+
};
|
|
54
|
+
const name = label[surface] || surface;
|
|
55
|
+
console.log(chalk_1.default.bold(`\n ${name} — Claude add-in (sign in with your Claude account)`));
|
|
56
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────'));
|
|
57
|
+
console.log(chalk_1.default.white(' The Claude Office add-in is a client-side panel you install from'));
|
|
58
|
+
console.log(chalk_1.default.white(' Microsoft AppSource and sign into with your claude.ai account.'));
|
|
59
|
+
console.log(chalk_1.default.white(' Connectors are per-account, so the FRAIM connector you configured'));
|
|
60
|
+
console.log(chalk_1.default.white(' on claude.ai is automatically available here — no extra setup needed.'));
|
|
61
|
+
console.log(chalk_1.default.white('\n Steps:'));
|
|
62
|
+
console.log(chalk_1.default.white(' 1. Open Word / Excel / PowerPoint'));
|
|
63
|
+
console.log(chalk_1.default.white(' 2. Insert → Add-ins → search for "Claude"'));
|
|
64
|
+
console.log(chalk_1.default.white(' 3. Sign in with the same account you used on claude.ai'));
|
|
65
|
+
console.log(chalk_1.default.white(' 4. FRAIM tools are available immediately via the connector'));
|
|
66
|
+
console.log(chalk_1.default.gray('\n Docs: https://support.claude.com/en/articles/13945233'));
|
|
67
|
+
}
|
|
68
|
+
function printDesktopBridgeNote() {
|
|
69
|
+
console.log(chalk_1.default.gray('\n Note: Claude Desktop and Claude Cowork (local mode) use Track A (local stdio).'));
|
|
70
|
+
console.log(chalk_1.default.gray(' Run `fraim add-ide` for those instead.'));
|
|
71
|
+
}
|
|
72
|
+
// ─── supported surfaces ───────────────────────────────────────────────────────
|
|
73
|
+
const WEB_SURFACES = ['claude.ai', 'design', 'cowork', 'mobile'];
|
|
74
|
+
const OFFICE_SURFACES = ['word', 'excel', 'powerpoint', 'outlook'];
|
|
75
|
+
const ALL_SURFACES = [...WEB_SURFACES, ...OFFICE_SURFACES];
|
|
76
|
+
// ─── command action ───────────────────────────────────────────────────────────
|
|
77
|
+
async function runAddSurface(options) {
|
|
78
|
+
if (options.list) {
|
|
79
|
+
console.log(chalk_1.default.bold('\nTrack B surfaces (remote HTTPS MCP):'));
|
|
80
|
+
console.log(chalk_1.default.cyan(' Web / Mobile'));
|
|
81
|
+
WEB_SURFACES.forEach(s => console.log(chalk_1.default.white(` • ${s}`)));
|
|
82
|
+
console.log(chalk_1.default.cyan('\n Microsoft 365 (admin manifest required)'));
|
|
83
|
+
OFFICE_SURFACES.forEach(s => console.log(chalk_1.default.white(` • ${s}`)));
|
|
84
|
+
console.log(chalk_1.default.gray('\nTrack A surfaces (local stdio — use `fraim add-ide` instead):'));
|
|
85
|
+
console.log(chalk_1.default.gray(' claude-desktop, claude-code, claude-cowork (local), cursor, vscode, codex, gemini, kiro, windsurf'));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const targets = options.all
|
|
89
|
+
? ALL_SURFACES
|
|
90
|
+
: options.surface
|
|
91
|
+
? [options.surface.toLowerCase()]
|
|
92
|
+
: [];
|
|
93
|
+
if (targets.length === 0) {
|
|
94
|
+
console.log(chalk_1.default.bold('\nUsage: fraim add-surface --surface <name> or --all or --list\n'));
|
|
95
|
+
console.log(chalk_1.default.white('Available surfaces (Track B — remote HTTPS MCP):'));
|
|
96
|
+
console.log(chalk_1.default.cyan(' Web/mobile: claude.ai | design | cowork | mobile'));
|
|
97
|
+
console.log(chalk_1.default.cyan(' Office: word | excel | powerpoint | outlook'));
|
|
98
|
+
console.log(chalk_1.default.gray('\nExample: fraim add-surface --surface claude.ai'));
|
|
99
|
+
console.log(chalk_1.default.gray(' fraim add-surface --all'));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const unknown = targets.filter(t => !ALL_SURFACES.includes(t));
|
|
103
|
+
if (unknown.length > 0) {
|
|
104
|
+
console.log(chalk_1.default.red(`✗ Unknown surface(s): ${unknown.join(', ')}`));
|
|
105
|
+
console.log(chalk_1.default.yellow(` Valid values: ${ALL_SURFACES.join(', ')}`));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
console.log(chalk_1.default.bold('\nFRAIM MCP — Surface Registration Instructions'));
|
|
109
|
+
console.log(chalk_1.default.gray(`Remote endpoint: ${MCP_ENDPOINT}`));
|
|
110
|
+
const webTargets = targets.filter(t => WEB_SURFACES.includes(t));
|
|
111
|
+
const officeTargets = targets.filter(t => OFFICE_SURFACES.includes(t));
|
|
112
|
+
webTargets.forEach(s => printClaudeWebInstructions(s));
|
|
113
|
+
officeTargets.forEach(s => printOfficeInstructions(s));
|
|
114
|
+
if (targets.some(t => ['cowork', 'claude-desktop'].includes(t))) {
|
|
115
|
+
printDesktopBridgeNote();
|
|
116
|
+
}
|
|
117
|
+
console.log(chalk_1.default.bold('\n MCP endpoint details:'));
|
|
118
|
+
console.log(chalk_1.default.white(` URL: ${MCP_ENDPOINT}`));
|
|
119
|
+
console.log(chalk_1.default.white(` Auth: OAuth 2.1 PKCE (Google / GitHub sign-in via fraimworks.ai)`));
|
|
120
|
+
console.log(chalk_1.default.white(` Discovery: ${FRAIM_REMOTE_URL}/.well-known/oauth-authorization-server`));
|
|
121
|
+
console.log(chalk_1.default.gray('\n Run `fraim doctor` to verify local (Track A) configuration.'));
|
|
122
|
+
}
|
|
123
|
+
exports.addSurfaceCommand = new commander_1.Command('add-surface')
|
|
124
|
+
.description('Register FRAIM as a remote MCP server on Track B Claude surfaces (claude.ai, Design, Cowork, Office add-ins)')
|
|
125
|
+
.option('--surface <name>', 'Target surface: claude.ai | design | cowork | mobile | word | excel | powerpoint | outlook')
|
|
126
|
+
.option('--all', 'Print instructions for all Track B surfaces')
|
|
127
|
+
.option('--list', 'List supported surfaces')
|
|
128
|
+
.action(runAddSurface);
|
|
@@ -65,11 +65,9 @@ function openBrowser(url) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
const runFirstRun = async (options) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
68
|
+
// Issue #646: the key is optional. When launched by the no-terminal macOS
|
|
69
|
+
// installer (.pkg), no key is passed — the wizard prompts the user to paste it.
|
|
70
|
+
const key = options.key || process.env.FRAIM_API_KEY || process.env.FRAIM_SETUP_KEY || process.env.FRAIM_INSTALL_KEY || '';
|
|
73
71
|
const sessionService = new session_service_1.FirstRunSessionService({
|
|
74
72
|
key,
|
|
75
73
|
headless: options.headless,
|
|
@@ -6,78 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.loginCommand = void 0;
|
|
7
7
|
const commander_1 = require("commander");
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
const device_flow_service_1 = require("../internal/device-flow-service");
|
|
10
|
-
const provider_client_1 = require("../api/provider-client");
|
|
11
|
-
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
12
|
-
const fs_1 = __importDefault(require("fs"));
|
|
13
|
-
const path_1 = __importDefault(require("path"));
|
|
14
|
-
const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
|
|
15
9
|
exports.loginCommand = new commander_1.Command('login')
|
|
16
10
|
.description('Login to platforms (GitHub, etc.)');
|
|
17
11
|
exports.loginCommand
|
|
18
12
|
.command('github')
|
|
19
|
-
.description('
|
|
13
|
+
.description('Configure GitHub MCP access (redirects to add-provider)')
|
|
20
14
|
.action(async () => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (fs_1.default.existsSync(globalConfigPath)) {
|
|
26
|
-
config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
27
|
-
}
|
|
28
|
-
if (!config.apiKey) {
|
|
29
|
-
console.error(chalk_1.default.red('\n❌ No FRAIM API key found. Please run "fraim setup" first.'));
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
const client = new provider_client_1.ProviderClient(config.apiKey);
|
|
33
|
-
let githubConfig;
|
|
34
|
-
try {
|
|
35
|
-
const providers = await client.getAllProviders();
|
|
36
|
-
githubConfig = providers.find(p => p.id === 'github')?.deviceFlowConfig;
|
|
37
|
-
if (!githubConfig) {
|
|
38
|
-
console.error(chalk_1.default.red('\n❌ Device flow configuration for GitHub not found on server.'));
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
console.error(chalk_1.default.red(`\n❌ Failed to fetch provider configuration: ${error.message}`));
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
const deviceFlow = new device_flow_service_1.DeviceFlowService(githubConfig);
|
|
47
|
-
try {
|
|
48
|
-
const token = await deviceFlow.login();
|
|
49
|
-
config.tokens = {
|
|
50
|
-
...(config.tokens || {}),
|
|
51
|
-
github: token
|
|
52
|
-
};
|
|
53
|
-
delete config.version;
|
|
54
|
-
config.updatedAt = new Date().toISOString();
|
|
55
|
-
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
|
|
56
|
-
console.log(chalk_1.default.green('\n✅ GitHub token saved to global configuration.'));
|
|
57
|
-
// Trigger MCP reconfiguration
|
|
58
|
-
if (config.apiKey) {
|
|
59
|
-
console.log(chalk_1.default.blue('🔌 Updating MCP server configurations...'));
|
|
60
|
-
const mcpTokens = {};
|
|
61
|
-
Object.entries(config.tokens).forEach(([id, t]) => {
|
|
62
|
-
mcpTokens[id] = t;
|
|
63
|
-
});
|
|
64
|
-
// Re-use existing provider configs if any
|
|
65
|
-
const providerConfigsMap = {};
|
|
66
|
-
if (config.providerConfigs) {
|
|
67
|
-
Object.entries(config.providerConfigs).forEach(([key, val]) => {
|
|
68
|
-
const providerId = key.replace('Config', '');
|
|
69
|
-
providerConfigsMap[providerId] = val;
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
await (0, auto_mcp_setup_1.autoConfigureMCP)(config.apiKey, mcpTokens, undefined, providerConfigsMap);
|
|
73
|
-
console.log(chalk_1.default.green('✅ MCP servers updated with new GitHub token.'));
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
console.log(chalk_1.default.yellow('⚠️ No FRAIM API key found. Run "fraim setup" to complete configuration.'));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
console.error(chalk_1.default.red(`\n❌ Login failed: ${error.message}`));
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
15
|
+
console.log(chalk_1.default.blue('\n💡 GitHub authentication is now handled natively by your IDE.'));
|
|
16
|
+
console.log(chalk_1.default.gray('To configure GitHub MCP access, run:'));
|
|
17
|
+
console.log(chalk_1.default.cyan('\n fraim add-provider github\n'));
|
|
18
|
+
console.log(chalk_1.default.gray('Your IDE will prompt for GitHub sign-in automatically on first use.'));
|
|
83
19
|
});
|