fraim 2.0.357 → 2.0.358-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli/api/get-provider-client.js +5 -3
- package/dist/src/cli/api/provider-client.js +1 -4
- package/dist/src/cli/commands/add-surface.js +2 -1
- package/dist/src/cli/commands/override.js +2 -1
- package/dist/src/cli/commands/sync.js +2 -1
- package/dist/src/cli/doctor/checks/fraim-mcp-diagnostics.js +2 -1
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +3 -2
- package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +9 -1
- package/dist/src/cli/mcp/mcp-server-registry.js +4 -4
- package/dist/src/cli/utils/manager-pack-sync.js +2 -2
- package/dist/src/cli/utils/manager-publish.js +1 -1
- package/dist/src/cli/utils/org-pack-sync.js +2 -2
- package/dist/src/cli/utils/org-publish.js +1 -1
- package/dist/src/cli/utils/remote-sync.js +2 -1
- package/dist/src/cli/utils/user-config.js +15 -0
- package/dist/src/local-mcp-server/stdio-server.js +23 -3
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
34
|
-
|
|
35
|
-
const serverUrl =
|
|
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
|
-
|
|
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
|
|
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 || (
|
|
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
|
-
: (
|
|
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 ||
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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: (
|
|
53
|
-
|
|
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 || (
|
|
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 || (
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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
|
-
|
|
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.
|