fraim-framework 2.0.81 → 2.0.83

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 (33) hide show
  1. package/dist/src/cli/api/get-provider-client.js +41 -0
  2. package/dist/src/cli/api/provider-client.js +107 -0
  3. package/dist/src/cli/commands/add-ide.js +144 -77
  4. package/dist/src/cli/commands/add-provider.js +223 -0
  5. package/dist/src/cli/commands/doctor.js +131 -111
  6. package/dist/src/cli/commands/init-project.js +67 -31
  7. package/dist/src/cli/commands/setup.js +247 -563
  8. package/dist/src/cli/commands/sync.js +2 -2
  9. package/dist/src/cli/commands/test-mcp.js +35 -1
  10. package/dist/src/cli/doctor/check-runner.js +199 -0
  11. package/dist/src/cli/doctor/checks/global-setup-checks.js +220 -0
  12. package/dist/src/cli/doctor/checks/ide-config-checks.js +250 -0
  13. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +381 -0
  14. package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
  15. package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
  16. package/dist/src/cli/doctor/checks/workflow-checks.js +247 -0
  17. package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
  18. package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
  19. package/dist/src/cli/doctor/types.js +6 -0
  20. package/dist/src/cli/fraim.js +44 -3
  21. package/dist/src/cli/mcp/ide-formats.js +243 -0
  22. package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
  23. package/dist/src/cli/mcp/mcp-server-registry.js +159 -0
  24. package/dist/src/cli/mcp/types.js +3 -0
  25. package/dist/src/cli/providers/local-provider-registry.js +145 -0
  26. package/dist/src/cli/providers/provider-registry.js +230 -0
  27. package/dist/src/cli/setup/auto-mcp-setup.js +56 -118
  28. package/dist/src/cli/setup/mcp-config-generator.js +64 -321
  29. package/dist/src/cli/setup/provider-prompts.js +300 -0
  30. package/dist/src/cli/utils/remote-sync.js +22 -2
  31. package/package.json +4 -2
  32. package/dist/src/cli/commands/install.js +0 -86
  33. package/dist/src/cli/setup/token-validator.js +0 -57
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+ // Generic provider prompt system - no hardcoded provider knowledge
3
+ // All provider information comes from the server via ProviderClient
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ var __importDefault = (this && this.__importDefault) || function (mod) {
38
+ return (mod && mod.__esModule) ? mod : { "default": mod };
39
+ };
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.promptForProviders = promptForProviders;
42
+ exports.promptForSingleProvider = promptForSingleProvider;
43
+ exports.promptForProviderToken = promptForProviderToken;
44
+ exports.promptForProviderConfig = promptForProviderConfig;
45
+ exports.promptForProviderCredentials = promptForProviderCredentials;
46
+ const chalk_1 = __importDefault(require("chalk"));
47
+ const prompts_1 = __importDefault(require("prompts"));
48
+ /**
49
+ * Prompt user to select multiple providers
50
+ */
51
+ async function promptForProviders(client, preselectedIds) {
52
+ console.log(chalk_1.default.blue('\n🔧 Platform Selection'));
53
+ console.log(chalk_1.default.gray('Which platforms do you want to integrate with?\n'));
54
+ // Get providers that support integrated mode
55
+ const integratedProviders = await client.getProvidersWithCapability('integrated');
56
+ // Default to first integrated provider (typically GitHub)
57
+ const defaultProviderId = integratedProviders[0]?.id;
58
+ const choices = integratedProviders.map(provider => ({
59
+ title: provider.displayName,
60
+ value: provider.id,
61
+ selected: preselectedIds?.includes(provider.id) ?? provider.id === defaultProviderId
62
+ }));
63
+ const response = await (0, prompts_1.default)({
64
+ type: 'multiselect',
65
+ name: 'providers',
66
+ message: 'Select platforms (Space to select, Enter to confirm)',
67
+ choices,
68
+ min: 1
69
+ });
70
+ if (!response.providers || response.providers.length === 0) {
71
+ const defaultProvider = await client.getProvider(defaultProviderId);
72
+ console.log(chalk_1.default.yellow(`\nℹ️ No platforms selected, defaulting to ${defaultProvider?.displayName || 'first available provider'}`));
73
+ return [defaultProviderId];
74
+ }
75
+ console.log(chalk_1.default.blue('\n✓ Selected platforms:'));
76
+ for (const id of response.providers) {
77
+ const provider = await client.getProvider(id);
78
+ if (provider) {
79
+ console.log(chalk_1.default.gray(` - ${provider.displayName}`));
80
+ }
81
+ }
82
+ console.log();
83
+ return response.providers;
84
+ }
85
+ /**
86
+ * Prompt user to select a single provider (for code repo or issue tracking)
87
+ */
88
+ async function promptForSingleProvider(client, purpose, availableIds) {
89
+ const purposeLabel = purpose === 'code' ? 'Code Repository' : 'Issue Tracking';
90
+ const purposeDesc = purpose === 'code' ? 'code hosting' : 'issue tracking';
91
+ console.log(chalk_1.default.blue(`\n📦 ${purposeLabel} Platform`));
92
+ console.log(chalk_1.default.gray(`Select the platform for ${purposeDesc}:\n`));
93
+ // Use capability-based filtering if no explicit filter provided
94
+ let providers;
95
+ if (availableIds) {
96
+ providers = [];
97
+ for (const id of availableIds) {
98
+ const provider = await client.getProvider(id);
99
+ if (provider)
100
+ providers.push(provider);
101
+ }
102
+ }
103
+ else {
104
+ providers = await client.getProvidersWithCapability(purpose);
105
+ }
106
+ // Default to first available provider
107
+ const defaultProviderId = providers[0]?.id;
108
+ const choices = providers.map(provider => ({
109
+ title: provider.displayName,
110
+ value: provider.id
111
+ }));
112
+ const response = await (0, prompts_1.default)({
113
+ type: 'select',
114
+ name: 'provider',
115
+ message: `Select ${purposeDesc} platform`,
116
+ choices,
117
+ initial: 0
118
+ });
119
+ if (!response.provider) {
120
+ const defaultProvider = await client.getProvider(defaultProviderId);
121
+ console.log(chalk_1.default.yellow(`\nℹ️ No platform selected, defaulting to ${defaultProvider?.displayName || 'first available provider'}`));
122
+ return defaultProviderId;
123
+ }
124
+ const provider = await client.getProvider(response.provider);
125
+ if (provider) {
126
+ console.log(chalk_1.default.blue(`\n✓ ${purposeLabel}: ${provider.displayName}\n`));
127
+ }
128
+ return response.provider;
129
+ }
130
+ /**
131
+ * Prompt for a provider's token
132
+ */
133
+ async function promptForProviderToken(client, providerId) {
134
+ let provider;
135
+ try {
136
+ provider = await client.getProvider(providerId);
137
+ }
138
+ catch (error) {
139
+ // Server unavailable - use local fallback
140
+ const { getLocalProvider } = await Promise.resolve().then(() => __importStar(require('../providers/local-provider-registry')));
141
+ provider = getLocalProvider(providerId);
142
+ }
143
+ if (!provider) {
144
+ throw new Error(`Unknown provider: ${providerId}`);
145
+ }
146
+ console.log(chalk_1.default.blue(`\n🔧 ${provider.displayName} Integration Setup`));
147
+ console.log(`FRAIM requires a ${provider.displayName} token for integration.\n`);
148
+ const hasToken = await (0, prompts_1.default)({
149
+ type: 'confirm',
150
+ name: 'hasToken',
151
+ message: `Do you have a ${provider.displayName} token?`,
152
+ initial: false
153
+ });
154
+ if (!hasToken.hasToken) {
155
+ console.log(chalk_1.default.yellow(`\n📝 To create a ${provider.displayName} token:`));
156
+ console.log(chalk_1.default.gray(` ${provider.setupInstructions}`));
157
+ console.log(chalk_1.default.gray(` Visit: ${provider.docsUrl}\n`));
158
+ }
159
+ let token = null;
160
+ let attempts = 0;
161
+ const maxAttempts = 3;
162
+ while (!token && attempts < maxAttempts) {
163
+ const tokenResponse = await (0, prompts_1.default)({
164
+ type: 'password',
165
+ name: 'token',
166
+ message: attempts === 0
167
+ ? `Enter your ${provider.displayName} token`
168
+ : `Enter your ${provider.displayName} token (attempt ${attempts + 1}/${maxAttempts})`,
169
+ validate: (value) => {
170
+ if (!value)
171
+ return `${provider.displayName} token is required`;
172
+ return true;
173
+ }
174
+ });
175
+ if (!tokenResponse.token) {
176
+ console.log(chalk_1.default.red(`\n❌ ${provider.displayName} token is required.`));
177
+ attempts++;
178
+ if (attempts < maxAttempts) {
179
+ const retry = await (0, prompts_1.default)({
180
+ type: 'confirm',
181
+ name: 'retry',
182
+ message: 'Would you like to try entering the token again?',
183
+ initial: true
184
+ });
185
+ if (!retry.retry)
186
+ break;
187
+ }
188
+ continue;
189
+ }
190
+ token = tokenResponse.token;
191
+ console.log(chalk_1.default.green(`✅ ${provider.displayName} token received\n`));
192
+ }
193
+ if (!token) {
194
+ throw new Error(`Failed to get ${provider.displayName} token`);
195
+ }
196
+ return token;
197
+ }
198
+ /**
199
+ * Prompt for additional provider configuration (e.g., Jira URL and email)
200
+ */
201
+ async function promptForProviderConfig(client, providerId) {
202
+ let provider;
203
+ let schema;
204
+ try {
205
+ provider = await client.getProvider(providerId);
206
+ schema = await client.getProviderSchema(providerId);
207
+ }
208
+ catch (error) {
209
+ // Server unavailable - use local fallback
210
+ const { getLocalProvider, getLocalProviderConfigRequirements } = await Promise.resolve().then(() => __importStar(require('../providers/local-provider-registry')));
211
+ provider = getLocalProvider(providerId);
212
+ if (!provider) {
213
+ throw new Error(`Unknown provider: ${providerId}`);
214
+ }
215
+ schema = { configRequirements: getLocalProviderConfigRequirements(providerId) };
216
+ }
217
+ if (!provider) {
218
+ throw new Error(`Unknown provider: ${providerId}`);
219
+ }
220
+ const requirements = schema.configRequirements;
221
+ if (requirements.length === 0) {
222
+ return {};
223
+ }
224
+ console.log(chalk_1.default.blue(`\n🔧 ${provider.displayName} Configuration`));
225
+ console.log(`Additional configuration required for ${provider.displayName}.\n`);
226
+ const config = {};
227
+ for (const req of requirements) {
228
+ const response = await (0, prompts_1.default)({
229
+ type: req.type === 'email' ? 'text' : req.type === 'url' ? 'text' : 'text',
230
+ name: 'value',
231
+ message: `Enter ${req.displayName}`,
232
+ validate: (value) => {
233
+ if (req.required && !value) {
234
+ return `${req.displayName} is required`;
235
+ }
236
+ if (req.type === 'email' && value && !value.includes('@')) {
237
+ return 'Please enter a valid email address';
238
+ }
239
+ if (req.type === 'url' && value) {
240
+ try {
241
+ new URL(value.startsWith('http') ? value : `https://${value}`);
242
+ }
243
+ catch {
244
+ return 'Please enter a valid URL';
245
+ }
246
+ }
247
+ return true;
248
+ }
249
+ });
250
+ if (!response.value && req.required) {
251
+ throw new Error(`${req.displayName} is required`);
252
+ }
253
+ config[req.key] = response.value;
254
+ }
255
+ console.log(chalk_1.default.green(`✅ ${provider.displayName} configuration received\n`));
256
+ return config;
257
+ }
258
+ /**
259
+ * Get token and config for a provider (handles both token and additional config)
260
+ */
261
+ async function promptForProviderCredentials(client, providerId, existingToken, existingConfig) {
262
+ let provider;
263
+ let schema;
264
+ try {
265
+ provider = await client.getProvider(providerId);
266
+ schema = await client.getProviderSchema(providerId);
267
+ }
268
+ catch (error) {
269
+ // Server unavailable - use local fallback
270
+ const { getLocalProvider, getLocalProviderConfigRequirements } = await Promise.resolve().then(() => __importStar(require('../providers/local-provider-registry')));
271
+ provider = getLocalProvider(providerId);
272
+ if (!provider) {
273
+ throw new Error(`Unknown provider: ${providerId}`);
274
+ }
275
+ schema = { configRequirements: getLocalProviderConfigRequirements(providerId) };
276
+ }
277
+ if (!provider) {
278
+ throw new Error(`Unknown provider: ${providerId}`);
279
+ }
280
+ // Get token if not provided
281
+ const token = existingToken || await promptForProviderToken(client, providerId);
282
+ // Get additional config if needed
283
+ const requirements = schema.configRequirements;
284
+ let config;
285
+ if (requirements.length > 0) {
286
+ // Check if we have all required config
287
+ const hasAllConfig = existingConfig && requirements.every(req => !req.required || existingConfig[req.key]);
288
+ if (!hasAllConfig) {
289
+ config = await promptForProviderConfig(client, providerId);
290
+ // Merge with existing config
291
+ if (existingConfig) {
292
+ config = { ...existingConfig, ...config };
293
+ }
294
+ }
295
+ else {
296
+ config = existingConfig;
297
+ }
298
+ }
299
+ return { token, config };
300
+ }
@@ -29,6 +29,7 @@ async function syncFromRemote(options) {
29
29
  workflowsSynced: 0,
30
30
  scriptsSynced: 0,
31
31
  coachingSynced: 0,
32
+ docsSynced: 0,
32
33
  error: 'FRAIM_API_KEY not set'
33
34
  };
34
35
  }
@@ -50,6 +51,7 @@ async function syncFromRemote(options) {
50
51
  workflowsSynced: 0,
51
52
  scriptsSynced: 0,
52
53
  coachingSynced: 0,
54
+ docsSynced: 0,
53
55
  error: 'No files received'
54
56
  };
55
57
  }
@@ -106,12 +108,29 @@ async function syncFromRemote(options) {
106
108
  (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
107
109
  console.log(chalk_1.default.gray(` + coaching-moments/${file.path}`));
108
110
  }
109
- console.log(chalk_1.default.green(`\n✅ Synced ${workflowFiles.length} workflows, ${scriptFiles.length} scripts, and ${coachingFiles.length} coaching files from remote`));
111
+ // Sync docs to .fraim/docs/
112
+ const docsFiles = files.filter(f => f.type === 'docs');
113
+ const docsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'docs');
114
+ if (!(0, fs_1.existsSync)(docsDir)) {
115
+ (0, fs_1.mkdirSync)(docsDir, { recursive: true });
116
+ }
117
+ cleanDirectory(docsDir);
118
+ for (const file of docsFiles) {
119
+ const filePath = (0, path_1.join)(docsDir, file.path);
120
+ const fileDir = (0, path_1.dirname)(filePath);
121
+ if (!(0, fs_1.existsSync)(fileDir)) {
122
+ (0, fs_1.mkdirSync)(fileDir, { recursive: true });
123
+ }
124
+ (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
125
+ console.log(chalk_1.default.gray(` + docs/${file.path}`));
126
+ }
127
+ console.log(chalk_1.default.green(`\n✅ Synced ${workflowFiles.length} workflows, ${scriptFiles.length} scripts, ${coachingFiles.length} coaching files, and ${docsFiles.length} docs from remote`));
110
128
  return {
111
129
  success: true,
112
130
  workflowsSynced: workflowFiles.length,
113
131
  scriptsSynced: scriptFiles.length,
114
- coachingSynced: coachingFiles.length
132
+ coachingSynced: coachingFiles.length,
133
+ docsSynced: docsFiles.length
115
134
  };
116
135
  }
117
136
  catch (error) {
@@ -121,6 +140,7 @@ async function syncFromRemote(options) {
121
140
  workflowsSynced: 0,
122
141
  scriptsSynced: 0,
123
142
  coachingSynced: 0,
143
+ docsSynced: 0,
124
144
  error: error.message
125
145
  };
126
146
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.81",
3
+ "version": "2.0.83",
4
4
  "description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -13,8 +13,9 @@
13
13
  "dev:prod": "npm run build && node dist/src/fraim-mcp-server.js > server.log 2>&1",
14
14
  "build": "tsc && npm run build:stubs && node scripts/copy-ai-manager-rules.js && npm run validate:registry && tsx scripts/validate-purity.ts",
15
15
  "build:stubs": "tsx scripts/build-stub-registry.ts",
16
- "test-all": "npm run test && npm run test:ui",
16
+ "test-all": "npm run test && npm run test:isolated && npm run test:ui",
17
17
  "test": "node scripts/test-with-server.js",
18
+ "test:isolated": "npx tsx --test --test-reporter=spec tests/isolated/test-*.ts",
18
19
  "test:ui": "playwright test",
19
20
  "test:ui:headed": "playwright test --headed",
20
21
  "start:fraim": "tsx src/fraim-mcp-server.ts",
@@ -111,6 +112,7 @@
111
112
  "mongodb": "^7.0.0",
112
113
  "prompts": "^2.4.2",
113
114
  "stripe": "^20.3.1",
115
+ "toml": "^3.0.0",
114
116
  "tree-kill": "^1.2.2"
115
117
  }
116
118
  }
@@ -1,86 +0,0 @@
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.installCommand = void 0;
7
- const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
- const child_process_1 = require("child_process");
10
- const https_1 = __importDefault(require("https"));
11
- const http_1 = __importDefault(require("http"));
12
- /**
13
- * fraim install - One-command installer with key or token
14
- * Usage: fraim install --key=<key> | fraim install --token=<token> [--api-url=<url>]
15
- *
16
- * Convenience wrapper over fraim setup: installs fraim-framework globally, fetches key from
17
- * token if needed (no copy-paste), then runs fraim setup --key. fraim setup alone requires
18
- * the user to already have fraim installed and know their key.
19
- */
20
- exports.installCommand = new commander_1.Command('install')
21
- .description('Install FRAIM globally and configure with your key (use --key or --token from dashboard)')
22
- .option('--key <key>', 'FRAIM API key (from dashboard)')
23
- .option('--token <token>', 'Installer token (one-time, from dashboard download link)')
24
- .option('--api-url <url>', 'API base URL for token fetch', process.env.FRAIM_API_URL || 'https://fraim.wellnessatwork.me')
25
- .action(async (opts) => {
26
- let key = opts.key;
27
- const token = opts.token;
28
- const apiUrl = (opts.apiUrl || '').replace(/\/$/, '') || 'https://fraim.wellnessatwork.me';
29
- if (token && !key) {
30
- console.log(chalk_1.default.blue('Fetching key from FRAIM...'));
31
- try {
32
- key = await fetchInstallerKey(token, apiUrl);
33
- console.log(chalk_1.default.green('Key retrieved.\n'));
34
- }
35
- catch (err) {
36
- console.error(chalk_1.default.red('Failed to fetch key:'), err.message);
37
- console.error(chalk_1.default.gray('Token may be expired. Get a new link from the FRAIM site.'));
38
- process.exit(1);
39
- }
40
- }
41
- if (!key) {
42
- console.error(chalk_1.default.red('Usage: fraim install --key=<your-key>'));
43
- console.error(chalk_1.default.gray(' or: fraim install --token=<installer-token> [--api-url=<url>]'));
44
- console.error(chalk_1.default.gray('\nGet your key at https://fraim.wellnessatwork.me (click Get Started)'));
45
- process.exit(1);
46
- }
47
- console.log(chalk_1.default.blue('Installing fraim-framework...'));
48
- const installResult = (0, child_process_1.spawnSync)('npm', ['install', '-g', 'fraim-framework'], {
49
- stdio: 'inherit',
50
- shell: true
51
- });
52
- if (installResult.status !== 0)
53
- process.exit(installResult.status || 1);
54
- console.log(chalk_1.default.blue('\nConfiguring FRAIM with your key...'));
55
- const setupResult = (0, child_process_1.spawnSync)('fraim', ['setup', '--key', key], {
56
- stdio: 'inherit',
57
- shell: true
58
- });
59
- if (setupResult.status !== 0)
60
- process.exit(setupResult.status || 1);
61
- console.log(chalk_1.default.green('\n✅ FRAIM is ready. Run "fraim init-project" in your project directory.'));
62
- });
63
- function fetchInstallerKey(token, apiUrl) {
64
- return new Promise((resolve, reject) => {
65
- const url = new URL(`${apiUrl}/api/installer-key`);
66
- url.searchParams.set('token', token);
67
- const lib = url.protocol === 'https:' ? https_1.default : http_1.default;
68
- const req = lib.get(url.toString(), (res) => {
69
- let data = '';
70
- res.on('data', (chunk) => (data += chunk));
71
- res.on('end', () => {
72
- try {
73
- const json = JSON.parse(data);
74
- if (json.key)
75
- resolve(json.key);
76
- else
77
- reject(new Error(json.error || 'No key in response'));
78
- }
79
- catch {
80
- reject(new Error(`Invalid response: ${data}`));
81
- }
82
- });
83
- });
84
- req.on('error', reject);
85
- });
86
- }
@@ -1,57 +0,0 @@
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.isValidTokenFormat = exports.validateGitHubToken = exports.validateFraimKey = void 0;
7
- const chalk_1 = __importDefault(require("chalk"));
8
- const validateFraimKey = async (key) => {
9
- // Basic format validation
10
- if (!key || !key.startsWith('fraim_')) {
11
- return false;
12
- }
13
- // TODO: Add actual API validation when FRAIM server is available
14
- // For now, just validate format
15
- return key.length > 15; // More reasonable minimum length
16
- };
17
- exports.validateFraimKey = validateFraimKey;
18
- const validateGitHubToken = async (token) => {
19
- if (!token)
20
- return false;
21
- // Validate token format
22
- if (!token.startsWith('ghp_') && !token.startsWith('github_pat_')) {
23
- return false;
24
- }
25
- try {
26
- const response = await fetch('https://api.github.com/user', {
27
- headers: {
28
- Authorization: `Bearer ${token}`,
29
- 'User-Agent': 'FRAIM-Setup/1.0'
30
- }
31
- });
32
- return response.ok;
33
- }
34
- catch (error) {
35
- console.log(chalk_1.default.yellow('⚠️ Could not validate GitHub token (network issue), proceeding anyway'));
36
- return true; // Assume valid if network issues
37
- }
38
- };
39
- exports.validateGitHubToken = validateGitHubToken;
40
- const isValidTokenFormat = (token, type) => {
41
- if (type === 'fraim') {
42
- return token.startsWith('fraim_') && token.length > 15; // More reasonable minimum length
43
- }
44
- if (type === 'github') {
45
- return token.startsWith('ghp_') || token.startsWith('github_pat_');
46
- }
47
- if (type === 'gitlab') {
48
- // GitLab PATs commonly use glpat- prefix, but keep a permissive fallback for self-managed/token variants.
49
- return token.startsWith('glpat-') || token.length >= 20;
50
- }
51
- if (type === 'jira') {
52
- // Jira API tokens typically start with ATATT3 and have a minimum length.
53
- return token.startsWith('ATATT3') && token.length >= 20;
54
- }
55
- return false;
56
- };
57
- exports.isValidTokenFormat = isValidTokenFormat;