fraim 2.0.357 → 2.0.358-next.0

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.
@@ -30,9 +30,11 @@ function getProviderClient() {
30
30
  if (!fraimKey) {
31
31
  throw new Error('FRAIM API key not found in config');
32
32
  }
33
- // Use FRAIM_REMOTE_URL if set (for tests or custom deployments)
34
- // Otherwise ProviderClient will use its default
35
- const serverUrl = process.env.FRAIM_REMOTE_URL || undefined;
33
+ // Resolve the remote URL: env var priority over config.json channel over PROD default.
34
+ const channel = (process.env.FRAIM_RELEASE_CHANNEL || config.releaseChannel || '').toLowerCase();
35
+ const serverUrl = channel === 'ppe'
36
+ ? 'https://app-ashley-fraim-ppe.azurewebsites.net'
37
+ : (process.env.FRAIM_REMOTE_URL || undefined);
36
38
  return new provider_client_1.ProviderClient(fraimKey, serverUrl);
37
39
  }
38
40
  catch (error) {
@@ -8,10 +8,7 @@ exports.ProviderClient = void 0;
8
8
  const axios_1 = __importDefault(require("axios"));
9
9
  class ProviderClient {
10
10
  constructor(fraimKey, serverUrl) {
11
- const isPpe = (process.env.FRAIM_RELEASE_CHANNEL || '').toUpperCase() === 'PPE';
12
- this.serverUrl = serverUrl ||
13
- (isPpe ? 'https://app-ashley-fraim-ppe.azurewebsites.net'
14
- : (process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me'));
11
+ this.serverUrl = serverUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
15
12
  this.client = axios_1.default.create({
16
13
  baseURL: this.serverUrl,
17
14
  headers: {
@@ -20,7 +20,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.addSurfaceCommand = void 0;
21
21
  const commander_1 = require("commander");
22
22
  const chalk_1 = __importDefault(require("chalk"));
23
- const FRAIM_REMOTE_URL = 'https://fraim.wellnessatwork.me';
23
+ const user_config_1 = require("../utils/user-config");
24
+ const FRAIM_REMOTE_URL = (0, user_config_1.getRemoteUrl)();
24
25
  const MCP_ENDPOINT = `${FRAIM_REMOTE_URL}/mcp`;
25
26
  // ─── surface handlers ────────────────────────────────────────────────────────
26
27
  function printClaudeWebInstructions(surface) {
@@ -10,6 +10,7 @@ const path_1 = __importDefault(require("path"));
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
11
  const axios_1 = __importDefault(require("axios"));
12
12
  const git_utils_1 = require("../../core/utils/git-utils");
13
+ const user_config_1 = require("../utils/user-config");
13
14
  const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
14
15
  exports.overrideCommand = new commander_1.Command('override')
15
16
  .description('Create a local override for a FRAIM registry file')
@@ -67,7 +68,7 @@ exports.overrideCommand = new commander_1.Command('override')
67
68
  serverUrl = `http://localhost:${localPort}`;
68
69
  }
69
70
  else {
70
- serverUrl = config.remoteUrl || ((process.env.FRAIM_RELEASE_CHANNEL || '').toUpperCase() === 'PPE' ? 'https://app-ashley-fraim-ppe.azurewebsites.net' : (process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me'));
71
+ serverUrl = config.remoteUrl || (0, user_config_1.getRemoteUrl)();
71
72
  }
72
73
  const apiKey = isLocal ? 'local-dev' : (config.apiKey || process.env.FRAIM_API_KEY);
73
74
  const headers = { 'Content-Type': 'application/json' };
@@ -48,6 +48,7 @@ const git_utils_1 = require("../../core/utils/git-utils");
48
48
  const project_catalog_cleanup_1 = require("../utils/project-catalog-cleanup");
49
49
  const fraim_gitignore_1 = require("../utils/fraim-gitignore");
50
50
  const remote_sync_1 = require("../utils/remote-sync");
51
+ const user_config_1 = require("../utils/user-config");
51
52
  const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
52
53
  function resolveExplicitLocalSyncUrl() {
53
54
  const candidates = [
@@ -125,7 +126,7 @@ const runSync = async (options) => {
125
126
  // Order: user-level config → env var → project config (only if in a project)
126
127
  const remoteUrl = useLocal
127
128
  ? resolveExplicitLocalSyncUrl() || `http://localhost:${process.env.FRAIM_MCP_PORT || (0, git_utils_1.getPort)()}`
128
- : (process.env.FRAIM_RELEASE_CHANNEL || '').toUpperCase() === 'PPE' ? 'https://app-ashley-fraim-ppe.azurewebsites.net' : (process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me');
129
+ : (0, user_config_1.getRemoteUrl)();
129
130
  let apiKey = useLocal ? 'local-dev' : loadUserApiKey() || process.env.FRAIM_API_KEY;
130
131
  const hasProject = (0, project_fraim_paths_1.workspaceFraimExists)(projectRoot);
131
132
  if (!apiKey && hasProject) {
@@ -14,6 +14,7 @@ exports.analyzeFraimProxyLog = analyzeFraimProxyLog;
14
14
  exports.diagnoseFraimServerConfigConsistency = diagnoseFraimServerConfigConsistency;
15
15
  const fs_1 = __importDefault(require("fs"));
16
16
  const redact_secrets_1 = require("../../../core/utils/redact-secrets");
17
+ const user_config_1 = require("../../utils/user-config");
17
18
  exports.DEFAULT_FRAIM_REMOTE_URL = 'https://fraim.wellnessatwork.me';
18
19
  exports.FRAIM_PROXY_LOG_FILE = 'fraim-mcp-proxy.log';
19
20
  function fingerprintSecret(secret) {
@@ -250,7 +251,7 @@ options = {}) {
250
251
  function diagnoseFraimServerConfigConsistency(fraimServer, options = {}) {
251
252
  const configuredKey = fraimServer?.env?.FRAIM_API_KEY ? String(fraimServer.env.FRAIM_API_KEY) : '';
252
253
  const configuredRemoteUrl = String(fraimServer?.env?.FRAIM_REMOTE_URL || exports.DEFAULT_FRAIM_REMOTE_URL);
253
- const expectedRemoteUrl = options.expectedRemoteUrl || process.env.FRAIM_REMOTE_URL || exports.DEFAULT_FRAIM_REMOTE_URL;
254
+ const expectedRemoteUrl = options.expectedRemoteUrl || (0, user_config_1.getRemoteUrl)();
254
255
  const apiKeyMatchesExpected = options.expectedApiKey && configuredKey
255
256
  ? configuredKey === options.expectedApiKey
256
257
  : null;
@@ -58,6 +58,7 @@ const toml = __importStar(require("toml"));
58
58
  const redact_secrets_1 = require("../../../core/utils/redact-secrets");
59
59
  const ide_detector_1 = require("../../setup/ide-detector");
60
60
  const fraim_mcp_latest_launcher_1 = require("../../mcp/fraim-mcp-latest-launcher");
61
+ const user_config_1 = require("../../utils/user-config");
61
62
  const fraim_mcp_diagnostics_1 = require("./fraim-mcp-diagnostics");
62
63
  const fraim_mcp_cache_repair_1 = require("./fraim-mcp-cache-repair");
63
64
  // Cache the npm major version so execFileSync is called at most once per process.
@@ -300,7 +301,7 @@ async function testFraimConnectivity() {
300
301
  }
301
302
  // Test connectivity by making a request to the FRAIM server
302
303
  const startTime = Date.now();
303
- const remoteUrl = process.env.FRAIM_REMOTE_URL || fraim_mcp_diagnostics_1.DEFAULT_FRAIM_REMOTE_URL;
304
+ const remoteUrl = (0, user_config_1.getRemoteUrl)();
304
305
  // Debug: Log what we're testing (helpful for troubleshooting)
305
306
  if (process.env.DEBUG_DOCTOR) {
306
307
  console.log('[DOCTOR DEBUG] Testing FRAIM connectivity:');
@@ -541,7 +542,7 @@ async function validateIDEMCPConfig(ide) {
541
542
  const keyDiagnostic = resolveFraimApiKeyForDiagnostics();
542
543
  const consistencyDiagnostic = (0, fraim_mcp_diagnostics_1.diagnoseFraimServerConfigConsistency)(fraimServer, {
543
544
  expectedApiKey: keyDiagnostic.apiKey,
544
- expectedRemoteUrl: process.env.FRAIM_REMOTE_URL || fraim_mcp_diagnostics_1.DEFAULT_FRAIM_REMOTE_URL
545
+ expectedRemoteUrl: (0, user_config_1.getRemoteUrl)()
545
546
  });
546
547
  if (consistencyDiagnostic.status !== 'passed') {
547
548
  return {
@@ -73,7 +73,15 @@ const CACHE_GENERATIONS_KEPT = ${CACHE_GENERATIONS_KEPT};
73
73
  const CACHE_MIN_AGE_MS = ${CACHE_MIN_AGE_MS};
74
74
  const USER_DIR = process.env.FRAIM_USER_DIR || path.join(os.homedir(), '.fraim');
75
75
  const STATE_DIR = path.join(USER_DIR, 'mcp-launcher');
76
- const DIST_TAG = (process.env.FRAIM_RELEASE_CHANNEL || '').toUpperCase() === 'PPE' ? 'next' : 'latest';
76
+ // Env var takes priority; fall back to ~/.fraim/config.json releaseChannel.
77
+ // Some MCP clients (codex) replace the inherited env and strip FRAIM_RELEASE_CHANNEL,
78
+ // so the config file is the stable cross-client source of truth.
79
+ const CHANNEL = (() => {
80
+ if (process.env.FRAIM_RELEASE_CHANNEL) return process.env.FRAIM_RELEASE_CHANNEL;
81
+ try { const c = JSON.parse(fs.readFileSync(path.join(USER_DIR, 'config.json'), 'utf8')); if (c.releaseChannel) return c.releaseChannel; } catch {}
82
+ return '';
83
+ })();
84
+ const DIST_TAG = CHANNEL.toUpperCase() === 'PPE' ? 'next' : 'latest';
77
85
  const STATE_PATH = path.join(STATE_DIR, 'latest-' + DIST_TAG + '.json');
78
86
  const CACHE_ROOT = path.join(USER_DIR, 'npm-cache', 'mcp');
79
87
  const REFRESH_LOCK = path.join(STATE_DIR, 'refresh-' + DIST_TAG + '.lock');
@@ -11,6 +11,7 @@ exports.buildAllBaseServers = buildAllBaseServers;
11
11
  const provider_registry_1 = require("../providers/provider-registry");
12
12
  const command_resolution_1 = require("./command-resolution");
13
13
  const fraim_mcp_latest_launcher_1 = require("./fraim-mcp-latest-launcher");
14
+ const user_config_1 = require("../utils/user-config");
14
15
  exports.BASE_MCP_SERVERS = [
15
16
  {
16
17
  id: 'git',
@@ -42,6 +43,7 @@ exports.BASE_MCP_SERVERS = [
42
43
  description: 'FRAIM job orchestration and mentoring',
43
44
  buildServer: (fraimKey) => {
44
45
  const launcher = (0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
46
+ const channel = (0, user_config_1.getReleaseChannel)();
45
47
  return {
46
48
  command: launcher.command,
47
49
  args: launcher.args,
@@ -49,10 +51,8 @@ exports.BASE_MCP_SERVERS = [
49
51
  // Include API key for IDE configs (Codex, VSCode, etc.)
50
52
  // The stdio-server will use this if set, otherwise falls back to ~/.fraim/config.json
51
53
  FRAIM_API_KEY: fraimKey,
52
- FRAIM_REMOTE_URL: (process.env.FRAIM_RELEASE_CHANNEL || '').toUpperCase() === 'PPE'
53
- ? 'https://app-ashley-fraim-ppe.azurewebsites.net'
54
- : 'https://fraim.wellnessatwork.me',
55
- ...(process.env.FRAIM_RELEASE_CHANNEL ? { FRAIM_RELEASE_CHANNEL: process.env.FRAIM_RELEASE_CHANNEL } : {})
54
+ FRAIM_REMOTE_URL: (0, user_config_1.getRemoteUrl)(),
55
+ ...(channel ? { FRAIM_RELEASE_CHANNEL: channel } : {})
56
56
  }
57
57
  };
58
58
  }
@@ -143,7 +143,7 @@ async function discoverCloudManagerStorage(options) {
143
143
  const apiKey = options?.apiKey || (0, user_config_1.readUserFraimConfig)().apiKey;
144
144
  if (!apiKey || apiKey === 'local-dev' || apiKey === 'test-mode-key')
145
145
  return null;
146
- const remoteUrl = options?.remoteUrl || ((process.env.FRAIM_RELEASE_CHANNEL || '').toUpperCase() === 'PPE' ? 'https://app-ashley-fraim-ppe.azurewebsites.net' : (process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me'));
146
+ const remoteUrl = options?.remoteUrl || (0, user_config_1.getRemoteUrl)();
147
147
  const pack = await fetchCloudPack(remoteUrl, apiKey);
148
148
  if (pack.files.length === 0)
149
149
  return null;
@@ -217,7 +217,7 @@ async function syncManagerCache(options) {
217
217
  writeHomeMetadata(metadata);
218
218
  return { status: 'synced', metadata };
219
219
  }
220
- const remoteUrl = options?.remoteUrl || ((process.env.FRAIM_RELEASE_CHANNEL || '').toUpperCase() === 'PPE' ? 'https://app-ashley-fraim-ppe.azurewebsites.net' : (process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me'));
220
+ const remoteUrl = options?.remoteUrl || (0, user_config_1.getRemoteUrl)();
221
221
  const apiKey = options?.apiKey || (0, user_config_1.readUserFraimConfig)().apiKey;
222
222
  if (!apiKey)
223
223
  throw new Error('No FRAIM API key available for the fraim-cloud manager backend.');
@@ -58,7 +58,7 @@ async function publishManagerArtifacts(artifacts, opts) {
58
58
  return { backend: 'local-folder', writtenPaths, deletedPaths };
59
59
  }
60
60
  if (managerStorage.backend === 'fraim-cloud') {
61
- const remoteUrl = opts?.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
61
+ const remoteUrl = opts?.remoteUrl || (0, user_config_1.getRemoteUrl)();
62
62
  const apiKey = opts?.apiKey || (0, user_config_1.readUserFraimConfig)().apiKey;
63
63
  if (!apiKey)
64
64
  throw new Error('No FRAIM API key available for the fraim-cloud manager backend.');
@@ -187,7 +187,7 @@ async function discoverCloudOrganization(options) {
187
187
  const apiKey = options?.apiKey || (0, user_config_1.readUserFraimConfig)().apiKey;
188
188
  if (!apiKey || apiKey === 'local-dev' || apiKey === 'test-mode-key')
189
189
  return null;
190
- const remoteUrl = options?.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
190
+ const remoteUrl = options?.remoteUrl || (0, user_config_1.getRemoteUrl)();
191
191
  const pack = await fetchCloudPack(remoteUrl, apiKey);
192
192
  if (pack.files.length === 0)
193
193
  return null;
@@ -266,7 +266,7 @@ async function syncOrgCache(options) {
266
266
  writeHomeMetadata(metadata);
267
267
  return { status: 'synced', metadata };
268
268
  }
269
- const remoteUrl = options?.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
269
+ const remoteUrl = options?.remoteUrl || (0, user_config_1.getRemoteUrl)();
270
270
  const apiKey = options?.apiKey || (0, user_config_1.readUserFraimConfig)().apiKey;
271
271
  if (!apiKey)
272
272
  throw new Error('No FRAIM API key available for the fraim-cloud org backend.');
@@ -93,7 +93,7 @@ async function publishOrgArtifacts(artifacts, opts) {
93
93
  return { backend: 'local-folder', writtenPaths, deletedPaths };
94
94
  }
95
95
  if (organization.backend === 'fraim-cloud') {
96
- const remoteUrl = opts?.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
96
+ const remoteUrl = opts?.remoteUrl || (0, user_config_1.getRemoteUrl)();
97
97
  const apiKey = opts?.apiKey || (0, user_config_1.readUserFraimConfig)().apiKey;
98
98
  if (!apiKey)
99
99
  throw new Error('No FRAIM API key available for the fraim-cloud org backend.');
@@ -23,6 +23,7 @@ const fs_1 = require("fs");
23
23
  const path_1 = require("path");
24
24
  const chalk_1 = __importDefault(require("chalk"));
25
25
  const script_sync_utils_1 = require("./script-sync-utils");
26
+ const user_config_1 = require("./user-config");
26
27
  const capability_index_1 = require("../../core/utils/capability-index");
27
28
  const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
28
29
  const LOCK_SYNCED_CONTENT_ENV = 'FRAIM_LOCK_SYNCED_CONTENT';
@@ -316,7 +317,7 @@ async function syncSkillsAndRulesToUserDir(files) {
316
317
  * Sync jobs and scripts from remote FRAIM server
317
318
  */
318
319
  async function syncFromRemote(options) {
319
- const remoteUrl = options.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
320
+ const remoteUrl = options.remoteUrl || (0, user_config_1.getRemoteUrl)();
320
321
  const apiKey = options.apiKey || process.env.FRAIM_API_KEY || '';
321
322
  if (!apiKey) {
322
323
  return {
@@ -8,6 +8,8 @@ exports.isLayerMisconfigured = isLayerMisconfigured;
8
8
  exports.setLayerBackend = setLayerBackend;
9
9
  exports.readUserFraimConfig = readUserFraimConfig;
10
10
  exports.writeUserFraimConfig = writeUserFraimConfig;
11
+ exports.getReleaseChannel = getReleaseChannel;
12
+ exports.getRemoteUrl = getRemoteUrl;
11
13
  exports.getInstalledIdes = getInstalledIdes;
12
14
  exports.addInstalledIde = addInstalledIde;
13
15
  exports.getOrganizationConfig = getOrganizationConfig;
@@ -89,6 +91,19 @@ function writeUserFraimConfig(patch) {
89
91
  fs_1.default.writeFileSync(getUserConfigPath(), JSON.stringify(merged, null, 2));
90
92
  return merged;
91
93
  }
94
+ /**
95
+ * Resolve the active release channel. Env var takes priority over config
96
+ * so per-session overrides work without touching the file.
97
+ */
98
+ function getReleaseChannel() {
99
+ return (process.env.FRAIM_RELEASE_CHANNEL || readUserFraimConfig().releaseChannel || '').toLowerCase();
100
+ }
101
+ /** Resolve the active FRAIM remote URL from channel + env. */
102
+ function getRemoteUrl() {
103
+ if (getReleaseChannel() === 'ppe')
104
+ return 'https://app-ashley-fraim-ppe.azurewebsites.net';
105
+ return process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
106
+ }
92
107
  function getInstalledIdes() {
93
108
  const raw = readUserFraimConfig().installedIdes;
94
109
  return Array.isArray(raw) ? raw : null;
@@ -560,9 +560,7 @@ class FraimLocalMCPServer {
560
560
  this.orgCacheRefreshInFlight = false;
561
561
  this.managerCacheRefreshInFlight = false;
562
562
  this.writer = writer || process.stdout.write.bind(process.stdout);
563
- const isPpe = (process.env.FRAIM_RELEASE_CHANNEL || '').toUpperCase() === 'PPE';
564
- this.remoteUrl = isPpe ? 'https://app-ashley-fraim-ppe.azurewebsites.net'
565
- : (process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me');
563
+ this.remoteUrl = this.loadRemoteUrl();
566
564
  this.apiKey = this.loadApiKey();
567
565
  this.localVersion = this.detectLocalVersion();
568
566
  if (!this.apiKey) {
@@ -622,6 +620,28 @@ class FraimLocalMCPServer {
622
620
  }
623
621
  return '';
624
622
  }
623
+ /**
624
+ * Priority: FRAIM_RELEASE_CHANNEL env var > config.releaseChannel > FRAIM_REMOTE_URL env var > default prod URL.
625
+ */
626
+ loadRemoteUrl() {
627
+ const channel = (() => {
628
+ if (process.env.FRAIM_RELEASE_CHANNEL)
629
+ return process.env.FRAIM_RELEASE_CHANNEL;
630
+ try {
631
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '';
632
+ if (!homeDir)
633
+ return '';
634
+ const config = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(homeDir, '.fraim', 'config.json'), 'utf8'));
635
+ if (config.releaseChannel)
636
+ return config.releaseChannel;
637
+ }
638
+ catch { }
639
+ return '';
640
+ })();
641
+ if (channel.toUpperCase() === 'PPE')
642
+ return 'https://app-ashley-fraim-ppe.azurewebsites.net';
643
+ return process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
644
+ }
625
645
  // #533: persist the authenticated user email into ~/.fraim/preferences.json so
626
646
  // the AI Hub (a separate process) can resolve personal learnings + show the real
627
647
  // profile. Merge-and-write; never throw out of the connect path.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.357",
3
+ "version": "2.0.358-next.0",
4
4
  "description": "FRAIM core CLI and MCP package.",
5
5
  "main": "index.js",
6
6
  "bin": {