fraim 2.0.271 → 2.0.272

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.
@@ -36,15 +36,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.setupCommandInitialization = exports.setupCommand = exports.runSetup = exports.saveGlobalConfig = void 0;
40
- // Refactored setup.ts - Generic provider system with zero hardcoded provider knowledge
39
+ exports.setupCommand = exports.runSetup = exports.saveGlobalConfig = void 0;
40
+ // Global FRAIM setup. Third-party provider setup is delegated to connect-mcp.
41
41
  const commander_1 = require("commander");
42
42
  const chalk_1 = __importDefault(require("chalk"));
43
43
  const prompts_1 = __importDefault(require("prompts"));
44
44
  const fs_1 = __importDefault(require("fs"));
45
45
  const path_1 = __importDefault(require("path"));
46
46
  const add_ide_1 = require("./add-ide");
47
- const provider_registry_1 = require("../providers/provider-registry");
48
47
  const script_sync_utils_1 = require("../utils/script-sync-utils");
49
48
  function parseModeOption(mode) {
50
49
  if (mode === 'conversational' || mode === 'integrated' || mode === 'split') {
@@ -204,10 +203,10 @@ const runSetup = async (options) => {
204
203
  if (!isKeyUpdate) {
205
204
  console.log(chalk_1.default.gray(' Current configuration:'));
206
205
  console.log(chalk_1.default.gray(` • Mode: ${mode}`));
207
- console.log(chalk_1.default.gray(' • Platforms: managed per-project via fraim add-provider'));
206
+ console.log(chalk_1.default.gray(' • Platforms: connected by your agent through the FRAIM connect-mcp skill'));
208
207
  console.log();
209
208
  if (process.env.FRAIM_NON_INTERACTIVE || process.env.CI) {
210
- console.log(chalk_1.default.gray('Setup already configured. Use "fraim add-provider <provider>" to connect platforms.'));
209
+ console.log(chalk_1.default.gray('Setup already configured. Ask your agent to use the FRAIM connect-mcp skill for platforms.'));
211
210
  return;
212
211
  }
213
212
  const response = await (0, prompts_1.default)({
@@ -252,9 +251,9 @@ const runSetup = async (options) => {
252
251
  console.log(chalk_1.default.blue('\n💾 Saving global configuration...'));
253
252
  (0, exports.saveGlobalConfig)(fraimKey, mode);
254
253
  console.log(chalk_1.default.blue('\n🔌 Configuring MCP servers...'));
255
- await (0, add_ide_1.runAddIDE)({ ide: options.ide, all: !options.ide, skipTokenPrompts: true });
254
+ await (0, add_ide_1.runAddIDE)({ ide: options.ide, all: !options.ide });
256
255
  console.log(chalk_1.default.green('\n🎯 Reconfiguration complete!'));
257
- console.log(chalk_1.default.cyan('\n💡 To connect platforms, run: fraim add-provider <github|gitlab|ado|jira>'));
256
+ console.log(chalk_1.default.cyan('\n💡 To connect a platform, ask your agent to use the FRAIM connect-mcp skill.'));
258
257
  return;
259
258
  }
260
259
  }
@@ -272,12 +271,12 @@ const runSetup = async (options) => {
272
271
  console.log(chalk_1.default.gray(' • Configure FRAIM MCP servers in your IDEs'));
273
272
  console.log(chalk_1.default.gray(' • Sync FRAIM scripts to your system\n'));
274
273
  console.log(chalk_1.default.gray(' Platforms (GitHub, GitLab, etc.) are connected per-project'));
275
- console.log(chalk_1.default.gray(' via "fraim add-provider" no tokens needed here.\n'));
274
+ console.log(chalk_1.default.gray(' by your agent through the FRAIM connect-mcp skill.\n'));
276
275
  fraimKey = options.key || await promptForFraimKey();
277
276
  console.log(chalk_1.default.green('✅ FRAIM key accepted\n'));
278
277
  mode = options.mode ? parseModeOption(options.mode) : await promptForMode();
279
278
  }
280
- // Save global configuration (key + mode only; provider tokens live in IDE MCP configs)
279
+ // Save global configuration (key + mode only; third-party authentication is provider/host-owned)
281
280
  console.log(chalk_1.default.blue('💾 Saving global configuration...'));
282
281
  (0, exports.saveGlobalConfig)(fraimKey, mode);
283
282
  // Configure MCP servers and install IDE surfaces (slash commands, rules)
@@ -285,10 +284,10 @@ const runSetup = async (options) => {
285
284
  console.log(chalk_1.default.blue(isUpdate ? '\n🔄 Updating IDE MCP configurations...' : '\n🔌 Configuring MCP servers...'));
286
285
  if (!isUpdate && mode === 'conversational') {
287
286
  console.log(chalk_1.default.yellow('ℹ️ Conversational mode: Configuring FRAIM MCP server'));
288
- console.log(chalk_1.default.gray(' FRAIM jobs will work; platform-specific features are added via fraim add-provider\n'));
287
+ console.log(chalk_1.default.gray(' FRAIM jobs will work; platform connections are handled through the FRAIM connect-mcp skill\n'));
289
288
  }
290
289
  try {
291
- await (0, add_ide_1.runAddIDE)({ ide: options.ide, all: isUpdate || !options.ide, skipTokenPrompts: true });
290
+ await (0, add_ide_1.runAddIDE)({ ide: options.ide, all: isUpdate || !options.ide });
292
291
  }
293
292
  catch (e) {
294
293
  console.log(chalk_1.default.yellow('⚠️ MCP configuration encountered issues'));
@@ -368,9 +367,9 @@ const runSetup = async (options) => {
368
367
  console.log(chalk_1.default.gray(' This enables project-specific customizations,'));
369
368
  console.log(chalk_1.default.gray(' GitHub workflows, and team learning.'));
370
369
  console.log(chalk_1.default.cyan('\n To connect platforms (GitHub, GitLab, ADO, Jira):'));
371
- console.log(chalk_1.default.white(' fraim add-provider <provider>'));
372
- console.log(chalk_1.default.gray(' Provider tokens are stored directly in IDE MCP configs.'));
373
- console.log(chalk_1.default.gray(' GitHub uses IDE-native OAuth no token needed.'));
370
+ console.log(chalk_1.default.white(' Ask your agent to use the FRAIM connect-mcp skill.'));
371
+ console.log(chalk_1.default.gray(' The agent follows current provider and host guidance for each selected target.'));
372
+ console.log(chalk_1.default.gray(' Authentication and credential entry stay outside model context.'));
374
373
  }
375
374
  }
376
375
  else {
@@ -390,44 +389,8 @@ const runSetup = async (options) => {
390
389
  };
391
390
  exports.runSetup = runSetup;
392
391
  exports.setupCommand = new commander_1.Command('setup')
393
- .description('Complete global FRAIM setup with platform configuration')
392
+ .description('Complete global FRAIM setup')
394
393
  .option('--key <key>', 'FRAIM API key')
395
394
  .option('--mode <mode>', 'Usage mode: integrated | split | conversational')
396
395
  .option('--ide <ides>', 'Configure specific IDEs');
397
- // Track initialization promise for CLI entry point
398
- exports.setupCommandInitialization = null;
399
- // Dynamically add provider options from registry (async initialization)
400
- exports.setupCommandInitialization = (async () => {
401
- try {
402
- const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
403
- for (const providerId of allProviderIds) {
404
- const provider = await (0, provider_registry_1.getProvider)(providerId);
405
- if (!provider)
406
- continue;
407
- // Add provider flag (e.g., --github)
408
- exports.setupCommand.option(`--${providerId}`, `Add/update ${provider.displayName} integration`);
409
- // Add token option (e.g., --github-token) - primarily for testing/automation
410
- exports.setupCommand.option(`--${providerId}-token <token>`, `${provider.displayName} token (optional - will prompt if not provided)`);
411
- // Add config options if provider requires them
412
- const configReqs = await (0, provider_registry_1.getProviderConfigRequirements)(providerId);
413
- configReqs.forEach(req => {
414
- // Use custom CLI option name if provided, otherwise convert key to kebab-case
415
- const optionSuffix = req.cliOptionName || req.key.replace(/([A-Z])/g, '-$1').toLowerCase();
416
- const optionName = `${providerId}-${optionSuffix}`;
417
- exports.setupCommand.option(`--${optionName} <value>`, `${req.description} (optional - will prompt if not provided)`);
418
- });
419
- }
420
- }
421
- catch (error) {
422
- // If we can't fetch providers (e.g., no config yet), that's okay
423
- // The command will still work, just without dynamic options
424
- }
425
- })();
426
- // Wrap the action to ensure initialization completes first
427
- exports.setupCommand.action(async (options) => {
428
- // Wait for dynamic options to be registered
429
- if (exports.setupCommandInitialization) {
430
- await exports.setupCommandInitialization;
431
- }
432
- return (0, exports.runSetup)(options);
433
- });
396
+ exports.setupCommand.action(exports.runSetup);
@@ -181,46 +181,34 @@ function failSync(mode, message) {
181
181
  }
182
182
  const runSync = async (options) => {
183
183
  const failHard = options.failHard ?? 'exit';
184
- // Handle --global flag: sync to user-level ~/.fraim/ instead of project
185
- if (options.global) {
186
- console.log(chalk_1.default.blue('Syncing FRAIM content to user-level directory (~/.fraim/)...'));
187
- try {
188
- const { syncUserLevelArtifacts } = await Promise.resolve().then(() => __importStar(require('../setup/user-level-sync')));
189
- await syncUserLevelArtifacts();
190
- console.log(chalk_1.default.green('\n✅ User-level FRAIM content sync complete.'));
191
- }
192
- catch (error) {
193
- console.error(chalk_1.default.red(`User-level sync failed: ${error.message}`));
194
- failSync(failHard, `User-level sync failed: ${error.message}`);
195
- }
196
- return;
197
- }
198
184
  const projectRoot = options.projectRoot ? path_1.default.resolve(options.projectRoot) : process.cwd();
199
- const config = (0, config_loader_1.loadFraimConfig)((0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot));
200
- const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectRoot);
201
- const refreshLocalIgnoreConfig = () => {
202
- const ignoreUpdate = (0, fraim_gitignore_1.ensureFraimSyncedContentLocallyExcluded)(projectRoot);
203
- if (ignoreUpdate.gitInfoExcludeUpdated) {
204
- console.log(chalk_1.default.green('Updated .git/info/exclude FRAIM managed block'));
185
+ // ─── Credential resolution (before project detection) ─────────────────────
186
+ // Order: user-level config → env var → project config (only if in a project)
187
+ const remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
188
+ let apiKey = loadUserApiKey() || process.env.FRAIM_API_KEY;
189
+ const hasProject = (0, project_fraim_paths_1.workspaceFraimExists)(projectRoot);
190
+ if (!apiKey && hasProject) {
191
+ const config = (0, config_loader_1.loadFraimConfig)((0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot));
192
+ apiKey = config.apiKey;
193
+ }
194
+ if (!apiKey) {
195
+ if (process.env.TEST_MODE === 'true') {
196
+ console.log(chalk_1.default.yellow('TEST_MODE: No API key configured. Using test placeholder key.'));
197
+ apiKey = 'test-mode-key';
205
198
  }
206
- if (ignoreUpdate.gitignoreUpdated) {
207
- console.log(chalk_1.default.green('Removed legacy FRAIM sync block from .gitignore'));
199
+ else {
200
+ console.error(chalk_1.default.red('No API key configured. Cannot sync.'));
201
+ console.error(chalk_1.default.yellow('Set FRAIM_API_KEY in your environment, or add apiKey to ~/.fraim/config.json'));
202
+ console.error(chalk_1.default.yellow('Or use --local to sync from a locally running FRAIM server.'));
203
+ failSync(failHard, 'No API key configured.');
208
204
  }
209
- };
210
- // Issue #563: refresh the machine-level org cache (~/.fraim/org/) from the
211
- // configured org backend. Failures never fail the sync: an existing cache is
212
- // served stale with its age (R2.3, R4.3).
213
- const refreshOrgCache = async (remoteUrl, apiKey) => {
214
- // Org sync is a network round-trip to the org backend. In automated
215
- // tests there is no org configured and no server to reach, so skip it
216
- // entirely rather than emit warnings or attempt a real request. Local
217
- // dev (--local / FRAIM_LOCAL_SYNC) passes the loopback URL + 'local-dev'
218
- // key below, which syncOrgCache treats as "no cloud org" unless a git
219
- // backend is configured, so it degrades cleanly without this guard.
205
+ }
206
+ // ─── Closures shared between layers ───────────────────────────────────────
207
+ const refreshOrgCache = async (url, key) => {
220
208
  if (process.env.TEST_MODE === 'true')
221
209
  return;
222
210
  const { syncOrgCache } = await Promise.resolve().then(() => __importStar(require('../utils/org-pack-sync')));
223
- const outcome = await syncOrgCache({ remoteUrl, apiKey });
211
+ const outcome = await syncOrgCache({ remoteUrl: url, apiKey: key });
224
212
  if (outcome.status === 'synced') {
225
213
  console.log(chalk_1.default.green(`Org context synced (${outcome.metadata.backend}, version ${outcome.metadata.version.slice(0, 12)})`));
226
214
  }
@@ -230,8 +218,6 @@ const runSync = async (options) => {
230
218
  else if (outcome.status === 'absent') {
231
219
  console.log(chalk_1.default.yellow(`Org context not synced: ${outcome.error}`));
232
220
  }
233
- // Migrate stranded content from the legacy standard path to contentRoot,
234
- // then report any files that still could not be moved.
235
221
  try {
236
222
  const { migrateStrandedContent, findStrandedLegacyContent, resolvePackHome } = await Promise.resolve().then(() => __importStar(require('../utils/pack-home')));
237
223
  migrateStrandedContent('org');
@@ -247,10 +233,6 @@ const runSync = async (options) => {
247
233
  }
248
234
  }
249
235
  catch { /* reporting must never fail a sync */ }
250
- // 'disabled' (no org configured) stays silent.
251
- // R8.1: one-time publish offer for legacy machine-local org files. The
252
- // publish itself runs through organization-onboarding (propose-and-approve);
253
- // sync only surfaces the offer and never moves files on its own (R8.2).
254
236
  if (outcome.status !== 'disabled') {
255
237
  const { detectLegacyOrgArtifacts } = await Promise.resolve().then(() => __importStar(require('../utils/org-migration')));
256
238
  const legacy = detectLegacyOrgArtifacts();
@@ -260,11 +242,11 @@ const runSync = async (options) => {
260
242
  }
261
243
  }
262
244
  };
263
- const refreshManagerCache = async (remoteUrl, apiKey) => {
245
+ const refreshManagerCache = async (url, key) => {
264
246
  if (process.env.TEST_MODE === 'true')
265
247
  return;
266
248
  const { syncManagerCache } = await Promise.resolve().then(() => __importStar(require('../utils/manager-pack-sync')));
267
- const outcome = await syncManagerCache({ remoteUrl, apiKey });
249
+ const outcome = await syncManagerCache({ remoteUrl: url, apiKey: key });
268
250
  if (outcome.status === 'synced') {
269
251
  console.log(chalk_1.default.green(`Manager context synced (${outcome.metadata.backend}, version ${outcome.metadata.version.slice(0, 12)})`));
270
252
  }
@@ -274,8 +256,6 @@ const runSync = async (options) => {
274
256
  else if (outcome.status === 'absent') {
275
257
  console.log(chalk_1.default.yellow(`Manager context not synced: ${outcome.error}`));
276
258
  }
277
- // Migrate stranded content from the legacy standard path to contentRoot,
278
- // then report any files that still could not be moved.
279
259
  try {
280
260
  const { migrateStrandedContent, findStrandedLegacyContent, resolvePackHome } = await Promise.resolve().then(() => __importStar(require('../utils/pack-home')));
281
261
  migrateStrandedContent('manager');
@@ -292,25 +272,17 @@ const runSync = async (options) => {
292
272
  }
293
273
  catch { /* reporting must never fail a sync */ }
294
274
  };
295
- const isNpx = process.env.npm_config_prefix === undefined || process.env.npm_lifecycle_event === 'npx';
296
- const isGlobal = !isNpx && (process.env.npm_config_global === 'true' || process.env.npm_config_prefix);
297
- if (isGlobal && !options.skipUpdates) {
298
- console.log(chalk_1.default.yellow('You are running a global installation of FRAIM.'));
299
- console.log(chalk_1.default.gray('Updates are not automatic in this mode.'));
300
- console.log(chalk_1.default.cyan('Recommended: Use "npx fraim@latest sync" instead.\n'));
301
- }
302
- const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
303
- // Allow `FRAIM_LOCAL_SYNC=1` to flip into local-mode without needing
304
- // the --local CLI flag. The FRE's runProjectRow path doesn't surface
305
- // a --local flag, but devs validating the FRE locally need a way to
306
- // point sync at their localhost MCP server. With this env var set,
307
- // any caller (including the FRE) routes through the local sync path
308
- // exactly as if the user had passed --local.
275
+ // ─── Local sync shortcut (dev mode) ───────────────────────────────────────
309
276
  const useLocal = options.local || process.env.FRAIM_LOCAL_SYNC === '1';
310
277
  if (useLocal) {
311
278
  console.log(chalk_1.default.blue('Syncing FRAIM jobs from local server...'));
312
279
  const localPort = process.env.FRAIM_MCP_PORT ? parseInt(process.env.FRAIM_MCP_PORT) : (0, git_utils_1.getPort)();
313
280
  const localUrl = resolveExplicitLocalSyncUrl() || `http://localhost:${localPort}`;
281
+ const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
282
+ if (!hasProject) {
283
+ console.error(chalk_1.default.red('Local sync requires a FRAIM project directory (fraim/ must exist).'));
284
+ failSync(failHard, 'Local sync requires a project directory.');
285
+ }
314
286
  const result = await syncFromRemote({
315
287
  remoteUrl: localUrl,
316
288
  apiKey: 'local-dev',
@@ -319,9 +291,16 @@ const runSync = async (options) => {
319
291
  });
320
292
  if (result.success) {
321
293
  console.log(chalk_1.default.green(`Successfully synced ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from local server`));
294
+ const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectRoot);
322
295
  removeLegacyVersionFromConfig(fraimDir);
323
296
  writeSyncMetadata('local', localUrl);
324
- refreshLocalIgnoreConfig();
297
+ const ignoreUpdate = (0, fraim_gitignore_1.ensureFraimSyncedContentLocallyExcluded)(projectRoot);
298
+ if (ignoreUpdate.gitInfoExcludeUpdated) {
299
+ console.log(chalk_1.default.green('Updated .git/info/exclude FRAIM managed block'));
300
+ }
301
+ if (ignoreUpdate.gitignoreUpdated) {
302
+ console.log(chalk_1.default.green('Removed legacy FRAIM sync block from .gitignore'));
303
+ }
325
304
  if (options.projectAdapters) {
326
305
  const allowedTypes = resolveAllowedConfigTypes();
327
306
  await cleanupStaleAdapterFiles(projectRoot, allowedTypes);
@@ -338,39 +317,91 @@ const runSync = async (options) => {
338
317
  console.error(chalk_1.default.yellow('Make sure the FRAIM MCP server is running locally (npm run dev).'));
339
318
  failSync(failHard, `Local sync failed: ${result.error}`);
340
319
  }
341
- let apiKey = loadUserApiKey() || config.apiKey || process.env.FRAIM_API_KEY;
342
- if (!apiKey) {
320
+ // ─── Global install notice ────────────────────────────────────────────────
321
+ const isNpx = process.env.npm_config_prefix === undefined || process.env.npm_lifecycle_event === 'npx';
322
+ const isGlobal = !isNpx && (process.env.npm_config_global === 'true' || process.env.npm_config_prefix);
323
+ if (isGlobal && !options.skipUpdates) {
324
+ console.log(chalk_1.default.yellow('You are running a global installation of FRAIM.'));
325
+ console.log(chalk_1.default.gray('Updates are not automatic in this mode.'));
326
+ console.log(chalk_1.default.cyan('Recommended: Use "npx fraim@latest sync" instead.\n'));
327
+ }
328
+ // ═══════════════════════════════════════════════════════════════════════════
329
+ // LAYER 1 — Machine-level sync (always runs)
330
+ // ═══════════════════════════════════════════════════════════════════════════
331
+ console.log(chalk_1.default.blue('Syncing machine-level FRAIM content (~/.fraim/)...'));
332
+ // 1a. Fetch registry files (single API call shared with Layer 2)
333
+ let registryFiles = [];
334
+ try {
335
+ registryFiles = await (0, remote_sync_1.fetchRegistryFiles)(remoteUrl, apiKey);
336
+ }
337
+ catch (error) {
343
338
  if (process.env.TEST_MODE === 'true') {
344
- console.log(chalk_1.default.yellow('TEST_MODE: No API key configured. Using test placeholder key.'));
345
- apiKey = 'test-mode-key';
339
+ console.log(chalk_1.default.yellow('TEST_MODE: Registry fetch failed (server may be unavailable). Continuing.'));
346
340
  }
347
341
  else {
348
- console.error(chalk_1.default.red('No API key configured. Cannot sync.'));
349
- console.error(chalk_1.default.yellow(`Set FRAIM_API_KEY in your environment, or add apiKey to ~/.fraim/config.json or ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`));
350
- console.error(chalk_1.default.yellow('Or use --local to sync from a locally running FRAIM server.'));
351
- failSync(failHard, 'No API key configured.');
342
+ console.error(chalk_1.default.red(`Registry sync failed: ${error.message}`));
343
+ console.error(chalk_1.default.yellow('Check your API key and network connection.'));
344
+ failSync(failHard, `Registry sync failed: ${error.message}`);
352
345
  }
353
346
  }
354
- console.log(chalk_1.default.blue('Syncing FRAIM jobs from remote server...'));
347
+ // 1b. Sync scripts to ~/.fraim/scripts/
348
+ const scriptsSynced = await (0, remote_sync_1.syncScriptsToUserDir)(registryFiles);
349
+ if (scriptsSynced > 0) {
350
+ console.log(chalk_1.default.green(` Synced ${scriptsSynced} scripts to ~/.fraim/scripts/`));
351
+ }
352
+ // 1c. Refresh org home
353
+ await refreshOrgCache(remoteUrl, apiKey);
354
+ // 1d. Refresh manager home
355
+ await refreshManagerCache(remoteUrl, apiKey);
356
+ // 1e. Refresh MCP proxy launcher
357
+ const { ensureFraimMcpLatestLauncher } = await Promise.resolve().then(() => __importStar(require('../mcp/fraim-mcp-latest-launcher')));
358
+ ensureFraimMcpLatestLauncher();
359
+ // 1f. Ensure user-level directories
360
+ const { ensureUserLevelDirectories } = await Promise.resolve().then(() => __importStar(require('../setup/user-level-sync')));
361
+ ensureUserLevelDirectories();
362
+ // 1g. Write sync metadata
363
+ writeSyncMetadata('remote', remoteUrl);
364
+ console.log(chalk_1.default.green('✅ Machine-level sync complete.'));
365
+ // ═══════════════════════════════════════════════════════════════════════════
366
+ // LAYER 2 — Project-level sync (conditional)
367
+ // ═══════════════════════════════════════════════════════════════════════════
368
+ if (options.global) {
369
+ console.log(chalk_1.default.gray('--global: skipping project-level sync.'));
370
+ return;
371
+ }
372
+ if (!hasProject) {
373
+ console.log(chalk_1.default.cyan('No FRAIM project detected at cwd. Machine-level sync complete.'));
374
+ console.log(chalk_1.default.cyan('Run from a project folder or pass --project-root to sync project stubs.'));
375
+ return;
376
+ }
377
+ console.log(chalk_1.default.blue('Syncing project-level FRAIM content (fraim/)...'));
378
+ const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
379
+ const config = (0, config_loader_1.loadFraimConfig)((0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot));
380
+ const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectRoot);
355
381
  const result = await syncFromRemote({
356
- remoteUrl: config.remoteUrl,
382
+ remoteUrl: config.remoteUrl || remoteUrl,
357
383
  apiKey,
358
384
  projectRoot,
359
- skipUpdates: options.skipUpdates || false
385
+ skipUpdates: options.skipUpdates || false,
386
+ registryFiles
360
387
  });
361
388
  if (!result.success) {
362
- console.error(chalk_1.default.red(`Remote sync failed: ${result.error}`));
363
- console.error(chalk_1.default.yellow('Check your API key and network connection.'));
389
+ console.error(chalk_1.default.red(`Project sync failed: ${result.error}`));
364
390
  if (process.env.TEST_MODE === 'true') {
365
- console.log(chalk_1.default.yellow('TEST_MODE: Continuing without remote sync (server may be unavailable).'));
391
+ console.log(chalk_1.default.yellow('TEST_MODE: Continuing without project sync.'));
366
392
  return;
367
393
  }
368
- failSync(failHard, `Remote sync failed: ${result.error}`);
394
+ failSync(failHard, `Project sync failed: ${result.error}`);
369
395
  }
370
- console.log(chalk_1.default.green(`Successfully synced ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from remote`));
396
+ console.log(chalk_1.default.green(`Successfully synced ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, and ${result.docsSynced} docs`));
371
397
  removeLegacyVersionFromConfig(fraimDir);
372
- writeSyncMetadata('remote', config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me');
373
- refreshLocalIgnoreConfig();
398
+ const ignoreUpdate = (0, fraim_gitignore_1.ensureFraimSyncedContentLocallyExcluded)(projectRoot);
399
+ if (ignoreUpdate.gitInfoExcludeUpdated) {
400
+ console.log(chalk_1.default.green('Updated .git/info/exclude FRAIM managed block'));
401
+ }
402
+ if (ignoreUpdate.gitignoreUpdated) {
403
+ console.log(chalk_1.default.green('Removed legacy FRAIM sync block from .gitignore'));
404
+ }
374
405
  if (options.projectAdapters) {
375
406
  const allowedTypes = resolveAllowedConfigTypes();
376
407
  await cleanupStaleAdapterFiles(projectRoot, allowedTypes);
@@ -379,8 +410,7 @@ const runSync = async (options) => {
379
410
  console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
380
411
  }
381
412
  }
382
- await refreshOrgCache(config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me', apiKey);
383
- await refreshManagerCache(config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me', apiKey);
413
+ console.log(chalk_1.default.green('✅ Project-level sync complete.'));
384
414
  };
385
415
  exports.runSync = runSync;
386
416
  exports.syncCommand = new commander_1.Command('sync')
@@ -388,6 +418,7 @@ exports.syncCommand = new commander_1.Command('sync')
388
418
  .option('-f, --force', 'Force sync even if digest matches')
389
419
  .option('--skip-updates', 'Skip checking for CLI updates (legacy)')
390
420
  .option('--local', 'Sync from local development server (port derived from git branch)')
391
- .option('--global', 'Sync user-level FRAIM content (~/.fraim/) instead of project')
421
+ .option('--global', 'Sync machine-level FRAIM content only (~/.fraim/), skip project stubs')
422
+ .option('--project-root <path>', 'Explicit project root (enables project-level sync from any cwd)')
392
423
  .option('--project-adapters', 'Write legacy project-local FRAIM agent adapter files')
393
424
  .action(exports.runSync);
@@ -1,38 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || (function () {
20
- var ownKeys = function(o) {
21
- ownKeys = Object.getOwnPropertyNames || function (o) {
22
- var ar = [];
23
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
- return ar;
25
- };
26
- return ownKeys(o);
27
- };
28
- return function (mod) {
29
- if (mod && mod.__esModule) return mod;
30
- var result = {};
31
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
- __setModuleDefault(result, mod);
33
- return result;
34
- };
35
- })();
36
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
37
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
38
5
  };
@@ -56,7 +23,6 @@ const workspace_config_1 = require("./commands/workspace-config");
56
23
  const org_1 = require("./commands/org");
57
24
  const manager_1 = require("./commands/manager");
58
25
  const cleanup_artifacts_1 = require("./commands/cleanup-artifacts");
59
- const learning_usage_1 = require("./commands/learning-usage");
60
26
  const fs_1 = __importDefault(require("fs"));
61
27
  const path_1 = __importDefault(require("path"));
62
28
  const program = new commander_1.Command();
@@ -102,13 +68,4 @@ program.addCommand(workspace_config_1.workspaceConfigCommand);
102
68
  program.addCommand(org_1.orgCommand);
103
69
  program.addCommand(manager_1.managerCommand);
104
70
  program.addCommand(cleanup_artifacts_1.cleanupArtifactsCommand);
105
- program.addCommand(learning_usage_1.learningUsageCommand);
106
- // Wait for async command initialization before parsing
107
- (async () => {
108
- // Import the initialization promise from setup command
109
- const { setupCommandInitialization } = await Promise.resolve().then(() => __importStar(require('./commands/setup')));
110
- if (setupCommandInitialization) {
111
- await setupCommandInitialization;
112
- }
113
- program.parse(process.argv);
114
- })();
71
+ program.parse(process.argv);
@@ -257,7 +257,7 @@ class CodexFormat {
257
257
  const escapedUrl = this.escapeToml(server.url);
258
258
  sections.push(`[mcp_servers.${key}]`);
259
259
  sections.push(`url = "${escapedUrl}"`);
260
- // OAuth-first providers (e.g. GitHub) have no Authorization header — IDE manages auth
260
+ // URL-only provider metadata serializes without a synthesized Authorization header.
261
261
  if (authHeader) {
262
262
  sections.push(`http_headers = { Authorization = "${this.escapeToml(authHeader)}" }`);
263
263
  }
@@ -81,9 +81,9 @@ async function buildProviderMCPServer(providerId, token, config) {
81
81
  }
82
82
  }
83
83
  /**
84
- * Build an HTTP MCP server.
85
- * When authHeaderTemplate is absent the provider uses IDE-native OAuth (e.g. GitHub);
86
- * write a URL-only entry so the IDE handles the OAuth flow on first tool use.
84
+ * Serialize an HTTP MCP server from explicit provider metadata.
85
+ * A missing authHeaderTemplate produces a URL-only shape; it does not establish
86
+ * authentication compatibility for any agent host.
87
87
  */
88
88
  function buildHTTPServer(mcpConfig, token) {
89
89
  if (!mcpConfig.url) {
@@ -22,7 +22,7 @@ const LOCAL_PROVIDERS = [
22
22
  description: 'GitHub repository and issue management',
23
23
  capabilities: ['code', 'issues', 'integrated'],
24
24
  docsUrl: 'https://docs.github.com',
25
- setupInstructions: 'Run "fraim add-provider github" your IDE handles OAuth automatically on first use',
25
+ setupInstructions: 'Ask your agent to use the FRAIM connect-mcp skill and follow current GitHub and host guidance',
26
26
  hasAdditionalConfig: false,
27
27
  mcpServer: {
28
28
  type: 'http',
@@ -35,7 +35,7 @@ const LOCAL_PROVIDERS = [
35
35
  description: 'GitLab repository and issue management',
36
36
  capabilities: ['code', 'issues', 'integrated'],
37
37
  docsUrl: 'https://docs.gitlab.com',
38
- setupInstructions: 'Create a Personal Access Token in your GitLab settings',
38
+ setupInstructions: 'Ask your agent to use the FRAIM connect-mcp skill and follow current GitLab and host guidance',
39
39
  hasAdditionalConfig: false,
40
40
  mcpServer: {
41
41
  type: 'http',
@@ -49,7 +49,7 @@ const LOCAL_PROVIDERS = [
49
49
  description: 'Azure DevOps repository and issue management',
50
50
  capabilities: ['code', 'issues', 'integrated'],
51
51
  docsUrl: 'https://docs.microsoft.com/azure/devops',
52
- setupInstructions: 'Create a Personal Access Token in Azure DevOps',
52
+ setupInstructions: 'Ask your agent to use the FRAIM connect-mcp skill and follow current Azure DevOps and host guidance',
53
53
  hasAdditionalConfig: true,
54
54
  mcpServer: {
55
55
  type: 'stdio',
@@ -66,7 +66,7 @@ const LOCAL_PROVIDERS = [
66
66
  description: 'Jira issue tracking and project management',
67
67
  capabilities: ['issues'],
68
68
  docsUrl: 'https://support.atlassian.com/jira',
69
- setupInstructions: 'Create an API token at https://id.atlassian.com/manage-profile/security/api-tokens',
69
+ setupInstructions: 'Ask your agent to use the FRAIM connect-mcp skill and follow current Jira and host guidance',
70
70
  hasAdditionalConfig: true,
71
71
  mcpServer: {
72
72
  type: 'stdio',
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.autoConfigureMCP = exports.validateSetupResults = exports.promptForGitHubToken = exports.promptForIDESelection = void 0;
39
+ exports.autoConfigureMCP = exports.validateSetupResults = exports.promptForIDESelection = void 0;
40
40
  const fs_1 = __importDefault(require("fs"));
41
41
  const path_1 = __importDefault(require("path"));
42
42
  const chalk_1 = __importDefault(require("chalk"));
@@ -60,7 +60,7 @@ const promptForIDESelection = async (detectedIDEs) => {
60
60
  console.log(chalk_1.default.gray(' • fraim (required for FRAIM jobs)'));
61
61
  console.log(chalk_1.default.gray(' • git (version control integration)'));
62
62
  console.log(chalk_1.default.gray(' • playwright (browser automation)'));
63
- console.log(chalk_1.default.blue('\n💡 Run "fraim add-provider <provider>" after setup to connect GitHub, GitLab, Jira, or other tools.'));
63
+ console.log(chalk_1.default.blue('\n💡 Ask your agent to use the FRAIM connect-mcp skill for GitHub, GitLab, Jira, or other tools.'));
64
64
  console.log(chalk_1.default.yellow('\n💡 Existing MCP servers will be preserved - only missing servers will be added.'));
65
65
  const response = await (0, prompts_1.default)({
66
66
  type: 'text',
@@ -87,15 +87,6 @@ const promptForIDESelection = async (detectedIDEs) => {
87
87
  return selectedIndices.map((i) => detectedIDEs[i]).filter((ide) => ide !== undefined);
88
88
  };
89
89
  exports.promptForIDESelection = promptForIDESelection;
90
- // Re-export promptForProviderToken for backward compatibility
91
- // This maintains the same interface but uses the generic system
92
- const promptForGitHubToken = async () => {
93
- const { promptForProviderToken } = await Promise.resolve().then(() => __importStar(require('./provider-prompts')));
94
- const { getProviderClient } = await Promise.resolve().then(() => __importStar(require('../api/get-provider-client')));
95
- const client = getProviderClient();
96
- return promptForProviderToken(client, 'github');
97
- };
98
- exports.promptForGitHubToken = promptForGitHubToken;
99
90
  const backupConfig = (configPath) => {
100
91
  if (fs_1.default.existsSync(configPath)) {
101
92
  const backupPath = `${configPath}.fraim-backup-${Date.now()}`;
@@ -188,8 +179,8 @@ const configureIDEMCP = async (ide, fraimKey) => {
188
179
  const addedServers = [];
189
180
  const updatedServers = [];
190
181
  const skippedServers = [];
191
- // Servers that should always be updated (not skipped)
192
- const alwaysUpdateServers = new Set(['fraim', 'github', 'gitlab', 'jira', 'ado', 'linear']);
182
+ // FRAIM owns its own base server entry. Third-party entries are preserved.
183
+ const alwaysUpdateServers = new Set(['fraim']);
193
184
  for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
194
185
  if (!existingMCPServers[serverName]) {
195
186
  mergedMCPServers[serverName] = serverConfig;