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.
- package/dist/src/cli/commands/add-ide.js +10 -131
- package/dist/src/cli/commands/add-provider.js +32 -268
- package/dist/src/cli/commands/login.js +5 -5
- package/dist/src/cli/commands/setup.js +15 -52
- package/dist/src/cli/commands/sync.js +111 -80
- package/dist/src/cli/fraim.js +1 -44
- package/dist/src/cli/mcp/ide-formats.js +1 -1
- package/dist/src/cli/mcp/mcp-server-registry.js +3 -3
- package/dist/src/cli/providers/local-provider-registry.js +4 -4
- package/dist/src/cli/setup/auto-mcp-setup.js +4 -13
- package/dist/src/cli/utils/remote-sync.js +41 -25
- package/dist/src/first-run/types.js +1 -1
- package/dist/src/local-mcp-server/learning-usage-command.js +408 -0
- package/dist/src/local-mcp-server/learning-usage-store.js +2 -2
- package/dist/src/services/provider-service.js +4 -4
- package/package.json +1 -1
|
@@ -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.
|
|
40
|
-
//
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
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('
|
|
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;
|
|
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
|
|
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
|
|
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('
|
|
372
|
-
console.log(chalk_1.default.gray('
|
|
373
|
-
console.log(chalk_1.default.gray('
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
207
|
-
console.
|
|
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
|
-
//
|
|
211
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
342
|
-
|
|
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:
|
|
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(
|
|
349
|
-
console.error(chalk_1.default.yellow(
|
|
350
|
-
|
|
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
|
-
|
|
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(`
|
|
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
|
|
391
|
+
console.log(chalk_1.default.yellow('TEST_MODE: Continuing without project sync.'));
|
|
366
392
|
return;
|
|
367
393
|
}
|
|
368
|
-
failSync(failHard, `
|
|
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,
|
|
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
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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
|
|
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);
|
package/dist/src/cli/fraim.js
CHANGED
|
@@ -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.
|
|
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
|
-
//
|
|
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
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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.
|
|
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💡
|
|
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
|
-
//
|
|
192
|
-
const alwaysUpdateServers = new Set(['fraim'
|
|
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;
|