fraim-framework 2.0.60 → 2.0.63

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.
@@ -66,16 +66,161 @@ const promptForFraimKey = async () => {
66
66
  console.log(chalk_1.default.gray('Please ensure you have a valid FRAIM key and try again.'));
67
67
  process.exit(1);
68
68
  };
69
- const saveGlobalConfig = (fraimKey, githubToken) => {
69
+ const promptForMode = async () => {
70
+ console.log(chalk_1.default.blue('\nšŸŽÆ Usage Mode Selection'));
71
+ console.log(chalk_1.default.gray('Choose how you want to use FRAIM:\n'));
72
+ const response = await (0, prompts_1.default)({
73
+ type: 'select',
74
+ name: 'mode',
75
+ message: 'Select your preferred mode',
76
+ choices: [
77
+ {
78
+ title: 'Integrated Mode',
79
+ value: 'integrated',
80
+ description: 'Full platform integration with GitHub/ADO for issue tracking and PRs'
81
+ },
82
+ {
83
+ title: 'Conversational Mode',
84
+ value: 'conversational',
85
+ description: 'AI workflows only, no platform integration required'
86
+ }
87
+ ],
88
+ initial: 0
89
+ });
90
+ if (!response.mode) {
91
+ console.log(chalk_1.default.yellow('\nā„¹ļø No mode selected, defaulting to Integrated Mode'));
92
+ return 'integrated';
93
+ }
94
+ if (response.mode === 'conversational') {
95
+ console.log(chalk_1.default.blue('\nāœ“ Conversational mode selected'));
96
+ console.log(chalk_1.default.gray(' You can use FRAIM workflows without platform credentials.'));
97
+ console.log(chalk_1.default.gray(' Platform features (issues, PRs) will be unavailable.\n'));
98
+ }
99
+ else {
100
+ console.log(chalk_1.default.blue('\nāœ“ Integrated mode selected'));
101
+ console.log(chalk_1.default.gray(' Full platform integration will be available.\n'));
102
+ }
103
+ return response.mode;
104
+ };
105
+ const promptForPlatforms = async () => {
106
+ console.log(chalk_1.default.blue('\nšŸ”§ Platform Selection'));
107
+ console.log(chalk_1.default.gray('Which platforms do you want to integrate with?\n'));
108
+ const response = await (0, prompts_1.default)({
109
+ type: 'multiselect',
110
+ name: 'platforms',
111
+ message: 'Select platforms (Space to select, Enter to confirm)',
112
+ choices: [
113
+ { title: 'GitHub', value: 'github', selected: true },
114
+ { title: 'Azure DevOps', value: 'ado', selected: false }
115
+ ],
116
+ min: 1
117
+ });
118
+ if (!response.platforms || response.platforms.length === 0) {
119
+ console.log(chalk_1.default.yellow('\nā„¹ļø No platforms selected, defaulting to GitHub'));
120
+ return { github: true, ado: false };
121
+ }
122
+ const platforms = {
123
+ github: response.platforms.includes('github'),
124
+ ado: response.platforms.includes('ado')
125
+ };
126
+ console.log(chalk_1.default.blue('\nāœ“ Selected platforms:'));
127
+ if (platforms.github)
128
+ console.log(chalk_1.default.gray(' • GitHub'));
129
+ if (platforms.ado)
130
+ console.log(chalk_1.default.gray(' • Azure DevOps'));
131
+ console.log();
132
+ return platforms;
133
+ };
134
+ const promptForADOToken = async () => {
135
+ console.log(chalk_1.default.blue('\nšŸ”§ Azure DevOps Integration Setup'));
136
+ console.log('FRAIM requires an Azure DevOps Personal Access Token for ADO integration.\n');
137
+ console.log(chalk_1.default.yellow('šŸ”‘ Why ADO token is required:'));
138
+ console.log(chalk_1.default.gray(' • Create and manage work items'));
139
+ console.log(chalk_1.default.gray(' • Access repository information'));
140
+ console.log(chalk_1.default.gray(' • Perform git operations'));
141
+ console.log(chalk_1.default.gray(' • Enable full development workflow automation\n'));
142
+ const hasToken = await (0, prompts_1.default)({
143
+ type: 'confirm',
144
+ name: 'hasToken',
145
+ message: 'Do you have an Azure DevOps Personal Access Token?',
146
+ initial: false
147
+ });
148
+ if (!hasToken.hasToken) {
149
+ console.log(chalk_1.default.yellow('\nšŸ“ To create an Azure DevOps Personal Access Token:'));
150
+ console.log(chalk_1.default.gray(' 1. Go to https://dev.azure.com/{your-org}/_usersSettings/tokens'));
151
+ console.log(chalk_1.default.gray(' 2. Click "New Token"'));
152
+ console.log(chalk_1.default.gray(' 3. Select these scopes:'));
153
+ console.log(chalk_1.default.gray(' • Code (Read & Write)'));
154
+ console.log(chalk_1.default.gray(' • Work Items (Read & Write)'));
155
+ console.log(chalk_1.default.gray(' • Project and Team (Read)'));
156
+ console.log(chalk_1.default.gray(' 4. Set expiration (recommend 1 year)'));
157
+ console.log(chalk_1.default.gray(' 5. Click "Create" and copy the token immediately'));
158
+ console.log(chalk_1.default.yellow(' āš ļø You won\'t be able to see the token again!\n'));
159
+ }
160
+ let token = null;
161
+ let attempts = 0;
162
+ const maxAttempts = 3;
163
+ while (!token && attempts < maxAttempts) {
164
+ const tokenResponse = await (0, prompts_1.default)({
165
+ type: 'password',
166
+ name: 'token',
167
+ message: attempts === 0
168
+ ? 'Enter your Azure DevOps PAT'
169
+ : `Enter your Azure DevOps PAT (attempt ${attempts + 1}/${maxAttempts})`,
170
+ validate: (value) => {
171
+ if (!value)
172
+ return 'ADO token is required';
173
+ if (value.length < 20)
174
+ return 'Token seems too short';
175
+ return true;
176
+ }
177
+ });
178
+ if (!tokenResponse.token) {
179
+ console.log(chalk_1.default.red('\nāŒ ADO token is required for Azure DevOps integration.'));
180
+ attempts++;
181
+ if (attempts < maxAttempts) {
182
+ const retry = await (0, prompts_1.default)({
183
+ type: 'confirm',
184
+ name: 'retry',
185
+ message: 'Would you like to try entering the token again?',
186
+ initial: true
187
+ });
188
+ if (!retry.retry)
189
+ break;
190
+ }
191
+ continue;
192
+ }
193
+ token = tokenResponse.token;
194
+ console.log(chalk_1.default.green('āœ… Azure DevOps token received\n'));
195
+ return token; // Token is definitely set here
196
+ }
197
+ throw new Error('Failed to get Azure DevOps token');
198
+ };
199
+ const saveGlobalConfig = (fraimKey, mode, tokens) => {
70
200
  const globalConfigDir = path_1.default.join(os_1.default.homedir(), '.fraim');
71
201
  const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
72
202
  if (!fs_1.default.existsSync(globalConfigDir)) {
73
203
  fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
74
204
  }
205
+ // Read existing config to preserve any existing tokens
206
+ let existingConfig = {};
207
+ if (fs_1.default.existsSync(globalConfigPath)) {
208
+ try {
209
+ existingConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
210
+ }
211
+ catch (e) {
212
+ // Ignore parse errors, will create new config
213
+ }
214
+ }
75
215
  const config = {
76
216
  version: (0, version_utils_1.getFraimVersion)(),
77
217
  apiKey: fraimKey,
78
- githubToken: githubToken,
218
+ mode: mode,
219
+ tokens: {
220
+ // Preserve existing tokens and merge with new ones
221
+ ...(existingConfig.tokens || {}),
222
+ ...tokens
223
+ },
79
224
  configuredAt: new Date().toISOString(),
80
225
  userPreferences: {
81
226
  autoSync: true,
@@ -87,6 +232,10 @@ const saveGlobalConfig = (fraimKey, githubToken) => {
87
232
  };
88
233
  const syncGlobalScripts = () => {
89
234
  console.log(chalk_1.default.blue('šŸ”„ Syncing FRAIM scripts to user directory...'));
235
+ // Ensure user directories exist and initialize user config
236
+ const { ensureUserFraimDirectories } = require('../../utils/script-sync-utils');
237
+ ensureUserFraimDirectories();
238
+ console.log(chalk_1.default.green('āœ… Initialized user configuration'));
90
239
  // Find registry path
91
240
  let registryPath = path_1.default.join(__dirname, '../../../../registry');
92
241
  if (!fs_1.default.existsSync(registryPath)) {
@@ -105,19 +254,43 @@ const syncGlobalScripts = () => {
105
254
  };
106
255
  const runSetup = async (options) => {
107
256
  console.log(chalk_1.default.blue('šŸš€ Welcome to FRAIM! Let\'s get you set up.\n'));
108
- // Show what we're about to do
109
- console.log(chalk_1.default.yellow('šŸ“‹ This setup will:'));
110
- console.log(chalk_1.default.gray(' • Validate your FRAIM and GitHub tokens'));
111
- console.log(chalk_1.default.gray(' • Create global FRAIM configuration'));
112
- console.log(chalk_1.default.gray(' • Detect and configure supported IDEs'));
113
- console.log(chalk_1.default.gray(' • Sync FRAIM scripts to your system'));
114
- console.log(chalk_1.default.gray(' • Set up MCP servers for AI integration\n'));
115
- // Get FRAIM key
116
- let fraimKey = options.key;
117
- if (!fraimKey) {
118
- fraimKey = await promptForFraimKey();
257
+ // Determine if this is an update (adding platforms to existing setup)
258
+ const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
259
+ const isUpdate = fs_1.default.existsSync(globalConfigPath) && (options.github || options.ado);
260
+ let fraimKey;
261
+ let mode;
262
+ const tokens = {};
263
+ if (isUpdate) {
264
+ // Update existing setup - add platforms
265
+ console.log(chalk_1.default.blue('šŸ“ Updating existing FRAIM configuration...\n'));
266
+ try {
267
+ const existingConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
268
+ fraimKey = existingConfig.apiKey;
269
+ mode = existingConfig.mode || 'integrated';
270
+ // Preserve existing tokens
271
+ if (existingConfig.tokens) {
272
+ if (existingConfig.tokens.github)
273
+ tokens.github = existingConfig.tokens.github;
274
+ if (existingConfig.tokens.ado)
275
+ tokens.ado = existingConfig.tokens.ado;
276
+ }
277
+ console.log(chalk_1.default.gray(` Current mode: ${mode}`));
278
+ console.log(chalk_1.default.gray(` Existing tokens: ${tokens.github ? 'GitHub āœ“' : ''} ${tokens.ado ? 'ADO āœ“' : ''}\n`));
279
+ }
280
+ catch (e) {
281
+ console.log(chalk_1.default.red('āŒ Failed to read existing configuration'));
282
+ process.exit(1);
283
+ }
119
284
  }
120
285
  else {
286
+ // Initial setup
287
+ console.log(chalk_1.default.yellow('šŸ“‹ This setup will:'));
288
+ console.log(chalk_1.default.gray(' • Validate your FRAIM key'));
289
+ console.log(chalk_1.default.gray(' • Choose your usage mode'));
290
+ console.log(chalk_1.default.gray(' • Configure platform integrations'));
291
+ console.log(chalk_1.default.gray(' • Sync FRAIM scripts to your system\n'));
292
+ // Get FRAIM key
293
+ fraimKey = options.key || await promptForFraimKey();
121
294
  if (!(0, token_validator_1.isValidTokenFormat)(fraimKey, 'fraim')) {
122
295
  console.log(chalk_1.default.red('āŒ Invalid FRAIM key format. Key must start with fraim_'));
123
296
  process.exit(1);
@@ -129,49 +302,104 @@ const runSetup = async (options) => {
129
302
  process.exit(1);
130
303
  }
131
304
  console.log(chalk_1.default.green('āœ… FRAIM key validated\n'));
305
+ // Ask for mode preference
306
+ mode = await promptForMode();
132
307
  }
133
- // Get GitHub token
134
- let githubToken = options.githubToken;
135
- if (!githubToken) {
136
- githubToken = await (0, auto_mcp_setup_1.promptForGitHubToken)();
137
- }
138
- else {
139
- if (!(0, token_validator_1.isValidTokenFormat)(githubToken, 'github')) {
140
- console.log(chalk_1.default.red('āŒ Invalid GitHub token format. Token must start with ghp_ or github_pat_'));
141
- process.exit(1);
308
+ // Handle platform tokens based on mode
309
+ if (mode === 'integrated') {
310
+ let needGitHub = options.github || false;
311
+ let needADO = options.ado || false;
312
+ // If no specific platform flags and not an update, ask user
313
+ if (!isUpdate && !needGitHub && !needADO) {
314
+ const platforms = await promptForPlatforms();
315
+ needGitHub = platforms.github;
316
+ needADO = platforms.ado;
142
317
  }
143
- console.log(chalk_1.default.blue('šŸ” Validating GitHub token...'));
144
- const isValid = await (0, token_validator_1.validateGitHubToken)(githubToken);
145
- if (!isValid) {
146
- console.log(chalk_1.default.red('āŒ Invalid GitHub token'));
147
- process.exit(1);
318
+ // Get GitHub token if needed
319
+ if (needGitHub && !tokens.github) {
320
+ if (options.githubToken) {
321
+ if (!(0, token_validator_1.isValidTokenFormat)(options.githubToken, 'github')) {
322
+ console.log(chalk_1.default.red('āŒ Invalid GitHub token format'));
323
+ process.exit(1);
324
+ }
325
+ console.log(chalk_1.default.blue('šŸ” Validating GitHub token...'));
326
+ const isValid = await (0, token_validator_1.validateGitHubToken)(options.githubToken);
327
+ if (!isValid) {
328
+ console.log(chalk_1.default.red('āŒ Invalid GitHub token'));
329
+ process.exit(1);
330
+ }
331
+ tokens.github = options.githubToken;
332
+ console.log(chalk_1.default.green('āœ… GitHub token validated\n'));
333
+ }
334
+ else {
335
+ try {
336
+ tokens.github = await (0, auto_mcp_setup_1.promptForGitHubToken)();
337
+ }
338
+ catch (e) {
339
+ // promptForGitHubToken calls process.exit on failure
340
+ process.exit(1);
341
+ }
342
+ }
343
+ }
344
+ // Get ADO token if needed
345
+ if (needADO && !tokens.ado) {
346
+ if (options.adoToken) {
347
+ tokens.ado = options.adoToken;
348
+ console.log(chalk_1.default.green('āœ… Azure DevOps token received\n'));
349
+ }
350
+ else {
351
+ try {
352
+ tokens.ado = await promptForADOToken();
353
+ }
354
+ catch (e) {
355
+ console.log(chalk_1.default.red('āŒ Failed to get Azure DevOps token'));
356
+ process.exit(1);
357
+ }
358
+ }
359
+ }
360
+ if (!tokens.github && !tokens.ado) {
361
+ console.log(chalk_1.default.yellow('āš ļø No platform tokens configured.'));
362
+ console.log(chalk_1.default.gray(' You can add them later with:'));
363
+ console.log(chalk_1.default.cyan(' fraim setup --github'));
364
+ console.log(chalk_1.default.cyan(' fraim setup --ado\n'));
148
365
  }
149
- console.log(chalk_1.default.green('āœ… GitHub token validated\n'));
366
+ }
367
+ else {
368
+ console.log(chalk_1.default.gray('ā„¹ļø Conversational mode: No platform tokens needed\n'));
150
369
  }
151
370
  // Save global configuration
152
371
  console.log(chalk_1.default.blue('šŸ’¾ Saving global configuration...'));
153
- saveGlobalConfig(fraimKey, githubToken);
154
- // Sync global scripts
155
- syncGlobalScripts();
156
- // Configure IDEs
157
- const selectedIDEs = options.ide ? options.ide.split(',') : undefined;
158
- const autoAll = options.all;
159
- if (autoAll && selectedIDEs) {
160
- console.log(chalk_1.default.yellow('āš ļø Both --all and --ide specified. Using --ide selection.'));
161
- }
162
- await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, githubToken, selectedIDEs);
163
- console.log(chalk_1.default.green('\nšŸŽÆ Setup complete! Next steps:'));
164
- console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
165
- console.log(chalk_1.default.cyan(' 2. Go to any project directory'));
166
- console.log(chalk_1.default.cyan(' 3. Run: fraim init-project'));
167
- console.log(chalk_1.default.cyan(' 4. Ask your AI agent: "list fraim workflows"'));
168
- console.log(chalk_1.default.blue('\nšŸ’” Use "fraim test-mcp" to verify your setup anytime.'));
372
+ saveGlobalConfig(fraimKey, mode, tokens);
373
+ // Sync global scripts (only on initial setup)
374
+ if (!isUpdate) {
375
+ syncGlobalScripts();
376
+ }
377
+ // Show summary
378
+ console.log(chalk_1.default.green('\nšŸŽÆ Configuration complete!'));
379
+ console.log(chalk_1.default.gray(` Mode: ${mode}`));
380
+ if (mode === 'integrated') {
381
+ console.log(chalk_1.default.gray(` Platforms: ${tokens.github ? 'GitHub āœ“' : 'GitHub āœ—'} ${tokens.ado ? 'ADO āœ“' : 'ADO āœ—'}`));
382
+ }
383
+ console.log(chalk_1.default.cyan('\nšŸ“ Next steps:'));
384
+ console.log(chalk_1.default.cyan(' 1. Go to any project directory'));
385
+ console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
386
+ console.log(chalk_1.default.cyan(' 3. Start using FRAIM workflows'));
387
+ if (mode === 'integrated' && (!tokens.github || !tokens.ado)) {
388
+ console.log(chalk_1.default.gray('\nšŸ’” To add more platforms later:'));
389
+ if (!tokens.github)
390
+ console.log(chalk_1.default.gray(' fraim setup --github'));
391
+ if (!tokens.ado)
392
+ console.log(chalk_1.default.gray(' fraim setup --ado'));
393
+ }
169
394
  };
170
395
  exports.runSetup = runSetup;
171
396
  exports.setupCommand = new commander_1.Command('setup')
172
- .description('Complete global FRAIM setup with IDE configuration')
397
+ .description('Complete global FRAIM setup with platform configuration')
173
398
  .option('--key <key>', 'FRAIM API key')
174
399
  .option('--github-token <token>', 'GitHub Personal Access Token')
175
- .option('--all', 'Auto-configure all detected IDEs')
176
- .option('--ide <ides>', 'Configure specific IDEs (comma-separated: claude,cursor,kiro)')
400
+ .option('--ado-token <token>', 'Azure DevOps Personal Access Token')
401
+ .option('--github', 'Add/update GitHub integration')
402
+ .option('--ado', 'Add/update Azure DevOps integration')
403
+ .option('--all', 'Configure all detected IDEs (deprecated)')
404
+ .option('--ide <ides>', 'Configure specific IDEs (deprecated)')
177
405
  .action(exports.runSetup);