From 60ac0899c7a2cb5b45482178344e938684eae4c9 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 25 Feb 2026 20:31:41 -0500 Subject: [PATCH] Matrix-js: support account-scoped env vars --- docs/channels/matrix-js.md | 14 ++ .../matrix-js/src/channel.directory.test.ts | 64 ++++++++- extensions/matrix-js/src/channel.ts | 16 ++- extensions/matrix-js/src/cli.ts | 7 +- .../matrix-js/src/matrix/client.test.ts | 24 +++- extensions/matrix-js/src/matrix/client.ts | 3 + .../matrix-js/src/matrix/client/config.ts | 127 ++++++++++++++++-- extensions/matrix-js/src/onboarding.test.ts | 46 ++++--- extensions/matrix-js/src/onboarding.ts | 41 ++++-- 9 files changed, 285 insertions(+), 57 deletions(-) diff --git a/docs/channels/matrix-js.md b/docs/channels/matrix-js.md index 5ed1036565c..2ad516f6e72 100644 --- a/docs/channels/matrix-js.md +++ b/docs/channels/matrix-js.md @@ -82,6 +82,20 @@ Environment variable equivalents (used when the config key is not set): - `MATRIX_DEVICE_ID` - `MATRIX_DEVICE_NAME` +For non-default accounts, use account-scoped env vars: + +- `MATRIX__HOMESERVER` +- `MATRIX__ACCESS_TOKEN` +- `MATRIX__USER_ID` +- `MATRIX__PASSWORD` +- `MATRIX__DEVICE_ID` +- `MATRIX__DEVICE_NAME` + +Example for account `ops`: + +- `MATRIX_OPS_HOMESERVER` +- `MATRIX_OPS_ACCESS_TOKEN` + ## Configuration example This is a practical baseline config with DM pairing, room allowlist, and E2EE enabled: diff --git a/extensions/matrix-js/src/channel.directory.test.ts b/extensions/matrix-js/src/channel.directory.test.ts index 90e4ef2eb7f..da907503cd6 100644 --- a/extensions/matrix-js/src/channel.directory.test.ts +++ b/extensions/matrix-js/src/channel.directory.test.ts @@ -222,13 +222,63 @@ describe("matrix directory", () => { }); }); - it("rejects useEnv for non-default matrix-js accounts", () => { - const error = matrixPlugin.setup!.validateInput?.({ - cfg: {} as CoreConfig, - accountId: "ops", - input: { useEnv: true }, - }); - expect(error).toBe("MATRIX_* env vars can only be used for the default account."); + it("requires account-scoped env vars when --use-env is set for non-default accounts", () => { + const envKeys = [ + "MATRIX_OPS_HOMESERVER", + "MATRIX_OPS_USER_ID", + "MATRIX_OPS_ACCESS_TOKEN", + "MATRIX_OPS_PASSWORD", + ] as const; + const previousEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])) as Record< + (typeof envKeys)[number], + string | undefined + >; + for (const key of envKeys) { + delete process.env[key]; + } + try { + const error = matrixPlugin.setup!.validateInput?.({ + cfg: {} as CoreConfig, + accountId: "ops", + input: { useEnv: true }, + }); + expect(error).toBe( + 'Set per-account env vars for "ops" (for example MATRIX_OPS_HOMESERVER + MATRIX_OPS_ACCESS_TOKEN or MATRIX_OPS_USER_ID + MATRIX_OPS_PASSWORD).', + ); + } finally { + for (const key of envKeys) { + if (previousEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = previousEnv[key]; + } + } + } + }); + + it("accepts --use-env for non-default account when scoped env vars are present", () => { + const envKeys = { + MATRIX_OPS_HOMESERVER: process.env.MATRIX_OPS_HOMESERVER, + MATRIX_OPS_ACCESS_TOKEN: process.env.MATRIX_OPS_ACCESS_TOKEN, + }; + process.env.MATRIX_OPS_HOMESERVER = "https://ops.example.org"; + process.env.MATRIX_OPS_ACCESS_TOKEN = "ops-token"; + try { + const error = matrixPlugin.setup!.validateInput?.({ + cfg: {} as CoreConfig, + accountId: "ops", + input: { useEnv: true }, + }); + expect(error).toBeNull(); + } finally { + for (const [key, value] of Object.entries(envKeys)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } }); it("resolves account id from input name when explicit account id is missing", () => { diff --git a/extensions/matrix-js/src/channel.ts b/extensions/matrix-js/src/channel.ts index 3740395bf5c..9f488fd6db2 100644 --- a/extensions/matrix-js/src/channel.ts +++ b/extensions/matrix-js/src/channel.ts @@ -27,7 +27,12 @@ import { resolveMatrixAccount, type ResolvedMatrixAccount, } from "./matrix/accounts.js"; -import { resolveMatrixAuth } from "./matrix/client.js"; +import { + getMatrixScopedEnvVarNames, + hasReadyMatrixEnvAuth, + resolveMatrixAuth, + resolveScopedMatrixEnvConfig, +} from "./matrix/client.js"; import { updateMatrixAccountConfig } from "./matrix/config-update.js"; import { normalizeMatrixAllowList, normalizeMatrixUserId } from "./matrix/monitor/allowlist.js"; import { probeMatrix } from "./matrix/probe.js"; @@ -292,10 +297,13 @@ export const matrixPlugin: ChannelPlugin = { alwaysUseAccounts: true, }), validateInput: ({ accountId, input }) => { - if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) { - return "MATRIX_* env vars can only be used for the default account."; - } if (input.useEnv) { + const scopedEnv = resolveScopedMatrixEnvConfig(accountId, process.env); + const scopedReady = hasReadyMatrixEnvAuth(scopedEnv); + if (accountId !== DEFAULT_ACCOUNT_ID && !scopedReady) { + const keys = getMatrixScopedEnvVarNames(accountId); + return `Set per-account env vars for "${accountId}" (for example ${keys.homeserver} + ${keys.accessToken} or ${keys.userId} + ${keys.password}).`; + } return null; } if (!input.homeserver?.trim()) { diff --git a/extensions/matrix-js/src/cli.ts b/extensions/matrix-js/src/cli.ts index 24045a814f0..c1c970333a4 100644 --- a/extensions/matrix-js/src/cli.ts +++ b/extensions/matrix-js/src/cli.ts @@ -447,7 +447,10 @@ export function registerMatrixJsCli(params: { program: Command }): void { .option("--password ", "Matrix password") .option("--device-name ", "Matrix device display name") .option("--initial-sync-limit ", "Matrix initial sync limit") - .option("--use-env", "Use MATRIX_* env vars (default account only)") + .option( + "--use-env", + "Use MATRIX_* env vars (or MATRIX__* for non-default accounts)", + ) .option("--verbose", "Show setup details") .option("--json", "Output as JSON") .action( @@ -483,7 +486,7 @@ export function registerMatrixJsCli(params: { program: Command }): void { console.log(`Saved matrix-js account: ${result.accountId}`); console.log(`Config path: ${result.configPath}`); console.log( - `Credentials source: ${result.useEnv ? "MATRIX_* env vars" : "inline config"}`, + `Credentials source: ${result.useEnv ? "MATRIX_* / MATRIX__* env vars" : "inline config"}`, ); const bindHint = `openclaw agents bind --agent --bind matrix-js:${result.accountId}`; console.log(`Bind this account to an agent: ${bindHint}`); diff --git a/extensions/matrix-js/src/matrix/client.test.ts b/extensions/matrix-js/src/matrix/client.test.ts index 08bb752c415..d01974626ac 100644 --- a/extensions/matrix-js/src/matrix/client.test.ts +++ b/extensions/matrix-js/src/matrix/client.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import type { CoreConfig } from "../types.js"; -import { resolveMatrixAuth, resolveMatrixConfig } from "./client.js"; +import { resolveMatrixAuth, resolveMatrixConfig, resolveMatrixConfigForAccount } from "./client.js"; import * as credentialsModule from "./credentials.js"; import * as sdkModule from "./sdk.js"; @@ -67,6 +67,28 @@ describe("resolveMatrixConfig", () => { expect(resolved.initialSyncLimit).toBeUndefined(); expect(resolved.encryption).toBe(false); }); + + it("uses account-scoped env vars for non-default accounts before global env", () => { + const cfg = { + channels: { + "matrix-js": { + homeserver: "https://base.example.org", + }, + }, + } as CoreConfig; + const env = { + MATRIX_HOMESERVER: "https://global.example.org", + MATRIX_ACCESS_TOKEN: "global-token", + MATRIX_OPS_HOMESERVER: "https://ops.example.org", + MATRIX_OPS_ACCESS_TOKEN: "ops-token", + MATRIX_OPS_DEVICE_NAME: "Ops Device", + } as NodeJS.ProcessEnv; + + const resolved = resolveMatrixConfigForAccount(cfg, "ops", env); + expect(resolved.homeserver).toBe("https://ops.example.org"); + expect(resolved.accessToken).toBe("ops-token"); + expect(resolved.deviceName).toBe("Ops Device"); + }); }); describe("resolveMatrixAuth", () => { diff --git a/extensions/matrix-js/src/matrix/client.ts b/extensions/matrix-js/src/matrix/client.ts index 53abe1c3d5f..82fe95d0fed 100644 --- a/extensions/matrix-js/src/matrix/client.ts +++ b/extensions/matrix-js/src/matrix/client.ts @@ -1,8 +1,11 @@ export type { MatrixAuth, MatrixResolvedConfig } from "./client/types.js"; export { isBunRuntime } from "./client/runtime.js"; export { + getMatrixScopedEnvVarNames, + hasReadyMatrixEnvAuth, resolveMatrixConfig, resolveMatrixConfigForAccount, + resolveScopedMatrixEnvConfig, resolveMatrixAuth, } from "./client/config.js"; export { createMatrixClient } from "./client/create-client.js"; diff --git a/extensions/matrix-js/src/matrix/client/config.ts b/extensions/matrix-js/src/matrix/client/config.ts index 1192f3f7d95..a38b0be84a8 100644 --- a/extensions/matrix-js/src/matrix/client/config.ts +++ b/extensions/matrix-js/src/matrix/client/config.ts @@ -1,4 +1,4 @@ -import { normalizeAccountId } from "openclaw/plugin-sdk/account-id"; +import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id"; import { getMatrixRuntime } from "../../runtime.js"; import type { CoreConfig } from "../../types.js"; import { MatrixClient } from "../sdk.js"; @@ -9,6 +9,80 @@ function clean(value?: string): string { return value?.trim() ?? ""; } +type MatrixEnvConfig = { + homeserver: string; + userId: string; + accessToken?: string; + password?: string; + deviceId?: string; + deviceName?: string; +}; + +function resolveGlobalMatrixEnvConfig(env: NodeJS.ProcessEnv): MatrixEnvConfig { + return { + homeserver: clean(env.MATRIX_HOMESERVER), + userId: clean(env.MATRIX_USER_ID), + accessToken: clean(env.MATRIX_ACCESS_TOKEN) || undefined, + password: clean(env.MATRIX_PASSWORD) || undefined, + deviceId: clean(env.MATRIX_DEVICE_ID) || undefined, + deviceName: clean(env.MATRIX_DEVICE_NAME) || undefined, + }; +} + +function resolveMatrixEnvAccountToken(accountId: string): string { + return normalizeAccountId(accountId) + .replace(/[^a-z0-9]+/g, "_") + .replace(/^_+|_+$/g, "") + .toUpperCase(); +} + +export function getMatrixScopedEnvVarNames(accountId: string): { + homeserver: string; + userId: string; + accessToken: string; + password: string; + deviceId: string; + deviceName: string; +} { + const token = resolveMatrixEnvAccountToken(accountId); + return { + homeserver: `MATRIX_${token}_HOMESERVER`, + userId: `MATRIX_${token}_USER_ID`, + accessToken: `MATRIX_${token}_ACCESS_TOKEN`, + password: `MATRIX_${token}_PASSWORD`, + deviceId: `MATRIX_${token}_DEVICE_ID`, + deviceName: `MATRIX_${token}_DEVICE_NAME`, + }; +} + +export function resolveScopedMatrixEnvConfig( + accountId: string, + env: NodeJS.ProcessEnv = process.env, +): MatrixEnvConfig { + const keys = getMatrixScopedEnvVarNames(accountId); + return { + homeserver: clean(env[keys.homeserver]), + userId: clean(env[keys.userId]), + accessToken: clean(env[keys.accessToken]) || undefined, + password: clean(env[keys.password]) || undefined, + deviceId: clean(env[keys.deviceId]) || undefined, + deviceName: clean(env[keys.deviceName]) || undefined, + }; +} + +export function hasReadyMatrixEnvAuth(config: { + homeserver?: string; + userId?: string; + accessToken?: string; + password?: string; +}): boolean { + const homeserver = clean(config.homeserver); + const userId = clean(config.userId); + const accessToken = clean(config.accessToken); + const password = clean(config.password); + return Boolean(homeserver && (accessToken || (userId && password))); +} + function findAccountConfig(cfg: CoreConfig, accountId: string): Record { const accounts = cfg.channels?.["matrix-js"]?.accounts; if (!accounts || typeof accounts !== "object") { @@ -35,12 +109,19 @@ export function resolveMatrixConfig( env: NodeJS.ProcessEnv = process.env, ): MatrixResolvedConfig { const matrix = cfg.channels?.["matrix-js"] ?? {}; - const homeserver = clean(matrix.homeserver) || clean(env.MATRIX_HOMESERVER); - const userId = clean(matrix.userId) || clean(env.MATRIX_USER_ID); - const accessToken = clean(matrix.accessToken) || clean(env.MATRIX_ACCESS_TOKEN) || undefined; - const password = clean(matrix.password) || clean(env.MATRIX_PASSWORD) || undefined; - const deviceId = clean(matrix.deviceId) || clean(env.MATRIX_DEVICE_ID) || undefined; - const deviceName = clean(matrix.deviceName) || clean(env.MATRIX_DEVICE_NAME) || undefined; + const defaultScopedEnv = resolveScopedMatrixEnvConfig(DEFAULT_ACCOUNT_ID, env); + const globalEnv = resolveGlobalMatrixEnvConfig(env); + const homeserver = + clean(matrix.homeserver) || defaultScopedEnv.homeserver || globalEnv.homeserver; + const userId = clean(matrix.userId) || defaultScopedEnv.userId || globalEnv.userId; + const accessToken = + clean(matrix.accessToken) || defaultScopedEnv.accessToken || globalEnv.accessToken || undefined; + const password = + clean(matrix.password) || defaultScopedEnv.password || globalEnv.password || undefined; + const deviceId = + clean(matrix.deviceId) || defaultScopedEnv.deviceId || globalEnv.deviceId || undefined; + const deviceName = + clean(matrix.deviceName) || defaultScopedEnv.deviceName || globalEnv.deviceName || undefined; const initialSyncLimit = typeof matrix.initialSyncLimit === "number" ? Math.max(0, Math.floor(matrix.initialSyncLimit)) @@ -65,6 +146,9 @@ export function resolveMatrixConfigForAccount( ): MatrixResolvedConfig { const matrix = cfg.channels?.["matrix-js"] ?? {}; const account = findAccountConfig(cfg, accountId); + const normalizedAccountId = normalizeAccountId(accountId); + const scopedEnv = resolveScopedMatrixEnvConfig(normalizedAccountId, env); + const globalEnv = resolveGlobalMatrixEnvConfig(env); const accountHomeserver = clean( typeof account.homeserver === "string" ? account.homeserver : undefined, @@ -83,16 +167,33 @@ export function resolveMatrixConfigForAccount( typeof account.deviceName === "string" ? account.deviceName : undefined, ); - const homeserver = accountHomeserver || clean(matrix.homeserver) || clean(env.MATRIX_HOMESERVER); - const userId = accountUserId || clean(matrix.userId) || clean(env.MATRIX_USER_ID); + const homeserver = + accountHomeserver || scopedEnv.homeserver || clean(matrix.homeserver) || globalEnv.homeserver; + const userId = accountUserId || scopedEnv.userId || clean(matrix.userId) || globalEnv.userId; const accessToken = - accountAccessToken || clean(matrix.accessToken) || clean(env.MATRIX_ACCESS_TOKEN) || undefined; + accountAccessToken || + scopedEnv.accessToken || + clean(matrix.accessToken) || + globalEnv.accessToken || + undefined; const password = - accountPassword || clean(matrix.password) || clean(env.MATRIX_PASSWORD) || undefined; + accountPassword || + scopedEnv.password || + clean(matrix.password) || + globalEnv.password || + undefined; const deviceId = - accountDeviceId || clean(matrix.deviceId) || clean(env.MATRIX_DEVICE_ID) || undefined; + accountDeviceId || + scopedEnv.deviceId || + clean(matrix.deviceId) || + globalEnv.deviceId || + undefined; const deviceName = - accountDeviceName || clean(matrix.deviceName) || clean(env.MATRIX_DEVICE_NAME) || undefined; + accountDeviceName || + scopedEnv.deviceName || + clean(matrix.deviceName) || + globalEnv.deviceName || + undefined; const accountInitialSyncLimit = typeof account.initialSyncLimit === "number" diff --git a/extensions/matrix-js/src/onboarding.test.ts b/extensions/matrix-js/src/onboarding.test.ts index 00d088e0091..2368d12ee1f 100644 --- a/extensions/matrix-js/src/onboarding.test.ts +++ b/extensions/matrix-js/src/onboarding.test.ts @@ -15,16 +15,21 @@ describe("matrix onboarding", () => { MATRIX_USER_ID: process.env.MATRIX_USER_ID, MATRIX_ACCESS_TOKEN: process.env.MATRIX_ACCESS_TOKEN, MATRIX_PASSWORD: process.env.MATRIX_PASSWORD, + MATRIX_OPS_HOMESERVER: process.env.MATRIX_OPS_HOMESERVER, + MATRIX_OPS_ACCESS_TOKEN: process.env.MATRIX_OPS_ACCESS_TOKEN, }; afterEach(() => { - process.env.MATRIX_HOMESERVER = previousEnv.MATRIX_HOMESERVER; - process.env.MATRIX_USER_ID = previousEnv.MATRIX_USER_ID; - process.env.MATRIX_ACCESS_TOKEN = previousEnv.MATRIX_ACCESS_TOKEN; - process.env.MATRIX_PASSWORD = previousEnv.MATRIX_PASSWORD; + for (const [key, value] of Object.entries(previousEnv)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } }); - it("does not offer env shortcut when adding a non-default account", async () => { + it("offers env shortcut for non-default account when scoped env vars are present", async () => { setMatrixRuntime({ state: { resolveStateDir: (_env: NodeJS.ProcessEnv, homeDir?: () => string) => @@ -39,6 +44,8 @@ describe("matrix onboarding", () => { process.env.MATRIX_USER_ID = "@env:example.org"; process.env.MATRIX_PASSWORD = "env-password"; process.env.MATRIX_ACCESS_TOKEN = ""; + process.env.MATRIX_OPS_HOMESERVER = "https://matrix.ops.env.example.org"; + process.env.MATRIX_OPS_ACCESS_TOKEN = "ops-env-token"; const confirmMessages: string[] = []; const prompter = { @@ -56,24 +63,12 @@ describe("matrix onboarding", () => { if (message === "Matrix account name") { return "ops"; } - if (message === "Matrix homeserver URL") { - return "https://matrix.ops.example.org"; - } - if (message === "Matrix access token") { - return "ops-token"; - } - if (message === "Matrix device name (optional)") { - return "Ops Device"; - } throw new Error(`unexpected text prompt: ${message}`); }), confirm: vi.fn(async ({ message }: { message: string }) => { confirmMessages.push(message); - if (message === "Enable end-to-end encryption (E2EE)?") { - return false; - } - if (message === "Configure Matrix rooms access?") { - return false; + if (message.startsWith("Matrix env vars detected")) { + return true; } return false; }), @@ -106,10 +101,17 @@ describe("matrix onboarding", () => { if (result !== "skip") { expect(result.accountId).toBe("ops"); expect(result.cfg.channels?.["matrix-js"]?.accounts?.ops).toMatchObject({ - homeserver: "https://matrix.ops.example.org", - accessToken: "ops-token", + enabled: true, }); + expect(result.cfg.channels?.["matrix-js"]?.accounts?.ops?.homeserver).toBeUndefined(); + expect(result.cfg.channels?.["matrix-js"]?.accounts?.ops?.accessToken).toBeUndefined(); } - expect(confirmMessages).not.toContain("Matrix env vars detected. Use env values?"); + expect( + confirmMessages.some((message) => + message.startsWith( + "Matrix env vars detected (MATRIX_OPS_HOMESERVER (+ auth vars)). Use env values?", + ), + ), + ).toBe(true); }); }); diff --git a/extensions/matrix-js/src/onboarding.ts b/extensions/matrix-js/src/onboarding.ts index 9e4ebfe049c..ce58af6aee0 100644 --- a/extensions/matrix-js/src/onboarding.ts +++ b/extensions/matrix-js/src/onboarding.ts @@ -20,6 +20,11 @@ import { resolveMatrixAccount, resolveMatrixAccountConfig, } from "./matrix/accounts.js"; +import { + getMatrixScopedEnvVarNames, + hasReadyMatrixEnvAuth, + resolveScopedMatrixEnvConfig, +} from "./matrix/client.js"; import { updateMatrixAccountConfig } from "./matrix/config-update.js"; import { ensureMatrixSdkInstalled, isMatrixSdkAvailable } from "./matrix/deps.js"; import { resolveMatrixTargets } from "./resolve-targets.js"; @@ -55,6 +60,7 @@ async function noteMatrixAuthHelp(prompter: WizardPrompter): Promise { "Use an access token (recommended) or password login to an existing account.", "With access token: user ID is fetched automatically.", "Env vars supported: MATRIX_HOMESERVER, MATRIX_USER_ID, MATRIX_ACCESS_TOKEN, MATRIX_PASSWORD.", + "Per-account env vars: MATRIX__HOMESERVER, MATRIX__USER_ID, MATRIX__ACCESS_TOKEN, MATRIX__PASSWORD.", `Docs: ${formatDocsLink("/channels/matrix-js", "channels/matrix-js")}`, ].join("\n"), "Matrix setup", @@ -246,23 +252,42 @@ async function runMatrixConfigure(params: { await noteMatrixAuthHelp(params.prompter); } - const envHomeserver = process.env.MATRIX_HOMESERVER?.trim(); - const envUserId = process.env.MATRIX_USER_ID?.trim(); - const envAccessToken = process.env.MATRIX_ACCESS_TOKEN?.trim(); - const envPassword = process.env.MATRIX_PASSWORD?.trim(); - const envReady = Boolean(envHomeserver && (envAccessToken || (envUserId && envPassword))); + const scopedEnv = resolveScopedMatrixEnvConfig(accountId, process.env); + const defaultScopedEnv = resolveScopedMatrixEnvConfig(DEFAULT_ACCOUNT_ID, process.env); + const globalEnv = { + homeserver: process.env.MATRIX_HOMESERVER?.trim() ?? "", + userId: process.env.MATRIX_USER_ID?.trim() ?? "", + accessToken: process.env.MATRIX_ACCESS_TOKEN?.trim() || undefined, + password: process.env.MATRIX_PASSWORD?.trim() || undefined, + }; + const scopedReady = hasReadyMatrixEnvAuth(scopedEnv); + const defaultScopedReady = hasReadyMatrixEnvAuth(defaultScopedEnv); + const globalReady = hasReadyMatrixEnvAuth(globalEnv); + const envReady = + scopedReady || (accountId === DEFAULT_ACCOUNT_ID && (defaultScopedReady || globalReady)); + const envHomeserver = + scopedEnv.homeserver || + (accountId === DEFAULT_ACCOUNT_ID + ? defaultScopedEnv.homeserver || globalEnv.homeserver + : undefined); + const envUserId = + scopedEnv.userId || + (accountId === DEFAULT_ACCOUNT_ID ? defaultScopedEnv.userId || globalEnv.userId : undefined); - const canUseEnvShortcut = accountId === DEFAULT_ACCOUNT_ID; if ( - canUseEnvShortcut && envReady && !existing.homeserver && !existing.userId && !existing.accessToken && !existing.password ) { + const scopedEnvNames = getMatrixScopedEnvVarNames(accountId); + const envSourceHint = + accountId === DEFAULT_ACCOUNT_ID + ? "MATRIX_* or MATRIX_DEFAULT_*" + : `${scopedEnvNames.homeserver} (+ auth vars)`; const useEnv = await params.prompter.confirm({ - message: "Matrix env vars detected. Use env values?", + message: `Matrix env vars detected (${envSourceHint}). Use env values?`, initialValue: true, }); if (useEnv) {