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.
- 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 +144 -77
- package/dist/src/cli/commands/add-provider.js +223 -0
- package/dist/src/cli/commands/doctor.js +131 -111
- package/dist/src/cli/commands/init-project.js +67 -31
- package/dist/src/cli/commands/setup.js +247 -563
- package/dist/src/cli/commands/sync.js +2 -2
- package/dist/src/cli/commands/test-mcp.js +35 -1
- package/dist/src/cli/doctor/check-runner.js +199 -0
- package/dist/src/cli/doctor/checks/global-setup-checks.js +220 -0
- package/dist/src/cli/doctor/checks/ide-config-checks.js +250 -0
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +381 -0
- package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
- package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
- package/dist/src/cli/doctor/checks/workflow-checks.js +247 -0
- package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
- package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
- package/dist/src/cli/doctor/types.js +6 -0
- package/dist/src/cli/fraim.js +44 -3
- package/dist/src/cli/mcp/ide-formats.js +243 -0
- package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
- package/dist/src/cli/mcp/mcp-server-registry.js +159 -0
- package/dist/src/cli/mcp/types.js +3 -0
- package/dist/src/cli/providers/local-provider-registry.js +145 -0
- package/dist/src/cli/providers/provider-registry.js +230 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +56 -118
- package/dist/src/cli/setup/mcp-config-generator.js +64 -321
- package/dist/src/cli/setup/provider-prompts.js +300 -0
- package/dist/src/cli/utils/remote-sync.js +22 -2
- package/package.json +4 -2
- package/dist/src/cli/commands/install.js +0 -86
- 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
|
-
|
|
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.
|
|
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;
|